diff --git a/apps/web/src/api/health/actionInbox.ts b/apps/web/src/api/health/actionInbox.ts index ca92bc7..2d73b88 100644 --- a/apps/web/src/api/health/actionInbox.ts +++ b/apps/web/src/api/health/actionInbox.ts @@ -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; + }, }; diff --git a/apps/web/src/pages/health/components/workbench/TodoList.tsx b/apps/web/src/pages/health/components/workbench/TodoList.tsx new file mode 100644 index 0000000..14decba --- /dev/null +++ b/apps/web/src/pages/health/components/workbench/TodoList.tsx @@ -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 = { + ai_suggestion: { label: 'AI 建议', color: 'blue', icon: }, + alert: { label: '告警', color: 'red', icon: }, + followup: { label: '随访', color: 'green', icon: }, + data_anomaly: { label: '数据异常', color: 'orange', icon: }, +}; + +const PRIORITY_COLOR: Record = { + urgent: 'red', + high: 'volcano', + medium: 'orange', + low: 'default', +}; + +interface TodoListProps { + onItemClick?: (item: ActionItem) => void; +} + +export default function TodoList({ onItemClick }: TodoListProps) { + const [items, setItems] = useState([]); + 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
; + } + + if (items.length === 0) { + return ; + } + + return ( + { + const cfg = TYPE_CONFIG[item.action_type]; + return ( + onItemClick?.(item)} + > + {cfg.icon}} + title={ + + {item.title} + {cfg.label} + {item.priority} + + } + description={ + + {item.summary} + + {item.patient_name} · {new Date(item.created_at).toLocaleDateString()} + + + } + /> + + + ); + }} + /> + ); +}