"use client"; import { useEffect, useState } from "react"; import moment from "moment-jalaali"; import { request } from "@/services/response"; import Cookies from "js-cookie"; import Container from "./Container"; import { safeJsonParse } from "@/lib/sanitize"; const defaultData = { phone: { value: "", isEdit: false }, national_code: { value: "", isEdit: true }, name: { value: "", isEdit: true }, family: { value: "", isEdit: true }, gender: { value: "", isEdit: true }, insurance_id: { value: "", isEdit: true }, basic_insurance: { value: "", isEdit: true }, supplementary_insurance: { value: "", isEdit: true }, }; function buildProfileData(profile, phone) { return { uuid: profile.uuid, phone: { value: phone, isEdit: false }, national_code: { value: profile.national_code || "", isEdit: !profile.national_code_approved, }, name: { value: profile.label || "", isEdit: true }, family: { value: profile.family || "", isEdit: true }, gender: { value: profile.gender || "", isEdit: true }, insurance_id: { value: profile.insurance_id || "", isEdit: true }, basic_insurance: { value: profile.basic_insurance_id ? { id: profile.basic_insurance_id } : "", isEdit: true, }, supplementary_insurance: { value: profile.supplementary_insurance_id ? { id: profile.supplementary_insurance_id } : "", isEdit: true, }, }; } function AppointmentPage({ doctor, disabledDates, matchedCity, initialClinicUuid = null }) { const [step, setStep] = useState(0); const [isForAnother, setIsForAnother] = useState(false); const [isLoading, setIsLoading] = useState(true); const [prevData, setPrevData] = useState(defaultData); const [data, setData] = useState(defaultData); // Login states const [num, setNum] = useState(""); const [uuid, setUuid] = useState(""); const [isSendMsg, setIsSendMsg] = useState(false); // Appointment slot states const [selectedSlot, setSelectedSlot] = useState(null); const [selectedDate, setSelectedDate] = useState(null); const [appointmentId, setAppointmentId] = useState(null); const [appointmentExpiresAt, setAppointmentExpiresAt] = useState(null); // Service-based booking (روش نوبت‌دهی سرویسی) const [selectedServiceUuids, setSelectedServiceUuids] = useState([]); // نوبت‌دهی منبع‌محور: دستگاه/اتاق/یونیتِ همین پزشک در همین محل. // `selectedResource === null` یعنی «نوبت با پزشک» — همان جریان همیشگی. const [resources, setResources] = useState([]); const [selectedResource, setSelectedResource] = useState(null); const [resourceConfirmed, setResourceConfirmed] = useState(false); // محل نوبت‌دهی: مطب شخصی یا یکی از کلینیک‌هایی که پزشک در آن برنامه دارد. const [bookingLocations, setBookingLocations] = useState([]); const [selectedLocation, setSelectedLocation] = useState(null); const [locationConfirmed, setLocationConfirmed] = useState(false); const [locationsLoading, setLocationsLoading] = useState(true); // روزی که کاربر در تقویم مرور می‌کند (timestamp) — با selectedDate فرق دارد، // که فقط پس از تأیید اسلات ست می‌شود. const [browsingDate, setBrowsingDate] = useState(null); useEffect(() => { if (!doctor?.uuid) return; request .getBookingLocations(doctor.uuid) .then((res) => { const d = res?.data?.data ?? res?.data ?? {}; const list = Array.isArray(d.booking_locations) ? d.booking_locations : []; setBookingLocations(list); // آرایه از قبل بر اساس زودترین نوبت آزاد مرتب است؛ دوباره مرتبش نکن. const fromUrl = initialClinicUuid ? list.find((l) => l.clinic_uuid === initialClinicUuid) : null; setSelectedLocation(fromUrl ?? list[0] ?? null); setLocationConfirmed(Boolean(fromUrl) || list.length <= 1); }) .catch(() => setBookingLocations([])) .finally(() => setLocationsLoading(false)); }, [doctor?.uuid, initialClinicUuid]); // با تغییر روز، فقط پرچم available_on_date تازه می‌شود — انتخاب کاربر دست‌نخورده // می‌ماند، وگرنه هر بار جابه‌جایی در تقویم مرحله را ریست می‌کرد. useEffect(() => { if (!doctor?.uuid || !browsingDate) return; const date = moment.unix(browsingDate).format("YYYY-MM-DD"); request .getBookingLocations(doctor.uuid, date) .then((res) => { const d = res?.data?.data ?? res?.data ?? {}; const byKey = new Map( (Array.isArray(d.booking_locations) ? d.booking_locations : []).map((l) => [ l.clinic_uuid ?? "personal", l.available_on_date, ]) ); setBookingLocations((prev) => prev.map((l) => ({ ...l, available_on_date: byKey.get(l.clinic_uuid ?? "personal") ?? false, })) ); }) .catch(() => {}); }, [doctor?.uuid, browsingDate]); // منابع **مستقل از محل‌ها** گرفته می‌شوند و یک‌بار برای همهٔ محیط‌های پزشک. // // وابسته‌کردنش به محل، پزشکی را که خودش برنامهٔ نوبت‌دهی ندارد ولی دستگاهش دارد از // دسترس خارج می‌کرد: «محل» از برنامهٔ هفتگیِ پزشک می‌آید و برای او خالی است، در حالی // که دستگاه تقویم و شعبهٔ خودش را دارد. هر منبع `clinic_uuid` خودش را همراه دارد. // // خطا عمداً بی‌صدا است — پزشکِ بدون منبع و خطای شبکه هر دو یعنی «مرحلهٔ منبع را نشان // نده»، و جریان پزشک‌محور باید دست‌نخورده ادامه یابد. useEffect(() => { if (!doctor?.uuid) return; let cancelled = false; request .getBookingResources(doctor.uuid) .then((res) => { if (cancelled) return; const d = res?.data?.data ?? res?.data ?? {}; setResources(Array.isArray(d.resources) ? d.resources : []); }) .catch(() => !cancelled && setResources([])); return () => { cancelled = true; }; }, [doctor?.uuid]); // منابعِ محلِ انتخاب‌شده. وقتی محلی وجود ندارد (پزشکِ بدون برنامهٔ هفتگی) همهٔ منابع // نشان داده می‌شوند، چون خودشان محیطشان را همراه دارند. const locationResources = bookingLocations.length === 0 ? resources : resources.filter( (r) => (r.clinic_uuid ?? null) === (selectedLocation?.clinic_uuid ?? null) ); // روش نوبت‌دهی و سرویس‌ها per-location هستند: یک پزشک می‌تواند در مطب شخصی // اسلاتی و در کلینیک سرویسی باشد. // // منبع این را بازنویسی می‌کند: منبع اسلاتِ ثابت ندارد و زمان‌هایش همیشه از مدتِ // سرویس‌های انتخاب‌شده ساخته می‌شوند، حتی اگر برنامهٔ همان محل اسلاتی باشد. const bookingMode = selectedResource !== null || selectedLocation?.booking_mode === "service" ? "service" : "slot"; const bookingServices = selectedResource?.services ?? selectedLocation?.services ?? []; // تعویض محل، انتخاب‌های وابسته را باطل می‌کند؛ وگرنه ترکیب سرویسِ یک محل با // اسلات محل دیگر ساخته می‌شود و ثبت نوبت با ۴۲۲ رد می‌شود. // // باطل‌سازی آبشاری است: محل ← منبع ← سرویس ← روز ← ساعت. هر سطح، همهٔ سطوح // پایین‌ترش را پاک می‌کند. const changeLocation = (location) => { setSelectedLocation(location); setLocationConfirmed(true); setSelectedResource(null); setResourceConfirmed(false); setSelectedServiceUuids([]); setSelectedSlot(null); setSelectedDate(null); }; // انتخاب «نوبت با پزشک» (`resource === null`) هم یک انتخاب است و مرحله را می‌بندد. const changeResource = (resource) => { setSelectedResource(resource); setResourceConfirmed(true); setSelectedServiceUuids([]); setSelectedSlot(null); setSelectedDate(null); }; const reopenResourceChoice = () => setResourceConfirmed(false); // منبعی وجود ندارد ⇒ مرحله اصلاً رندر نمی‌شود و تجربهٔ فعلی بدون تغییر می‌ماند. const resourceStepNeeded = locationResources.length > 0 && !resourceConfirmed; // بازگشت به مرحلهٔ انتخاب محل const reopenLocationChoice = () => setLocationConfirmed(false); // محلِ انتخاب‌شده در روزِ در حال مرور بسته است. نباید بی‌صدا فهرست خالی نشان داد. const selectedClosedOnDate = Boolean(browsingDate) && selectedLocation?.available_on_date === false; useEffect(() => { const fetchUserData = async () => { setIsLoading(true); const userInfo = Cookies.get("userInfo"); const parsedData = safeJsonParse(userInfo); const usernameFromCookie = parsedData?.username || ""; // ابتدا شماره موبایل را از Cookie ست می‌کنیم if (usernameFromCookie) { setData(prev => ({ ...prev, phone: { value: usernameFromCookie, isEdit: false }, })); } // سپس اگر uuid داریم، بقیه اطلاعات را از API می‌گیریم if (userInfo && parsedData) { try { const res = await request.getUserProfile(parsedData.uuid); const profile = res?.data?.data; if (profile) { const newData = buildProfileData(profile, usernameFromCookie); setData(newData); setPrevData(newData); } } catch (error) { // در صورت خطا، شماره موبایل حفظ می‌شود و بقیه فیلدها قابل ویرایش می‌مانند } finally { setIsLoading(false); } } else { setIsLoading(false); } }; fetchUserData(); }, []); // فقط یک بار در mount اولیه اجرا می‌شه // useEffect جداگانه برای زمانی که step تغییر می‌کنه useEffect(() => { const refetchUserData = async () => { if (step === 3) { const userInfo = Cookies.get("userInfo"); const parsedData = safeJsonParse(userInfo); const usernameFromCookie = parsedData?.username || ""; if (userInfo && parsedData && !data.uuid) { try { const res = await request.getUserProfile(parsedData.uuid); const profile = res?.data?.data; if (profile) { const newData = buildProfileData(profile, usernameFromCookie); setData(newData); setPrevData(newData); } } catch (error) { // در صورت خطا، فرم خالی قابل ویرایش باقی می‌ماند } } } }; refetchUserData(); }, [step]); if (isLoading || locationsLoading) { return (

در حال بارگذاری...

); } return ( ); } export default AppointmentPage;