feat: add fee workspace and polish AI presentation
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user