Both pages were partial ports — HospitalCourse had a single "notes
textarea separated by blank lines" instead of the dynamic note cards
vanilla shipped, and ChartReview lacked per-visit type / specialist
/ specialty fields plus the Additional Labs block. These commits
close the gap.
HospitalCourse (ports public/components/hospital.html +
public/js/hospitalCourse.js @be14578):
client/src/pages/hospital/DictatableCard.tsx — reusable card
(title + date + meta children + content + per-card Recorder).
Each note gets its own recorder so dictating into one card
doesn't interrupt another in progress.
client/src/pages/hospital/LabsList.tsx — dynamic (date, values)
rows with add/remove.
client/src/pages/hospital/ClarifyButton.tsx — "What's Missing?"
→ POST /api/hospital-course-clarify, renders the returned
questions inline.
client/src/pages/HospitalCourse.tsx — rewritten: ED Note card
(date + ED labs + content + dictate), H&P card (date + content
+ dictate), Progress Notes as dynamic cards (date + type
select matching vanilla's 6 options + content + per-card
dictate + remove), separate Labs list, instructions,
EditableResult output, ClarifyButton. Save/Load round-trips the
entire structured note-set via JSON in the transcript column.
ChartReview (ports public/components/chart.html +
public/js/chartReview.js @be14578):
client/src/pages/ChartReview.tsx — rewritten: each visit now has
its own date + visit-type select (outpatient/subspecialty/ed),
and when subspecialty is selected the specialist-name +
specialty fields appear inline. Per-visit labs textarea.
New Additional Labs block (reuses hospital/LabsList) for labs
not tied to a visit. Submit splits visits by type into the
server's visits / subspecialty / edVisits arrays, matching
src/routes/chartReview.ts.
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
// ============================================================
|
|
// LabsList — dynamic (date, values) rows. Port of hc-labs-container
|
|
// / cr-labs-container in the vanilla hospital.html / chart.html
|
|
// components. Pure controlled component — parent owns the array.
|
|
// ============================================================
|
|
|
|
export interface LabRow { date: string; values: string }
|
|
|
|
interface Props {
|
|
value: LabRow[];
|
|
onChange: (next: LabRow[]) => void;
|
|
placeholder?: string;
|
|
testIdPrefix?: string;
|
|
}
|
|
|
|
const input = 'rounded-md border border-input bg-background px-2 py-1 text-sm';
|
|
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';
|
|
|
|
export function emptyLab(): LabRow { return { date: '', values: '' }; }
|
|
|
|
export default function LabsList({ value, onChange, placeholder, testIdPrefix = 'labs' }: Props) {
|
|
function patch(i: number, next: Partial<LabRow>) {
|
|
onChange(value.map((r, idx) => (idx === i ? { ...r, ...next } : r)));
|
|
}
|
|
function add() { onChange([...value, emptyLab()]); }
|
|
function remove(i: number) { onChange(value.filter((_, idx) => idx !== i)); }
|
|
|
|
return (
|
|
<div className="space-y-2" data-testid={testIdPrefix}>
|
|
{value.length === 0 && (
|
|
<div className="text-xs text-muted-foreground italic">No labs added.</div>
|
|
)}
|
|
{value.map((row, i) => (
|
|
<div key={i} className="flex flex-col sm:flex-row items-start gap-2" data-testid={testIdPrefix + '-row-' + i}>
|
|
<input
|
|
type="date"
|
|
value={row.date}
|
|
onChange={(e) => patch(i, { date: e.target.value })}
|
|
className={input + ' w-40 shrink-0'}
|
|
/>
|
|
<textarea
|
|
value={row.values}
|
|
onChange={(e) => patch(i, { values: e.target.value })}
|
|
placeholder={placeholder || 'e.g. WBC 12.5, H/H 10.2/31, BMP Na 138, K 3.5'}
|
|
className={input + ' flex-1 min-h-[60px] font-mono text-xs'}
|
|
/>
|
|
<button type="button" onClick={() => remove(i)} className="text-xs text-destructive hover:text-red-700" title="Remove">
|
|
🗑
|
|
</button>
|
|
</div>
|
|
))}
|
|
<button type="button" onClick={add} className={btn}>+ Add lab</button>
|
|
</div>
|
|
);
|
|
}
|