feat: Implement resource booking functionality
- Add service timeline builder for appointments to manage available slots. - Create a hook to fetch resource booking services with effective durations. - Develop ResourceBookingSlotController to handle API requests for resource booking slots. - Implement ResourceBookingSlotService to calculate available time slots based on resource occupancy and service durations. - Add tests for resource appointment creation and booking slot functionality to ensure correct behavior and edge cases.
This commit is contained in:
@@ -18,10 +18,17 @@ export interface ServicePick { serviceUuids: string[]; durations: Record<string,
|
||||
* با اعمال همان override محاسبه میشوند. انتخاب را از طریق onSelect بالا میفرستد.
|
||||
*/
|
||||
export default function ServiceSlotPicker({
|
||||
doctorUuid, date, services, onSelect, editableDuration = true, clinicUuidOverride,
|
||||
doctorUuid, resourceUuid, date, services, onSelect, editableDuration = true, clinicUuidOverride,
|
||||
excludeAppointmentUuid, initialSelection,
|
||||
}: {
|
||||
doctorUuid: string;
|
||||
/**
|
||||
* نوبتدهی روی یک منبع: زمانها از تقویم خودِ منبع میآیند، نه از برنامهٔ پزشک.
|
||||
*
|
||||
* فرمِ انتخاب سرویس در هر دو حالت یکی است (بخش → سرویس → مدت → زمان)، پس فقط
|
||||
* منبعِ زمان عوض میشود نه کامپوننت — دو نسخه یعنی دو رفتار.
|
||||
*/
|
||||
resourceUuid?: string;
|
||||
date: string;
|
||||
services: BookingService[];
|
||||
onSelect: (v: ServicePick) => void;
|
||||
@@ -59,8 +66,8 @@ export default function ServiceSlotPicker({
|
||||
useEffect(() => {
|
||||
if (!mounted.current) { mounted.current = true; return; }
|
||||
setSelected([]); setSectionUuid('');
|
||||
}, [doctorUuid]);
|
||||
useEffect(() => { setPickedSlot(null); }, [selected, date, doctorUuid]);
|
||||
}, [doctorUuid, resourceUuid]);
|
||||
useEffect(() => { setPickedSlot(null); }, [selected, date, doctorUuid, resourceUuid]);
|
||||
|
||||
const serviceUuids = useMemo(() => selected.map(s => s.uuid), [selected]);
|
||||
const durations = useMemo(
|
||||
@@ -71,16 +78,19 @@ export default function ServiceSlotPicker({
|
||||
useEffect(() => { onSelect({ serviceUuids, durations, slot: pickedSlot }); }, [serviceUuids, durations, pickedSlot]);
|
||||
|
||||
const durationsQs = selected.map(s => `&durations[${encodeURIComponent(s.uuid)}]=${s.duration}`).join('');
|
||||
const servicesQs = serviceUuids.map(u => `&service_item_uuids[]=${encodeURIComponent(u)}`).join('');
|
||||
const slotsQ = useQuery<ApiResponse<any>>({
|
||||
queryKey: ['service-slots-picker', doctorUuid, date, serviceUuids, durations, clinicUuid, excludeAppointmentUuid],
|
||||
queryKey: ['service-slots-picker', resourceUuid ?? doctorUuid, date, serviceUuids, durations, clinicUuid, excludeAppointmentUuid],
|
||||
queryFn: () => api.get(
|
||||
`/api/v1/appointment-service-slots?doctor_uuid=${doctorUuid}&date=${date}&management=1`
|
||||
+ serviceUuids.map(u => `&service_item_uuids[]=${encodeURIComponent(u)}`).join('')
|
||||
+ durationsQs
|
||||
+ (clinicUuid ? `&clinic_uuid=${encodeURIComponent(clinicUuid)}` : '')
|
||||
+ (excludeAppointmentUuid ? `&exclude_appointment_uuid=${encodeURIComponent(excludeAppointmentUuid)}` : ''),
|
||||
resourceUuid
|
||||
? `/api/v1/resource/${resourceUuid}/service-slots?date=${date}` + servicesQs + durationsQs
|
||||
: `/api/v1/appointment-service-slots?doctor_uuid=${doctorUuid}&date=${date}&management=1`
|
||||
+ servicesQs
|
||||
+ durationsQs
|
||||
+ (clinicUuid ? `&clinic_uuid=${encodeURIComponent(clinicUuid)}` : '')
|
||||
+ (excludeAppointmentUuid ? `&exclude_appointment_uuid=${encodeURIComponent(excludeAppointmentUuid)}` : ''),
|
||||
),
|
||||
enabled: !!doctorUuid && !!date && serviceUuids.length > 0,
|
||||
enabled: (!!resourceUuid || !!doctorUuid) && !!date && serviceUuids.length > 0,
|
||||
});
|
||||
const startTimes: ServiceSlot[] = (slotsQ.data?.data as any)?.start_times ?? [];
|
||||
const totalMinutes = (slotsQ.data?.data as any)?.total_duration_minutes as number | undefined;
|
||||
@@ -98,7 +108,9 @@ export default function ServiceSlotPicker({
|
||||
if (services.length === 0) {
|
||||
return (
|
||||
<div style={{ fontSize: 12.5, color: 'var(--danger)', margin: '6px 0' }}>
|
||||
سرویسی با «نمایش در نوبتدهی» برای این پزشک تعریف نشده است.
|
||||
{resourceUuid
|
||||
? 'برای این منبع سرویسی تعریف نشده است — از تب «سرویسها»ی همین منبع اضافه کنید.'
|
||||
: 'سرویسی با «نمایش در نوبتدهی» برای این پزشک تعریف نشده است.'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user