// Historique du dossier patient : qui l'a ouvert, qui a modifié quoi. // // Les deux vues sont séparées parce qu'une ligne est écrite à chaque ouverture : // mélangées, les consultations noieraient les modifications. // // Les consultations viennent du journal d'audit, qui les écrit depuis la // première migration. Les modifications viennent du journal de champs que le // PATCH du dossier écrit dans sa propre transaction. Les deux se lisent par // `GET /api/v1/patients/{id}/history`, et rien ici n'est illustré : sans // serveur, l'écran dit l'erreur qu'il a reçue. const PH_T = window.VascoDesignContract; const PH_OPENER = { open: null }; const PH_PAGE_SIZE = 20; function PatientHistoryIcon({ color }) { return ; } // Un champ modifié : l'ancienne valeur barrée, la nouvelle en clair. // Une valeur absente se dit « Non renseigné » au lieu de laisser un vide // qu'on lirait comme une donnée. function PatientHistoryChange({ field }) { const { fieldText } = window.VascoPatientHistory; return (
{field.label} {fieldText(field.before)} {fieldText(field.after)}
); } function PatientHistoryEntry({ who, at, section = null, fields = null }) { return (
{who} {at}
{section &&
{section}
} {fields && fields.length > 0 && (
{fields.map((field, index) => )}
)}
); } // Une consultation n'a pas de section côté serveur : elle dit une ouverture, // pas une écriture. C'est l'écran qui nomme ce fait, et lui seul. function patientHistorySection(entry) { return entry.kind === 'viewed' ? 'Dossier ouvert' : entry.section; } function PatientHistoryView({ patientId, name, onClose }) { const { DetailSheet, ChoiceChip, Button, InlineAction } = window.VascoUI; const history = window.VascoPatientHistory; const [view, setView] = React.useState('changes'); const [page, setPage] = React.useState(null); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [reloadToken, setReloadToken] = React.useState(0); React.useEffect(() => { const controller = new AbortController(); let active = true; setLoading(true); setPage(null); setError(null); window.VascoApi.patient.history(patientId, { view, limit: PH_PAGE_SIZE, signal: controller.signal, }) .then((result) => { if (active) setPage(result); }) .catch((failure) => { if (active) setError(history.errorMessage(failure, 'Historique indisponible.')); }) .finally(() => { if (active) setLoading(false); }); return () => { active = false; controller.abort(); }; }, [patientId, view, reloadToken]); const loadMore = async () => { if (!page || !page.nextCursor || loading) return; setLoading(true); setError(null); try { const next = await window.VascoApi.patient.history(patientId, { view, limit: PH_PAGE_SIZE, cursor: page.nextCursor, }); setPage(history.mergePage(page, next)); } catch (failure) { setError(history.errorMessage(failure, 'Historique indisponible.')); } finally { setLoading(false); } }; const items = page?.items || []; const state = history.listState({ items, loading, error }); return (
setView('changes')}> Modifications setView('views')}> Consultations
{error && items.length > 0 &&
{error}
} {state === 'loading' &&
Chargement de l’historique…
} {state === 'error' &&
{error}
setReloadToken((value) => value + 1)}> Réessayer
} {state === 'empty' &&
{history.emptyMessage(view)}
} {items.length > 0 &&
{items.map((entry) => ( ))}
} {page?.hasMore && }
); } // Le nom se lit dans le dossier déjà chargé par la liste : l'historique parle // du dossier ouvert, il n'a pas à le recharger pour afficher son titre. function patientHistoryRecordName(patientId) { const patient = (window.PATIENTS_FULL || []).find((item) => item.id === patientId); if (!patient) return 'Ce dossier'; return [patient.last, patient.first].filter(Boolean).join(' ') || 'Ce dossier'; } // La feuille vit DANS le dossier, comme celle des évènements : c'est ce qui lui // donne le cadre du téléphone au téléphone, et la colonne du dossier au bureau. // Un portail vers la scène la ferait prendre toute la fenêtre de bureau, par- // dessus la barre de navigation et la liste des patients. // // Le menu « Plus » est rendu par la barre d'onglets de la référence figée, qui // n'a pas d'état à nous prêter : l'enveloppe s'annonce donc comme l'ouvreur, // tant qu'elle est montée. const PatientHistoryDetailBase = PatientDetail; PatientDetail = function PatientHistoryDetail(props) { const [open, setOpen] = React.useState(false); React.useEffect(() => { PH_OPENER.open = () => setOpen(true); return () => { PH_OPENER.open = null; }; }, []); // Changer de dossier referme l'historique du précédent plutôt que de lui // faire afficher celui d'un autre patient. React.useEffect(() => { setOpen(false); }, [props.patientId]); return <> {open && props.patientId && setOpen(false)} />} ; }; window.VASCO_PATIENT_MORE = window.VASCO_PATIENT_MORE || []; window.VASCO_PATIENT_MORE.push({ id: 'history', label: 'Historique', icon: (color) => , onSelect: () => { if (PH_OPENER.open) PH_OPENER.open(); }, });