feat: Implement resource-based appointment booking flow

- Updated DoctorPage component to accept bookingResources prop for appointment list.
- Added serviceQuery function to serialize service item UUIDs for API requests.
- Introduced new API endpoints for fetching booking resources and resource slots.
- Enhanced tests for new resource-based booking functionality, including resource selection and service availability.
- Created ResourceSelect component for selecting appointment types, including doctor and resource options.
- Updated appointment submission logic to include resource_uuid in payload when applicable.
- Ensured UI reflects changes in booking flow without disrupting existing doctor-centric experience.
This commit is contained in:
hamed
2026-08-09 10:45:52 +03:30
parent 1668ef8890
commit 54bebbd41a
23 changed files with 1094 additions and 32 deletions
+31 -2
View File
@@ -1,5 +1,6 @@
import Date from "./date";
import ServiceSelect from "./service";
import ResourceSelect from "./resource";
import LocationSelect from "./location/LocationSelect";
// Components
@@ -53,13 +54,29 @@ function Container({
reopenLocationChoice,
onDateChange,
selectedClosedOnDate,
// نوبت‌دهی منبع‌محور (دستگاه / اتاق / یونیت)
resources = [],
selectedResource = null,
resourceStepNeeded = false,
changeResource,
reopenResourceChoice,
}) {
const serviceMode = bookingMode === "service";
const clinicUuid = selectedLocation?.clinic_uuid ?? null;
const multiLocation = bookingLocations.length > 1;
const resourceUuid = selectedResource?.uuid ?? null;
// محیطِ رزرو از خودِ منبع خوانده می‌شود وقتی منبعی انتخاب شده: پزشکِ بدون برنامهٔ
// هفتگی هیچ محلی ندارد که `clinic_uuid` از آن بیاید، ولی دستگاهش محیطش را می‌داند.
const clinicUuid = selectedResource
? selectedResource.clinic_uuid ?? null
: selectedLocation?.clinic_uuid ?? null;
// «محل ندارد» دیگر به معنی «نوبت‌دهی ندارد» نیست: منبع تقویم خودش را دارد، پس پزشکی
// که برنامهٔ هفتگی ندارد ولی دستگاهش قابل رزرو است باید به مرحلهٔ منبع برسد.
const noBookingAtAll = bookingLocations.length === 0 && resources.length === 0;
let dateStep;
if (bookingLocations.length === 0) {
if (noBookingAtAll) {
dateStep = (
<p className="w-full max-w-[520px] mx-auto py-[40px] text-center text-[14px] text-[#7A7A7A]">
نوبتدهی آنلاین برای این پزشک فعال نیست.
@@ -73,6 +90,15 @@ function Container({
onSelect={changeLocation}
/>
);
} else if (resourceStepNeeded) {
dateStep = (
<ResourceSelect
resources={resources}
selected={selectedResource}
onSelect={changeResource}
allowDoctorOption={bookingLocations.length > 0}
/>
);
} else if (serviceMode && selectedServiceUuids.length === 0) {
dateStep = (
<ServiceSelect
@@ -93,7 +119,9 @@ function Container({
onChangeService={() => setSelectedServiceUuids([])}
selectedLocation={selectedLocation}
clinicUuid={clinicUuid}
resourceUuid={resourceUuid}
onChangeLocation={multiLocation ? reopenLocationChoice : null}
onChangeResource={resources.length > 0 ? reopenResourceChoice : null}
onDateChange={onDateChange}
closedOnDate={selectedClosedOnDate}
/>
@@ -134,6 +162,7 @@ function Container({
setAppointmentExpiresAt={setAppointmentExpiresAt}
selectedServiceUuids={selectedServiceUuids}
clinicUuid={clinicUuid}
resourceUuid={resourceUuid}
/>,
<Paying
setStep={setStep}