import { useState, useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Table, Select, Button, Modal, Form, Input, DatePicker, Space, Popconfirm, } from 'antd'; import { PlusOutlined, EditOutlined, SwapOutlined, DeleteOutlined } from '@ant-design/icons'; import type { ColumnsType, TablePaginationConfig } from 'antd/es/table'; import { dayjs } from '../../utils/dayjs'; import { followUpApi, type FollowUpTask, type CreateFollowUpTaskReq, type UpdateFollowUpTaskReq } from '../../api/health/followUp'; import { StatusTag } from './components/StatusTag'; import { PatientSelect } from './components/PatientSelect'; import { DoctorSelect } from './components/DoctorSelect'; import { AuthButton } from '../../components/AuthButton'; import { PageContainer } from '../../components/PageContainer'; import { EntityName } from '../../components/EntityName'; import { DrawerForm } from '../../components/DrawerForm'; import { formatDate, formatDateTime } from '../../utils/format'; import { usePaginatedData } from '../../hooks/usePaginatedData'; import { useApiRequest } from '../../hooks/useApiRequest'; import { useDictionary } from '../../hooks/useDictionary'; const STATUS_OPTIONS = [ { value: 'pending', label: '待处理' }, { value: 'in_progress', label: '进行中' }, { value: 'completed', label: '已完成' }, { value: 'overdue', label: '逾期' }, { value: 'cancelled', label: '已取消' }, ]; const FOLLOW_UP_TYPE_FALLBACK = [ { value: 'phone', label: '电话' }, { value: 'outpatient', label: '门诊' }, { value: 'home_visit', label: '家访' }, { value: 'visit', label: '上门' }, { value: 'online', label: '线上' }, { value: 'wechat', label: '微信' }, ]; const FOLLOW_UP_TYPE_MAP: Record = { phone: '电话', outpatient: '门诊', home_visit: '家访', visit: '上门', online: '线上', wechat: '微信', }; interface FollowUpFilters { status?: string; dateRange?: [string, string]; followUpType?: string; assigneeId?: string; } interface AssignFormValues { assigned_to: string; } export default function FollowUpTaskList() { const { options: FOLLOW_UP_TYPE_OPTIONS } = useDictionary('health_follow_up_type', FOLLOW_UP_TYPE_FALLBACK); const [searchParams] = useSearchParams(); const urlPatientId = searchParams.get('patient_id'); // --- Paginated data with usePaginatedData --- const fetchFn = useCallback( async (page: number, pageSize: number, filters: FollowUpFilters) => { const params: Record = { page, page_size: pageSize }; if (filters.status) params.status = filters.status; if (filters.followUpType) params.follow_up_type = filters.followUpType; if (filters.assigneeId) params.assigned_to = filters.assigneeId; if (urlPatientId) params.patient_id = urlPatientId; if (filters.dateRange) { params.planned_date_start = filters.dateRange[0]; params.planned_date_end = filters.dateRange[1]; } return followUpApi.listTasks(params as Parameters[0]); }, [urlPatientId], ); const { data: tasks, total, page, loading, filters, setFilters, refresh, } = usePaginatedData(fetchFn, { pageSize: 20, defaultFilters: {}, }); const { execute } = useApiRequest(); // 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 [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); // --- Handlers --- const handleTableChange = (pagination: TablePaginationConfig) => { refresh(pagination.current ?? 1); }; // Create task const handleCreate = async () => { let values: CreateFollowUpTaskReq; try { values = await createForm.validateFields(); } catch (err: unknown) { if (err && typeof err === 'object' && 'errorFields' in err) return; return; } setCreateLoading(true); const plannedDate = values.planned_date; const result = await execute( () => 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, }), '随访任务创建成功', ); setCreateLoading(false); if (result !== null) { setCreateOpen(false); createForm.resetFields(); refresh(page); } }; // Fill record const openRecordModal = (task: FollowUpTask) => { setActiveTask(task); setRecordOpen(true); }; const handleRecordSubmit = async (values: Record) => { if (!activeTask) return; setRecordLoading(true); const result = await execute( () => followUpApi.createRecord(activeTask.id, { executed_date: (values.executed_date as dayjs.Dayjs).format('YYYY-MM-DD'), result: values.result as string, patient_condition: values.patient_condition as string, medical_advice: values.medical_advice as string, next_follow_up_date: values.next_follow_up_date ? (values.next_follow_up_date as dayjs.Dayjs).format('YYYY-MM-DD') : undefined, }), '随访记录填写成功', ); setRecordLoading(false); if (result !== null) { setRecordOpen(false); setActiveTask(null); refresh(page); } }; // 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; let values: { assigned_to: string }; try { values = await assignForm.validateFields(); } catch (err: unknown) { if (err && typeof err === 'object' && 'errorFields' in err) return; return; } setAssignLoading(true); const req: UpdateFollowUpTaskReq & { version: number } = { assigned_to: values.assigned_to, version: assignTask.version, }; const result = await execute( () => followUpApi.updateTask(assignTask.id, req), '分配成功', ); setAssignLoading(false); if (result !== null) { setAssignOpen(false); setAssignTask(null); refresh(page); } }; // Delete const handleDelete = async (record: FollowUpTask) => { const result = await execute( () => followUpApi.deleteTask(record.id, record.version), '删除成功', ); if (result !== null) refresh(page); }; // --- Columns --- const columns: ColumnsType = [ { title: '患者', dataIndex: 'patient_name', key: 'patient_name', width: 140, render: (_: unknown, record: FollowUpTask) => ( ), }, { 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) => formatDate(v), }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => , }, { title: '负责人', dataIndex: 'assigned_to', key: 'assigned_to', width: 140, render: (_: unknown, record: FollowUpTask) => ( ), }, { 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 ( setFilters((prev) => ({ ...prev, followUpType: value }))} /> {/* Fill Record Drawer */} { setRecordOpen(false); setActiveTask(null); }} onSubmit={handleRecordSubmit} loading={recordLoading} width={560} columns={1} sections={[ { title: '执行信息', fields: ( <> ), }, { title: '详细记录', fields: ( <> ), }, ]} /> {/* Assign Modal */} { setAssignOpen(false); setAssignTask(null); }} confirmLoading={assignLoading} okText="确认" cancelText="取消" destroyOnHidden >
); }