import React, { useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { ArrowRightIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import type { Appointment, AppointmentStatus, AppointmentEvent } from '../types'; import { formatDate, formatDateTime, toDate } from '../lib/utils'; import PageHeader from '../components/ui/PageHeader'; import StatusBadge from '../components/ui/StatusBadge'; import ConfirmDialog from '../components/ui/ConfirmDialog'; import SearchableSelect from '../components/ui/SearchableSelect'; import ConfirmAppointmentModal from '../components/appointments/ConfirmAppointmentModal'; const ALL_STATUSES: { value: AppointmentStatus; label: string }[] = [ { value: 'pending', label: 'رزرو شده' }, { value: 'confirmed', label: 'تأیید شده' }, { value: 'completed', label: 'تکمیل شده' }, { value: 'cancelled_by_doctor', label: 'لغو پزشک' }, { value: 'cancelled_by_user', label: 'لغو بیمار' }, { value: 'no_show', label: 'غیبت' }, { value: 'expired', label: 'منقضی' }, ]; // روزِ محلیِ نوبت (YYYY-MM-DD) برای بازگشت به همان تاریخ در لیست. const isoDay = (ts?: number | null) => { 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')}`; }; const timeOf = (ts?: number | null) => { const d = toDate(ts ?? null); return d ? new Intl.DateTimeFormat('fa-IR-u-nu-latn', { hour: '2-digit', minute: '2-digit', hour12: false }).format(d) : '—'; }; function InfoRow({ label, value }: { label: string; value: React.ReactNode }) { return (
{label} {value ?? '—'}
); } export default function AppointmentDetailPage() { const { uuid } = useParams<{ uuid: string }>(); const navigate = useNavigate(); const qc = useQueryClient(); const [cancelOpen, setCancelOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); const [cancelReason, setCancelReason] = useState(''); const [newStatus, setNewStatus] = useState(''); const { data, isLoading } = useQuery({ queryKey: ['appointment', uuid], queryFn: () => api.get>(`/api/v1/appointment/${uuid}`), enabled: !!uuid, }); const eventsQuery = useQuery({ queryKey: ['appointment-events', uuid], queryFn: () => api.get>(`/api/v1/appointment/${uuid}/events`), enabled: !!uuid, }); const events: AppointmentEvent[] = (eventsQuery.data?.data as any) ?? []; const statusMutation = useMutation({ mutationFn: (status: string) => api.patch>(`/api/v1/appointment/${uuid}/status`, { status, version: appt?.version }), onSuccess: () => { toast.success('وضعیت نوبت بروزرسانی شد'); qc.invalidateQueries({ queryKey: ['appointment', uuid] }); qc.invalidateQueries({ queryKey: ['appointment-events', uuid] }); }, onError: (err: Error) => toast.error(err.message), }); const cancelMutation = useMutation({ mutationFn: () => api.patch>(`/api/v1/appointment/${uuid}/status`, { status: 'cancelled_by_doctor', version: appt?.version, ...(cancelReason.trim() ? { cancel_reason: cancelReason.trim() } : {}), }), onSuccess: () => { toast.success('نوبت لغو شد'); setCancelOpen(false); setCancelReason(''); qc.invalidateQueries({ queryKey: ['appointment', uuid] }); qc.invalidateQueries({ queryKey: ['appointment-events', uuid] }); }, onError: (err: Error) => toast.error(err.message), }); // پاسخ single تودرتو است: { data: { data: {...} } } const appt: any = (data?.data as any)?.data ?? data?.data; // قطعی‌کردن هزینه و پرداخت دارد؛ از مسیر مودال می‌رود، نه PATCH وضعیت. function applyStatus() { if (!newStatus) return; if (newStatus === 'confirmed') { setConfirmOpen(true); return; } statusMutation.mutate(newStatus); } // بازگشت به همان روزِ نوبت (نه امروز). const day = isoDay(appt?.slot_start); const backTo = day ? `/admin/appointments?date=${day}` : '/admin/appointments'; return (
navigate(backTo)} className="flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-100 transition-colors"> بازگشت } /> {isLoading ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : appt ? (

اطلاعات بیمار

{appt.patient_mobile}} /> {appt.patient_reason && } {appt.note && }

وضعیت و اقدامات

وضعیت فعلی:

{appt.status === 'pending' && ( )}
({ value: s.value, label: s.label }))} value={newStatus || null} onChange={(v) => setNewStatus(v ? String(v) : '')} placeholder="انتخاب وضعیت..." isClearable />

تاریخچه رویدادها

{eventsQuery.isLoading ? (
) : events.length === 0 ? (

رویدادی برای این نوبت ثبت نشده است.

) : (
    {events.map((ev, i) => (
  1. {ev.title} {formatDateTime(ev.created_at)}
    {ev.actor_name && (
    توسط: {ev.actor_name}
    )} {ev.reason && (
    دلیل: {ev.reason}
    )}
  2. ))}
)}
) : (
نوبتی یافت نشد
)} cancelMutation.mutate()} onCancel={() => { setCancelOpen(false); setCancelReason(''); }} >