import { useState, useCallback, useEffect } from 'react'; import { Table, Button, Space, Modal, Form, Select, DatePicker, TimePicker, Input, Dropdown, message, Alert, } from 'antd'; import { PlusOutlined, DownOutlined, } from '@ant-design/icons'; import type { Dayjs } from 'dayjs'; import { appointmentApi, type Appointment, type CreateAppointmentReq } from '../../api/health/appointments'; 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 { DrawerForm } from '../../components/DrawerForm'; import { EntityName } from '../../components/EntityName'; import { formatDateTime } from '../../utils/format'; import { usePaginatedData } from '../../hooks/usePaginatedData'; /** 预约类型选项 */ const APPOINTMENT_TYPE_OPTIONS = [ { value: 'outpatient', label: '门诊' }, { value: 'recheck', label: '复诊' }, { value: 'health_checkup', label: '体检' }, { value: 'consultation', label: '咨询' }, { value: 'dialysis', label: '透析' }, ]; const APPOINTMENT_TYPE_MAP: Record = { outpatient: '门诊', recheck: '复诊', health_checkup: '体检', consultation: '咨询', dialysis: '透析', }; /** 状态筛选选项 */ const STATUS_OPTIONS = [ { value: 'pending', label: '待确认' }, { value: 'confirmed', label: '已确认' }, { value: 'completed', label: '已完成' }, { value: 'cancelled', label: '已取消' }, { value: 'no_show', label: '未到诊' }, ]; /** 状态流转规则 */ const STATUS_TRANSITIONS: Record = { pending: [ { value: 'confirmed', label: '确认' }, { value: 'cancelled', label: '取消' }, ], confirmed: [ { value: 'completed', label: '完成' }, { value: 'no_show', label: '未到诊' }, { value: 'cancelled', label: '取消' }, ], completed: [], cancelled: [], no_show: [ { value: 'confirmed', label: '重新确认' }, ], }; /** 筛选器类型 */ interface AppointmentFilters { status: string | undefined; dateRange: [Dayjs | null, Dayjs | null] | null; patientSearch: string; appointmentType: string | undefined; } export default function AppointmentList() { const [drawerOpen, setDrawerOpen] = useState(false); const [submitting, setSubmitting] = useState(false); // 患者选择状态(受控组件,不挂在 Form.Item 上) const [selectedPatientId, setSelectedPatientId] = useState(undefined); const [selectedDoctorId, setSelectedDoctorId] = useState(undefined); // 排班校验 const [scheduleHint, setScheduleHint] = useState(null); const [selectedDate, setSelectedDate] = useState(null); // ---- 数据获取 ---- const fetcher = useCallback( async (page: number, pageSize: number, filters: AppointmentFilters) => { const dateStart = filters.dateRange?.[0]?.format('YYYY-MM-DD'); const dateEnd = filters.dateRange?.[1]?.format('YYYY-MM-DD'); return appointmentApi.list({ page, page_size: pageSize, status: filters.status || undefined, date: dateStart === dateEnd ? dateStart : undefined, patient_id: undefined, // 后端暂不支持 patientSearch 文本搜索 }); }, [], ); const { data, total, page, loading, filters, setFilters, refresh, } = usePaginatedData(fetcher, { pageSize: 20, defaultFilters: { status: undefined, dateRange: null, patientSearch: '', appointmentType: undefined, }, }); const handleFilterChange = useCallback( (key: keyof AppointmentFilters, value: unknown) => { setFilters((prev) => ({ ...prev, [key]: value })); refresh(1); }, [setFilters, refresh], ); const resetFilters = useCallback(() => { setFilters({ status: undefined, dateRange: null, patientSearch: '', appointmentType: undefined, }); refresh(1); }, [setFilters, refresh]); // ---- 状态变更 ---- const DESTRUCTIVE_STATUSES = new Set(['cancelled', 'no_show']); const handleStatusChange = (record: Appointment, newStatus: string) => { const transition = STATUS_TRANSITIONS[record.status]?.find((t) => t.value === newStatus); if (!transition) return; if (DESTRUCTIVE_STATUSES.has(newStatus)) { let cancelReason = ''; Modal.confirm({ title: `确认${transition.label}`, content: newStatus === 'cancelled' ? ( { cancelReason = e.target.value; }} /> ) : ( 确定将此预约标记为"{transition.label}"? ), okText: '确认', cancelText: '取消', onOk: async () => { try { await appointmentApi.updateStatus(record.id, { status: newStatus, version: record.version, ...(newStatus === 'cancelled' && { cancel_reason: cancelReason }), }); message.success('状态更新成功'); refresh(); } catch { message.error('状态更新失败'); } }, }); } else { Modal.confirm({ title: `确认${transition.label}`, content: `确定将此预约状态变更为"${transition.label}"?`, okText: '确认', cancelText: '取消', onOk: async () => { try { await appointmentApi.updateStatus(record.id, { status: newStatus, version: record.version, }); message.success('状态更新成功'); refresh(); } catch { message.error('状态更新失败'); } }, }); } }; // ---- 新建预约 ---- const openCreate = () => { setSelectedPatientId(undefined); setSelectedDoctorId(undefined); setScheduleHint(null); setSelectedDate(null); setDrawerOpen(true); }; // 排班校验:医生 + 日期选定后查询排班 useEffect(() => { if (!selectedDoctorId || !selectedDate || !drawerOpen) { setScheduleHint(null); return; } let cancelled = false; appointmentApi.listSchedules({ doctor_id: selectedDoctorId, date: selectedDate, page: 1, page_size: 50 }) .then((result) => { if (cancelled) return; const schedules = result.data; if (schedules.length === 0) { setScheduleHint(`该医生在 ${selectedDate} 暂无排班,请确认是否需要先创建排班`); } else { const slots = schedules .filter((s) => s.status === 'active' && s.current_appointments < s.max_appointments) .map((s) => `${s.start_time}-${s.end_time}(${s.current_appointments}/${s.max_appointments})`) .join('、'); setScheduleHint(slots ? `可约时段:${slots}` : `该医生在 ${selectedDate} 排班已满或已停用`); } }) .catch(() => { if (!cancelled) setScheduleHint(null); }); return () => { cancelled = true; }; }, [selectedDoctorId, selectedDate, drawerOpen]); const handleSubmit = async (values: Record) => { if (!selectedPatientId) { message.warning('请选择患者'); return; } if (!selectedDoctorId) { message.warning('请选择医护'); return; } try { setSubmitting(true); const req: CreateAppointmentReq = { patient_id: selectedPatientId, doctor_id: selectedDoctorId, appointment_date: (values.appointment_date as Dayjs).format('YYYY-MM-DD'), start_time: (values.start_time as Dayjs).format('HH:mm'), end_time: (values.end_time as Dayjs).format('HH:mm'), appointment_type: (values.appointment_type as string) || 'outpatient', notes: (values.notes as string) || undefined, }; await appointmentApi.create(req); message.success('预约创建成功'); setDrawerOpen(false); setSelectedPatientId(undefined); setSelectedDoctorId(undefined); refresh(); } catch { message.error('创建预约失败'); } finally { setSubmitting(false); } }; // ---- 列定义 ---- const columns = [ { title: '患者', dataIndex: 'patient_name', key: 'patient_name', width: 100, render: (_: unknown, record: Appointment) => ( ), }, { title: '医护', dataIndex: 'doctor_name', key: 'doctor_name', width: 100, render: (_: unknown, record: Appointment) => ( ), }, { title: '预约类型', dataIndex: 'appointment_type', key: 'appointment_type', width: 90, render: (val: string) => APPOINTMENT_TYPE_MAP[val] || val, }, { title: '预约日期', dataIndex: 'appointment_date', key: 'appointment_date', width: 120, render: (val: string) => val || '-', }, { title: '时段', key: 'time_range', width: 120, render: (_: unknown, record: Appointment) => record.start_time && record.end_time ? `${record.start_time} - ${record.end_time}` : '-', }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (val: string) => , }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 180, render: (val: string) => formatDateTime(val), }, { title: '备注', dataIndex: 'notes', key: 'notes', width: 180, ellipsis: true, render: (val: string) => val || '-', }, { title: '操作', key: 'action', width: 100, fixed: 'right' as const, render: (_: unknown, record: Appointment) => { const transitions = STATUS_TRANSITIONS[record.status] || []; if (transitions.length === 0) { return 无可用操作; } return ( ({ key: t.value, label: t.label, onClick: () => handleStatusChange(record, t.value), })), }} > ); }, }, ]; return ( handleFilterChange('patientSearch', e.target.value)} allowClear style={{ width: 180 }} /> setSelectedDate(d ? d.format('YYYY-MM-DD') : null)} /> ), }, { title: '医生与排班', fields: ( <> setSelectedDoctorId(val)} placeholder="搜索选择医护" /> {scheduleHint && ( )} ), }, { title: '备注', fields: ( ), }, ]} /> ); }