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:
+29
-3
@@ -9,6 +9,15 @@ import api from "./api";
|
||||
const clinicQuery = (clinicUuid, prefix = "&") =>
|
||||
clinicUuid ? `${prefix}clinic_uuid=${encodeURIComponent(clinicUuid)}` : "";
|
||||
|
||||
/**
|
||||
* آرایه را با `[]` سریالایز میکند؛ `params` اکسیوس این کار را نمیکند و backend
|
||||
* فهرست سرویسها را خالی میبیند.
|
||||
*/
|
||||
const serviceQuery = (serviceItemUuids = []) =>
|
||||
serviceItemUuids
|
||||
.map((u) => `&service_item_uuids[]=${encodeURIComponent(u)}`)
|
||||
.join("");
|
||||
|
||||
export const request = {
|
||||
sendCode: (mobile, altcha, domain) =>
|
||||
api.post(
|
||||
@@ -81,12 +90,29 @@ export const request = {
|
||||
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("") +
|
||||
serviceQuery(serviceItemUuids) +
|
||||
clinicQuery(clinic_uuid),
|
||||
removeTokenHead
|
||||
),
|
||||
// نوبتدهی منبعمحور: منبع تقویم خودش را دارد، پس اسلات و تقویمِ ماهش هم از
|
||||
// اندپوینتهای خودش میآید نه از برنامهٔ هفتگی پزشک.
|
||||
getBookingResources: (doctor_uuid, clinic_uuid = null) =>
|
||||
api.get(
|
||||
`api/v1/appointment-booking-resources/${doctor_uuid}${clinicQuery(clinic_uuid, "?")}`,
|
||||
removeTokenHead
|
||||
),
|
||||
getResourceSlots: (resource_uuid, date, serviceItemUuids = []) =>
|
||||
api.get(
|
||||
`api/v1/appointment-resource-slots?resource_uuid=${encodeURIComponent(resource_uuid)}&date=${date}` +
|
||||
serviceQuery(serviceItemUuids),
|
||||
removeTokenHead
|
||||
),
|
||||
getResourceMonthAvailability: (resource_uuid, year, month, serviceItemUuids = []) =>
|
||||
api.get(
|
||||
`api/v1/appointment-resource-month-availability/${resource_uuid}?year=${year}&month=${month}` +
|
||||
serviceQuery(serviceItemUuids),
|
||||
removeTokenHead
|
||||
),
|
||||
postAppointment: (data) => api.post(`api/v1/appointment`, data, { requireAuth: true }),
|
||||
getMyAppointments: (params) => api.get(`api/v1/appointments/user`, { params, requireAuth: true }),
|
||||
getPaymentConfig: () => api.get(`api/v1/payment/config`, { requireAuth: true }),
|
||||
|
||||
@@ -73,4 +73,56 @@ describe('request wrappers — قرارداد به لایهی API', () => {
|
||||
request.postAppointment({ slot: 5 });
|
||||
expect(api.post).toHaveBeenCalledWith('api/v1/appointment', { slot: 5 }, { requireAuth: true });
|
||||
});
|
||||
|
||||
// --- نوبتدهی منبعمحور ---
|
||||
|
||||
it('getBookingResources → بدون کلینیک، مسیر ساده', () => {
|
||||
request.getBookingResources('doc-1');
|
||||
expect(api.get.mock.calls[0][0]).toBe('api/v1/appointment-booking-resources/doc-1');
|
||||
});
|
||||
|
||||
it('getBookingResources → با کلینیک، clinic_uuid با ? میآید', () => {
|
||||
request.getBookingResources('doc-1', 'cl-1');
|
||||
expect(api.get.mock.calls[0][0]).toBe(
|
||||
'api/v1/appointment-booking-resources/doc-1?clinic_uuid=cl-1'
|
||||
);
|
||||
});
|
||||
|
||||
it('getResourceSlots → هر سرویس یک service_item_uuids[] جدا میگیرد', () => {
|
||||
request.getResourceSlots('res-1', '2026-08-10', ['s1', 's2']);
|
||||
const url = api.get.mock.calls[0][0];
|
||||
expect(url).toContain('resource_uuid=res-1');
|
||||
expect(url).toContain('date=2026-08-10');
|
||||
expect(url).toContain('&service_item_uuids[]=s1');
|
||||
expect(url).toContain('&service_item_uuids[]=s2');
|
||||
});
|
||||
|
||||
it('getResourceSlots → uuid با کاراکتر ویژه encode میشود', () => {
|
||||
request.getResourceSlots('res-1', '2026-08-10', ['a/b']);
|
||||
expect(api.get.mock.calls[0][0]).toContain('service_item_uuids[]=a%2Fb');
|
||||
});
|
||||
|
||||
it('getResourceSlots → فهرست خالی هیچ پارامتر سرویسی نمیسازد', () => {
|
||||
request.getResourceSlots('res-1', '2026-08-10', []);
|
||||
expect(api.get.mock.calls[0][0]).not.toContain('service_item_uuids');
|
||||
});
|
||||
|
||||
it('getResourceMonthAvailability → سال و ماه و سرویسها در query', () => {
|
||||
request.getResourceMonthAvailability('res-1', 2026, 9, ['s1']);
|
||||
const url = api.get.mock.calls[0][0];
|
||||
expect(url).toContain('api/v1/appointment-resource-month-availability/res-1');
|
||||
expect(url).toContain('year=2026');
|
||||
expect(url).toContain('month=9');
|
||||
expect(url).toContain('&service_item_uuids[]=s1');
|
||||
});
|
||||
|
||||
it('هر سه اندپوینت عمومیاند — هدر Authorization حذف میشود', () => {
|
||||
request.getBookingResources('doc-1');
|
||||
request.getResourceSlots('res-1', '2026-08-10', ['s1']);
|
||||
request.getResourceMonthAvailability('res-1', 2026, 9, ['s1']);
|
||||
|
||||
for (const call of api.get.mock.calls) {
|
||||
expect(call[1]?.headers?.Authorization).toBe('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user