"use client"; import { FormEvent, useMemo, useState } from "react"; import { Activity, ClipboardPlus, FileText, HeartPulse, Search, ShieldAlert, Stethoscope } 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 { Dialog } from "@/components/ui/dialog"; import { Input, 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 { ChronicCondition, ChronicConditionStatus, HealthAdminData, HealthAnomalyReview, HealthProfile, HealthReviewSeverity, HealthReviewStatus, VitalRecord, VitalSource, } from "@/modules/health/types"; import { CHRONIC_CONDITION_STATUS_LABELS, HEALTH_REVIEW_SEVERITY_LABELS, HEALTH_REVIEW_STATUS_LABELS, VITAL_SOURCE_LABELS, } from "@/modules/health/types"; type HealthAdminClientProps = { canManage: boolean; initialData: HealthAdminData; }; type HealthTab = "profiles" | "vitals" | "conditions" | "reviews"; type ProfileForm = { allergyNotes: string; medicalHistory: string; medicationNotes: string; careRestrictions: string; emergencyNotes: string; }; const tabs: Array<{ value: HealthTab; label: string }> = [ { value: "profiles", label: "健康档案" }, { value: "vitals", label: "生命体征" }, { value: "conditions", label: "慢病记录" }, { value: "reviews", label: "异常复核" }, ]; const emptyProfileForm: ProfileForm = { allergyNotes: "", medicalHistory: "", medicationNotes: "", careRestrictions: "", emergencyNotes: "", }; const emptyVitalForm = { elderId: "", recordedAt: "", source: "manual" as VitalSource, systolicBp: "", diastolicBp: "", heartRate: "", temperatureTenths: "", spo2: "", bloodGlucoseTenths: "", weightTenths: "", notes: "", }; const emptyConditionForm = { elderId: "", name: "", status: "active" as ChronicConditionStatus, diagnosedAt: "", treatmentNotes: "", followUpNotes: "", }; function formatDateTime(value: string | undefined): string { return value ? new Date(value).toLocaleString("zh-CN") : "-"; } function formatTenths(value: number | undefined, suffix = ""): string { return value === undefined ? "-" : `${(value / 10).toFixed(1)}${suffix}`; } function optionalNumber(value: string): number | undefined { const trimmed = value.trim(); if (!trimmed) { return undefined; } const numeric = Number(trimmed); return Number.isFinite(numeric) ? numeric : undefined; } function optionalTenths(value: string): number | undefined { const numeric = optionalNumber(value); return numeric === undefined ? undefined : Math.round(numeric * 10); } function profileToForm(profile: HealthProfile | undefined): ProfileForm { return profile ? { allergyNotes: profile.allergyNotes, medicalHistory: profile.medicalHistory, medicationNotes: profile.medicationNotes, careRestrictions: profile.careRestrictions, emergencyNotes: profile.emergencyNotes, } : emptyProfileForm; } function severityBadgeVariant(severity: HealthReviewSeverity): "secondary" | "warning" | "danger" { if (severity === "critical") { return "danger"; } return severity === "warning" ? "warning" : "secondary"; } function reviewStatusBadgeVariant(status: HealthReviewStatus): "success" | "secondary" | "warning" { if (status === "pending") { return "warning"; } return status === "resolved" ? "success" : "secondary"; } export function HealthAdminClient({ canManage, initialData }: HealthAdminClientProps): React.ReactElement { const [data, setData] = useState(initialData); const [activeTab, setActiveTab] = useState("profiles"); const [query, setQuery] = useState(""); const [vitalSourceFilter, setVitalSourceFilter] = useState<"all" | VitalSource>("all"); const [conditionStatusFilter, setConditionStatusFilter] = useState<"all" | ChronicConditionStatus>("all"); const [reviewStatusFilter, setReviewStatusFilter] = useState<"all" | HealthReviewStatus>("all"); const [reviewSeverityFilter, setReviewSeverityFilter] = useState<"all" | HealthReviewSeverity>("all"); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const [editingProfile, setEditingProfile] = useState(); const [profileElderId, setProfileElderId] = useState(""); const [profileForm, setProfileForm] = useState(emptyProfileForm); const [isVitalOpen, setIsVitalOpen] = useState(false); const [vitalForm, setVitalForm] = useState(emptyVitalForm); const [isConditionOpen, setIsConditionOpen] = useState(false); const [conditionForm, setConditionForm] = useState(emptyConditionForm); const [reviewTarget, setReviewTarget] = useState(); const [reviewStatus, setReviewStatus] = useState("reviewed"); const [reviewNotes, setReviewNotes] = useState(""); const elderOptions: SelectOption[] = useMemo( () => [ { value: "", label: "选择老人", disabled: true }, ...data.elders.map((elder) => ({ value: elder.id, label: elder.name })), ], [data.elders], ); const normalizedQuery = query.trim().toLowerCase(); const profilesByElderId = useMemo( () => new Map(data.profiles.map((profile) => [profile.elderId, profile])), [data.profiles], ); const filteredElders = useMemo( () => data.elders.filter((elder) => { if (!normalizedQuery) { return true; } const profile = profilesByElderId.get(elder.id); return [elder.name, profile?.allergyNotes, profile?.medicalHistory, profile?.emergencyNotes] .join(" ") .toLowerCase() .includes(normalizedQuery); }), [data.elders, normalizedQuery, profilesByElderId], ); const filteredVitals = useMemo( () => data.vitals.filter((vital) => { const matchesQuery = !normalizedQuery || vital.elderName.toLowerCase().includes(normalizedQuery); const matchesSource = vitalSourceFilter === "all" || vital.source === vitalSourceFilter; return matchesQuery && matchesSource; }), [data.vitals, normalizedQuery, vitalSourceFilter], ); const filteredConditions = useMemo( () => data.chronicConditions.filter((condition) => { const matchesQuery = !normalizedQuery ? true : [condition.elderName, condition.name].join(" ").toLowerCase().includes(normalizedQuery); const matchesStatus = conditionStatusFilter === "all" || condition.status === conditionStatusFilter; return matchesQuery && matchesStatus; }), [conditionStatusFilter, data.chronicConditions, normalizedQuery], ); const filteredReviews = useMemo( () => data.reviews.filter((review) => { const matchesQuery = !normalizedQuery ? true : [review.elderName, review.title, review.description].join(" ").toLowerCase().includes(normalizedQuery); const matchesStatus = reviewStatusFilter === "all" || review.status === reviewStatusFilter; const matchesSeverity = reviewSeverityFilter === "all" || review.severity === reviewSeverityFilter; return matchesQuery && matchesStatus && matchesSeverity; }), [data.reviews, normalizedQuery, reviewSeverityFilter, reviewStatusFilter], ); async function refreshData(): Promise { const response = await fetch("/api/health/admin"); const result = (await response.json()) as ApiResult<{ data: HealthAdminData }>; if (result.success) { setData(result.data); } } function openProfileDialog(elderId: string): void { const profile = profilesByElderId.get(elderId); setEditingProfile(profile); setProfileElderId(elderId); setProfileForm(profileToForm(profile)); setMessage(""); } function closeProfileDialog(): void { if (isPending) { return; } setEditingProfile(undefined); setProfileElderId(""); setProfileForm(emptyProfileForm); } async function saveProfile(event: FormEvent): Promise { event.preventDefault(); if (!canManage || !profileElderId) { setMessage("当前角色无权维护健康档案"); return; } setIsPending(true); setMessage(""); const response = await fetch(`/api/health/profiles/${profileElderId}`, { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify(profileForm), }); const result = (await response.json()) as ApiResult<{ profile: HealthProfile }>; setIsPending(false); setMessage(result.reason); if (result.success) { closeProfileDialog(); await refreshData(); } } async function saveVital(event: FormEvent): Promise { event.preventDefault(); if (!canManage) { setMessage("当前角色无权录入生命体征"); return; } setIsPending(true); setMessage(""); const response = await fetch("/api/health/vitals", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ elderId: vitalForm.elderId, recordedAt: vitalForm.recordedAt, source: vitalForm.source, systolicBp: optionalNumber(vitalForm.systolicBp), diastolicBp: optionalNumber(vitalForm.diastolicBp), heartRate: optionalNumber(vitalForm.heartRate), temperatureTenths: optionalTenths(vitalForm.temperatureTenths), spo2: optionalNumber(vitalForm.spo2), bloodGlucoseTenths: optionalTenths(vitalForm.bloodGlucoseTenths), weightTenths: optionalTenths(vitalForm.weightTenths), notes: vitalForm.notes, }), }); const result = (await response.json()) as ApiResult<{ vital: VitalRecord; review?: HealthAnomalyReview }>; setIsPending(false); setMessage(result.reason); if (result.success) { setIsVitalOpen(false); setVitalForm(emptyVitalForm); await refreshData(); } } async function saveCondition(event: FormEvent): Promise { event.preventDefault(); if (!canManage) { setMessage("当前角色无权新增慢病记录"); return; } setIsPending(true); setMessage(""); const response = await fetch("/api/health/chronic-conditions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(conditionForm), }); const result = (await response.json()) as ApiResult<{ condition: ChronicCondition }>; setIsPending(false); setMessage(result.reason); if (result.success) { setIsConditionOpen(false); setConditionForm(emptyConditionForm); await refreshData(); } } function openReviewDialog(review: HealthAnomalyReview): void { setReviewTarget(review); setReviewStatus(review.status === "pending" ? "reviewed" : review.status); setReviewNotes(review.resolutionNotes); setMessage(""); } function closeReviewDialog(): void { if (isPending) { return; } setReviewTarget(undefined); setReviewStatus("reviewed"); setReviewNotes(""); } async function saveReview(event: FormEvent): Promise { event.preventDefault(); if (!canManage || !reviewTarget) { setMessage("当前角色无权处理异常复核"); return; } setIsPending(true); setMessage(""); const response = await fetch(`/api/health/reviews/${reviewTarget.id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ status: reviewStatus, resolutionNotes: reviewNotes }), }); const result = (await response.json()) as ApiResult<{ review: HealthAnomalyReview }>; setIsPending(false); setMessage(result.reason); if (result.success) { closeReviewDialog(); await refreshData(); } } return (
{tabs.map((tab) => ( ))}
{activeTab === "vitals" ? ( setConditionStatusFilter(value as "all" | ChronicConditionStatus)} options={[ { value: "all", label: "全部状态" }, ...Object.entries(CHRONIC_CONDITION_STATUS_LABELS).map(([value, label]) => ({ value, label })), ]} value={conditionStatusFilter} /> ) : null} {activeTab === "reviews" ? ( <> setReviewSeverityFilter(value as "all" | HealthReviewSeverity)} options={[ { value: "all", label: "全部级别" }, ...Object.entries(HEALTH_REVIEW_SEVERITY_LABELS).map(([value, label]) => ({ value, label })), ]} value={reviewSeverityFilter} /> ) : null} {activeTab === "vitals" ? ( ) : null} {activeTab === "conditions" ? ( ) : null}
{message ? (

{message}

) : null} {activeTab === "profiles" ? ( 健康档案 按老人维护过敏、病史、用药和紧急说明。 老人 过敏 用药 紧急说明 操作 {filteredElders.map((elder) => { const profile = profilesByElderId.get(elder.id); return ( {elder.name} {profile?.allergyNotes || "-"} {profile?.medicationNotes || "-"} {profile?.emergencyNotes || "-"} ); })}
) : null} {activeTab === "vitals" ? ( [ vital.elderName, formatDateTime(vital.recordedAt), VITAL_SOURCE_LABELS[vital.source], vital.systolicBp && vital.diastolicBp ? `${vital.systolicBp}/${vital.diastolicBp}` : "-", vital.heartRate?.toString() ?? "-", formatTenths(vital.temperatureTenths, "°C"), vital.spo2 === undefined ? "-" : `${vital.spo2}%`, vital.notes || "-", ])} title="生命体征" /> ) : null} {activeTab === "conditions" ? ( [ condition.elderName, condition.name, CHRONIC_CONDITION_STATUS_LABELS[condition.status], formatDateTime(condition.diagnosedAt), condition.treatmentNotes || "-", condition.followUpNotes || "-", ])} title="慢病记录" /> ) : null} {activeTab === "reviews" ? ( 异常复核 处理生命体征触发或手工创建的健康异常。 老人 异常 级别 状态 处理人 操作 {filteredReviews.map((review) => ( {review.elderName}

{review.title}

{review.description}

{HEALTH_REVIEW_SEVERITY_LABELS[review.severity]} {HEALTH_REVIEW_STATUS_LABELS[review.status]} {review.reviewedByName ?? "-"}
))} {filteredReviews.length === 0 ? ( 暂无异常复核 ) : null}
) : null} 0} title={editingProfile ? "编辑健康档案" : "新增健康档案"} width="lg" >