import { useState, useEffect, useCallback } from 'react'; import { Table, Select, Button, Modal, Form, Input, DatePicker, Space, Popconfirm, message, } from 'antd'; import { PlusOutlined, EditOutlined, SwapOutlined, DeleteOutlined } from '@ant-design/icons'; import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'; import dayjs from 'dayjs'; import { followUpApi, type FollowUpTask, type CreateFollowUpTaskReq, type UpdateFollowUpTaskReq } from '../../api/health/followUp'; import { patientApi } from '../../api/health/patients'; import { getUser } from '../../api/users'; import { StatusTag } from './components/StatusTag'; import { PatientSelect } from './components/PatientSelect'; import { DoctorSelect } from './components/DoctorSelect'; import { useThemeMode } from '../../hooks/useThemeMode'; import { AuthButton } from '../../components/AuthButton'; const STATUS_OPTIONS = [ { value: 'pending', label: '待处理' }, { value: 'in_progress', label: '进行中' }, { value: 'completed', label: '已完成' }, { value: 'overdue', label: '逾期' }, { value: 'cancelled', label: '已取消' }, ]; const FOLLOW_UP_TYPE_OPTIONS = [ { value: 'phone', label: '电话' }, { value: 'outpatient', label: '门诊' }, { value: 'home_visit', label: '家访' }, { value: 'wechat', label: '微信' }, ]; const FOLLOW_UP_TYPE_MAP: Record = { phone: '电话', outpatient: '门诊', home_visit: '家访', wechat: '微信', }; function formatDateTime(value: string): string { return new Date(value).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); } interface RecordFormValues { executed_date: dayjs.Dayjs; result: string; patient_condition: string; medical_advice: string; next_follow_up_date?: dayjs.Dayjs; } interface AssignFormValues { assigned_to: string; } export default function FollowUpTaskList() { const [tasks, setTasks] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [query, setQuery] = useState<{ page: number; page_size: number; status?: string }>({ page: 1, page_size: 20, }); // Create task modal const [createOpen, setCreateOpen] = useState(false); const [createLoading, setCreateLoading] = useState(false); const [createForm] = Form.useForm(); // Fill record modal const [recordOpen, setRecordOpen] = useState(false); const [recordLoading, setRecordLoading] = useState(false); const [recordForm] = Form.useForm(); const [activeTask, setActiveTask] = useState(null); // Assign modal const [assignOpen, setAssignOpen] = useState(false); const [assignLoading, setAssignLoading] = useState(false); const [assignForm] = Form.useForm(); const [assignTask, setAssignTask] = useState(null); // Patient/doctor label cache for display const [patientLabels, setPatientLabels] = useState>({}); const [doctorLabels, setDoctorLabels] = useState>({}); const isDark = useThemeMode(); // --- Data fetching --- const fetchTasks = useCallback(async (params: { page: number; page_size: number; status?: string }) => { setLoading(true); try { const result = await followUpApi.listTasks(params); setTasks(result.data); setTotal(result.total); // Batch resolve patient names const patientIds = [...new Set(result.data.map((t: FollowUpTask) => t.patient_id).filter(Boolean))]; const newLabels: Record = {}; await Promise.allSettled( patientIds.map(async (id: string) => { try { const p = await patientApi.get(id); newLabels[id] = p.name; } catch { /* skip */ } }), ); if (Object.keys(newLabels).length > 0) { setPatientLabels((prev) => ({ ...prev, ...newLabels })); } // Batch resolve assignee names const assigneeIds = [...new Set(result.data.map((t: FollowUpTask) => t.assigned_to).filter(Boolean))]; const newDoctorLabels: Record = {}; await Promise.allSettled( assigneeIds.map(async (id: string) => { try { const u = await getUser(id); newDoctorLabels[id] = u.display_name || u.username; } catch { /* skip */ } }), ); if (Object.keys(newDoctorLabels).length > 0) { setDoctorLabels((prev) => ({ ...prev, ...newDoctorLabels })); } } catch { message.error('加载随访任务失败'); } finally { setLoading(false); } }, []); useEffect(() => { fetchTasks(query); }, [query, fetchTasks]); // --- Handlers --- const handleFilterChange = (field: 'status', value: string | undefined) => { setQuery((prev) => ({ ...prev, [field]: value || undefined, page: 1 })); }; const handleTableChange = (pagination: TablePaginationConfig) => { setQuery((prev) => ({ ...prev, page: pagination.current ?? 1, page_size: pagination.pageSize ?? 20, })); }; // Create task const handleCreate = async () => { try { const values = await createForm.validateFields(); setCreateLoading(true); const plannedDate = values.planned_date; await followUpApi.createTask({ patient_id: values.patient_id, follow_up_type: values.follow_up_type, planned_date: dayjs.isDayjs(plannedDate) ? plannedDate.format('YYYY-MM-DD') : plannedDate, assigned_to: values.assigned_to, content_template: values.content_template, }); message.success('随访任务创建成功'); setCreateOpen(false); createForm.resetFields(); fetchTasks(query); } catch (err: unknown) { if (err && typeof err === 'object' && 'errorFields' in err) return; // form validation message.error('创建随访任务失败'); } finally { setCreateLoading(false); } }; // Fill record const openRecordModal = (task: FollowUpTask) => { setActiveTask(task); recordForm.resetFields(); setRecordOpen(true); }; const handleRecordSubmit = async () => { if (!activeTask) return; try { const values = await recordForm.validateFields(); setRecordLoading(true); await followUpApi.createRecord(activeTask.id, { executed_date: values.executed_date.format('YYYY-MM-DD'), result: values.result, patient_condition: values.patient_condition, medical_advice: values.medical_advice, next_follow_up_date: values.next_follow_up_date?.format('YYYY-MM-DD'), }); message.success('随访记录填写成功'); setRecordOpen(false); setActiveTask(null); fetchTasks(query); } catch (err: unknown) { if (err && typeof err === 'object' && 'errorFields' in err) return; message.error('填写随访记录失败'); } finally { setRecordLoading(false); } }; // Assign const openAssignModal = (task: FollowUpTask) => { setAssignTask(task); assignForm.resetFields(); if (task.assigned_to) { assignForm.setFieldsValue({ assigned_to: task.assigned_to }); } setAssignOpen(true); }; const handleAssign = async () => { if (!assignTask) return; try { const values = await assignForm.validateFields(); setAssignLoading(true); const req: UpdateFollowUpTaskReq & { version: number } = { assigned_to: values.assigned_to, version: assignTask.version, }; await followUpApi.updateTask(assignTask.id, req); message.success('分配成功'); setAssignOpen(false); setAssignTask(null); fetchTasks(query); } catch (err: unknown) { if (err && typeof err === 'object' && 'errorFields' in err) return; message.error('分配失败'); } finally { setAssignLoading(false); } }; // Delete const handleDelete = async (record: FollowUpTask) => { try { await followUpApi.deleteTask(record.id, record.version); message.success('删除成功'); fetchTasks(query); } catch { message.error('删除失败'); } }; // Store labels from selects const handlePatientLabel = (id: string, label: string) => { setPatientLabels((prev) => ({ ...prev, [id]: label })); }; const handleDoctorLabel = (id: string, label: string) => { setDoctorLabels((prev) => ({ ...prev, [id]: label })); }; // --- Columns --- const columns: ColumnsType = [ { title: '患者', dataIndex: 'patient_id', key: 'patient_id', width: 140, render: (id: string) => patientLabels[id] || id.slice(0, 8), }, { title: '随访类型', dataIndex: 'follow_up_type', key: 'follow_up_type', width: 100, render: (v: string) => FOLLOW_UP_TYPE_MAP[v] || v, }, { title: '计划日期', dataIndex: 'planned_date', key: 'planned_date', width: 120, render: (v: string) => v, }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => , }, { title: '负责人', dataIndex: 'assigned_to', key: 'assigned_to', width: 140, render: (id: string | undefined) => id ? doctorLabels[id] || id.slice(0, 8) : '-', }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 160, render: (v: string) => ( {formatDateTime(v)} ), }, { title: '操作', key: 'actions', width: 220, render: (_: unknown, record: FollowUpTask) => ( handleDelete(record)} okText="确认" cancelText="取消" > ), }, ]; return (
{/* Toolbar */}
handleDoctorLabel(_val, label)} /> {/* Fill Record Modal */} { setRecordOpen(false); setActiveTask(null); }} confirmLoading={recordLoading} okText="提交" cancelText="取消" destroyOnClose width={560} >
{/* Assign Modal */} { setAssignOpen(false); setAssignTask(null); }} confirmLoading={assignLoading} okText="确认" cancelText="取消" destroyOnClose >
handleDoctorLabel(_val, label)} />
); }