/** * «لیست نوبت‌های جدید» table — ported from clinic-pro-tauri * `src/components/dashboard/list/` (CustomTable + DetailT + Status). * * Source data was static mock; here it is fed by the real dashboard API * (`/api/v1/dashboard/{clinic,doctor}` → `today_appointments`) or, when a * `queryKey` is supplied, by the filtered doctor list endpoint. Status is * editable in place only when the row carries a `version` (optimistic lock) * and the parent passes the query key to invalidate. */ import React from 'react'; import { Link } from 'react-router-dom'; import AppointmentStatusDropdown, { STATUS_META } from '../ui/AppointmentStatusDropdown'; export interface ApptRow { uuid: string; patient_name: string | null; patient_mobile?: string | null; doctor_name?: string | null; service_name?: string | null; slot_start: number; slot_end?: number | null; status: string; /** لازم برای قفل خوش‌بینانهٔ PATCH وضعیت؛ نبودنش یعنی وضعیت فقط‌خواندنی است. */ version?: number; } /** * روزِ محلیِ نوبت (YYYY-MM-DD) برای دیپ‌لینک «مشاهده» به همان تاریخ در لیست نوبت‌ها. * همان قراردادی که AppointmentDetailPage/AppointmentsPage استفاده می‌کنند. */ const isoDay = (ts?: number | null): string => { if (!ts) return ''; const d = new Date(ts * 1000); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; }; function formatTime(ts?: number | null): string { if (!ts) return '—'; return new Intl.DateTimeFormat('fa-IR', { timeZone: 'Asia/Tehran', hour: '2-digit', minute: '2-digit' }).format(new Date(ts * 1000)); } const HEAD = ['ردیف', 'نام بیمار', 'شماره تماس', 'شروع', 'پایان', 'سرویس', 'پرسنل', 'وضعیت', 'عملیات']; interface Props { rows: ApptRow[]; loading?: boolean; /** کلید کوئری برای invalidate پس از تغییر وضعیت. بدون آن وضعیت فقط نمایش داده می‌شود. */ queryKey?: unknown[]; emptyText?: string; } export function NewAppointmentsTable({ rows, loading, queryKey, emptyText }: Props) { if (loading) { return
; } if (!rows.length) { return (

{emptyText ?? 'نوبتی برای امروز ثبت نشده'}

); } return (
{HEAD.map((h, i) => ( ))} {rows.map((r, idx) => ( {new Intl.NumberFormat('fa-IR').format(idx + 1)}{r.patient_name || '—'}{r.patient_mobile ? {r.patient_mobile} : '—'}{formatTime(r.slot_start)}{formatTime(r.slot_end)}{r.service_name || '—'}{r.doctor_name || '—'} {queryKey && r.version != null ? : } ))}
{h}
مشاهده
); } /** نمایش فقط‌خواندنی وضعیت — وقتی version یا queryKey در دسترس نیست. */ function StatusPill({ status }: { status: string }) { const meta = STATUS_META[status] ?? { label: status, color: '#9ca3af' }; return ( {meta.label} ); } /** همه‌ی سلول‌ها text-start هستند تا دقیقاً زیر هدر هم‌نامشان بنشینند. */ function Cell({ children, className = '' }: { children: React.ReactNode; className?: string }) { return ( {children} ); }