// encounter.jsx — Encounter screen wired to /api/sessions/{id}. // The route param can be a session_id directly (from a visit-history click) or // an appointment_id (from Dashboard / Schedule). For appointments we look up // the linked session_id from the store; if there's no session yet, we show a // "Start encounter" placeholder that routes the user to Transcribe. const { useState: useStateE, useEffect: useEffectE, useRef: useRefE } = React; function VitalChip({ label, value, unit }) { return (
{label}
{value || "—"} {unit && value && {unit}}
); } function copyClinicalNoteText(text, onCopied) { const value = text || ""; const done = () => { if (onCopied) onCopied(); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(value).then(done).catch(() => { const el = document.createElement("textarea"); el.value = value; el.style.position = "fixed"; el.style.opacity = "0"; document.body.appendChild(el); el.select(); document.execCommand("copy"); document.body.removeChild(el); done(); }); return; } const el = document.createElement("textarea"); el.value = value; el.style.position = "fixed"; el.style.opacity = "0"; document.body.appendChild(el); el.select(); document.execCommand("copy"); document.body.removeChild(el); done(); } function cleanNoteLine(line) { return String(line || "") .replace(/^#{1,6}\s+/, "") .replace(/^[-*]\s+/, "") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/\*([^*]+)\*/g, "$1") .replace(/`([^`]+)`/g, "$1") .trim(); } function presentableNoteSections(text) { const lines = (text || "").split(/\r?\n/); const sections = []; let current = { title: "Summary", items: [] }; const flush = () => { if (current.items.length || current.title !== "Summary") sections.push(current); }; lines.forEach(line => { const trimmed = line.trim(); if (!trimmed) return; if (/^#{1,6}\s+/.test(trimmed) || /^\*\*[^*]+:\*\*$/.test(trimmed) || /^[A-Z][A-Za-z /&-]{2,}:$/.test(trimmed)) { flush(); current = { title: cleanNoteLine(trimmed).replace(/:$/, ""), items: [] }; return; } current.items.push(cleanNoteLine(trimmed)); }); flush(); return sections.length ? sections : [{ title: "Clinical note", items: [cleanNoteLine(text)] }]; } function extractFollowupTextFromNote(text) { const lines = String(text || "").split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const cleaned = cleanNoteLine(lines[i]).replace(/^follow[- ]?up\s*/i, "Follow-up").trim(); if (!/follow[- ]?up/i.test(cleaned)) continue; if (cleaned.includes(":")) { const value = cleaned.split(":").slice(1).join(":").trim(); if (value && !/^(not discussed|none|n\/?a)$/i.test(value)) return value; } for (let j = i + 1; j < Math.min(lines.length, i + 4); j++) { const value = cleanNoteLine(lines[j]); if (value && !/^(not discussed|none|n\/?a)$/i.test(value)) return value; } } const match = String(text || "").match(/(follow[- ]?up[^.\n]*(?:in|on|within|after)\s+[^.\n]+)/i); return match ? cleanNoteLine(match[1]) : ""; } function followupTextFromSession(sessionData, noteText) { const note = (sessionData && sessionData.clinical_note) || {}; const structured = note.structured_data || {}; const structuredFollow = structured && typeof structured === "object" ? String(structured.follow_up || "").trim() : ""; if (structuredFollow && !/^(not discussed|none|n\/?a)$/i.test(structuredFollow)) return structuredFollow; const noteFollow = extractFollowupTextFromNote(noteText || note.note_text || ""); if (noteFollow) return noteFollow; const transcriptText = ((sessionData && sessionData.transcripts) || []) .map(seg => seg.corrected_text || seg.auto_corrected_text || seg.text || "") .join("\n"); return extractFollowupTextFromNote(transcriptText); } function MarkdownPreview({ text, followupText }) { const sections = presentableNoteSections(text); const icons = [Icon.Clipboard, Icon.Pulse, Icon.Pill, Icon.CalendarPlus, Icon.Heart, Icon.Document]; return (
Clinical Note Preview
Formatted for review and printing
{followupText && (

Follow-up

{followupText}

)}
{sections.map((section, i) => { const I = icons[i % icons.length]; return (

{section.title}

{section.items.map((item, j) => (

{item}

))}
); })}
); } function escapeHtml(value) { return String(value || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function noteMarkdownToHtml(text) { return presentableNoteSections(text).map(section => `

${escapeHtml(section.title)}

${section.items.map(item => `

${escapeHtml(item)}

`).join("")}
`).join(""); } function ClinicalNotePreviewModal({ open, noteText, patientName, followupText, onClose }) { const [copied, setCopied] = useStateE(false); if (!open) return null; const title = patientName ? `Clinical note - ${patientName}` : "Clinical note"; function handleCopy() { copyClinicalNoteText(noteText, () => { setCopied(true); setTimeout(() => setCopied(false), 1600); }); } function handlePrint() { const printWindow = window.open("", "_blank", "width=900,height=720"); if (!printWindow) { const printRoot = document.createElement("div"); printRoot.className = "ms-print-note-root"; printRoot.innerHTML = `

${escapeHtml(title)}

${noteMarkdownToHtml(noteText)}
`; document.body.appendChild(printRoot); document.body.classList.add("print-note-preview"); window.print(); setTimeout(() => { document.body.classList.remove("print-note-preview"); printRoot.remove(); }, 300); return; } printWindow.document.write(`${escapeHtml(title)}

${escapeHtml(title)}

${noteMarkdownToHtml(noteText)}
`); printWindow.document.close(); printWindow.focus(); printWindow.print(); } return (
{title}
Formatted preview
); } window.ClinicalNotePreviewModal = ClinicalNotePreviewModal; window.copyClinicalNoteText = copyClinicalNoteText; window.MSExtractFollowupText = extractFollowupTextFromNote; window.MSFollowupTextFromSession = followupTextFromSession; function Encounter({ appointmentId, onClose }) { const store = window.MSStore.useStore(); const [session, setSession] = useStateE(null); const [loading, setLoading] = useStateE(true); const [error, setError] = useStateE(null); const [noteText, setNoteText] = useStateE(""); const [savingNote, setSavingNote] = useStateE(false); const [savedNote, setSavedNote] = useStateE(false); const [appointment, setAppointment] = useStateE(null); const [previewOpen, setPreviewOpen] = useStateE(false); const [copiedNote, setCopiedNote] = useStateE(false); // Resolve which session to load from the route param. useEffectE(() => { let cancelled = false; setLoading(true); setError(null); setSession(null); setAppointment(null); setNoteText(""); (async () => { // First, see if the param matches an appointment in the store. const appt = (store.appointments || []).find(a => a.id === appointmentId); if (appt) setAppointment(appt); const candidateSessionId = appt && appt.sessionId ? appt.sessionId : appointmentId; try { const data = await store.getSession(candidateSessionId); if (cancelled) return; setSession(data); setNoteText((data && data.clinical_note && data.clinical_note.note_text) || ""); setLoading(false); } catch (e) { if (cancelled) return; setError(e && e.message || "Could not load encounter"); setLoading(false); } })(); return () => { cancelled = true; }; }, [appointmentId, store.appointments]); async function saveNote() { if (!session || !session.id || !noteText.trim() || savingNote) return; setSavingNote(true); try { await store.saveClinicalNote(session.id, noteText); setSavedNote(true); setTimeout(() => setSavedNote(false), 1800); } catch (e) { // Surfaced via error state if needed. } finally { setSavingNote(false); } } function copyNote() { if (!noteText.trim()) return; window.copyClinicalNoteText(noteText, () => { setCopiedNote(true); setTimeout(() => setCopiedNote(false), 1600); }); } // Loading if (loading) { return (
Loading encounter…
); } // No session yet — appointment-only path if (error || !session) { const patientName = appointment ? appointment.patientName : null; return (
{patientName && (
{(patientName || "?").split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase()}

{patientName}

{appointment &&
{appointment.type} · {appointment.duration}m · {appointment.time}
}
)}
No recorded session yet
{error ? error : "Start a transcription session from the Transcribe tab to capture this visit."}
); } // Real session view ─────────────────────────────────────────────────────── const patientName = session.patient_name || (appointment && appointment.patientName) || "Patient"; const initials = (patientName || "?").split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase(); const transcripts = session.transcripts || []; const note = session.clinical_note; const followupText = window.MSFollowupTextFromSession ? window.MSFollowupTextFromSession(session, noteText) : ""; const startedAt = session.started_at ? new Date(session.started_at) : null; const duration = session.duration_seconds ? `${Math.round(session.duration_seconds / 60)}m` : ""; return (
{/* Top patient banner */}
{initials}

{patientName}

{session.patient_age && · {session.patient_age}}
{note && Note generated} {!note && Awaiting note}
{startedAt ? startedAt.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }) : ""} {duration &&
{duration} session
}
{/* Left rail */} {/* Main content */}
{savingNote ? "Saving…" : savedNote ? "Saved." : note && note.created_at ? `Generated ${new Date(note.created_at).toLocaleString()}` : ""}
{/* Clinical note */}
N Clinical note