import { useEffect, useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { api } from '../../lib/api'; import type { ApiResponse } from '../../lib/api'; import type { BookingService } from '../../hooks/useDoctorBookingServices'; import { useClinicContext } from '../../hooks/useClinicContext'; import SearchableSelect from '../ui/SearchableSelect'; import DigitInput from '../ui/DigitInput'; interface ServiceSlot { start: number; end: number; start_time: string } export interface PickedService { uuid: string; name: string; section: string; duration: number } export interface ServicePick { serviceUuids: string[]; durations: Record; slot: ServiceSlot | null } /** * انتخاب سرویس بر اساس بخش (بخش → سرویس، انباشته از چند بخش) + زمان‌های خالیِ پیشنهادی * برای نوبت‌دهی سرویسی. مدتِ هر سرویس در پنل قابل ویرایش است (فقط برای همین نوبت؛ پیش‌فرضِ * سرویس تغییر نمی‌کند). مدت کل = مجموع مدت‌ها؛ زمان‌ها از `appointment-service-slots` * با اعمال همان override محاسبه می‌شوند. انتخاب را از طریق onSelect بالا می‌فرستد. */ export default function ServiceSlotPicker({ doctorUuid, date, services, onSelect, editableDuration = true, clinicUuidOverride, }: { doctorUuid: string; date: string; services: BookingService[]; onSelect: (v: ServicePick) => void; editableDuration?: boolean; /** undefined = context محیط جاری؛ مقدار صریح (شامل null) = محل انتخاب‌شده خارج از context */ clinicUuidOverride?: string | null; }) { const contextClinicUuid = useClinicContext(); const clinicUuid = clinicUuidOverride === undefined ? contextClinicUuid : clinicUuidOverride; const [sectionUuid, setSectionUuid] = useState(''); const [selected, setSelected] = useState([]); const [pickedSlot, setPickedSlot] = useState(null); // بخش‌های یکتا از روی سرویس‌های bookable (بدون endpoint اضافه — همه یکجا آمده‌اند). const sections = useMemo(() => { const map = new Map(); services.forEach(s => { if (s.service_section) map.set(s.service_section.uuid, s.service_section); }); return [...map.values()]; }, [services]); const sectionServices = useMemo( () => services.filter(s => s.service_section?.uuid === sectionUuid), [services, sectionUuid], ); // تعویض پزشک ⇒ لیست سرویس‌ها عوض می‌شود؛ انتخاب‌ها ریست شوند. useEffect(() => { setSelected([]); setSectionUuid(''); }, [doctorUuid]); useEffect(() => { setPickedSlot(null); }, [selected, date, doctorUuid]); const serviceUuids = useMemo(() => selected.map(s => s.uuid), [selected]); const durations = useMemo( () => Object.fromEntries(selected.map(s => [s.uuid, s.duration])) as Record, [selected], ); useEffect(() => { onSelect({ serviceUuids, durations, slot: pickedSlot }); }, [serviceUuids, durations, pickedSlot]); const durationsQs = selected.map(s => `&durations[${encodeURIComponent(s.uuid)}]=${s.duration}`).join(''); const slotsQ = useQuery>({ queryKey: ['service-slots-picker', doctorUuid, date, serviceUuids, durations, clinicUuid], queryFn: () => api.get( `/api/v1/appointment-service-slots?doctor_uuid=${doctorUuid}&date=${date}` + serviceUuids.map(u => `&service_item_uuids[]=${encodeURIComponent(u)}`).join('') + durationsQs + (clinicUuid ? `&clinic_uuid=${encodeURIComponent(clinicUuid)}` : ''), ), enabled: !!doctorUuid && !!date && serviceUuids.length > 0, }); const startTimes: ServiceSlot[] = (slotsQ.data?.data as any)?.start_times ?? []; const totalMinutes = (slotsQ.data?.data as any)?.total_duration_minutes as number | undefined; const label = { fontSize: 12.5, color: 'var(--text-3)', display: 'block' } as const; const toggle = (s: BookingService) => setSelected(prev => prev.some(p => p.uuid === s.uuid) ? prev.filter(p => p.uuid !== s.uuid) : [...prev, { uuid: s.uuid, name: s.name, section: s.service_section.name, duration: s.duration_minutes ?? 0 }]); const remove = (uuid: string) => setSelected(prev => prev.filter(p => p.uuid !== uuid)); const setDuration = (uuid: string, minutes: number) => setSelected(prev => prev.map(p => p.uuid === uuid ? { ...p, duration: minutes } : p)); if (services.length === 0) { return (
سرویسی با «نمایش در نوبت‌دهی» برای این پزشک تعریف نشده است.
); } return (
{/* انتخاب بخش */}
({ value: s.uuid, label: s.name }))} value={sectionUuid || null} onChange={v => setSectionUuid(v ? String(v) : '')} placeholder="ابتدا بخش را انتخاب کنید" isClearable height={44} />
{/* سرویس‌های بخشِ انتخاب‌شده — چند انتخابی */} {sectionUuid && ( <> {sectionServices.length === 0 ? (
سرویسی در این بخش تعریف نشده است.
) : (
{sectionServices.map(s => { const active = selected.some(p => p.uuid === s.uuid); return ( ); })}
)} )} {/* لیستِ انباشتهٔ سرویس‌های انتخاب‌شده (از هر بخش) — «بخش → سرویس» + مدت قابل‌ویرایش + حذف */} {selected.length > 0 && (
{selected.map(s => (
{s.section} {' ← '}{s.name} {editableDuration ? ( setDuration(s.uuid, Number(v) || 0)} maxDigits={3} style={{ width: 34, border: 'none', outline: 'none', background: 'transparent', textAlign: 'center', fontFamily: 'inherit', fontSize: 13, color: 'var(--text)' }} /> دقیقه ) : ( {s.duration} دقیقه )}
))}
)} {/* زمان‌های خالی پیشنهادی */} {selected.length > 0 && ( <> {slotsQ.isLoading ? (
در حال محاسبه...
) : startTimes.length === 0 ? (
برای این سرویس در این روز زمان خالی کافی نیست؛ روز دیگری انتخاب کنید.
) : (
{startTimes.map(s => { const active = pickedSlot?.start === s.start; return ( ); })}
)} )}
); }