- actionInbox.ts: API 调用层,list + getThread - ActionThreadDrawer: 上下文线程抽屉,时间线 + 操作按钮 - ActionInbox: 列表页,Tabs 筛选 + 分页 + 点击打开 Drawer - App.tsx: 注册 /health/action-inbox 路由
171 lines
4.8 KiB
TypeScript
171 lines
4.8 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import { Tag, List, Badge, Tabs, Spin, Empty } from 'antd';
|
|
import { PageContainer } from '../../components/PageContainer';
|
|
import ActionThreadDrawer from '../../components/ActionThreadDrawer';
|
|
import {
|
|
actionInboxApi,
|
|
type ActionItem,
|
|
type ActionType,
|
|
type ActionPriority,
|
|
} from '../../api/health/actionInbox';
|
|
import { formatRelative } from '../../utils/format';
|
|
|
|
const TYPE_CONFIG: Record<ActionType, { label: string; color: string }> = {
|
|
ai_suggestion: { label: 'AI建议', color: '#722ed1' },
|
|
alert: { label: '告警', color: '#f5222d' },
|
|
followup: { label: '随访', color: '#1890ff' },
|
|
data_anomaly: { label: '异常', color: '#fa8c16' },
|
|
};
|
|
|
|
const PRIORITY_LABEL: Record<ActionPriority, string> = {
|
|
urgent: '紧急',
|
|
high: '高',
|
|
medium: '中',
|
|
low: '低',
|
|
};
|
|
|
|
const PRIORITY_COLOR: Record<ActionPriority, string> = {
|
|
urgent: 'red',
|
|
high: 'orange',
|
|
medium: 'blue',
|
|
low: 'default',
|
|
};
|
|
|
|
const STATUS_TABS = [
|
|
{ key: 'all', label: '全部' },
|
|
{ key: 'pending', label: '待处理' },
|
|
{ key: 'in_progress', label: '进行中' },
|
|
{ key: 'completed', label: '已完成' },
|
|
];
|
|
|
|
const BADGE_STATUS: Record<string, 'error' | 'processing' | 'default'> = {
|
|
pending: 'error',
|
|
in_progress: 'processing',
|
|
completed: 'default',
|
|
};
|
|
|
|
export default function ActionInbox() {
|
|
const [items, setItems] = useState<ActionItem[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [page, setPage] = useState(1);
|
|
const [loading, setLoading] = useState(false);
|
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
const [selectedItem, setSelectedItem] = useState<ActionItem | null>(null);
|
|
|
|
const fetchData = useCallback(
|
|
async (p: number, status?: string) => {
|
|
setLoading(true);
|
|
try {
|
|
const resp = await actionInboxApi.list({
|
|
page: p,
|
|
page_size: 20,
|
|
status: status === 'all' ? undefined : status,
|
|
});
|
|
setItems(resp.data);
|
|
setTotal(resp.total);
|
|
setPage(p);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
useEffect(() => {
|
|
fetchData(1, 'all');
|
|
}, [fetchData]);
|
|
|
|
const handleTabChange = (key: string) => {
|
|
setStatusFilter(key);
|
|
fetchData(1, key);
|
|
};
|
|
|
|
const handleItemClick = (item: ActionItem) => {
|
|
setSelectedItem(item);
|
|
setDrawerOpen(true);
|
|
};
|
|
|
|
const handleActionComplete = () => {
|
|
fetchData(page, statusFilter);
|
|
};
|
|
|
|
return (
|
|
<PageContainer
|
|
title="行动收件箱"
|
|
subtitle={`共 ${total} 项待办`}
|
|
>
|
|
<Tabs
|
|
activeKey={statusFilter}
|
|
onChange={handleTabChange}
|
|
items={STATUS_TABS.map((tab) => ({
|
|
key: tab.key,
|
|
label: tab.label,
|
|
}))}
|
|
/>
|
|
|
|
<Spin spinning={loading}>
|
|
{items.length === 0 && !loading ? (
|
|
<Empty description="暂无待办事项" />
|
|
) : (
|
|
<List
|
|
dataSource={items}
|
|
pagination={{
|
|
current: page,
|
|
total,
|
|
pageSize: 20,
|
|
onChange: (p) => fetchData(p, statusFilter),
|
|
showTotal: (t) => `共 ${t} 条`,
|
|
}}
|
|
renderItem={(item) => {
|
|
const typeConf =
|
|
TYPE_CONFIG[item.action_type] ??
|
|
({ label: '未知', color: '#999' } as {
|
|
label: string;
|
|
color: string;
|
|
});
|
|
return (
|
|
<List.Item
|
|
style={{ cursor: 'pointer', padding: '12px 16px' }}
|
|
onClick={() => handleItemClick(item)}
|
|
>
|
|
<List.Item.Meta
|
|
avatar={
|
|
<Badge
|
|
status={BADGE_STATUS[item.status] ?? 'default'}
|
|
/>
|
|
}
|
|
title={
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
}}
|
|
>
|
|
<Tag color={typeConf.color}>{typeConf.label}</Tag>
|
|
<span>{item.title}</span>
|
|
<Tag color={PRIORITY_COLOR[item.priority]}>
|
|
{PRIORITY_LABEL[item.priority]}
|
|
</Tag>
|
|
</div>
|
|
}
|
|
description={`${item.patient_name} · ${formatRelative(item.created_at)}`}
|
|
/>
|
|
</List.Item>
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
</Spin>
|
|
|
|
<ActionThreadDrawer
|
|
open={drawerOpen}
|
|
item={selectedItem}
|
|
onClose={() => setDrawerOpen(false)}
|
|
onActionComplete={handleActionComplete}
|
|
/>
|
|
</PageContainer>
|
|
);
|
|
}
|