import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useNavigate, useParams, Link } from 'react-router-dom'; import { ChevronRightIcon } from '@heroicons/react/24/outline'; import { toast } from 'sonner'; import { api } from '../lib/api'; import type { ApiResponse } from '../lib/api'; import PersianDateInput from '../components/ui/PersianDateInput'; import PriceInput from '../components/ui/PriceInput'; import SearchableSelect from '../components/ui/SearchableSelect'; import { WalletChargeLink } from '../components/AppointmentActions'; import { rialToToman, tomanToRial } from '../lib/utils'; interface Option { uuid: string; name?: string; full_name?: string } interface AppointmentDetail { uuid: string; slot_start: number; slot_end: number; status: string; version: number; note?: string | null; patient_mobile?: string | null; user?: { uuid: string; mobile: string } | null; deposit_required?: boolean; deposit_amount_rials?: number | null; service_section?: Option | null; service_item?: Option | null; staff?: Option | null; } const isoDate = (ts: number) => { const d = new Date(ts * 1000); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; }; const isoTime = (ts: number) => { const d = new Date(ts * 1000); return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }; const toEpoch = (date: string, time: string) => Math.floor(new Date(`${date}T${time || '00:00'}`).getTime() / 1000); /** ویرایش نوبت (Figma edit.pdf) — full-page edit of service specs, timing, deposit, status and notes. */ export default function AppointmentEditPage() { const { uuid = '' } = useParams(); const navigate = useNavigate(); const qc = useQueryClient(); const { data, isLoading } = useQuery>({ queryKey: ['appointment-edit', uuid], queryFn: () => api.get(`/api/v1/appointment/${uuid}`), enabled: !!uuid, }); const a = data?.data?.data; const [sectionUuid, setSectionUuid] = useState(''); const [itemUuid, setItemUuid] = useState(''); const [staffUuid, setStaffUuid] = useState(''); const [date, setDate] = useState(''); const [start, setStart] = useState(''); const [end, setEnd] = useState(''); const [depositRequired, setDepositRequired] = useState(false); const [depositToman, setDepositToman] = useState(0); const [status, setStatus] = useState(''); const [note, setNote] = useState(''); // hydrate once the appointment arrives useEffect(() => { if (!a) return; setSectionUuid(a.service_section?.uuid ?? ''); setItemUuid(a.service_item?.uuid ?? ''); setStaffUuid(a.staff?.uuid ?? ''); setDate(isoDate(a.slot_start)); setStart(isoTime(a.slot_start)); setEnd(isoTime(a.slot_end)); setDepositRequired(!!a.deposit_required); setDepositToman(rialToToman(a.deposit_amount_rials ?? 0)); setStatus(a.status); setNote(a.note ?? ''); }, [a]); const sectionsQ = useQuery>({ queryKey: ['service-sections'], queryFn: () => api.get('/api/v1/service-sections') }); const itemsQ = useQuery>({ queryKey: ['service-items', sectionUuid], queryFn: () => api.get(`/api/v1/service-items/${sectionUuid}`), enabled: !!sectionUuid, }); const staffQ = useQuery>({ queryKey: ['staff-list'], queryFn: () => api.get('/api/v1/staff') }); const save = useMutation({ mutationFn: () => api.patch(`/api/v1/appointment/${uuid}`, { slot_start: toEpoch(date, start), slot_end: toEpoch(date, end), service_section_uuid: sectionUuid, service_item_uuid: itemUuid, staff_uuid: staffUuid, deposit_required: depositRequired, deposit_amount_rials: depositRequired ? tomanToRial(depositToman) : null, note, ...(status !== a?.status ? { status } : {}), version: a?.version, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['appointments'] }); toast.success('نوبت به‌روزرسانی شد'); // به همان روزِ نوبت برگرد، نه امروز. navigate(`/admin/appointments?date=${date}`); }, onError: (e: any) => toast.error(e.message || 'خطا در ذخیره اطلاعات'), }); const label = { fontSize: 12.5, color: 'var(--text-3)' } as const; if (isLoading || !a) return
در حال بارگذاری...
; const statusOptions: [string, string][] = [ ['pending', 'ثبت شده'], ['confirmed', 'قطعی شده'], ['following_up', 'در حال پیگیری'], ['salon', 'سالن'], ['completed', 'ویزیت شده'], ['cancelled_by_doctor', 'لغو شده'], ]; return (
بازگشت
مشخصات سرویس:
({ value: o.uuid, label: o.name ?? '' }))} value={sectionUuid || null} onChange={v => { setSectionUuid(v ? String(v) : ''); setItemUuid(''); }} placeholder="انتخاب بخش" isLoading={sectionsQ.isLoading} isClearable height={38} />
({ value: o.uuid, label: o.name ?? '' }))} value={itemUuid || null} onChange={v => setItemUuid(v ? String(v) : '')} placeholder="انتخاب سرویس" isDisabled={!sectionUuid} isLoading={itemsQ.isLoading} isClearable height={38} />
({ value: o.uuid, label: o.full_name ?? '' }))} value={staffUuid || null} onChange={v => setStaffUuid(v ? String(v) : '')} placeholder="انتخاب پرسنل" isLoading={staffQ.isLoading} isClearable height={38} />
زمان نوبت:
setStart(e.target.value)} dir="ltr" />
setEnd(e.target.value)} dir="ltr" />
بیعانه:
{depositRequired && ( <>
)}
({ value: v, label: l }))} value={status || null} onChange={v => setStatus(v ? String(v) : '')} placeholder="انتخاب وضعیت" height={38} />