import { useEffect, useState, useCallback } from 'react'; import { Table, Button, Space, Modal, Form, Select, DatePicker, TimePicker, InputNumber, Segmented, Spin, message, Card, Row, Col, Empty, } from 'antd'; import { PlusOutlined, EditOutlined } from '@ant-design/icons'; import { dayjs } from '../../utils/dayjs'; import type { Dayjs } from 'dayjs'; import { appointmentApi, type Schedule, type CreateScheduleReq, type UpdateScheduleReq, type CalendarDay, } from '../../api/health/appointments'; import { DoctorSelect } from './components/DoctorSelect'; import { CalendarView, type ScheduleItem } from './components/CalendarView'; import { StatusTag } from './components/StatusTag'; import { AuthButton } from '../../components/AuthButton'; /** 时段选项 */ const PERIOD_OPTIONS = [ { value: 'am', label: '上午' }, { value: 'pm', label: '下午' }, ]; const PERIOD_LABEL: Record = { am: '上午', pm: '下午', }; /** 排班状态选项 */ const SCHEDULE_STATUS_OPTIONS = [ { value: 'active', label: '启用' }, { value: 'inactive', label: '停用' }, { value: 'cancelled', label: '已取消' }, ]; export default function DoctorSchedule() { // ---- 状态 ---- const [selectedDoctorId, setSelectedDoctorId] = useState(undefined); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(50); const [loading, setLoading] = useState(false); // 视图模式 const [viewMode, setViewMode] = useState('列表'); // 日历数据 const [calendarData, setCalendarData] = useState([]); const [calendarLoading, setCalendarLoading] = useState(false); // 弹窗 const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [form] = Form.useForm(); // ---- 列表数据获取 ---- const fetchSchedules = useCallback(async (p = page, ps = pageSize) => { if (!selectedDoctorId) { setData([]); setTotal(0); return; } setLoading(true); try { const result = await appointmentApi.listSchedules({ page: p, page_size: ps, doctor_id: selectedDoctorId, }); setData(result.data); setTotal(result.total); } catch { message.error('加载排班列表失败'); } finally { setLoading(false); } }, [page, pageSize, selectedDoctorId]); // ---- 日历数据获取 ---- const [calendarMonth, setCalendarMonth] = useState(dayjs()); const fetchCalendar = useCallback(async (month?: Dayjs) => { if (!selectedDoctorId) { setCalendarData([]); return; } const target = month ?? calendarMonth; setCalendarLoading(true); try { const result = await appointmentApi.calendar({ start_date: target.startOf('month').format('YYYY-MM-DD'), end_date: target.endOf('month').format('YYYY-MM-DD'), doctor_id: selectedDoctorId, }); setCalendarData(result); } catch { message.error('加载日历数据失败'); } finally { setCalendarLoading(false); } }, [selectedDoctorId, calendarMonth]); // 切换医护或视图模式时加载数据 useEffect(() => { if (viewMode === '列表') { fetchSchedules(); } else { fetchCalendar(); } }, [fetchSchedules, fetchCalendar, viewMode]); // 切换医护时重置页码 const handleDoctorChange = (val: string) => { setSelectedDoctorId(val || undefined); setPage(1); }; // ---- 新建 / 编辑排班 ---- const openCreate = () => { setEditing(null); form.resetFields(); form.setFieldsValue({ period_type: 'am', max_appointments: 10 }); setModalOpen(true); }; const openEdit = (record: Schedule) => { setEditing(record); form.setFieldsValue({ schedule_date: dayjs(record.schedule_date), period_type: record.period_type, start_time: dayjs(record.start_time, 'HH:mm'), end_time: dayjs(record.end_time, 'HH:mm'), max_appointments: record.max_appointments, status: record.status, }); setModalOpen(true); }; const handleSubmit = async (values: { schedule_date: Dayjs; period_type: string; start_time: Dayjs; end_time: Dayjs; max_appointments: number; status?: string; }) => { if (!selectedDoctorId) { message.warning('请先选择医护'); return; } try { if (editing) { const req: UpdateScheduleReq & { version: number } = { start_time: values.start_time.format('HH:mm'), end_time: values.end_time.format('HH:mm'), max_appointments: values.max_appointments, status: values.status, version: editing.version, }; await appointmentApi.updateSchedule(editing.id, req); message.success('排班更新成功'); } else { const req: CreateScheduleReq = { doctor_id: selectedDoctorId, schedule_date: values.schedule_date.format('YYYY-MM-DD'), period_type: values.period_type, start_time: values.start_time.format('HH:mm'), end_time: values.end_time.format('HH:mm'), max_appointments: values.max_appointments, }; await appointmentApi.createSchedule(req); message.success('排班创建成功'); } setModalOpen(false); form.resetFields(); if (viewMode === '列表') { fetchSchedules(page, pageSize); } else { fetchCalendar(); } } catch { message.error(editing ? '更新排班失败' : '创建排班失败'); } }; // ---- 列定义 ---- const columns = [ { title: '日期', dataIndex: 'schedule_date', key: 'schedule_date', width: 120, render: (val: string) => val || '-', }, { title: '时段', dataIndex: 'period_type', key: 'period_type', width: 80, render: (val: string) => PERIOD_LABEL[val] || val, }, { title: '开始时间', dataIndex: 'start_time', key: 'start_time', width: 100, render: (val: string) => val || '-', }, { title: '结束时间', dataIndex: 'end_time', key: 'end_time', width: 100, render: (val: string) => val || '-', }, { title: '已约/上限', key: 'appointment_ratio', width: 110, render: (_: unknown, record: Schedule) => { const ratio = record.max_appointments > 0 ? record.current_appointments / record.max_appointments : 0; const color = ratio >= 1 ? '#ff4d4f' : ratio >= 0.8 ? '#faad14' : '#52c41a'; return ( {record.current_appointments}/{record.max_appointments} ); }, }, { title: '状态', dataIndex: 'status', key: 'status', width: 90, render: (val: string) => , }, { title: '操作', key: 'action', width: 120, render: (_: unknown, record: Schedule) => ( ), }, ]; // ---- 将日历数据转换为 CalendarView 所需格式 ---- const calendarScheduleMap: Record = {}; for (const day of calendarData) { calendarScheduleMap[day.date] = day.schedules.map((s) => ({ id: s.id, start_time: s.start_time, end_time: s.end_time, current_appointments: s.current_appointments, max_appointments: s.max_appointments, status: s.status, })); } return ( {/* 顶部操作栏 */} 选择医护: {selectedDoctorId && ( setViewMode(val as string)} options={['列表', '日历']} /> )} {selectedDoctorId && ( )} {/* 内容区 */} {!selectedDoctorId ? ( ) : viewMode === '列表' ? ( `共 ${t} 条`, onChange: (p, ps) => { setPage(p); setPageSize(ps); }, }} /> ) : (
{ setCalendarMonth(date); fetchCalendar(date); }} />
)} {/* 新建 / 编辑排班弹窗 */} { setModalOpen(false); form.resetFields(); }} onOk={() => form.submit()} destroyOnClose width={520} >
)} ); }