diff --git a/README.md b/README.md index e854f2f..52a8a42 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ Each open patient has eight focused tabs: | Tab | Purpose | |---|---| -| **Profile** | Demographics plus case-review notes (clinical summary, chief complaint, HPI, PMH, PE), diagnosis, and clinical details | -| **FRICHMOND** | Daily progress notes (Fluid, Respiratory, Infectious, Cardiovascular, Hema, Metabolic, Output, Neuro, Drugs) with a Copy latest entry action to carry forward all daily fields | +| **Profile** | Demographics plus case-review notes (clinical summary, chief complaint, HPI, PMH, PE), diagnosis, and clerk notes | +| **FRICHMOND** | Daily progress notes (Fluid, Respiratory, Infectious, Cardiovascular, Hema, Metabolic, Output, Neuro, Drugs), assessment, plan, and checklist with Copy latest entry carrying forward pending checklist items only | | **Vitals** | Temp, BP, HR, RR, O₂ saturation with history | | **Labs** | CBC, urinalysis, Blood Chemistry, ABG (with auto-calculated pO2/FiO2 and conditional Desired FiO2 when FiO2 > 21% or pO2 < 60; target PaO2 = 60), and Others (custom label + freeform result); trend comparison applies to structured templates, while Others stays plain | | **Medications** | Active medication list with status tracking | @@ -64,7 +64,7 @@ Each open patient has eight focused tabs: ### Reporting & Export - **Profile summary** follows room/name header, main/referral service split, `Dx`, and optional `Notes` blocks. -- **FRICHMOND summary** uses `ROOM - LASTNAME, First — MM-DD-YYYY`, removes orders, and includes daily vitals min–max ranges. +- **FRICHMOND summary** uses `ROOM - LASTNAME, First — MM-DD-YYYY`, removes orders, includes daily vitals min–max ranges, and outputs checklist pending/completed lines when present. - **Vitals summary** supports multi-patient selection and date/time window filtering. - **Labs summary** supports arbitrary instance selection per patient; comparison mode runs only when exactly 2 instances of the same non-Others lab template are selected. - **Orders summary** supports date/time filtering using order date/time fields and preserves order text exactly as entered. diff --git a/package.json b/package.json index d654078..2416a7f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "app", "private": true, - "version": "1.3.8", + "version": "1.3.9", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 75334e6..034debb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import { import { useLiveQuery } from 'dexie-react-hooks' import { db } from './db' import type { + DailyChecklistItem, DailyUpdate, LabEntry, MedicationEntry, @@ -230,8 +231,22 @@ const initialDailyUpdateForm: DailyUpdateFormState = { other: '', assessment: '', plans: '', + checklist: [], } +const normalizeDailyChecklist = (checklist: DailyChecklistItem[] | undefined): DailyChecklistItem[] => { + if (!Array.isArray(checklist)) return [] + return checklist + .map((item) => ({ + text: typeof item?.text === 'string' ? item.text.trim() : '', + completed: item?.completed === true, + })) + .filter((item) => item.text.length > 0) +} + +const toPendingDailyChecklist = (checklist: DailyChecklistItem[] | undefined): DailyChecklistItem[] => + normalizeDailyChecklist(checklist).filter((item) => !item.completed) + const getNormalAaDo2 = (age: number): number => { const decadesAboveThirty = age > 30 ? Math.floor((age - 30) / 10) : 0 return 15 + decadesAboveThirty * 3 @@ -326,6 +341,7 @@ function App() { const [profileForm, setProfileForm] = useState(initialProfileForm) const [dailyDate, setDailyDate] = useState(() => toLocalISODate()) const [dailyUpdateForm, setDailyUpdateForm] = useState(initialDailyUpdateForm) + const [dailyChecklistDraft, setDailyChecklistDraft] = useState('') const [dailyUpdateId, setDailyUpdateId] = useState(undefined) const [vitalForm, setVitalForm] = useState(() => initialVitalForm()) const [editingVitalId, setEditingVitalId] = useState(null) @@ -1118,8 +1134,26 @@ function App() { const loadDailyUpdate = async (patientId: number, date: string) => { const update = await db.dailyUpdates.where('[patientId+date]').equals([patientId, date]).first() if (!update) { + const updates = await db.dailyUpdates.where('patientId').equals(patientId).toArray() + const latestPriorUpdate = updates + .filter((candidate) => candidate.date < date) + .reduce((latest, candidate) => { + if (!latest) return candidate + if (candidate.date !== latest.date) return candidate.date > latest.date ? candidate : latest + const latestTimestamp = Date.parse(latest.lastUpdated) + const candidateTimestamp = Date.parse(candidate.lastUpdated) + if (Number.isFinite(candidateTimestamp) && Number.isFinite(latestTimestamp)) { + return candidateTimestamp >= latestTimestamp ? candidate : latest + } + return candidate + }, null) + setDailyUpdateId(undefined) - setDailyUpdateForm(initialDailyUpdateForm) + setDailyUpdateForm({ + ...initialDailyUpdateForm, + checklist: toPendingDailyChecklist(latestPriorUpdate?.checklist), + }) + setDailyChecklistDraft('') setDailyDirty(false) return } @@ -1138,7 +1172,9 @@ function App() { other: update.other, assessment: update.assessment, plans: update.plans, + checklist: normalizeDailyChecklist(update.checklist), }) + setDailyChecklistDraft('') setDailyDirty(false) } @@ -1156,6 +1192,7 @@ function App() { other: update.other, assessment: update.assessment, plans: update.plans, + checklist: toPendingDailyChecklist(update.checklist), }) setDailyDirty(true) }, []) @@ -2506,6 +2543,10 @@ function App() { other: 'Ambulates with minimal assistance; tolerates soft diet.', assessment: 'CAP, clinically improving with stable cardiorespiratory parameters.', plans: 'Continue current antibiotics today then reassess de-escalation.\nRepeat CBC/electrolytes tomorrow.\nCoordinate discharge planning once clinically stable.', + checklist: [ + { text: 'For CTT insertion, 02-25', completed: false }, + { text: 'Chest CT with contrast', completed: true }, + ], lastUpdated: now, }) @@ -3138,32 +3179,6 @@ function App() { onOpenPhotoById={openPhotoById} /> -
- - updateProfileField('plans', nextValue)} - attachments={mentionableAttachments} - attachmentByTitle={mentionableAttachmentByTitle} - onOpenPhotoById={openPhotoById} - /> -
-
- - updateProfileField('pendings', nextValue)} - attachments={mentionableAttachments} - attachmentByTitle={mentionableAttachmentByTitle} - onOpenPhotoById={openPhotoById} - /> -
No saved daily entries yet.

)}
-

Copies all daily fields (FRICHMOND, assessment, plan) from the latest saved date.

+

Copies FRICHMOND fields, assessment, plan, and only pending checklist items from the latest saved date.

+
+ +
+ setDailyChecklistDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key !== 'Enter') return + const nextItem = dailyChecklistDraft.trim() + if (!nextItem) return + event.preventDefault() + setDailyUpdateForm({ + ...dailyUpdateForm, + checklist: [...dailyUpdateForm.checklist, { text: nextItem, completed: false }], + }) + setDailyChecklistDraft('') + setDailyDirty(true) + }} + /> + +
+ {dailyUpdateForm.checklist.length > 0 ? ( +
+ {dailyUpdateForm.checklist.map((item, index) => ( +
+ { + setDailyUpdateForm({ + ...dailyUpdateForm, + checklist: dailyUpdateForm.checklist.map((entry, entryIndex) => + entryIndex === index ? { ...entry, completed: event.target.checked } : entry, + ), + }) + setDailyDirty(true) + }} + /> +

+ {item.text} +

+ +
+ ))} +
+ ) : ( +

No checklist items yet.

+ )} +
@@ -4531,7 +4626,7 @@ function App() { ['Open a patient', 'Tap Open on any patient card to enter the patient view with all clinical tabs.'], ['Navigate on mobile', 'The bottom bar shows all 8 patient sections in a 2-row grid — tap any to switch. Use ← Back to return to the patient list.'], ['Switch patients', 'Tap the patient name at the top of any tab to jump to a different patient while staying on the same section.'], - ['Write daily notes', 'Open FRICH, pick today\'s date, fill F-R-I-C-H-M-O-N-D fields and plan. Tap Copy latest entry to carry forward yesterday\'s note.'], + ['Write daily notes', 'Open FRICH, pick today\'s date, fill F-R-I-C-H-M-O-N-D fields, plan, and checklist. Copy latest carries only pending checklist items.'], ['Generate reports', 'Open Report, configure filters, tap any export button to preview, then Copy full text to paste into a handoff or chart.'], ['Back up your data', 'Go to Settings → Export backup regularly, especially before switching devices or browsers.'], ] as [string, string][]).map(([title, detail], i) => ( @@ -4548,8 +4643,8 @@ function App() {

Patient tabs

{([ - ['Profile', 'Demographics, diagnosis, clinical summary, HPI, PMH, PE, plans, pendings'], - ['FRICH', 'Date-based F-R-I-C-H-M-O-N-D daily notes, assessment & plan'], + ['Profile', 'Demographics, diagnosis, clinical summary, HPI, PMH, PE, and clerk notes'], + ['FRICH', 'Date-based F-R-I-C-H-M-O-N-D daily notes, assessment, plan, and checklist'], ['Vitals', 'Structured BP/HR/RR/Temp/SpO2 log with date & time entries'], ['Labs', 'CBC, UA, Blood Chem, ABG templates + free-text with date/time'], ['Meds', 'Structured medication list: drug, dose, route, frequency, status'], @@ -4864,6 +4959,7 @@ function App() { {dailyDate} and replace it with a duplicate of {pendingLatestDailyUpdate?.date ?? '-'}? + Only pending checklist items are carried over.

diff --git a/src/features/reporting/reportBuilders.ts b/src/features/reporting/reportBuilders.ts index edd5722..306398b 100644 --- a/src/features/reporting/reportBuilders.ts +++ b/src/features/reporting/reportBuilders.ts @@ -53,6 +53,10 @@ type DailySummaryInput = { other: string assessment: string plans: string + checklist: Array<{ + text: string + completed: boolean + }> } const labTemplatesById = new Map(LAB_TEMPLATES.map((template) => [template.id, template] as const)) @@ -410,6 +414,14 @@ export const toDailySummary = ( dailyDate: string, ) => { const vitalsLine = buildDailyVitalsRangeLine(vitalsEntries, dailyDate) + const pendingChecklist = (update.checklist ?? []) + .filter((item) => !item.completed) + .map((item) => item.text.trim()) + .filter(Boolean) + const completedChecklist = (update.checklist ?? []) + .filter((item) => item.completed) + .map((item) => item.text.trim()) + .filter(Boolean) const lines = [ `${formatPatientHeader(patient)} — ${formatDateMMDDYYYY(dailyDate)}`, @@ -426,6 +438,8 @@ export const toDailySummary = ( update.other ? `Other: ${update.other}` : '', update.assessment ? `Assessment: ${update.assessment}` : '', update.plans ? `Plan: ${update.plans}` : '', + pendingChecklist.length > 0 ? `Checklist pending: ${pendingChecklist.join('; ')}` : '', + completedChecklist.length > 0 ? `Checklist completed: ${completedChecklist.join('; ')}` : '', ] return lines.filter(Boolean).join('\n') @@ -541,4 +555,4 @@ export const toLabsSummary = (patient: Patient, labEntries: LabEntry[], selected return `${formatPatientHeader(patient)}\nNo selected labs.` } return [formatPatientHeader(patient), ...blocks].join('\n\n') -} \ No newline at end of file +} diff --git a/src/types.ts b/src/types.ts index 8c1cd87..a3a4c12 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,9 +41,15 @@ export interface DailyUpdate { other: string assessment: string plans: string + checklist: DailyChecklistItem[] lastUpdated: string } +export interface DailyChecklistItem { + text: string + completed: boolean +} + export interface VitalEntry { id?: number patientId: number