import Link from "next/link"; import { Activity, AlertTriangle, BedDouble, BrainCircuit, CheckCircle2, HeartPulse, ListChecks, Megaphone, ShieldAlert, TabletSmartphone, Users } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Table } from "@/components/ui/table"; import type { ElderAiAnalysisBoardItem, ElderAiRiskLevel } from "@/modules/ai/types"; import type { AlertTriggerStatus } from "@/modules/alerts/types"; import { ALERT_TRIGGER_STATUS_LABELS } from "@/modules/alerts/types"; import type { AdmissionStatus, BedStatus, IncidentSeverity } from "@/modules/core/types"; import type { CareTaskPriority, CareTaskStatus } from "@/modules/care/types"; import { CARE_TASK_PRIORITY_LABELS, CARE_TASK_STATUS_LABELS } from "@/modules/care/types"; import { BedOccupancyChart, type BedOccupancyDatum } from "@/modules/dashboard/components/BedOccupancyChart"; import type { MaintenanceTicketPriority, MaintenanceTicketStatus } from "@/modules/devices/types"; import { MAINTENANCE_TICKET_PRIORITY_LABELS, MAINTENANCE_TICKET_STATUS_LABELS } from "@/modules/devices/types"; import { INCIDENT_SEVERITY_LABELS, INCIDENT_STATUS_LABELS } from "@/modules/emergency/types"; import type { FamilyFeedbackStatus, FamilyVisitStatus } from "@/modules/family/types"; import { FAMILY_FEEDBACK_STATUS_LABELS, FAMILY_VISIT_STATUS_LABELS } from "@/modules/family/types"; import type { HealthReviewSeverity, HealthReviewStatus } from "@/modules/health/types"; import { HEALTH_REVIEW_SEVERITY_LABELS, HEALTH_REVIEW_STATUS_LABELS } from "@/modules/health/types"; import type { NoticeStatus } from "@/modules/notices/types"; import { NOTICE_STATUS_LABELS } from "@/modules/notices/types"; export type DashboardMetric = { label: string; value: string; trend: string; icon: "admissions" | "ai" | "alerts" | "beds" | "care" | "devices" | "family" | "health" | "incidents" | "notices" | "users"; }; export type DashboardAdmission = { id: string; elderName: string; bedLabel: string; status: AdmissionStatus; admittedAt: string; dischargedAt?: string; }; export type DashboardIncident = { id: string; severity: IncidentSeverity; status: string; title: string; source: string; createdAt: string; }; export type DashboardCareTask = { id: string; assigneeLabel: string; elderName: string; priority: CareTaskPriority; scheduledAt: string; status: CareTaskStatus; title: string; }; export type DashboardHealthReview = { id: string; elderName: string; severity: HealthReviewSeverity; status: HealthReviewStatus; title: string; }; export type DashboardDeviceTicket = { id: string; deviceName: string; priority: MaintenanceTicketPriority; status: MaintenanceTicketStatus; title: string; updatedAt: string; }; export type DashboardNotice = { id: string; audience: string; status: NoticeStatus; title: string; updatedAt: string; }; export type DashboardAlertTrigger = { id: string; elderName: string; status: AlertTriggerStatus; title: string; updatedAt: string; }; export type DashboardFamilyVisit = { id: string; contactName: string; elderName: string; scheduledAt: string; status: FamilyVisitStatus; }; export type DashboardFamilyFeedback = { id: string; elderName: string; status: FamilyFeedbackStatus; updatedAt: string; }; type DashboardHomeProps = { alertTriggers?: DashboardAlertTrigger[]; alertsHref: string; aiBoardItems?: ElderAiAnalysisBoardItem[]; bedsHref: string; careHref: string; careTasks?: DashboardCareTask[]; deviceTickets?: DashboardDeviceTicket[]; devicesHref: string; eldersHref: string; emergencyHref: string; emergencyIncidents?: DashboardIncident[]; familyFeedback?: DashboardFamilyFeedback[]; familyHref: string; familyVisits?: DashboardFamilyVisit[]; healthHref: string; healthReviews?: DashboardHealthReview[]; metrics: DashboardMetric[]; notices?: DashboardNotice[]; noticesHref: string; occupancyData?: BedOccupancyDatum[]; recentAdmissions?: DashboardAdmission[]; incidents?: DashboardIncident[]; }; const metricIcons: Record = { admissions: CheckCircle2, ai: BrainCircuit, alerts: ShieldAlert, beds: BedDouble, care: ListChecks, devices: TabletSmartphone, family: Users, health: HeartPulse, incidents: HeartPulse, notices: Megaphone, users: Users, }; const admissionStatusLabels: Record = { active: "在住", transferred: "已换床", discharged: "已退住", }; const bedStatusLabels: Record = { available: "空闲", occupied: "占用", maintenance: "维修", disabled: "停用", }; function formatDateTime(value: string): string { return new Date(value).toLocaleString("zh-CN"); } function incidentVariant(severity: IncidentSeverity): "secondary" | "warning" | "danger" { switch (severity) { case "critical": return "danger"; case "warning": return "warning"; case "info": return "secondary"; } } const analysisRiskLabels: Record = { critical: "高危", high: "高风险", low: "低风险", medium: "中风险", unknown: "未知", }; function analysisRiskVariant(risk: ElderAiRiskLevel): "danger" | "secondary" | "success" | "warning" { switch (risk) { case "critical": return "danger"; case "high": case "medium": return "warning"; case "low": return "success"; case "unknown": return "secondary"; } } export function DashboardHome({ alertTriggers, alertsHref, aiBoardItems, bedsHref, careHref, careTasks, deviceTickets, devicesHref, eldersHref, emergencyHref, emergencyIncidents, familyFeedback, familyHref, familyVisits, healthHref, healthReviews, metrics, notices, noticesHref, occupancyData, recentAdmissions, incidents, }: DashboardHomeProps): React.ReactElement { const familyQueueItems: QueueItem[] = [ ...(familyVisits ?? []).map((visit) => ({ id: `visit-${visit.id}`, title: `${visit.elderName} 探访`, detail: `${visit.contactName} / ${formatDateTime(visit.scheduledAt)}`, badge: FAMILY_VISIT_STATUS_LABELS[visit.status], variant: visit.status === "requested" ? "warning" as const : "secondary" as const, meta: "探访", })), ...(familyFeedback ?? []).map((feedback) => ({ id: `feedback-${feedback.id}`, title: `${feedback.elderName} 家属反馈`, detail: formatDateTime(feedback.updatedAt), badge: FAMILY_FEEDBACK_STATUS_LABELS[feedback.status], variant: feedback.status === "open" || feedback.status === "in_progress" ? "warning" as const : "secondary" as const, meta: "反馈", })), ].slice(0, 6); const hasCoreQueues = careTasks !== undefined || healthReviews !== undefined || emergencyIncidents !== undefined; const hasCollaborationQueues = deviceTickets !== undefined || notices !== undefined || alertTriggers !== undefined || familyVisits !== undefined || familyFeedback !== undefined; return (

运营看板

老人、床位、护理、健康和应急事件概况。

{metrics.map((metric) => { const Icon = metricIcons[metric.icon]; return ( {metric.label} {metric.value}

{metric.trend}

); })}
{hasCoreQueues ? (
{careTasks !== undefined ? ( ({ id: task.id, title: task.title, detail: `${task.elderName || "公共区域"} / ${task.assigneeLabel || "未分配"} / ${formatDateTime(task.scheduledAt)}`, badge: CARE_TASK_STATUS_LABELS[task.status], variant: task.priority === "urgent" ? "danger" : task.priority === "high" ? "warning" : "secondary", meta: CARE_TASK_PRIORITY_LABELS[task.priority], }))} title="护理待办" /> ) : null} {healthReviews !== undefined ? ( ({ id: review.id, title: review.title, detail: review.elderName, badge: HEALTH_REVIEW_STATUS_LABELS[review.status], variant: review.severity === "critical" ? "danger" : "warning", meta: HEALTH_REVIEW_SEVERITY_LABELS[review.severity], }))} title="健康复核" /> ) : null} {emergencyIncidents !== undefined ? ( ({ id: incident.id, title: incident.title, detail: `${incident.source} / ${formatDateTime(incident.createdAt)}`, badge: INCIDENT_STATUS_LABELS[incident.status as keyof typeof INCIDENT_STATUS_LABELS], variant: incidentVariant(incident.severity), meta: INCIDENT_SEVERITY_LABELS[incident.severity], }))} title="安全应急" /> ) : null}
) : null} {hasCollaborationQueues ? (
{deviceTickets !== undefined ? ( ({ id: ticket.id, title: ticket.title, detail: `${ticket.deviceName} / ${formatDateTime(ticket.updatedAt)}`, badge: MAINTENANCE_TICKET_STATUS_LABELS[ticket.status], variant: ticket.priority === "urgent" ? "danger" : ticket.priority === "high" ? "warning" : "secondary", meta: MAINTENANCE_TICKET_PRIORITY_LABELS[ticket.priority], }))} title="设备工单" /> ) : null} {notices !== undefined ? ( ({ id: notice.id, title: notice.title, detail: `${notice.audience} / ${formatDateTime(notice.updatedAt)}`, badge: NOTICE_STATUS_LABELS[notice.status], variant: notice.status === "published" ? "success" : "secondary", meta: "公告", }))} title="公告通知" /> ) : null} {alertTriggers !== undefined ? ( ({ id: trigger.id, title: trigger.title, detail: `${trigger.elderName || "公共区域"} / ${formatDateTime(trigger.updatedAt)}`, badge: ALERT_TRIGGER_STATUS_LABELS[trigger.status], variant: trigger.status === "open" ? "danger" : trigger.status === "acknowledged" ? "warning" : "secondary", meta: "预警", }))} title="规则预警" /> ) : null} {familyVisits !== undefined || familyFeedback !== undefined ? ( ) : null}
) : null} {occupancyData !== undefined || recentAdmissions !== undefined ? (
{occupancyData !== undefined ? ( {occupancyData.map((item) => `${bedStatusLabels[item.label as BedStatus] ?? item.label} ${item.count}`).join(" / ")} ) : null} {recentAdmissions !== undefined ? ( 最近入住记录
老人 床位 状态 时间 {recentAdmissions.map((admission) => ( {admission.elderName} {admission.bedLabel} {admissionStatusLabels[admission.status]} {formatDateTime(admission.dischargedAt ?? admission.admittedAt)} ))} {recentAdmissions.length === 0 ? ( 暂无入住记录 ) : null}
) : null}
) : null} {aiBoardItems !== undefined ? (
来自已保存老人 AI 分析历史,按数据权限自动脱敏。
老人档案
{aiBoardItems.map((item) => (

{item.elderName}

{item.restricted ? "结果受限:缺少一个或多个数据范围权限" : item.result?.summary ?? item.errorReason ?? "暂无摘要"}

{item.dataScopes.join(" / ")} / {formatDateTime(item.createdAt)}

{item.status === "failed" ? "失败" : "已完成"} {!item.restricted && item.result ? ( {analysisRiskLabels[item.result.overallRiskLevel]} ) : null}
))} {aiBoardItems.length === 0 ?

暂无智能分析记录

: null}
) : null} {incidents !== undefined ? ( 未关闭故障 {incidents.map((incident) => (

{incident.source} / {formatDateTime(incident.createdAt)}

{incident.status}
))} {incidents.length === 0 ?

暂无未关闭故障

: null}
) : null}
); } type QueueItem = { id: string; badge: string; detail: string; meta: string; title: string; variant: "danger" | "secondary" | "success" | "warning"; }; function QueueCard({ emptyText, href, items, title, }: { emptyText: string; href: string; items: QueueItem[]; title: string; }): React.ReactElement { return ( {title} 进入 {items.map((item) => (

{item.title}

{item.detail}

{item.meta} {item.badge}
))} {items.length === 0 ?

{emptyText}

: null}
); }