Files
nobat724_front/components/appointment/Container.js
T
hamedandClaude Opus 4.8 eba0c6a5ba feat(booking): let patients choose the booking location
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) <noreply@anthropic.com>
2026-07-18 13:48:31 +03:30

181 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Date from "./date";
import ServiceSelect from "./service";
import LocationSelect from "./location/LocationSelect";
// Components
import Content from "./Content";
import Layout from "@/components/layout";
import Paying from "@/components/appointment/paying";
import Detail from "@/components/appointment/detail";
import FailedPay from "@/components/appointment/failedPay";
import SuccessPay from "@/components/appointment/successPay";
import LogInPage from "@/components/register/LogInPage";
import VerificationPage from "@/components/register/verificationPage";
import LayoutRegister from "@/components/register/LayoutRegister";
function Container({
step,
data,
doctor,
setData,
setStep,
prevData,
matchedCity,
isForAnother,
setIsForAnother,
disabledDates,
// Login states
num,
setNum,
uuid,
setUuid,
isSendMsg,
setIsSendMsg,
// Appointment slot states
selectedSlot,
setSelectedSlot,
selectedDate,
setSelectedDate,
appointmentId,
setAppointmentId,
appointmentExpiresAt,
setAppointmentExpiresAt,
// Service-based booking
bookingMode,
bookingServices,
selectedServiceUuids,
setSelectedServiceUuids,
// Booking location (مطب شخصی / کلینیک)
bookingLocations,
selectedLocation,
locationConfirmed,
changeLocation,
reopenLocationChoice,
}) {
const serviceMode = bookingMode === "service";
const clinicUuid = selectedLocation?.clinic_uuid ?? null;
const multiLocation = bookingLocations.length > 1;
let dateStep;
if (bookingLocations.length === 0) {
dateStep = (
<p className="w-full max-w-[520px] mx-auto py-[40px] text-center text-[14px] text-[#7A7A7A]">
نوبتدهی آنلاین برای این پزشک فعال نیست.
</p>
);
} else if (multiLocation && !locationConfirmed) {
dateStep = (
<LocationSelect
locations={bookingLocations}
selected={selectedLocation}
onSelect={changeLocation}
/>
);
} else if (serviceMode && selectedServiceUuids.length === 0) {
dateStep = (
<ServiceSelect
services={bookingServices}
onContinue={(uuids) => setSelectedServiceUuids(uuids)}
/>
);
} else {
dateStep = (
<Date
doctor={doctor}
setStep={setStep}
disabledDates={disabledDates}
setSelectedSlot={setSelectedSlot}
setSelectedDate={setSelectedDate}
serviceMode={serviceMode}
selectedServiceUuids={selectedServiceUuids}
onChangeService={() => setSelectedServiceUuids([])}
selectedLocation={selectedLocation}
clinicUuid={clinicUuid}
onChangeLocation={multiLocation ? reopenLocationChoice : null}
/>
);
}
const elements = [
dateStep,
<LogInPage
num={num}
setNum={setNum}
setUuid={setUuid}
setStep={setStep}
setIsSendMsg={setIsSendMsg}
matchedCity={matchedCity}
/>,
<VerificationPage
num={num}
uuid={uuid}
setStep={setStep}
setUuid={setUuid}
isSendMsg={isSendMsg}
setIsSendMsg={setIsSendMsg}
link={false}
/>,
<Detail
data={data}
setStep={setStep}
setData={setData}
prevData={prevData}
isForAnother={isForAnother}
setIsForAnother={setIsForAnother}
doctor={doctor}
selectedSlot={selectedSlot}
selectedDate={selectedDate}
setAppointmentId={setAppointmentId}
setAppointmentExpiresAt={setAppointmentExpiresAt}
selectedServiceUuids={selectedServiceUuids}
clinicUuid={clinicUuid}
/>,
<Paying
setStep={setStep}
appointmentId={appointmentId}
expiresAt={appointmentExpiresAt}
doctor={doctor}
data={data}
isForAnother={isForAnother}
selectedSlot={selectedSlot}
selectedDate={selectedDate}
/>,
<SuccessPay setStep={setStep} />,
<FailedPay setStep={setStep} />,
];
return (
<>
{step === 1 || step === 2 ? (
<LayoutRegister matchedCity={matchedCity}>
{elements[step]}
</LayoutRegister>
) : (
<Layout
title="رزرو"
name="booking"
disableFooter={step > 4}
matchedCity={matchedCity}
paddingBottom="pb-[80px]"
>
<Content
disableSide={step === 5 || step === 6}
isFirst={step === 0}
setStep={setStep}
doctor={doctor}
step={step}
selectedSlot={selectedSlot}
selectedDate={selectedDate}
selectedLocation={selectedLocation}
>
{elements[step]}
</Content>
</Layout>
)}
</>
);
}
export default Container;