// Surface Notes connectée. La maquette V5 reste figée : cette extension prend
// en charge le rendu produit, tout en conservant les routes versionnées déjà en place.
const PATIENT_NOTE_MAX_LENGTH = 10000;
const PN_INK = window.VT?.ink || '#0a0a0a';
const PN_SUB = window.VT?.sub || '#6b7280';
const PN_HAIR = window.VT?.hair || 'rgba(10,10,10,.10)';
const PN_HAIR2 = window.VT?.hair2 || 'rgba(10,10,10,.14)';
const PN_DANGER = window.VT?.danger || '#b91c1c';
const PatientNotesBaseResume = TabResume;
function PatientNotePinIcon() {
return ;
}
function PatientNoteTrashIcon() {
return ;
}
function PatientNoteIconButton({ label, onClick, disabled, danger = false, active = false, children }) {
const { IconButton } = window.VascoUI;
return {children};
}
function PatientNoteCard({ note, accent, busy, canPin, canDelete, onTogglePin, onDelete }) {
const moment = note.when || note.d || 'Date non renseignée';
return
{note.pinned && }
{note.text}
{moment} · {note.author}
{canPin &&
onTogglePin(note)}>
}
{canDelete &&
onDelete(note)}>
}
;
}
function ConnectedPatientNotes({ p, accent, runtime = null, focusToken = 0 }) {
const { Button } = window.VascoUI;
const [draft, setDraft] = React.useState('');
const draftRef = React.useRef(null);
const [notes, setNotes] = React.useState(p.notes || []);
const [busy, setBusy] = React.useState(false);
const [error, setError] = React.useState(null);
const capabilities = p.capabilities || {};
const canCreate = capabilities.create_note !== false;
const canPin = capabilities.pin_note !== false;
const canDelete = capabilities.delete_note !== false;
const pinned = notes.filter((note) => note.pinned);
const recent = notes.filter((note) => !note.pinned);
const reload = React.useCallback(async () => {
if (!runtime) return;
setBusy(true);
try {
setNotes(await runtime.notes(p.id));
setError(null);
} catch (failure) {
setError(failure?.message || 'Chargement des notes impossible');
} finally {
setBusy(false);
}
}, [runtime, p.id]);
React.useEffect(() => { void reload(); }, [reload]);
React.useEffect(() => {
if (!focusToken || !draftRef.current) return;
draftRef.current.focus({ preventScroll: true });
draftRef.current.scrollIntoView({ block: 'center', behavior: 'smooth' });
}, [focusToken]);
const add = async () => {
if (!draft.trim() || !runtime) return;
setBusy(true);
setError(null);
try {
const created = await runtime.createNote(p.id, draft.trim());
setNotes((current) => [created, ...current]);
setDraft('');
} catch (failure) {
setError(failure?.message || 'Création de la note impossible');
} finally {
setBusy(false);
}
};
const togglePin = async (note) => {
if (!runtime) return;
setBusy(true);
setError(null);
try {
const updated = await runtime.pinNote(p.id, note, !note.pinned);
setNotes((current) => current.map((item) => item.id === updated.id ? updated : item));
} catch (failure) {
setError(failure?.message || 'Épinglage impossible');
if (failure?.status === 409) await reload();
} finally {
setBusy(false);
}
};
const deleteNote = async (note) => {
if (!runtime) return;
setBusy(true);
setError(null);
try {
const deleted = await runtime.deleteNote(p.id, note);
setNotes((current) => current.filter((item) => item.id !== note.id));
const undoable = Boolean(deleted.undoExpiresAt)
&& new Date(deleted.undoExpiresAt).getTime() > Date.now();
showToast('Note supprimée', 'info', undoable ? {
duration: 5000,
action: {
label: 'Annuler',
onClick: () => {
runtime.restoreNote(p.id, deleted)
.then((restored) => setNotes((current) => [restored, ...current]))
.catch((failure) => setError(failure?.message || 'Restauration impossible'));
},
},
} : { duration: 5000 });
} catch (failure) {
setError(failure?.message || 'Suppression impossible');
if (failure?.status === 409) await reload();
} finally {
setBusy(false);
}
};
return
{error &&
{error}
}
{pinned.length > 0 &&
Épinglées
{pinned.map((note) =>
void togglePin(item)} onDelete={(item) => void deleteNote(item)} />)}
}
Notes récentes
{recent.length === 0
?
Aucune note récente.
: recent.map((note) =>
void togglePin(item)} onDelete={(item) => void deleteNote(item)} />)}
;
}
function PatientPinnedNotesSummary({ patient, runtime, accent }) {
const [pinned, setPinned] = React.useState([]);
React.useEffect(() => {
if (!runtime) return undefined;
let active = true;
runtime.notes(patient.id)
.then((notes) => {
if (active) setPinned(notes.filter((note) => note.pinned));
})
.catch(() => { if (active) setPinned([]); });
return () => { active = false; };
}, [runtime, patient.id]);
if (!pinned.length) return null;
return
Notes épinglées
{pinned.map((note) =>
{note.text}
)}
;
}
function PatientNotesResume(props) {
return <>
>;
}
TabNotes = ConnectedPatientNotes;
TabResume = PatientNotesResume;