feat: complete ops dashboard AI board
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { listAiAnalysisBoard } from "@/modules/ai/server/analysis";
|
||||
import { listAlertCenterData } from "@/modules/alerts/server/operations";
|
||||
import { getCurrentAuthContext } from "@/modules/core/server/auth";
|
||||
import {
|
||||
listOperationalAdmissions,
|
||||
@@ -11,12 +13,27 @@ import { hasPermission } from "@/modules/core/server/permissions";
|
||||
import type { BedStatus, Permission } from "@/modules/core/types";
|
||||
import { listCareExecutionData } from "@/modules/care/server/operations";
|
||||
import { DashboardHome, type DashboardMetric } from "@/modules/dashboard/components/DashboardHome";
|
||||
import { listDeviceOperationsData } from "@/modules/devices/server/operations";
|
||||
import { listEmergencyIncidentData } from "@/modules/emergency/server/operations";
|
||||
import { listFamilyServiceData } from "@/modules/family/server/operations";
|
||||
import { listHealthAdminData } from "@/modules/health/server/operations";
|
||||
import { listNoticeCenterData } from "@/modules/notices/server/operations";
|
||||
import { getWorkspaceHref } from "@/modules/shared/lib/workspace-routing";
|
||||
|
||||
const BED_STATUSES: BedStatus[] = ["available", "occupied", "maintenance", "disabled"];
|
||||
const DASHBOARD_PERMISSIONS: Permission[] = ["elder:read", "facility:read", "admission:read", "incident:read", "care:read", "health:read"];
|
||||
const DASHBOARD_PERMISSIONS: Permission[] = [
|
||||
"elder:read",
|
||||
"facility:read",
|
||||
"admission:read",
|
||||
"incident:read",
|
||||
"care:read",
|
||||
"health:read",
|
||||
"device:read",
|
||||
"notice:read",
|
||||
"alert:read",
|
||||
"family:read",
|
||||
"ai:read",
|
||||
];
|
||||
|
||||
export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
const context = await getCurrentAuthContext();
|
||||
@@ -30,18 +47,31 @@ export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
redirect(getWorkspaceHref(context.organization?.slug, "/family"));
|
||||
}
|
||||
|
||||
const canReadElders = hasPermission(context.permissions, "elder:read");
|
||||
const canReadFacility = hasPermission(context.permissions, "facility:read");
|
||||
const canReadAdmissions = hasPermission(context.permissions, "admission:read");
|
||||
const canReadCare = hasPermission(context.permissions, "care:read");
|
||||
const canReadHealth = hasPermission(context.permissions, "health:read");
|
||||
const canReadIncidents = hasPermission(context.permissions, "incident:read");
|
||||
const canReadDevices = hasPermission(context.permissions, "device:read");
|
||||
const canReadNotices = hasPermission(context.permissions, "notice:read");
|
||||
const canReadAlerts = hasPermission(context.permissions, "alert:read");
|
||||
const canReadFamily = hasPermission(context.permissions, "family:read");
|
||||
const canReadAi = hasPermission(context.permissions, "ai:read");
|
||||
|
||||
const [elders, beds, admissions, incidents, careData, healthData, emergencyData] = await Promise.all([
|
||||
listOperationalElders(organizationId),
|
||||
listOperationalBeds(organizationId),
|
||||
listOperationalAdmissions(organizationId),
|
||||
listOperationalIncidents(organizationId),
|
||||
const [elders, beds, admissions, incidents, careData, healthData, emergencyData, deviceData, noticeData, alertData, familyData, aiBoardResult] = await Promise.all([
|
||||
organizationId && canReadElders ? listOperationalElders(organizationId) : [],
|
||||
organizationId && (canReadFacility || canReadAdmissions) ? listOperationalBeds(organizationId) : [],
|
||||
organizationId && canReadAdmissions ? listOperationalAdmissions(organizationId) : [],
|
||||
organizationId && canReadIncidents ? listOperationalIncidents(organizationId) : [],
|
||||
organizationId && canReadCare ? listCareExecutionData(organizationId) : undefined,
|
||||
organizationId && canReadHealth ? listHealthAdminData(organizationId) : undefined,
|
||||
organizationId && canReadIncidents ? listEmergencyIncidentData(organizationId) : undefined,
|
||||
organizationId && canReadDevices ? listDeviceOperationsData(organizationId) : undefined,
|
||||
organizationId && canReadNotices ? listNoticeCenterData(organizationId, context.account.id) : undefined,
|
||||
organizationId && canReadAlerts ? listAlertCenterData(organizationId) : undefined,
|
||||
organizationId && canReadFamily ? listFamilyServiceData(organizationId) : undefined,
|
||||
canReadAi ? listAiAnalysisBoard(context, 6) : undefined,
|
||||
]);
|
||||
|
||||
const activeElders = elders.filter((elder) => elder.status === "active").length;
|
||||
@@ -56,42 +86,62 @@ export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
const activeChronicConditions = healthData?.metrics.activeChronicConditions ?? 0;
|
||||
const openEmergency = emergencyData?.metrics.open ?? 0;
|
||||
const criticalEmergency = emergencyData?.metrics.critical ?? 0;
|
||||
const openDeviceTickets = deviceData?.metrics.openTickets ?? 0;
|
||||
const urgentDeviceTickets = deviceData?.metrics.urgentTickets ?? 0;
|
||||
const publishedNotices = noticeData?.metrics.published ?? 0;
|
||||
const unreadNotices = noticeData?.metrics.unread ?? 0;
|
||||
const openAlertTriggers = alertData?.metrics.openTriggers ?? 0;
|
||||
const criticalAlertTriggers = alertData?.metrics.criticalTriggers ?? 0;
|
||||
const pendingFamilyVisits = familyData?.metrics.pendingVisits ?? 0;
|
||||
const openFamilyFeedback = familyData?.metrics.openFeedback ?? 0;
|
||||
const aiBoardItems = aiBoardResult?.success ? aiBoardResult.data.items : undefined;
|
||||
|
||||
const metrics: DashboardMetric[] = [
|
||||
{ label: "在院老人", value: String(activeElders), trend: `待入住 ${pendingElders}`, icon: "users" },
|
||||
{ label: "床位占用", value: `${occupiedBeds}/${beds.length}`, trend: `空闲 ${availableBeds}`, icon: "beds" },
|
||||
{ label: "护理任务", value: String(pendingCare + inProgressCare), trend: `待处理 ${pendingCare} / 进行中 ${inProgressCare}`, icon: "care" },
|
||||
{ label: "健康复核", value: String(pendingHealthReviews), trend: `慢病管理 ${activeChronicConditions}`, icon: "health" },
|
||||
{ label: "当前入住", value: String(activeAdmissions), trend: `历史 ${admissions.length}`, icon: "admissions" },
|
||||
{ label: "应急事件", value: String(openEmergency), trend: `紧急 ${criticalEmergency} / 未关闭 ${openIncidents}`, icon: "incidents" },
|
||||
...(canReadElders ? [{ label: "在院老人", value: String(activeElders), trend: `待入住 ${pendingElders}`, icon: "users" as const }] : []),
|
||||
...(canReadFacility || canReadAdmissions ? [{ label: "床位占用", value: `${occupiedBeds}/${beds.length}`, trend: `空闲 ${availableBeds}`, icon: "beds" as const }] : []),
|
||||
...(canReadCare ? [{ label: "护理任务", value: String(pendingCare + inProgressCare), trend: `待处理 ${pendingCare} / 进行中 ${inProgressCare}`, icon: "care" as const }] : []),
|
||||
...(canReadHealth ? [{ label: "健康复核", value: String(pendingHealthReviews), trend: `慢病管理 ${activeChronicConditions}`, icon: "health" as const }] : []),
|
||||
...(canReadAdmissions ? [{ label: "当前入住", value: String(activeAdmissions), trend: `历史 ${admissions.length}`, icon: "admissions" as const }] : []),
|
||||
...(canReadIncidents ? [{ label: "应急事件", value: String(openEmergency), trend: `紧急 ${criticalEmergency} / 未关闭 ${openIncidents}`, icon: "incidents" as const }] : []),
|
||||
...(canReadDevices ? [{ label: "设备工单", value: String(openDeviceTickets), trend: `紧急 ${urgentDeviceTickets}`, icon: "devices" as const }] : []),
|
||||
...(canReadNotices ? [{ label: "公告通知", value: String(publishedNotices), trend: `未读 ${unreadNotices}`, icon: "notices" as const }] : []),
|
||||
...(canReadAlerts ? [{ label: "规则预警", value: String(openAlertTriggers), trend: `关键 ${criticalAlertTriggers}`, icon: "alerts" as const }] : []),
|
||||
...(canReadFamily ? [{ label: "家属服务", value: String(pendingFamilyVisits + openFamilyFeedback), trend: `探访 ${pendingFamilyVisits} / 反馈 ${openFamilyFeedback}`, icon: "family" as const }] : []),
|
||||
...(canReadAi && aiBoardItems ? [{ label: "智能分析", value: String(aiBoardItems.length), trend: "已保存分析历史", icon: "ai" as const }] : []),
|
||||
];
|
||||
|
||||
const occupancyData = BED_STATUSES.map((status) => ({
|
||||
label: status,
|
||||
count: beds.filter((bed) => bed.status === status).length,
|
||||
}));
|
||||
const occupancyData = canReadFacility || canReadAdmissions
|
||||
? BED_STATUSES.map((status) => ({
|
||||
label: status,
|
||||
count: beds.filter((bed) => bed.status === status).length,
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const recentAdmissions = admissions.slice(0, 6).map((admission) => ({
|
||||
id: admission.id,
|
||||
elderName: admission.elderName,
|
||||
bedLabel: `${admission.roomName}-${admission.bedCode}`,
|
||||
status: admission.status,
|
||||
admittedAt: admission.admittedAt,
|
||||
dischargedAt: admission.dischargedAt,
|
||||
}));
|
||||
const recentAdmissions = canReadAdmissions
|
||||
? admissions.slice(0, 6).map((admission) => ({
|
||||
id: admission.id,
|
||||
elderName: admission.elderName,
|
||||
bedLabel: `${admission.roomName}-${admission.bedCode}`,
|
||||
status: admission.status,
|
||||
admittedAt: admission.admittedAt,
|
||||
dischargedAt: admission.dischargedAt,
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const visibleIncidents = incidents
|
||||
.filter((incident) => incident.status !== "closed")
|
||||
.slice(0, 6)
|
||||
.map((incident) => ({
|
||||
id: incident.id,
|
||||
severity: incident.severity,
|
||||
status: incident.status,
|
||||
title: incident.title,
|
||||
source: incident.source,
|
||||
createdAt: incident.createdAt,
|
||||
}));
|
||||
const careTasks = (careData?.tasks ?? [])
|
||||
const visibleIncidents = canReadIncidents
|
||||
? incidents
|
||||
.filter((incident) => incident.status !== "closed")
|
||||
.slice(0, 6)
|
||||
.map((incident) => ({
|
||||
id: incident.id,
|
||||
severity: incident.severity,
|
||||
status: incident.status,
|
||||
title: incident.title,
|
||||
source: incident.source,
|
||||
createdAt: incident.createdAt,
|
||||
}))
|
||||
: undefined;
|
||||
const careTasks = careData?.tasks
|
||||
.filter((task) => task.status === "pending" || task.status === "in_progress" || task.priority === "urgent" || task.priority === "high")
|
||||
.slice(0, 6)
|
||||
.map((task) => ({
|
||||
@@ -103,7 +153,7 @@ export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
status: task.status,
|
||||
title: task.title,
|
||||
}));
|
||||
const healthReviews = (healthData?.reviews ?? [])
|
||||
const healthReviews = healthData?.reviews
|
||||
.filter((review) => review.status === "pending" || review.severity === "critical")
|
||||
.slice(0, 6)
|
||||
.map((review) => ({
|
||||
@@ -113,7 +163,7 @@ export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
status: review.status,
|
||||
title: review.title,
|
||||
}));
|
||||
const emergencyIncidents = (emergencyData?.incidents ?? [])
|
||||
const emergencyIncidents = emergencyData?.incidents
|
||||
.filter((incident) => incident.status === "open" || incident.status === "acknowledged" || incident.severity === "critical")
|
||||
.slice(0, 6)
|
||||
.map((incident) => ({
|
||||
@@ -124,18 +174,76 @@ export default async function DashboardPage(): Promise<React.ReactElement> {
|
||||
source: incident.source,
|
||||
createdAt: incident.createdAt,
|
||||
}));
|
||||
const deviceTickets = deviceData?.tickets
|
||||
.filter((ticket) => ticket.status === "open" || ticket.status === "assigned" || ticket.priority === "urgent" || ticket.priority === "high")
|
||||
.slice(0, 6)
|
||||
.map((ticket) => ({
|
||||
id: ticket.id,
|
||||
deviceName: ticket.deviceName,
|
||||
priority: ticket.priority,
|
||||
status: ticket.status,
|
||||
title: ticket.title,
|
||||
updatedAt: ticket.updatedAt,
|
||||
}));
|
||||
const notices = noticeData?.notices.slice(0, 6).map((notice) => ({
|
||||
id: notice.id,
|
||||
audience: notice.audience,
|
||||
status: notice.status,
|
||||
title: notice.title,
|
||||
updatedAt: notice.updatedAt,
|
||||
}));
|
||||
const alertTriggers = alertData?.triggers
|
||||
.filter((trigger) => trigger.status === "open" || trigger.status === "acknowledged")
|
||||
.slice(0, 6)
|
||||
.map((trigger) => ({
|
||||
id: trigger.id,
|
||||
elderName: trigger.elderName,
|
||||
status: trigger.status,
|
||||
title: trigger.title,
|
||||
updatedAt: trigger.updatedAt,
|
||||
}));
|
||||
const familyVisits = familyData?.visits
|
||||
.filter((visit) => visit.status === "requested" || visit.status === "approved")
|
||||
.slice(0, 3)
|
||||
.map((visit) => ({
|
||||
id: visit.id,
|
||||
contactName: visit.contactName,
|
||||
elderName: visit.elderName,
|
||||
scheduledAt: visit.scheduledAt,
|
||||
status: visit.status,
|
||||
}));
|
||||
const familyFeedback = familyData?.feedback
|
||||
.filter((feedback) => feedback.status === "open" || feedback.status === "in_progress")
|
||||
.slice(0, 3)
|
||||
.map((feedback) => ({
|
||||
id: feedback.id,
|
||||
elderName: feedback.elderName,
|
||||
status: feedback.status,
|
||||
updatedAt: feedback.updatedAt,
|
||||
}));
|
||||
|
||||
return (
|
||||
<DashboardHome
|
||||
alertTriggers={alertTriggers}
|
||||
alertsHref={getWorkspaceHref(context.organization?.slug, "/alerts")}
|
||||
aiBoardItems={canReadAi ? aiBoardItems ?? [] : undefined}
|
||||
bedsHref={getWorkspaceHref(context.organization?.slug, "/beds")}
|
||||
careHref={getWorkspaceHref(context.organization?.slug, "/care")}
|
||||
careTasks={careTasks}
|
||||
deviceTickets={deviceTickets}
|
||||
devicesHref={getWorkspaceHref(context.organization?.slug, "/devices")}
|
||||
eldersHref={getWorkspaceHref(context.organization?.slug, "/elders")}
|
||||
emergencyHref={getWorkspaceHref(context.organization?.slug, "/emergency")}
|
||||
emergencyIncidents={emergencyIncidents}
|
||||
familyFeedback={familyFeedback}
|
||||
familyHref={getWorkspaceHref(context.organization?.slug, "/family")}
|
||||
familyVisits={familyVisits}
|
||||
healthHref={getWorkspaceHref(context.organization?.slug, "/health")}
|
||||
healthReviews={healthReviews}
|
||||
incidents={visibleIncidents}
|
||||
metrics={metrics}
|
||||
notices={notices}
|
||||
noticesHref={getWorkspaceHref(context.organization?.slug, "/notices")}
|
||||
occupancyData={occupancyData}
|
||||
recentAdmissions={recentAdmissions}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user