// 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 &&
; } 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
Nouvelle note