"use client"; import { FormEvent, useMemo, useState } from "react"; import { BedDouble, DoorOpen, History, MoveRight, UserCheck, UserMinus, Wrench } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Textarea } from "@/components/ui/input"; import { Select, type SelectOption } from "@/components/ui/select"; 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; initialRooms: Room[]; initialBeds: FacilityBed[]; initialAdmissions: Admission[]; initialElders: Elder[]; permissions: Permission[]; }; type WorkspaceTab = "overview" | "operations" | "history"; const workspaceTabs: Array<{ value: WorkspaceTab; label: string }> = [ { value: "overview", label: "概览" }, { value: "operations", label: "办理" }, { value: "history", label: "记录" }, ]; const bedStatusLabels: Record = { available: "空闲", occupied: "占用", maintenance: "维修", disabled: "停用", }; const admissionStatusLabels: Record = { active: "在住", transferred: "已换床", discharged: "已退住", }; function formatDateTime(value: string): string { return new Date(value).toLocaleString("zh-CN"); } function bedBadgeVariant(status: BedStatus): "success" | "secondary" | "warning" | "danger" { switch (status) { case "available": return "success"; case "occupied": return "secondary"; case "maintenance": return "warning"; case "disabled": return "danger"; } } function admissionBedLabel(admission: Admission): string { return `${admission.roomName}-${admission.bedCode}`; } function makeSelectOptions(items: Array<{ id: string; label: string }>, emptyLabel: string): SelectOption[] { return [ { value: "", label: emptyLabel, disabled: true }, ...items.map((item) => ({ value: item.id, label: item.label })), ]; } export function BedsWorkspace({ bedContexts = {}, initialRooms, initialBeds, initialAdmissions, initialElders, permissions, }: BedsWorkspaceProps): React.ReactElement { const [rooms, setRooms] = useState(initialRooms); const [beds, setBeds] = useState(initialBeds); const [admissions, setAdmissions] = useState(initialAdmissions); const [elders, setElders] = useState(initialElders); const [activeTab, setActiveTab] = useState("overview"); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const [admitElderId, setAdmitElderId] = useState(""); const [admitBedId, setAdmitBedId] = useState(""); const [admitNotes, setAdmitNotes] = useState(""); const [transferAdmissionId, setTransferAdmissionId] = useState(""); const [transferBedId, setTransferBedId] = useState(""); const [transferNotes, setTransferNotes] = useState(""); const [dischargeAdmissionId, setDischargeAdmissionId] = useState(""); const [dischargeNotes, setDischargeNotes] = useState(""); const canManageAdmissions = permissions.includes("admission:manage"); const activeAdmissions = useMemo(() => admissions.filter((admission) => admission.status === "active"), [admissions]); const activeElderIds = useMemo(() => new Set(activeAdmissions.map((admission) => admission.elderId)), [activeAdmissions]); const availableBeds = useMemo(() => beds.filter((bed) => bed.status === "available"), [beds]); const occupiedBeds = beds.filter((bed) => bed.status === "occupied").length; const maintenanceBeds = beds.filter((bed) => bed.status === "maintenance").length; const occupancyRate = beds.length > 0 ? Math.round((occupiedBeds / beds.length) * 100) : 0; const admissionEligibleElders = elders.filter((elder) => !activeElderIds.has(elder.id)); const elderOptions = makeSelectOptions( admissionEligibleElders.map((elder) => ({ id: elder.id, label: `${elder.name} / ${elder.phone}` })), "选择老人", ); const bedOptions = makeSelectOptions( availableBeds.map((bed) => ({ id: bed.id, label: `${bed.roomName}-${bed.code}` })), "选择空闲床位", ); const activeAdmissionOptions = makeSelectOptions( activeAdmissions.map((admission) => ({ id: admission.id, label: `${admission.elderName} / ${admissionBedLabel(admission)}`, })), "选择在住记录", ); async function refreshData(): Promise { const [eldersResponse, roomsResponse, bedsResponse, admissionsResponse] = await Promise.all([ fetch("/api/elders"), fetch("/api/facilities/rooms"), fetch("/api/facilities/beds"), fetch("/api/admissions"), ]); const [eldersResult, roomsResult, bedsResult, admissionsResult] = (await Promise.all([ eldersResponse.json(), roomsResponse.json(), bedsResponse.json(), admissionsResponse.json(), ])) as [ ApiResult<{ elders: Elder[] }>, ApiResult<{ rooms: Room[] }>, ApiResult<{ beds: FacilityBed[] }>, ApiResult<{ admissions: Admission[] }>, ]; if (eldersResult.success) { setElders(eldersResult.elders); } if (roomsResult.success) { setRooms(roomsResult.rooms); } if (bedsResult.success) { setBeds(bedsResult.beds); } if (admissionsResult.success) { setAdmissions(admissionsResult.admissions); } } async function submitAdmission(event: FormEvent): Promise { event.preventDefault(); if (!canManageAdmissions) { setMessage("当前角色无权办理入住"); return; } if (!admitElderId || !admitBedId) { setMessage("请选择老人和空闲床位"); return; } setIsPending(true); setMessage(""); const response = await fetch("/api/admissions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ elderId: admitElderId, bedId: admitBedId, notes: admitNotes }), }); const result = (await response.json()) as ApiResult<{ admission: Admission }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setAdmitElderId(""); setAdmitBedId(""); setAdmitNotes(""); await refreshData(); setMessage(result.reason); } async function submitTransfer(event: FormEvent): Promise { event.preventDefault(); const admission = activeAdmissions.find((item) => item.id === transferAdmissionId); if (!canManageAdmissions) { setMessage("当前角色无权办理换床"); return; } if (!admission || !transferBedId) { setMessage("请选择在住记录和新床位"); return; } setIsPending(true); setMessage(""); const response = await fetch("/api/admissions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ elderId: admission.elderId, bedId: transferBedId, notes: transferNotes || "办理换床" }), }); const result = (await response.json()) as ApiResult<{ admission: Admission }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setTransferAdmissionId(""); setTransferBedId(""); setTransferNotes(""); await refreshData(); setMessage("换床已办理"); } async function submitDischarge(event: FormEvent): Promise { event.preventDefault(); if (!canManageAdmissions) { setMessage("当前角色无权办理退住"); return; } if (!dischargeAdmissionId) { setMessage("请选择在住记录"); return; } setIsPending(true); setMessage(""); const response = await fetch(`/api/admissions/${dischargeAdmissionId}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ notes: dischargeNotes }), }); const result = (await response.json()) as ApiResult<{ admission: Admission }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setDischargeAdmissionId(""); setDischargeNotes(""); await refreshData(); setMessage(result.reason); } return (
{workspaceTabs.map((tab) => ( ))}
{message ? (

{message}

) : null} {activeTab === "overview" ? ( <>
房间 {rooms.length}
床位 {beds.length}

占用率 {occupancyRate}%

0 ? "border-amber-200 bg-amber-50/70" : undefined}>
0 ? "text-amber-700" : undefined}>维修床位 0 ? "mt-2 text-3xl text-amber-700" : "mt-2 text-3xl"}> {maintenanceBeds}
0 ? "size-5 text-amber-700" : "size-5 text-primary"} aria-hidden="true" />
在住 {activeAdmissions.length}
房间台账
房间 编号 容量 床位 状态 {rooms.map((room) => ( {room.name} {room.code} {room.capacity} {room.occupiedBedCount}/{room.bedCount} {room.status} ))} {rooms.length === 0 ? ( 暂无房间主数据 ) : null}
床位状态
房间 床位 状态 当前老人 入住上下文 备注 {beds.map((bed) => { const context = bedContexts[bed.id]; return ( {bed.roomName} {bed.code} {bedStatusLabels[bed.status]} {bed.notes || "-"} ); })} {beds.length === 0 ? ( 暂无床位主数据 ) : null}
) : null} {activeTab === "operations" ? (
选择未在住老人和空闲床位。
void submitAdmission(event)}>