1. Écran E02 – Upload ordonnance papier

Sous-titre de section

Écrivez un ou deux paragraphes décrivant votre produit ou vos services. Pour réussir, votre contenu doit être utile à vos lecteurs.

Commencez par le client: trouvez ce qu'il veut et donner-le lui.


Découvrir plus

Code REACT JS 

🧩 Structure UI

Composants proposés :

  • <UploadPrescriptionPage /> (page)
    • <PageHeader /> – titre + sous-titre
    • <UploadCard /> – drag & drop + bouton fichier
    • <DocTypeSelector /> – Ordonnance / Compte-rendu / Autre
    • <PatientQuickSelect /> – recherche patient + “Créer nouveau”
    • <OcrStatusPanel /> – état du traitement (en attente / en cours / terminé)

💻 Code React + Tailwind (TypeScript, mais facile à passer en JS)

// E02 - UploadPrescriptionPage.tsx import React, { useState } from "react"; type DocType = "prescription" | "report" | "other"; export const UploadPrescriptionPage: React.FC = () => { const [file, setFile] = useState<File | null>(null); const [docType, setDocType] = useState<DocType>("prescription"); const [isUploading, setIsUploading] = useState(false); const [ocrStatus, setOcrStatus] = useState< "idle" | "processing" | "success" | "error" >("idle"); const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const f = e.target.files?.[0]; if (f) setFile(f); }; const handleDrop = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); const f = e.dataTransfer.files?.[0]; if (f) setFile(f); }; const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault(); }; const handleStartOcr = async () => { if (!file) return; setIsUploading(true); setOcrStatus("processing"); // TODO: plug API upload + OCR call try { await new Promise((res) => setTimeout(res, 1500)); // fake delay setOcrStatus("success"); } catch (e) { setOcrStatus("error"); } finally { setIsUploading(false); } }; return ( <div className="min-h-screen bg-slate-50"> <div className="max-w-5xl mx-auto px-4 py-8"> <PageHeader title="Importer une ordonnance" subtitle="Téléversez une ordonnance papier pour la transformer en prescription structurée." /> <div className="mt-6 grid gap-6 lg:grid-cols-[3fr,2fr]"> <UploadCard file={file} onFileChange={handleFileChange} onDrop={handleDrop} onDragOver={handleDragOver} /> <div className="space-y-4"> <DocTypeSelector docType={docType} onChange={setDocType} /> <PatientQuickSelect /> <OcrStatusPanel status={ocrStatus} isUploading={isUploading} onStartOcr={handleStartOcr} disabled={!file} /> </div> </div> </div> </div> ); }; const PageHeader: React.FC<{ title: string; subtitle?: string }> = ({ title, subtitle, }) => ( <header className="flex flex-col gap-1"> <h1 className="text-2xl font-semibold text-slate-900">{title}</h1> {subtitle && ( <p className="text-sm text-slate-600 max-w-2xl">{subtitle}</p> )} </header> ); type UploadCardProps = { file: File | null; onFileChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onDrop: (e: React.DragEvent<HTMLDivElement>) => void; onDragOver: (e: React.DragEvent<HTMLDivElement>) => void; }; const UploadCard: React.FC<UploadCardProps> = ({ file, onFileChange, onDrop, onDragOver, }) => ( <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 flex flex-col gap-4"> <h2 className="text-sm font-medium text-slate-800"> Fichier ordonnance (photo ou PDF) </h2> <div onDrop={onDrop} onDragOver={onDragOver} className="flex flex-col items-center justify-center gap-2 border-2 border-dashed rounded-lg border-slate-300 bg-slate-50 px-4 py-10 text-center cursor-pointer hover:border-slate-400 transition" > <p className="text-sm font-medium text-slate-800"> Glissez-déposez le fichier ici </p> <p className="text-xs text-slate-500"> ou cliquez pour sélectionner un fichier </p> <label className="mt-3 inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border border-slate-300 bg-white hover:bg-slate-50 cursor-pointer"> Choisir un fichier <input type="file" accept="image/*,.pdf" className="hidden" onChange={onFileChange} /> </label> {file && ( <div className="mt-4 text-xs text-slate-600"> Fichier sélectionné :{" "} <span className="font-medium text-slate-800">{file.name}</span> </div> )} </div> <p className="text-xs text-slate-500"> Conseil : photo nette, ordonnance complète, pas de reflet, contraste suffisant. </p> </div> ); type DocTypeSelectorProps = { docType: DocType; onChange: (value: DocType) => void; }; const DocTypeSelector: React.FC<DocTypeSelectorProps> = ({ docType, onChange, }) => ( <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 space-y-2"> <h3 className="text-xs font-medium text-slate-800 uppercase tracking-wide"> Type de document </h3> <div className="flex flex-wrap gap-2"> <DocTypePill label="Ordonnance" active={docType === "prescription"} onClick={() => onChange("prescription")} /> <DocTypePill label="Compte-rendu" active={docType === "report"} onClick={() => onChange("report")} /> <DocTypePill label="Autre" active={docType === "other"} onClick={() => onChange("other")} /> </div> </div> ); const DocTypePill: React.FC<{ label: string; active: boolean; onClick: () => void; }> = ({ label, active, onClick }) => ( <button type="button" onClick={onClick} className={`px-3 py-1.5 rounded-full text-xs font-medium border transition ${ active ? "bg-sky-600 text-white border-sky-600" : "bg-slate-50 text-slate-700 border-slate-300 hover:bg-slate-100" }`} > {label} </button> ); const PatientQuickSelect: React.FC = () => ( <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 space-y-3"> <h3 className="text-xs font-medium text-slate-800 uppercase tracking-wide"> Patient </h3> <div className="flex flex-col gap-2"> <input type="text" placeholder="Rechercher un patient (nom, NIR...)" className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500" /> <button type="button" className="self-start text-xs font-medium text-sky-700 hover:underline" > + Créer un nouveau patient </button> </div> </div> ); type OcrStatusPanelProps = { status: "idle" | "processing" | "success" | "error"; isUploading: boolean; onStartOcr: () => void; disabled?: boolean; }; const OcrStatusPanel: React.FC<OcrStatusPanelProps> = ({ status, isUploading, onStartOcr, disabled, }) => { const getLabel = () => { switch (status) { case "idle": return "Prêt à lancer l'analyse OCR."; case "processing": return "Analyse en cours…"; case "success": return "Analyse terminée avec succès. Les données sont prêtes à être contrôlées."; case "error": return "Une erreur est survenue pendant l'analyse. Merci de réessayer."; } }; return ( <div className="bg-white rounded-xl shadow-sm border border-slate-200 p-4 flex flex-col gap-3"> <h3 className="text-xs font-medium text-slate-800 uppercase tracking-wide"> Analyse OCR </h3> <p className="text-xs text-slate-600">{getLabel()}</p> {status === "processing" && ( <div className="h-1.5 w-full bg-slate-100 rounded-full overflow-hidden"> <div className="h-full w-1/2 animate-pulse bg-sky-500 rounded-full" /> </div> )} <button type="button" disabled={disabled || isUploading} onClick={onStartOcr} className="inline-flex items-center justify-center rounded-lg px-3 py-2 text-sm font-medium text-white bg-sky-600 hover:bg-sky-700 disabled:bg-slate-300 disabled:cursor-not-allowed" > {isUploading ? "Analyse en cours…" : "Lancer l’analyse OCR"} </button> </div> ); };

ODOO studio liée à cette page