feat(web): 工作台前端 API 客户端 + TodoList 组件

- actionInbox.ts 新增 WorkbenchStats/TeamOverview 类型和 stats()/team() API
- 新建 workbench/TodoList.tsx 待办列表组件(分页 + 类型/优先级标签)
This commit is contained in:
iven
2026-05-01 21:17:39 +08:00
parent 61397186e7
commit 620af8988b
2 changed files with 148 additions and 0 deletions

View File

@@ -42,6 +42,35 @@ export interface ThreadResponse {
available_actions: ActionDefinition[];
}
export interface WorkbenchStats {
total_pending: number;
ai_suggestion_pending: number;
urgent_alerts: number;
followup_due: number;
completion_rate: number | null;
}
export interface TeamMemberOverview {
user_id: string;
name: string;
title: string;
pending_count: number;
completed_count: number;
overdue_count: number;
completion_rate: number;
}
export interface TeamOverview {
members: TeamMemberOverview[];
risk_distribution: {
high: number;
medium: number;
low: number;
};
total_pending: number;
total_completed: number;
}
export const actionInboxApi = {
list: async (params?: {
status?: string;
@@ -63,4 +92,20 @@ export const actionInboxApi = {
}>(`/health/action-inbox/${encodeURIComponent(sourceRef)}/thread`);
return data.data;
},
stats: async () => {
const { data } = await client.get<{
success: boolean;
data: WorkbenchStats;
}>('/health/action-inbox/stats');
return data.data;
},
team: async () => {
const { data } = await client.get<{
success: boolean;
data: TeamOverview;
}>('/health/action-inbox/team');
return data.data;
},
};

View File

@@ -0,0 +1,103 @@
import { useEffect, useState, useCallback } from 'react';
import { List, Tag, Empty, Spin, Button, Space } from 'antd';
import {
BellOutlined,
RobotOutlined,
TeamOutlined,
WarningOutlined,
} from '@ant-design/icons';
import { actionInboxApi, type ActionItem, type ActionType } from '../../../../api/health/actionInbox';
const TYPE_CONFIG: Record<ActionType, { label: string; color: string; icon: React.ReactNode }> = {
ai_suggestion: { label: 'AI 建议', color: 'blue', icon: <RobotOutlined /> },
alert: { label: '告警', color: 'red', icon: <WarningOutlined /> },
followup: { label: '随访', color: 'green', icon: <TeamOutlined /> },
data_anomaly: { label: '数据异常', color: 'orange', icon: <BellOutlined /> },
};
const PRIORITY_COLOR: Record<string, string> = {
urgent: 'red',
high: 'volcano',
medium: 'orange',
low: 'default',
};
interface TodoListProps {
onItemClick?: (item: ActionItem) => void;
}
export default function TodoList({ onItemClick }: TodoListProps) {
const [items, setItems] = useState<ActionItem[]>([]);
const [loading, setLoading] = useState(true);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const fetchItems = useCallback(async (p: number) => {
setLoading(true);
try {
const res = await actionInboxApi.list({
status: 'pending',
page: p,
page_size: 10,
});
setItems(res.items);
setTotal(res.total);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchItems(page);
}, [page, fetchItems]);
if (loading) {
return <div style={{ textAlign: 'center', padding: 40 }}><Spin /></div>;
}
if (items.length === 0) {
return <Empty description="暂无待处理事项" />;
}
return (
<List
dataSource={items}
pagination={{
current: page,
total,
pageSize: 10,
size: 'small',
onChange: setPage,
}}
renderItem={(item) => {
const cfg = TYPE_CONFIG[item.action_type];
return (
<List.Item
style={{ cursor: 'pointer', padding: '8px 12px' }}
onClick={() => onItemClick?.(item)}
>
<List.Item.Meta
avatar={<span style={{ fontSize: 18 }}>{cfg.icon}</span>}
title={
<Space>
<span>{item.title}</span>
<Tag color={cfg.color}>{cfg.label}</Tag>
<Tag color={PRIORITY_COLOR[item.priority]}>{item.priority}</Tag>
</Space>
}
description={
<Space direction="vertical" size={2}>
<span>{item.summary}</span>
<span style={{ color: '#999', fontSize: 12 }}>
{item.patient_name} · {new Date(item.created_at).toLocaleDateString()}
</span>
</Space>
}
/>
<Button type="link" size="small"></Button>
</List.Item>
);
}}
/>
);
}