feat: add fee workspace and polish AI presentation

This commit is contained in:
2026-07-09 17:05:19 -07:00
parent 3aabdf0c4a
commit ef4dd21d32
19 changed files with 1589 additions and 15 deletions

View File

@@ -7,8 +7,14 @@ 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 type { ElderAiAnalysisHistoryItem } from "@/modules/ai/types";
import type {
ElderAiAnalysisHistoryItem,
ElderAiDataScope,
ElderAiRecommendationPriority,
ElderAiSeverity,
} from "@/modules/ai/types";
import type { ApiResult } from "@/modules/core/server/api";
import { CARE_LEVEL_LABELS, ELDER_STATUS_LABELS } from "@/modules/elders/types";
import type { Elder } from "@/modules/elders/types";
type ElderAiAnalysisDialogProps = {
@@ -30,6 +36,51 @@ const statusLabels: Record<ElderAiAnalysisHistoryItem["status"], string> = {
failed: "未完成",
};
const severityLabels: Record<ElderAiSeverity, string> = {
info: "提示",
warning: "关注",
critical: "紧急",
};
const recommendationPriorityLabels: Record<ElderAiRecommendationPriority, string> = {
low: "低",
normal: "常规",
high: "高",
urgent: "紧急",
};
const dataScopeLabels: Record<ElderAiDataScope, string> = {
elder: "基础档案",
health: "健康数据",
care: "护理服务",
family: "家属服务",
admission: "入住信息",
facility: "床位设施",
alert: "规则预警",
incident: "安全事件",
knowledge: "知识库",
};
function severityVariant(severity: ElderAiSeverity): "danger" | "secondary" | "warning" {
if (severity === "critical") {
return "danger";
}
if (severity === "warning") {
return "warning";
}
return "secondary";
}
function recommendationVariant(priority: ElderAiRecommendationPriority): "danger" | "secondary" | "warning" {
if (priority === "urgent") {
return "danger";
}
if (priority === "high") {
return "warning";
}
return "secondary";
}
function formatDateTime(value: string): string {
return new Date(value).toLocaleString("zh-CN");
}
@@ -82,10 +133,10 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
return (
<Dialog
description={elder ? `${elder.name} 的授权数据范围内 AI 分析历史和最新结论。` : undefined}
description={elder ? `${elder.name} 的授权数据范围内智能分析历史和最新结论。` : undefined}
onClose={onClose}
open={open}
title="AI 分析"
title="智能照护分析"
width="wide"
>
{elder ? (
@@ -94,7 +145,7 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
<div>
<p className="text-lg font-semibold">{elder.name}</p>
<p className="text-sm text-muted-foreground">
{elder.age} · {elder.careLevel} · {elder.status} · {elder.room && elder.bed ? `${elder.room}-${elder.bed}` : "未绑定床位"}
{elder.age} · {CARE_LEVEL_LABELS[elder.careLevel]} · {ELDER_STATUS_LABELS[elder.status]} · {elder.room && elder.bed ? `${elder.room}-${elder.bed}` : "未绑定床位"}
</p>
</div>
<div className="flex flex-wrap gap-2">
@@ -104,7 +155,7 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
</Button>
<Button disabled={isGenerating} onClick={() => void generate()} type="button">
<Sparkles className="size-4" aria-hidden="true" />
{isGenerating ? "生成中" : "生成分析"}
{isGenerating ? "分析中" : "更新分析"}
</Button>
</div>
</div>
@@ -137,7 +188,7 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
<div className="rounded-md border p-3 text-sm" key={`${finding.category}-${index}`}>
<div className="flex items-center justify-between gap-2">
<span className="font-medium">{finding.category}</span>
<Badge variant={finding.severity === "critical" ? "destructive" : "secondary"}>{finding.severity}</Badge>
<Badge variant={severityVariant(finding.severity)}>{severityLabels[finding.severity]}</Badge>
</div>
<p className="mt-2 text-muted-foreground">{finding.evidence}</p>
</div>
@@ -151,7 +202,9 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
<div className="rounded-md border p-3 text-sm" key={`${recommendation.title}-${index}`}>
<div className="flex items-center justify-between gap-2">
<span className="font-medium">{recommendation.title}</span>
<Badge variant={recommendation.priority === "urgent" ? "destructive" : "secondary"}>{recommendation.priority}</Badge>
<Badge variant={recommendationVariant(recommendation.priority)}>
{recommendationPriorityLabels[recommendation.priority]}
</Badge>
</div>
<p className="mt-2 text-muted-foreground">{recommendation.rationale}</p>
<p className="mt-1">{recommendation.suggestedNextStep}</p>
@@ -197,13 +250,13 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
</CardHeader>
<CardContent className="grid gap-2 text-sm text-muted-foreground">
<p>{latest.restricted ? "当前角色缺少该分析所使用数据范围的读取权限。" : latest.errorReason ?? "暂无可展示分析。"}</p>
{latest.status === "failed" ? <p></p> : null}
{latest.status === "failed" ? <p></p> : null}
</CardContent>
</Card>
) : (
<Card>
<CardContent className="flex min-h-32 items-center justify-center text-sm text-muted-foreground">
{isLoading ? "正在加载分析历史" : "暂无 AI 分析历史"}
{isLoading ? "正在加载分析历史" : "暂无智能分析历史"}
</CardContent>
</Card>
)}
@@ -215,7 +268,9 @@ export function ElderAiAnalysisDialog({ elder, onClose, open }: ElderAiAnalysisD
<div className="flex flex-col gap-2 rounded-md border p-3 text-sm sm:flex-row sm:items-center sm:justify-between" key={item.id}>
<div>
<p className="font-medium">{formatDateTime(item.createdAt)}</p>
<p className="text-xs text-muted-foreground">{item.dataScopes.join(", ") || "-"}</p>
<p className="text-xs text-muted-foreground">
{item.dataScopes.map((scope) => dataScopeLabels[scope]).join("、") || "-"}
</p>
</div>
<div className="flex items-center gap-2">
{item.restricted ? <Badge variant="secondary"></Badge> : null}