227 lines
8.9 KiB
TypeScript
227 lines
8.9 KiB
TypeScript
import { useRef } from "react";
|
|
import { useParams, useLocation } from "wouter";
|
|
import { trpc } from "@/lib/trpc";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ChevronLeft, Printer, Loader2, QrCode } from "lucide-react";
|
|
|
|
export default function QrPoster() {
|
|
const params = useParams<{ clinicId: string }>();
|
|
const [, navigate] = useLocation();
|
|
const clinicId = parseInt(params.clinicId || "0");
|
|
const printRef = useRef<HTMLDivElement>(null);
|
|
|
|
const clinicQuery = trpc.clinic.get.useQuery({ id: clinicId }, { enabled: !!clinicId });
|
|
const qrQuery = trpc.clinic.getQrCode.useQuery({ id: clinicId }, { enabled: !!clinicId });
|
|
|
|
const clinic = clinicQuery.data;
|
|
const qrDataUrl = qrQuery.data?.qrDataUrl;
|
|
|
|
const handlePrint = () => {
|
|
window.print();
|
|
};
|
|
|
|
if (clinicQuery.isLoading || qrQuery.isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<Loader2 className="w-8 h-8 text-primary animate-spin" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen">
|
|
{/* Controls — hidden on print */}
|
|
<div className="print:hidden relative z-10 max-w-2xl mx-auto px-4 py-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<button
|
|
onClick={() => navigate(`/dashboard/queue/${clinicId}`)}
|
|
className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors text-sm"
|
|
>
|
|
<ChevronLeft className="w-4 h-4" />
|
|
Retour à la gestion
|
|
</button>
|
|
<Button
|
|
onClick={handlePrint}
|
|
className="bg-primary text-primary-foreground hover:bg-primary/90 glow-teal"
|
|
>
|
|
<Printer className="w-4 h-4 mr-2" />
|
|
Imprimer l'affiche
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="glass-card rounded-2xl p-4 mb-6 flex items-start gap-3">
|
|
<QrCode className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
|
|
<div className="text-sm text-muted-foreground">
|
|
<strong className="text-foreground">Conseils d'impression :</strong> Utilisez du papier A4, imprimez en couleur si possible.
|
|
Plastifiez l'affiche pour la durabilité. Placez-la à hauteur des yeux à l'entrée du cabinet.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Printable poster */}
|
|
<div
|
|
ref={printRef}
|
|
className="print:m-0 max-w-2xl mx-auto px-4 pb-12 print:p-0 print:max-w-none"
|
|
>
|
|
<div
|
|
className="bg-white text-gray-900 rounded-3xl print:rounded-none overflow-hidden shadow-2xl print:shadow-none"
|
|
style={{ fontFamily: "'Inter', 'Segoe UI', sans-serif" }}
|
|
>
|
|
{/* Header band */}
|
|
<div style={{ background: "linear-gradient(135deg, #0d9488, #0f766e)", padding: "32px 40px" }}>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "8px" }}>
|
|
<div style={{
|
|
width: "40px", height: "40px", borderRadius: "10px",
|
|
background: "rgba(255,255,255,0.2)", display: "flex",
|
|
alignItems: "center", justifyContent: "center"
|
|
}}>
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
|
|
<path d="M3 9h6V3H3v6zm2-4h2v2H5V5zm8-2v6h6V3h-6zm4 4h-2V5h2v2zM3 21h6v-6H3v6zm2-4h2v2H5v-2zm13-2h-3v2h2v2h-2v2h3v-3h2v-3h-2v2zm-5 6h2v-2h-2v2zm-3-6h2v2h-2v-2zm-2-2h2v2h-2v-2zm2-2h2v2h-2v-2zm2 2h2v2h-2v-2z"/>
|
|
</svg>
|
|
</div>
|
|
<span style={{ color: "white", fontSize: "22px", fontWeight: "800", letterSpacing: "-0.02em" }}>
|
|
QueueMed
|
|
</span>
|
|
</div>
|
|
<p style={{ color: "rgba(255,255,255,0.85)", fontSize: "14px", margin: 0 }}>
|
|
Salle d'attente virtuelle
|
|
</p>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div style={{ padding: "40px", textAlign: "center" }}>
|
|
<h1 style={{
|
|
fontSize: "28px", fontWeight: "800", color: "#0f172a",
|
|
marginBottom: "8px", lineHeight: "1.2"
|
|
}}>
|
|
{clinic?.name ?? "Cabinet médical"}
|
|
</h1>
|
|
{clinic?.address && (
|
|
<p style={{ color: "#64748b", fontSize: "14px", marginBottom: "32px" }}>
|
|
📍 {clinic.address}
|
|
</p>
|
|
)}
|
|
|
|
<p style={{
|
|
fontSize: "18px", fontWeight: "600", color: "#1e293b",
|
|
marginBottom: "8px"
|
|
}}>
|
|
Rejoignez la file d'attente sans attendre ici
|
|
</p>
|
|
<p style={{ color: "#64748b", fontSize: "14px", marginBottom: "32px" }}>
|
|
Scannez le QR code avec votre téléphone et suivez votre position en temps réel
|
|
</p>
|
|
|
|
{/* QR Code */}
|
|
<div style={{
|
|
display: "inline-block",
|
|
padding: "20px",
|
|
borderRadius: "20px",
|
|
border: "3px solid #e2e8f0",
|
|
background: "white",
|
|
marginBottom: "32px",
|
|
boxShadow: "0 8px 32px rgba(0,0,0,0.08)"
|
|
}}>
|
|
{qrDataUrl ? (
|
|
<img
|
|
src={qrDataUrl}
|
|
alt="QR Code file d'attente"
|
|
style={{ width: "220px", height: "220px", display: "block" }}
|
|
/>
|
|
) : (
|
|
<div style={{
|
|
width: "220px", height: "220px",
|
|
background: "#f1f5f9", borderRadius: "12px",
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
color: "#94a3b8", fontSize: "14px"
|
|
}}>
|
|
QR Code non disponible
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Steps */}
|
|
<div style={{
|
|
display: "grid", gridTemplateColumns: "1fr 1fr 1fr",
|
|
gap: "16px", marginBottom: "32px"
|
|
}}>
|
|
{[
|
|
{ num: "1", icon: "📱", title: "Scannez", desc: "Ouvrez l'appareil photo et pointez vers le QR code" },
|
|
{ num: "2", icon: "👆", title: "Rejoignez", desc: "Appuyez sur le lien et entrez dans la file" },
|
|
{ num: "3", icon: "🔔", title: "Revenez", desc: "Vous serez alerté quand votre tour approche" },
|
|
].map(step => (
|
|
<div key={step.num} style={{
|
|
padding: "16px 12px",
|
|
borderRadius: "16px",
|
|
background: "#f8fafc",
|
|
border: "1px solid #e2e8f0"
|
|
}}>
|
|
<div style={{ fontSize: "28px", marginBottom: "8px" }}>{step.icon}</div>
|
|
<div style={{ fontSize: "13px", fontWeight: "700", color: "#0f172a", marginBottom: "4px" }}>
|
|
{step.title}
|
|
</div>
|
|
<div style={{ fontSize: "11px", color: "#64748b", lineHeight: "1.4" }}>
|
|
{step.desc}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Info box */}
|
|
<div style={{
|
|
padding: "14px 20px",
|
|
borderRadius: "12px",
|
|
background: "#f0fdf4",
|
|
border: "1px solid #bbf7d0",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "10px",
|
|
textAlign: "left"
|
|
}}>
|
|
<span style={{ fontSize: "20px" }}>✅</span>
|
|
<div>
|
|
<strong style={{ fontSize: "13px", color: "#166534" }}>
|
|
Aucune application à installer
|
|
</strong>
|
|
<p style={{ fontSize: "12px", color: "#15803d", margin: "2px 0 0 0" }}>
|
|
Fonctionne directement dans votre navigateur. Gratuit pour les patients.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* No smartphone note */}
|
|
<p style={{
|
|
marginTop: "20px", fontSize: "12px", color: "#94a3b8",
|
|
borderTop: "1px solid #f1f5f9", paddingTop: "16px"
|
|
}}>
|
|
Pas de smartphone ? Demandez un ticket imprimé à l'accueil.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div style={{
|
|
background: "#f8fafc", padding: "16px 40px",
|
|
display: "flex", justifyContent: "space-between",
|
|
alignItems: "center", borderTop: "1px solid #e2e8f0"
|
|
}}>
|
|
<span style={{ fontSize: "12px", color: "#94a3b8" }}>
|
|
Propulsé par QueueMed
|
|
</span>
|
|
<span style={{ fontSize: "12px", color: "#94a3b8" }}>
|
|
queuemed.fr
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Print styles */}
|
|
<style>{`
|
|
@media print {
|
|
body { background: white !important; }
|
|
.print\\:hidden { display: none !important; }
|
|
@page { margin: 0; size: A4; }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|