import React, { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { MagnifyingGlassIcon, EyeIcon, TableCellsIcon, CalendarDaysIcon as CalendarViewIcon, CalendarIcon, ClockIcon, CheckCircleIcon, XCircleIcon, FunnelIcon, } from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import type { PaginatedResponse } from '../lib/api'; import type { Appointment } from '../types'; import { formatDate, formatRial, maskMobile } from '../lib/utils'; import DataTable, { Column } from '../components/ui/DataTable'; import StatusBadge from '../components/ui/StatusBadge'; import Pagination from '../components/ui/Pagination'; import { useAuthStore } from '../stores/authStore'; // ── Status helpers ──────────────────────────────────────────────────────── const STATUS_FILTERS = [ { value: '', label: 'همه' }, { value: 'waiting_for_payment', label: 'در انتظار پرداخت' }, { value: 'reserved', label: 'رزرو شده' }, { value: 'checked_in', label: 'ورود به مطب' }, { value: 'waiting', label: 'صف انتظار' }, { value: 'in_progress', label: 'در حال ویزیت' }, { value: 'visited', label: 'ویزیت شده' }, { value: 'completed', label: 'تکمیل شده' }, { value: 'cancelled_by_doctor', label: 'لغو پزشک' }, { value: 'cancelled_by_user', label: 'لغو بیمار' }, { value: 'no_show', label: 'غیبت' }, ]; const APPT_CLS: Record = { waiting_for_payment: 'amber', reserved: 'blue', checked_in: 'violet', waiting: 'amber', in_progress: 'violet', visited: 'green', completed: 'green', cancelled_by_doctor: 'red', cancelled_by_user: 'red', auto_cancel_unpaid: 'gray', no_show: 'gray', }; const APPT_LABEL: Record = { waiting_for_payment: 'انتظار پرداخت', reserved: 'رزرو شده', checked_in: 'ورود به مطب', waiting: 'صف انتظار', in_progress: 'در حال ویزیت', visited: 'ویزیت شده', completed: 'تکمیل شده', cancelled_by_doctor: 'لغو پزشک', cancelled_by_user: 'لغو بیمار', auto_cancel_unpaid: 'لغو خودکار', no_show: 'غیبت', }; // ── Timeline View ───────────────────────────────────────────────────────── interface TimelineProps { items: Appointment[]; loading: boolean; onView: (uuid: string) => void; } function TimelineView({ items, loading, onView }: TimelineProps) { if (loading) { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
); } if (!items.length) { return

هیچ نوبتی یافت نشد

; } // گروه‌بندی بر اساس تاریخ const grouped = items.reduce>((acc, a) => { const key = a.appointment_date; if (!acc[key]) acc[key] = []; acc[key].push(a); return acc; }, {}); return (
{Object.entries(grouped).map(([date, appts]) => (
{new Date(date).toLocaleDateString('fa-IR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
{appts.map((a) => (
onView(a.uuid)} onMouseEnter={e => (e.currentTarget.style.boxShadow = 'var(--shadow-sm, 0 2px 8px rgba(0,0,0,.08))')} onMouseLeave={e => (e.currentTarget.style.boxShadow = 'none')} > {/* ساعت */}
{a.appointment_time}
{/* اطلاعات */}
{a.patient_name || maskMobile(a.patient_mobile)}
دکتر {a.doctor_name}{a.clinic_name ? ` · ${a.clinic_name}` : ''}
{/* وضعیت */} {APPT_LABEL[a.status] ?? a.status}
))}
))}
); } // ── Main Component ──────────────────────────────────────────────────────── export default function AppointmentsPage() { const navigate = useNavigate(); const primaryRole = useAuthStore(s => s.primaryRole); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [dateFilter, setDateFilter] = useState(''); const [viewMode, setViewMode] = useState<'table' | 'timeline'>('table'); const limit = 15; const isAdmin = primaryRole === 'admin'; const endpoint = isAdmin ? '/api/v1/admin/appointments' : '/api/v1/my/appointments'; const { data, isLoading } = useQuery({ queryKey: ['appointments', endpoint, page, search, statusFilter, dateFilter], queryFn: () => { const params = new URLSearchParams({ page: String(page), limit: String(limit) }); if (search) params.set('search', search); if (statusFilter) params.set('status', statusFilter); if (dateFilter) params.set('date', dateFilter); return api.get>(`${endpoint}?${params}`); }, }); const columns: Column[] = [ { key: 'patient', header: 'بیمار', render: (a) => (
{(a.patient_name ?? '؟').slice(0, 2)}
{a.patient_name || '—'}
{maskMobile(a.patient_mobile)}
), }, { key: 'doctor_name', header: 'پزشک', render: (a) => `دکتر ${a.doctor_name}` }, { key: 'clinic_name', header: 'کلینیک', render: (a) => {a.clinic_name ?? '—'} }, { key: 'appointment_date', header: 'تاریخ نوبت', render: (a) => (
{formatDate(a.appointment_date)}
{a.appointment_time}
), }, { key: 'status', header: 'وضعیت', render: (a) => , }, { key: 'amount', header: 'مبلغ', render: (a) => <>{formatRial(a.amount)} تومان, }, { key: 'created_at', header: 'تاریخ ثبت', render: (a) => {formatDate(a.created_at)} }, ]; const items = data?.data ?? []; const total = data?.meta?.totalRecords ?? 0; const pageTitle = isAdmin ? 'نوبت‌ها' : (primaryRole === 'doctor' ? 'نوبت‌های من' : primaryRole === 'secretary' ? 'نوبت‌های پزشک' : 'نوبت‌های کلینیک'); const statCards = [ { label: 'کل نوبت‌ها', value: total > 0 ? String(total) : null, bg: 'var(--info-bg)', color: 'var(--info)', Icon: CalendarIcon }, { label: 'در انتظار', value: null, bg: 'var(--warning-bg)', color: 'var(--warning)', Icon: ClockIcon }, { label: 'ویزیت شده', value: null, bg: 'var(--success-bg)', color: 'var(--success)', Icon: CheckCircleIcon }, { label: 'لغو شده', value: null, bg: 'var(--danger-bg)', color: 'var(--danger)', Icon: XCircleIcon }, ]; return (
{/* Header */}

{pageTitle}

مدیریت و پیگیری نوبت‌های درمانی
{/* Stat cards */}
{statCards.map((c) => (
{c.label}
{isLoading ? : c.value ?? '—'}
))}
{/* Main card */}
{/* جستجو */}
{ setSearch(e.target.value); setPage(1); }} />
{/* فیلتر تاریخ */}
{ setDateFilter(e.target.value); setPage(1); }} style={{ direction: 'ltr' }} />
{/* فیلتر وضعیت */}
{/* تغییر نما */}
{viewMode === 'table' ? ( <> columns={columns} data={items} loading={isLoading} emptyMessage="هیچ نوبتی یافت نشد" actions={(appt) => ( )} /> ) : ( <> navigate(`/admin/appointments/${uuid}`)} /> )}
); }