// Patients en page de bureau : la liste et le dossier sur la même page. // // Cette couche ne remplace ni la liste ni le dossier du téléphone, et elle n'en // recopie aucune logique. Le volet de gauche monte l'écran Patients tel qu'il // est : c'est lui qui tient la recherche serveur, sa cadence de frappe, le tri, // les filtres, le compte du serveur et le changement de vue. Le volet de droite // monte `PatientDetail` tel qu'il est, avec ses onglets et ses sections. // // Une seule chose change au bureau, et c'est de l'affichage : la pile de cartes // Wallet, faite pour le pouce, devient une liste de lignes qui tient dans un // volet de 360 px et montre laquelle est ouverte. La donnée qu'elle reçoit est // déjà cherchée, triée et filtrée par l'écran ; cette couche n'en refait rien. // // Sous le seuil de bureau, rien de tout cela n'est monté : la coque rend la // liste puis le dossier en plein écran, comme aujourd'hui. // Dernière version enveloppée de chaque symbole, capturée après toutes les // couches produit (vues filtrées, suivi, documents, notes, statistiques). const VASCO_PATIENTS_DESKTOP_LIST = PatientFolders; const VASCO_PATIENTS_DESKTOP_DETAIL = PatientDetail; const VASCO_PATIENTS_DESKTOP_CARDS = WalletCards; // Les gestes que la coque téléphone donne au répertoire : ouvrir un dossier, // créer un patient, ouvrir une note, ouvrir un rendez-vous. Sur la route // `patient`, la coque ne les donne plus – elle rend un dossier, pas une liste – // alors que la page de bureau montre les deux ensemble. On garde donc ceux // qu'elle vient de donner. Ce sont des fermetures sur les `setState` et les refs // de la coque, stables par construction : aucune valeur de rendu n'y est // capturée. Rien n'est recréé ici, rien n'est deviné. const VASCO_PATIENTS_DESKTOP_SHELL = { accent: null, onOpenPatient: null, onNewPatient: null, onNewNote: null, onNewAppointment: null, onTabChange: null, onBack: null, }; // Dossier actuellement ouvert, pour que la ligne correspondante le montre. C'est // de l'affichage : la coque reste seule à savoir quel dossier est chargé. const VASCO_PATIENTS_DESKTOP_SELECTION = { id: null }; function vascoPatientsDesktopDisplayName(patient) { if (!patient) return ''; const last = String(patient.last || ''); const first = String(patient.first || ''); return `${last.toUpperCase()} ${first}`.trim(); } function vascoPatientsDesktopSummary(patient) { if (!patient) return null; const age = Number.isFinite(patient.age) ? `${patient.age} ans` : null; return [age, patient.summary || null].filter(Boolean).join(' · ') || null; } // Vignette d'identité : le même dessin que la liste du téléphone, réduit à ce // qu'une ligne de 48 px peut porter. function VascoPatientsDesktopAvatar({ patient, selected }) { const tokens = window.VascoUI.tokens; const initials = `${String(patient.first || '?')[0]}${String(patient.last || '?')[0]}`; return ; } // Où le patient est hospitalisé, au bout de la ligne de soutien de sa rangée. Le // libellé vient de la seule fonction qui sache le composer, celle de la fiche et // de la liste du téléphone : le bureau n'en recopie pas la règle. Sans place, pas // de pastille. // // Il partage la ligne du résumé et non celle du nom : dans un volet de 360 px, // une place à trois parties posée au bout de la rangée entière abrégeait le nom // du patient. Le résumé, lui, s'abrège déjà et se relit dans le dossier. function VascoPatientsDesktopStay({ patient }) { const tokens = window.VascoUI.tokens; const label = window.VascoPatientContactDetails.stayLabel(patient); if (!label) return null; return {label}; } // La présentation en lignes de ce que l'écran a déjà cherché, trié et filtré. // Elle ne connaît ni requête, ni cadence de frappe, ni critère : elle reçoit la // liste et les gestes, exactement comme la pile de cartes qu'elle remplace. function VascoPatientsDesktopRoster({ patients, onOpenPatient, onNewPatient, selectedId }) { const tokens = window.VascoUI.tokens; const { Button, InteractiveRow } = window.VascoUI; return
{onNewPatient &&
} {!patients.length &&
Aucun patient ne correspond.
} {patients.map((patient) => { const selected = patient.id === selectedId; return onOpenPatient(patient.id)} leading={} style={{ minHeight: tokens.layout.panel.rowHeight, marginBottom: tokens.space.xs, borderColor: selected ? tokens.color.accent : 'transparent', background: selected ? tokens.color.accentTint : 'transparent', }}> {vascoPatientsDesktopDisplayName(patient)} {vascoPatientsDesktopSummary(patient) || 'Dossier sans résumé'} ; })}
; } // La pile de cartes Wallet est un geste de pouce : une carte par écran, feuilletée // au doigt. Dans un volet de 360 px elle n'a plus de sens, et la liste de lignes // prend sa place. Elle reçoit exactement les mêmes propriétés, donc exactement la // même donnée : l'écran garde la recherche, le tri et les filtres. WalletCards = function VascoPatientCardsForLayout(props) { const desktop = window.VascoUI.useLayout() === 'desktop'; // Appel inconditionnel de la couche de filtre : elle reste la seule a lire le // critere et a reduire la liste. On lit la liste qu'elle a reduite au lieu // d'appliquer le filtre une seconde fois. const filtered = VASCO_PATIENTS_DESKTOP_CARDS(props); if (!desktop) return filtered; const patients = React.isValidElement(filtered) && Array.isArray(filtered.props.patients) ? filtered.props.patients : props.patients; return ; }; window.WalletCards = WalletCards; // Le retour du dossier vit dans l'en-tête du dossier, pas dans le dossier // lui-même : c'est donc cet en-tête qu'on corrige, une fois, pour toutes les // compositions de bureau. Sous le seuil il rend exactement l'original. const VascoLegacyPatientHeader = PatientHeader; PatientHeader = function VascoPatientsDesktopHeader(props) { const desktop = window.VascoUI.useLayout() === 'desktop'; const tree = VascoLegacyPatientHeader(props); if (!desktop) return tree; return window.VascoUI.hideAction(tree, props.onBack); }; window.PatientHeader = PatientHeader; // Le dossier tel qu'il est, moins le retour vers une liste déjà visible. La // commande est repérée par son gestionnaire, pas par sa place : la référence // figée n'est pas touchée et son geste de suppression continue de désélectionner. function VascoPatientsDesktopDossier({ onDeselect, ...rest }) { const tree = VASCO_PATIENTS_DESKTOP_DETAIL({ ...rest, onBack: onDeselect }); return window.VascoUI.hideAction(tree, onDeselect); } // Volet de droite au repos : une phrase, rien d'autre. Pas d'illustration, pas // de bouton qui ferait semblant d'ouvrir quelque chose. function VascoPatientsDesktopEmptyDetail() { const tokens = window.VascoUI.tokens; return
Choisissez un patient dans la liste pour ouvrir son dossier.
; } function VascoPatientsDesktopPage({ accent, patientId = null, patientTab = 'resume', onOpenPatient, onNewPatient, onNewAppointment, onNewNote, onDeselect, onOpenChat, onTabChange, onBack, runtime = null }) { const tokens = window.VascoUI.tokens; const { SplitView } = window.VascoUI; // La sélection est posée avant que le volet de gauche ne se rende : ses lignes // la lisent au moment de leur propre rendu, qui vient après celui-ci. VASCO_PATIENTS_DESKTOP_SELECTION.id = patientId; return
} detail={patientId ? : } />
; } window.VascoPatientsDesktop = VascoPatientsDesktopPage; // Au bureau, le répertoire et le dossier sont la même page : le dossier ouvert // est un patient choisi dans une liste visible, pas un autre écran. Les deux // symboles de la coque téléphone désignent donc le même composant, et React ne // remonte rien quand la route passe de l'un à l'autre : le volet de gauche garde // sa recherche, son tri et son défilement. Sous le seuil, chacun rend l'écran // que la coque attend de lui. const VascoPatientsRouteForLayout = function VascoPatientsRouteForLayout(props) { const desktop = window.VascoUI.useLayout() === 'desktop'; // La coque rend un dossier quand elle nomme un patient, sinon le répertoire. const dossier = Object.prototype.hasOwnProperty.call(props, 'patientId'); const shell = VASCO_PATIENTS_DESKTOP_SHELL; React.useEffect(() => { if (!desktop || dossier) return; shell.accent = props.accent; shell.onOpenPatient = props.onOpenPatient || null; shell.onNewPatient = props.onNewPatient || null; shell.onNewNote = props.onNewNote || null; shell.onNewAppointment = props.onNewAppointment || null; shell.onTabChange = props.onTabChange || null; shell.onBack = props.onBack || null; }); if (!desktop) { return dossier ? : ; } if (!dossier) { return {}} onOpenChat={props.onOpenChat} />; } return ; }; PatientFolders = VascoPatientsRouteForLayout; PatientDetail = VascoPatientsRouteForLayout; window.PatientFolders = PatientFolders; window.PatientDetail = PatientDetail;