// ============================================================ // Encounter Toolbar — Save / New Patient / Load + label input. // Mirrors the vanilla saveFromTab / clearTab flow from // public/js/encounters.js. Each note page gets one of these so // transcripts + generated notes persist across sign-outs. // ============================================================ import { useEffect, useState } from 'react'; import { saveEncounter, loadEncounter, listSavedEncounters, getSavedEncId, clearTabState, type EncType, type SavedEncounterListEntry, type LoadedEncounter, } from '@/lib/encounter-persistence'; const input = 'rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring'; const btn = 'inline-flex items-center gap-1 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium hover:bg-muted disabled:opacity-50'; const btnPrimary = 'inline-flex items-center gap-1 rounded-md bg-primary text-primary-foreground px-3 py-1.5 text-xs font-medium disabled:opacity-50'; interface Props { type: EncType; label: string; setLabel: (s: string) => void; transcript: string; generatedNote: string; partialData?: unknown; onLoad: (enc: LoadedEncounter) => void; onClear: () => void; } export default function EncounterToolbar({ type, label, setLabel, transcript, generatedNote, partialData, onLoad, onClear, }: Props) { const [msg, setMsg] = useState<{ kind: 'ok' | 'err' | 'info'; text: string } | null>(null); const [saving, setSaving] = useState(false); const [popoverOpen, setPopoverOpen] = useState(false); const [saved, setSaved] = useState([]); const [loadingList, setLoadingList] = useState(false); const [search, setSearch] = useState(''); const [savedId, setSavedId] = useState(getSavedEncId(type)); // Sync the savedId chip whenever sessionStorage changes (e.g. after Load). useEffect(() => { const v = getSavedEncId(type); setSavedId(v); // eslint-disable-next-line react-hooks/exhaustive-deps }, [transcript, generatedNote]); async function save() { if (!label.trim()) { setMsg({ kind: 'err', text: 'Enter a patient label first' }); return; } setMsg(null); setSaving(true); try { const r = await saveEncounter({ type, label, transcript, generatedNote, partialData }); setSavedId(r.id); setMsg({ kind: 'ok', text: 'Saved (' + label + ')' }); } catch (e) { setMsg({ kind: 'err', text: (e as Error).message }); } finally { setSaving(false); } } function newPatient() { clearTabState(type); setSavedId(null); setLabel(''); onClear(); setMsg({ kind: 'info', text: 'New patient — fields cleared.' }); } async function openLoad() { if (popoverOpen) { setPopoverOpen(false); return; } setPopoverOpen(true); setLoadingList(true); try { const list = await listSavedEncounters(); setSaved(list.filter((e) => e.enc_type === type)); } finally { setLoadingList(false); } } async function pick(id: number) { try { const enc = await loadEncounter(id); setSavedId(id); onLoad(enc); setPopoverOpen(false); setMsg({ kind: 'ok', text: 'Loaded ' + (enc.label || '') }); } catch (e) { setMsg({ kind: 'err', text: (e as Error).message }); } } const filtered = search ? saved.filter((e) => (e.label || '').toLowerCase().includes(search.toLowerCase())) : saved; return (
setLabel(e.target.value)} data-testid="enc-label" />
{popoverOpen && (
setSearch(e.target.value)} autoFocus data-testid="enc-load-search" />
{loadingList &&
Loading…
} {!loadingList && filtered.length === 0 && (
No saved {type} encounters.
)} {filtered.map((e) => ( ))}
)}
{savedId != null && ( Draft #{savedId} )}
{msg && (
{msg.text}
)}
); }