feat: add elder bed operational context

This commit is contained in:
2026-07-03 00:46:17 -07:00
parent f620d6c3dd
commit 0ce8cb9644
9 changed files with 477 additions and 30 deletions

View File

@@ -12,8 +12,10 @@ import { Table } from "@/components/ui/table";
import type { ApiResult } from "@/modules/core/server/api";
import type { Admission, BedStatus, FacilityBed, Permission, Room } from "@/modules/core/types";
import type { Elder } from "@/modules/elders/types";
import type { BedOperationalContext, OperationalContextLine } from "@/modules/operations/types";
type BedsWorkspaceProps = {
bedContexts?: Record<string, BedOperationalContext>;
initialRooms: Room[];
initialBeds: FacilityBed[];
initialAdmissions: Admission[];
@@ -23,6 +25,12 @@ type BedsWorkspaceProps = {
type WorkspaceTab = "overview" | "operations" | "history";
const workspaceTabs: Array<{ value: WorkspaceTab; label: string }> = [
{ value: "overview", label: "概览" },
{ value: "operations", label: "办理" },
{ value: "history", label: "记录" },
];
const bedStatusLabels: Record<BedStatus, string> = {
available: "空闲",
occupied: "占用",
@@ -65,6 +73,7 @@ function makeSelectOptions(items: Array<{ id: string; label: string }>, emptyLab
}
export function BedsWorkspace({
bedContexts = {},
initialRooms,
initialBeds,
initialAdmissions,
@@ -246,22 +255,14 @@ export function BedsWorkspace({
return (
<div className="mx-auto flex w-full max-w-7xl flex-col gap-5">
<section className="flex flex-col gap-4 border-b pb-5 lg:flex-row lg:items-end lg:justify-between">
<div>
<Badge variant="success"></Badge>
<h1 className="mt-3 text-3xl font-semibold tracking-normal"></h1>
<p className="mt-2 text-sm text-muted-foreground">退</p>
</div>
<section className="flex border-b pb-4">
<div className="flex flex-wrap gap-2" role="tablist" aria-label="床位工作区">
{[
{ value: "overview", label: "概览" },
{ value: "operations", label: "办理" },
{ value: "history", label: "记录" },
].map((tab) => (
{workspaceTabs.map((tab) => (
<Button
aria-selected={activeTab === tab.value}
key={tab.value}
onClick={() => setActiveTab(tab.value as WorkspaceTab)}
onClick={() => setActiveTab(tab.value)}
role="tab"
type="button"
variant={activeTab === tab.value ? "default" : "outline"}
>
@@ -380,24 +381,33 @@ export function BedsWorkspace({
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body className="divide-y bg-card">
{beds.map((bed) => (
<Table.Row key={bed.id}>
<Table.Cell className="px-4 py-3">{bed.roomName}</Table.Cell>
<Table.Cell className="px-4 py-3 font-medium">{bed.code}</Table.Cell>
<Table.Cell className="px-4 py-3">
<Badge variant={bedBadgeVariant(bed.status)}>{bedStatusLabels[bed.status]}</Badge>
</Table.Cell>
<Table.Cell className="px-4 py-3">{bed.currentElderName ?? "-"}</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">{bed.notes || "-"}</Table.Cell>
</Table.Row>
))}
{beds.map((bed) => {
const context = bedContexts[bed.id];
return (
<Table.Row key={bed.id}>
<Table.Cell className="px-4 py-3">{bed.roomName}</Table.Cell>
<Table.Cell className="px-4 py-3 font-medium">{bed.code}</Table.Cell>
<Table.Cell className="px-4 py-3">
<Badge variant={bedBadgeVariant(bed.status)}>{bedStatusLabels[bed.status]}</Badge>
</Table.Cell>
<Table.Cell className="px-4 py-3">
<OccupantSummary context={context} fallbackName={bed.currentElderName} />
</Table.Cell>
<Table.Cell className="px-4 py-3">
<ContextLines lines={context?.lines ?? []} />
</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">{bed.notes || "-"}</Table.Cell>
</Table.Row>
);
})}
{beds.length === 0 ? (
<Table.Row>
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
</Table.Cell>
</Table.Row>
@@ -576,3 +586,50 @@ export function BedsWorkspace({
</div>
);
}
function contextToneClass(tone: OperationalContextLine["tone"]): string {
if (tone === "danger") {
return "text-destructive";
}
if (tone === "warning") {
return "text-amber-700";
}
return "text-muted-foreground";
}
function ContextLines({ lines }: { lines: OperationalContextLine[] }): React.ReactElement {
if (lines.length === 0) {
return <span className="text-muted-foreground">-</span>;
}
return (
<div className="grid gap-1">
{lines.map((line) => (
<p className={`max-w-[260px] truncate text-xs ${contextToneClass(line.tone)}`} key={`${line.kind}-${line.label}-${line.value}`}>
<span className="font-medium">{line.label}</span> {line.value}
</p>
))}
</div>
);
}
function OccupantSummary({
context,
fallbackName,
}: {
context: BedOperationalContext | undefined;
fallbackName: string | undefined;
}): React.ReactElement {
if (!context?.occupant) {
return <span className="text-muted-foreground">{fallbackName ?? "-"}</span>;
}
return (
<div>
<p className="font-medium">{context.occupant.elderName}</p>
<p className="text-xs text-muted-foreground">
{context.occupant.careLevel} / {context.occupant.status}
</p>
</div>
);
}