import { useState, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { Table, Button, Space, Form, Input, Select, Popconfirm, DatePicker, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import { patientApi } from '../../api/health/patients'; import type { PatientListItem, PatientDetail, CreatePatientReq, UpdatePatientReq, } from '../../api/health/patients'; import { StatusTag } from './components/StatusTag'; import { GENDER_OPTIONS, BLOOD_TYPE_OPTIONS, STATUS_OPTIONS } from '../../constants/health'; import { AuthButton } from '../../components/AuthButton'; import { PageContainer } from '../../components/PageContainer'; import { DrawerForm } from '../../components/DrawerForm'; import { usePaginatedData } from '../../hooks/usePaginatedData'; import { useApiRequest } from '../../hooks/useApiRequest'; import { calcAge, formatDateTime } from '../../utils/format'; import { dayjs } from '../../utils/dayjs'; /** 筛选器结构 */ interface PatientFilters { search: string; status: string; gender: string; dateRange: [string, string] | null; } const DEFAULT_FILTERS: PatientFilters = { search: '', status: '', gender: '', dateRange: null, }; export default function PatientList() { const navigate = useNavigate(); const [modalOpen, setModalOpen] = useState(false); const [editingPatient, setEditingPatient] = useState(null); const [selectedRowKeys, setSelectedRowKeys] = useState([]); // ---- API 请求 Hook ---- const { execute } = useApiRequest(); // ---- 分页数据 Hook ---- const { data: patients, total, page, loading, filters, setFilters, refresh, } = usePaginatedData( async (p, pageSize, f) => { const result = await patientApi.list({ page: p, page_size: pageSize, search: f.search || undefined, status: f.status || undefined, }); return result; }, { pageSize: 20, defaultFilters: { ...DEFAULT_FILTERS } }, ); // ---- 筛选回调 ---- const debounceTimer = useRef | null>(null); const handleSearchChange = useCallback( (value: string) => { setFilters((prev) => ({ ...prev, search: value })); if (debounceTimer.current) clearTimeout(debounceTimer.current); debounceTimer.current = setTimeout(() => { refresh(1); }, 300); }, [setFilters, refresh], ); const handleFilterChange = useCallback( (key: keyof PatientFilters, value: string | [string, string] | null) => { setFilters((prev) => ({ ...prev, [key]: value })); refresh(1); }, [setFilters, refresh], ); const handleResetFilters = useCallback(() => { setFilters({ ...DEFAULT_FILTERS }); refresh(1); }, [setFilters, refresh]); // ---- CRUD 操作 ---- const handleCreateOrEdit = async (values: Record) => { const birthDate = values.birth_date; const formattedBirthDate = birthDate && typeof birthDate === 'object' && 'format' in (birthDate as object) ? (birthDate as { format: (f: string) => string }).format('YYYY-MM-DD') : (birthDate as string | undefined); const payload = { name: values.name as string, gender: values.gender as string | undefined, birth_date: formattedBirthDate, blood_type: values.blood_type as string | undefined, id_number: values.id_number as string | undefined, allergy_history: values.allergy_history as string | undefined, medical_history_summary: values.medical_history_summary as string | undefined, emergency_contact_name: values.emergency_contact_name as string | undefined, emergency_contact_phone: values.emergency_contact_phone as string | undefined, source: values.source as string | undefined, notes: values.notes as string | undefined, }; const successMsg = editingPatient ? '患者信息更新成功' : '患者创建成功'; const result = await execute( async () => { if (editingPatient) { const req: UpdatePatientReq & { version: number } = { ...payload, version: editingPatient.version, }; return patientApi.update(editingPatient.id, req); } const req: CreatePatientReq = payload; return patientApi.create(req); }, successMsg, '操作失败', ); if (result !== null) { closeModal(); refresh(); } }; const handleDelete = async (id: string) => { const patient = patients.find((p) => p.id === id); const version = patient?.version ?? 0; const result = await execute( () => patientApi.delete(id, version), '患者已删除', ); if (result !== null) refresh(); }; const openCreateModal = () => { setEditingPatient(null); setModalOpen(true); }; const openEditModal = async (record: PatientListItem) => { const detail = await execute( () => patientApi.get(record.id), undefined, '获取患者详情失败', ); if (detail) { setEditingPatient(detail); setModalOpen(true); } }; const closeModal = () => { setModalOpen(false); setEditingPatient(null); }; // ---- 列定义 ---- const columns = [ { title: '姓名', dataIndex: 'name', key: 'name', render: (name: string, record: PatientListItem) => (
{(name?.[0] || 'P').toUpperCase()}
{name}
{record.source && 来源: {record.source}}
), }, { title: '性别', dataIndex: 'gender', key: 'gender', width: 80, render: (v?: string) => { if (!v) return '-'; const map: Record = { male: '男', female: '女', other: '其他', }; return map[v] || v; }, }, { title: '年龄', dataIndex: 'birth_date', key: 'birth_date', width: 100, render: (v?: string) => calcAge(v), }, { title: '血型', dataIndex: 'blood_type', key: 'blood_type', width: 80, render: (v?: string) => v || '-', }, { title: '状态', key: 'status', width: 140, render: (_: unknown, record: PatientListItem) => ( ), }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 150, render: (v: string) => formatDateTime(v), }, { title: '操作', key: 'actions', width: 140, render: (_: unknown, record: PatientListItem) => ( } selectedCount={selectedRowKeys.length} onClearSelection={() => setSelectedRowKeys([])} batchActions={ 已选择 {selectedRowKeys.length} 项 } loading={loading} > setSelectedRowKeys(keys as string[]), }} onRow={(record) => ({ onClick: () => navigate(`/health/patients/${record.id}`), style: { cursor: 'pointer' }, })} pagination={{ current: page, total, pageSize: 20, onChange: (p) => refresh(p), showTotal: (t) => `共 ${t} 条记录`, style: { padding: '12px 16px', margin: 0 }, }} /> {/* 新建/编辑患者抽屉 */} ), }, { title: '联系方式', fields: ( <> ), }, { title: '医疗信息', fields: ( <> ), }, { title: '紧急联系人', fields: ( <> ), }, ]} /> ); }