From eba0c6a5ba32410c06df675e89f05a3c02b6393d Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 18 Jul 2026 13:48:31 +0330 Subject: [PATCH] feat(booking): let patients choose the booking location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A doctor now has one booking schedule per context — the personal practice plus one per clinic — and every booking endpoint takes an optional clinic_uuid where omitting it means the personal practice, not a wildcard. This site sent none, so a clinic-only doctor showed no availability at all and a doctor working in both places silently booked into the wrong one. - services/response.js: getBookingLocations + clinic_uuid on slots, service-slots, booking-services and month-availability. The manual query building is kept so the service_item_uuids[] serialisation does not change. - AppointmentPage owns the selected location; booking_mode and services are derived from it instead of a separate getBookingServices call, which drops a request. Changing location clears the selected service, slot and date, since a service from one location cannot be booked into another. - New LocationSelect step, shown only when there is more than one location. The list arrives sorted by earliest free slot, so the first item is the default and is not re-sorted here. - DatePicker drops its month cache when the location changes; otherwise the previous location's disabled days stayed on the calendar. - The slot address now comes from the selected location rather than doctor.address, which does not contain clinic addresses. - clinic_uuid rides through to the appointment payload, and /appointment/[doctorId]?clinic_uuid=… preselects a location. - Doctor page JSON-LD gains availableService from the bookable services. openingHoursSpecification still needs a public weekly-hours endpoint. Removed the dead locateVisit state, which was initialised true and never unset. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/appointment/[doctorId]/page.js | 4 +- app/component/date/datePicker/index.js | 15 +++- app/component/date/dateTime/index.js | 18 +++-- app/doctor/[slug]/page.js | 43 ++++++++++- components/appointment/Container.js | 40 ++++++++++- components/appointment/Content.js | 4 +- .../appointment/date/Time/SelectDatePicker.js | 4 +- components/appointment/date/Time/index.js | 4 +- components/appointment/date/hour/index.js | 5 +- components/appointment/date/index.js | 43 +++++++---- components/appointment/detail/SubmitData.js | 4 +- components/appointment/detail/index.js | 2 + components/appointment/index.js | 53 +++++++++++--- components/appointment/location/Address.js | 19 ++++- .../appointment/location/LocationSelect.js | 71 +++++++++++++++++++ components/appointment/location/index.js | 4 +- services/response.js | 31 +++++--- 17 files changed, 307 insertions(+), 57 deletions(-) create mode 100644 components/appointment/location/LocationSelect.js diff --git a/app/appointment/[doctorId]/page.js b/app/appointment/[doctorId]/page.js index 82be7d6..047c09a 100644 --- a/app/appointment/[doctorId]/page.js +++ b/app/appointment/[doctorId]/page.js @@ -6,8 +6,9 @@ export const metadata = { robots: { index: false, follow: false }, }; -async function Appointment({ params }) { +async function Appointment({ params, searchParams }) { const { doctorId } = await params; + const { clinic_uuid: clinicUuid } = (await searchParams) ?? {}; const { matchedCity } = await getStateInfo(); const API_URL = process.env.NEXT_PUBLIC_API_URL; @@ -23,6 +24,7 @@ async function Appointment({ params }) { doctor={doctor} disabledDates={[]} matchedCity={matchedCity} + initialClinicUuid={typeof clinicUuid === "string" ? clinicUuid : null} /> ); } diff --git a/app/component/date/datePicker/index.js b/app/component/date/datePicker/index.js index 7bccd58..51331c2 100644 --- a/app/component/date/datePicker/index.js +++ b/app/component/date/datePicker/index.js @@ -21,7 +21,7 @@ function gregorianMonthsOf(jalaliMonth) { }); } -function DatePicker({ setDate }) { +function DatePicker({ setDate, clinicUuid = null }) { const params = useParams(); const doctorUuid = params?.doctorId; @@ -34,6 +34,15 @@ function DatePicker({ setDate }) { const secondMonth = moment(baseMonth).add(1, "jMonth"); + // هر محل، روزهای غیرفعال و وضعیت نوبت‌دهی آنلاین خودش را دارد؛ کش ماه‌ها باید + // با تعویض محل دور ریخته شود وگرنه تقویم محل قبلی باقی می‌ماند. + useEffect(() => { + loadedMonths.current = new Set(); + setDisabledSet(new Set()); + setOnlineEnabled(true); + setAutoSelected(false); + }, [clinicUuid]); + useEffect(() => { if (!doctorUuid) return; @@ -47,7 +56,7 @@ function DatePicker({ setDate }) { targets.forEach(({ year, month }) => { loadedMonths.current.add(`${year}-${month}`); request - .getMonthAvailability(doctorUuid, year, month) + .getMonthAvailability(doctorUuid, year, month, clinicUuid) .then((res) => { const data = res?.data ?? {}; if (data.online_booking_enabled === false) setOnlineEnabled(false); @@ -64,7 +73,7 @@ function DatePicker({ setDate }) { loadedMonths.current.delete(`${year}-${month}`); }); }); - }, [doctorUuid, baseMonth]); + }, [doctorUuid, baseMonth, clinicUuid]); const isDisabled = (date) => { const day = moment(date).startOf("day"); diff --git a/app/component/date/dateTime/index.js b/app/component/date/dateTime/index.js index 0fb6c7d..3364f2d 100644 --- a/app/component/date/dateTime/index.js +++ b/app/component/date/dateTime/index.js @@ -16,6 +16,8 @@ function DateTime({ setSelectedDate, serviceMode = false, selectedServiceUuids = [], + clinicUuid = null, + selectedLocation = null, }) { const [value, setValue] = useState(0); const [hour, setHour] = useState(); @@ -30,9 +32,13 @@ function DateTime({ setValue(newValue); }; - const locationAddress = hour?.location_id - ? doctor?.address?.find((a) => String(a.id) === String(hour.location_id))?.address ?? null - : null; + // آدرس از محل انتخاب‌شده می‌آید، نه از doctor.address: در محیط کلینیک ممکن است + // آدرس اصلاً در فهرست آدرس‌های شخصی پزشک نباشد. + const locationAddress = + selectedLocation?.address ?? + (hour?.location_id + ? doctor?.address?.find((a) => String(a.id) === String(hour.location_id))?.address ?? null + : null); useEffect(() => { if (!date || !doctor?.uuid) return; @@ -44,9 +50,9 @@ function DateTime({ const req = serviceMode ? request - .getServiceSlots(doctor.uuid, dateStr, selectedServiceUuids) + .getServiceSlots(doctor.uuid, dateStr, selectedServiceUuids, clinicUuid) .then(adaptServiceSlots) - : request.getAppointmentSlots(doctor.uuid, dateStr).then(adaptSlots); + : request.getAppointmentSlots(doctor.uuid, dateStr, clinicUuid).then(adaptSlots); req .then((parsed) => { @@ -63,7 +69,7 @@ function DateTime({ setAppo("در حال حاضر، نوبتی برای این روز موجود نمی‌باشد."); setValue(0); }); - }, [date, doctor?.uuid, serviceMode, selectedServiceUuids]); + }, [date, doctor?.uuid, serviceMode, selectedServiceUuids, clinicUuid]); return (
diff --git a/app/doctor/[slug]/page.js b/app/doctor/[slug]/page.js index 1b7eccb..5680b47 100644 --- a/app/doctor/[slug]/page.js +++ b/app/doctor/[slug]/page.js @@ -36,6 +36,26 @@ const getDoctorAddresses = cache(async (doctorId) => { } }); +/** + * محل‌های نوبت‌دهی پزشک (مطب شخصی + هر کلینیک). هر محل روش نوبت‌دهی و سرویس‌های + * خودش را دارد؛ سرویس‌ها بین محل‌ها مشترک نیستند. + */ +const getBookingLocations = cache(async (doctorUuid) => { + if (!doctorUuid) return []; + try { + const res = await fetch( + `${API_URL}/api/v1/appointment-booking-locations/${doctorUuid}`, + { next: { revalidate: 3600, tags: [`booking-locations-${doctorUuid}`] } } + ); + if (!res.ok) return []; + const json = await res.json(); + const data = json?.data?.data ?? json?.data ?? {}; + return Array.isArray(data.booking_locations) ? data.booking_locations : []; + } catch { + return []; + } +}); + export async function generateMetadata({ params }) { const { slug } = await params; const { matchedCity } = await getStateInfo(); @@ -105,7 +125,19 @@ async function Doctor({ params }) { } catch (error) {} } - const addresses = doctor ? await getDoctorAddresses(doctor.id) : []; + const [addresses, bookingLocations] = doctor + ? await Promise.all([getDoctorAddresses(doctor.id), getBookingLocations(doctor.uuid)]) + : [[], []]; + + // سرویس‌های قابل رزرو، تجمیع‌شده از همهٔ محل‌ها و یکتاشده بر اساس uuid. + const bookableServices = Object.values( + bookingLocations + .flatMap((location) => location.services ?? []) + .reduce((acc, service) => { + if (service?.uuid && !acc[service.uuid]) acc[service.uuid] = service; + return acc; + }, {}) + ); const specialtyNames = doctor?.specialties?.map((s) => s.name).join(" و ") || ""; const isUnclaimed = doctor?.owner_status !== "claimed"; @@ -142,6 +174,15 @@ async function Doctor({ params }) { ...(doctor.social_media && { sameAs: Object.values(doctor.social_media).filter(Boolean), }), + ...(bookableServices.length > 0 && { + availableService: bookableServices.map((service) => ({ + "@type": "MedicalProcedure", + name: service.name, + ...(service.duration_minutes && { + estimatedDuration: `PT${service.duration_minutes}M`, + }), + })), + }), ...(addresses.length > 0 && { workLocation: addresses.map((addr) => ({ "@type": "MedicalClinic", diff --git a/components/appointment/Container.js b/components/appointment/Container.js index a437b42..a0f0ca4 100644 --- a/components/appointment/Container.js +++ b/components/appointment/Container.js @@ -1,5 +1,6 @@ import Date from "./date"; import ServiceSelect from "./service"; +import LocationSelect from "./location/LocationSelect"; // Components import Content from "./Content"; @@ -44,15 +45,41 @@ function Container({ bookingServices, selectedServiceUuids, setSelectedServiceUuids, + // Booking location (مطب شخصی / کلینیک) + bookingLocations, + selectedLocation, + locationConfirmed, + changeLocation, + reopenLocationChoice, }) { const serviceMode = bookingMode === "service"; - const dateStep = - serviceMode && selectedServiceUuids.length === 0 ? ( + const clinicUuid = selectedLocation?.clinic_uuid ?? null; + const multiLocation = bookingLocations.length > 1; + + let dateStep; + if (bookingLocations.length === 0) { + dateStep = ( +

+ نوبت‌دهی آنلاین برای این پزشک فعال نیست. +

+ ); + } else if (multiLocation && !locationConfirmed) { + dateStep = ( + + ); + } else if (serviceMode && selectedServiceUuids.length === 0) { + dateStep = ( setSelectedServiceUuids(uuids)} /> - ) : ( + ); + } else { + dateStep = ( setSelectedServiceUuids([])} + selectedLocation={selectedLocation} + clinicUuid={clinicUuid} + onChangeLocation={multiLocation ? reopenLocationChoice : null} /> ); + } + const elements = [ dateStep, @@ -97,6 +129,7 @@ function Container({ setAppointmentId={setAppointmentId} setAppointmentExpiresAt={setAppointmentExpiresAt} selectedServiceUuids={selectedServiceUuids} + clinicUuid={clinicUuid} />, {elements[step]} diff --git a/components/appointment/Content.js b/components/appointment/Content.js index 32b7e9a..703f504 100644 --- a/components/appointment/Content.js +++ b/components/appointment/Content.js @@ -1,13 +1,13 @@ import Information from "./information"; import Location from "./location"; -function Content({ doctor, step, setStep, children, isFirst, disableSide, selectedSlot, selectedDate }) { +function Content({ doctor, step, setStep, children, isFirst, disableSide, selectedSlot, selectedDate, selectedLocation }) { return (
{isFirst ? ( - + ) : ( ; +function SelectDatePicker({ setDate, disabledDates, clinicUuid }) { + return ; } export default SelectDatePicker; diff --git a/components/appointment/date/Time/index.js b/components/appointment/date/Time/index.js index 6a75bad..b11d6a1 100644 --- a/components/appointment/date/Time/index.js +++ b/components/appointment/date/Time/index.js @@ -1,12 +1,12 @@ import SelectDatePicker from "./SelectDatePicker"; -function Time({ isStep, setDate, disabledDates }) { +function Time({ isStep, setDate, disabledDates, clinicUuid }) { return (

1. انتخاب روز

- + {!isStep && (
)} diff --git a/components/appointment/date/hour/index.js b/components/appointment/date/hour/index.js index 9ea94ec..778365b 100644 --- a/components/appointment/date/hour/index.js +++ b/components/appointment/date/hour/index.js @@ -1,6 +1,6 @@ import DateTime from "@/app/component/date/dateTime"; -function Hour({ doctor, setStep, date, locateVisit, isStep, setSelectedSlot, setSelectedDate, serviceMode = false, selectedServiceUuids = [] }) { +function Hour({ doctor, setStep, date, isStep, setSelectedSlot, setSelectedDate, serviceMode = false, selectedServiceUuids = [], clinicUuid = null, selectedLocation = null }) { return (

@@ -10,11 +10,12 @@ function Hour({ doctor, setStep, date, locateVisit, isStep, setSelectedSlot, set date={date} doctor={doctor} setStep={setStep} - locateVisit={locateVisit} setSelectedSlot={setSelectedSlot} setSelectedDate={setSelectedDate} serviceMode={serviceMode} selectedServiceUuids={selectedServiceUuids} + clinicUuid={clinicUuid} + selectedLocation={selectedLocation} /> {!isStep && (

diff --git a/components/appointment/date/index.js b/components/appointment/date/index.js index 8a9b1c0..f614cab 100644 --- a/components/appointment/date/index.js +++ b/components/appointment/date/index.js @@ -11,8 +11,10 @@ function Date({ serviceMode = false, selectedServiceUuids = [], onChangeService, + selectedLocation = null, + clinicUuid = null, + onChangeLocation, }) { - const [locateVisit, setLocateVisit] = useState(true); const [date, setDate] = useState(); return ( @@ -20,25 +22,36 @@ function Date({
- {serviceMode && onChangeService && ( - + {(onChangeLocation || (serviceMode && onChangeService)) && ( +
+ {onChangeLocation && ( + + )} + {serviceMode && onChangeService && ( + + )} +
)}
diff --git a/components/appointment/detail/SubmitData.js b/components/appointment/detail/SubmitData.js index 5a6532f..6b6db13 100644 --- a/components/appointment/detail/SubmitData.js +++ b/components/appointment/detail/SubmitData.js @@ -7,7 +7,7 @@ import Cookies from "js-cookie"; import { setAccessToken } from "@/lib/tokenStore"; import { useProvince } from "@/context/ProvinceProvider"; -function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, selectedSlot, selectedDate, setAppointmentId, setAppointmentExpiresAt, selectedServiceUuids = [] }) { +function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, selectedSlot, selectedDate, setAppointmentId, setAppointmentExpiresAt, selectedServiceUuids = [], clinicUuid = null }) { const [loading, setLoading] = useState(false); const { cityId } = useProvince(); const newStep = () => setStep((prev) => prev + 1); @@ -137,6 +137,8 @@ function SubmitData({ setStep, data, prevData, setErrors, doctor, isForAnother, if (doctor && selectedSlot) { const appointmentPayload = { doctor_uuid: doctor.uuid, + // محل نوبت‌دهی؛ null یعنی مطب شخصی. بدون این، نوبت در محل اشتباه ثبت می‌شود. + clinic_uuid: clinicUuid, slot_start: selectedSlot.start, slot_end: selectedSlot.end, for_self: !isForAnother, diff --git a/components/appointment/detail/index.js b/components/appointment/detail/index.js index 9c99554..57e132a 100644 --- a/components/appointment/detail/index.js +++ b/components/appointment/detail/index.js @@ -21,6 +21,7 @@ function Detail({ setAppointmentId, setAppointmentExpiresAt, selectedServiceUuids = [], + clinicUuid = null, }) { const [insurance, setInsurance] = useState(); const [supplementaryInsurance, setSupplementaryInsurance] = useState(); @@ -122,6 +123,7 @@ function Detail({ setAppointmentId={setAppointmentId} setAppointmentExpiresAt={setAppointmentExpiresAt} selectedServiceUuids={selectedServiceUuids} + clinicUuid={clinicUuid} />
diff --git a/components/appointment/index.js b/components/appointment/index.js index f5bc846..36f3bc9 100644 --- a/components/appointment/index.js +++ b/components/appointment/index.js @@ -42,7 +42,7 @@ function buildProfileData(profile, phone) { }; } -function AppointmentPage({ doctor, disabledDates, matchedCity }) { +function AppointmentPage({ doctor, disabledDates, matchedCity, initialClinicUuid = null }) { const [step, setStep] = useState(0); const [isForAnother, setIsForAnother] = useState(false); const [isLoading, setIsLoading] = useState(true); @@ -62,21 +62,51 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) { const [appointmentExpiresAt, setAppointmentExpiresAt] = useState(null); // Service-based booking (روش نوبت‌دهی سرویسی) - const [bookingMode, setBookingMode] = useState("slot"); - const [bookingServices, setBookingServices] = useState([]); const [selectedServiceUuids, setSelectedServiceUuids] = useState([]); + // محل نوبت‌دهی: مطب شخصی یا یکی از کلینیک‌هایی که پزشک در آن برنامه دارد. + const [bookingLocations, setBookingLocations] = useState([]); + const [selectedLocation, setSelectedLocation] = useState(null); + const [locationConfirmed, setLocationConfirmed] = useState(false); + const [locationsLoading, setLocationsLoading] = useState(true); + useEffect(() => { if (!doctor?.uuid) return; request - .getBookingServices(doctor.uuid) + .getBookingLocations(doctor.uuid) .then((res) => { const d = res?.data?.data ?? res?.data ?? {}; - setBookingMode(d.booking_mode === "service" ? "service" : "slot"); - setBookingServices(Array.isArray(d.services) ? d.services : []); + 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(() => setBookingMode("slot")); - }, [doctor?.uuid]); + .catch(() => setBookingLocations([])) + .finally(() => setLocationsLoading(false)); + }, [doctor?.uuid, initialClinicUuid]); + + // روش نوبت‌دهی و سرویس‌ها per-location هستند: یک پزشک می‌تواند در مطب شخصی + // اسلاتی و در کلینیک سرویسی باشد. + const bookingMode = selectedLocation?.booking_mode === "service" ? "service" : "slot"; + const bookingServices = selectedLocation?.services ?? []; + + // تعویض محل، انتخاب‌های وابسته را باطل می‌کند؛ وگرنه ترکیب سرویسِ یک محل با + // اسلات محل دیگر ساخته می‌شود و ثبت نوبت با ۴۲۲ رد می‌شود. + const changeLocation = (location) => { + setSelectedLocation(location); + setLocationConfirmed(true); + setSelectedServiceUuids([]); + setSelectedSlot(null); + setSelectedDate(null); + }; + + // بازگشت به مرحلهٔ انتخاب محل + const reopenLocationChoice = () => setLocationConfirmed(false); useEffect(() => { const fetchUserData = async () => { @@ -147,7 +177,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) { }, [step]); - if (isLoading) { + if (isLoading || locationsLoading) { return (
@@ -188,6 +218,11 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) { bookingServices={bookingServices} selectedServiceUuids={selectedServiceUuids} setSelectedServiceUuids={setSelectedServiceUuids} + bookingLocations={bookingLocations} + selectedLocation={selectedLocation} + locationConfirmed={locationConfirmed} + changeLocation={changeLocation} + reopenLocationChoice={reopenLocationChoice} /> ); } diff --git a/components/appointment/location/Address.js b/components/appointment/location/Address.js index af1a7cd..e791cde 100644 --- a/components/appointment/location/Address.js +++ b/components/appointment/location/Address.js @@ -1,4 +1,21 @@ -function Address({ doctor }) { +/** + * آدرس‌های پزشک در سایدبار. وقتی محل نوبت‌دهی انتخاب شده باشد، فقط همان محل نشان + * داده می‌شود تا با اسلات‌های نمایش‌داده‌شده هم‌خوان بماند. + */ +function Address({ doctor, selectedLocation }) { + if (selectedLocation) { + return ( +
    +
  • +
    +

    {`${selectedLocation.title}: `}

    +

    {selectedLocation.address || "—"}

    +
    +
  • +
+ ); + } + return (
    {doctor?.address?.map((item, idx) => ( diff --git a/components/appointment/location/LocationSelect.js b/components/appointment/location/LocationSelect.js new file mode 100644 index 0000000..d8c39e3 --- /dev/null +++ b/components/appointment/location/LocationSelect.js @@ -0,0 +1,71 @@ +"use client"; + +import moment from "moment-jalaali"; + +function nextAvailableLabel(timestamp) { + if (!timestamp) return null; + return `اولین نوبت آزاد: ${moment.unix(timestamp).format("jD jMMMM")}`; +} + +/** + * انتخاب محل نوبت‌دهی — پیش از انتخاب سرویس و روز. + * + * فهرست از سمت سرور بر اساس زودترین نوبت آزاد مرتب شده، پس اولین آیتم پیش‌فرض + * درست است و اینجا دوباره مرتب نمی‌شود. + */ +function LocationSelect({ locations = [], selected, onSelect }) { + return ( +
    +

    ۱. انتخاب محل نوبت‌دهی

    + +
    + {locations.map((location) => { + const key = location.clinic_uuid ?? location.location_uuid ?? "personal"; + const active = + (selected?.clinic_uuid ?? null) === (location.clinic_uuid ?? null); + const nextLabel = nextAvailableLabel(location.next_available_at); + + return ( + + ); + })} +
    +
    + ); +} + +export default LocationSelect; diff --git a/components/appointment/location/index.js b/components/appointment/location/index.js index 34e7a0e..617ab98 100644 --- a/components/appointment/location/index.js +++ b/components/appointment/location/index.js @@ -1,7 +1,7 @@ import Address from "./Address"; import Head from "./Head"; -function Location({ doctor }) { +function Location({ doctor, selectedLocation }) { return (
    -
    +
    ); diff --git a/services/response.js b/services/response.js index 6cca86d..bbe66be 100644 --- a/services/response.js +++ b/services/response.js @@ -1,6 +1,14 @@ import { removeTokenHead } from "@/helper"; import api from "./api"; +/** + * محل نوبت‌دهی. نبودِ clinic_uuid یعنی «مطب شخصی» — نه «هر محلی که پیدا شد». + * پزشک می‌تواند هم‌زمان برنامهٔ شخصی و برنامهٔ کلینیک داشته باشد و این دو داده‌ی + * جدا دارند. + */ +const clinicQuery = (clinicUuid, prefix = "&") => + clinicUuid ? `${prefix}clinic_uuid=${encodeURIComponent(clinicUuid)}` : ""; + export const request = { sendCode: (mobile, altcha, domain) => api.post( @@ -49,24 +57,31 @@ export const request = { api.patch(`api/v1/appointment-settings/weekly-schedule/${uuid}`), deleteAppointmentWeeklySchedule: (uuid) => api.delete(`api/v1/appointment-settings/weekly-schedule/${uuid}`), - getAppointmentSlots: (doctor_uuid, date) => + getBookingLocations: (doctor_uuid) => + api.get(`api/v1/appointment-booking-locations/${doctor_uuid}`, removeTokenHead), + getAppointmentSlots: (doctor_uuid, date, clinic_uuid = null) => api.get( - `api/v1/appointment-slots?doctor_uuid=${doctor_uuid}&date=${date}`, + `api/v1/appointment-slots?doctor_uuid=${doctor_uuid}&date=${date}` + + clinicQuery(clinic_uuid), removeTokenHead ), - getMonthAvailability: (doctor_uuid, year, month) => + getMonthAvailability: (doctor_uuid, year, month, clinic_uuid = null) => api.get(`api/v1/appointment-settings/month-availability/${doctor_uuid}`, { - params: { year, month }, + params: { year, month, ...(clinic_uuid ? { clinic_uuid } : {}) }, ...removeTokenHead, }), - getBookingServices: (doctor_uuid) => - api.get(`api/v1/appointment-booking-services/${doctor_uuid}`, removeTokenHead), - getServiceSlots: (doctor_uuid, date, serviceItemUuids = []) => + getBookingServices: (doctor_uuid, clinic_uuid = null) => + api.get( + `api/v1/appointment-booking-services/${doctor_uuid}${clinicQuery(clinic_uuid, "?")}`, + removeTokenHead + ), + getServiceSlots: (doctor_uuid, date, serviceItemUuids = [], clinic_uuid = null) => api.get( `api/v1/appointment-service-slots?doctor_uuid=${doctor_uuid}&date=${date}` + serviceItemUuids .map((u) => `&service_item_uuids[]=${encodeURIComponent(u)}`) - .join(""), + .join("") + + clinicQuery(clinic_uuid), removeTokenHead ), postAppointment: (data) => api.post(`api/v1/appointment`, data, { requireAuth: true }),