feat(admin): service-aware time picking on the appointment edit page
In service mode the page now mounts the existing ServiceSlotPicker and hides the three free-form time inputs plus the single-service select: a 45-minute service could previously be shortened to 20 and the next patient would sit on top of it. Hidden rather than disabled — a disabled field reads as "you must do something here". Saving splits in two: the service-aware endpoint takes the time and services (the client sends no duration), then the usual PATCH carries deposit, insurance, status and note without slot_start/slot_end/version, since the reschedule already advanced the optimistic-lock version. Booking mode is read from the appointment's own schedule via an explicit clinic_uuid, not from the panel's current environment: a doctor can be slot-based in their office and service-based in a clinic. That required exposing clinic_uuid in Appointment::toArray(), which was missing. appointment-service-slots accepts exclude_appointment_uuid, gated on canManage of that appointment — an ungated parameter would let anyone fabricate availability. ServiceSlotPicker gained two optional props; its existing callers pass neither and are unaffected. Its reset-on-doctor-change effect now skips the first run, which would otherwise wipe the initial selection. Task: docs/new_feture/taskes/task-00-service-mode-completion/ Slot-mode contract: unchanged (--group=slot-mode-frozen green) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '../../lib/api';
|
||||
import type { ApiResponse } from '../../lib/api';
|
||||
@@ -19,6 +19,7 @@ export interface ServicePick { serviceUuids: string[]; durations: Record<string,
|
||||
*/
|
||||
export default function ServiceSlotPicker({
|
||||
doctorUuid, date, services, onSelect, editableDuration = true, clinicUuidOverride,
|
||||
excludeAppointmentUuid, initialSelection,
|
||||
}: {
|
||||
doctorUuid: string;
|
||||
date: string;
|
||||
@@ -27,11 +28,18 @@ export default function ServiceSlotPicker({
|
||||
editableDuration?: boolean;
|
||||
/** undefined = context محیط جاری؛ مقدار صریح (شامل null) = محل انتخابشده خارج از context */
|
||||
clinicUuidOverride?: string | null;
|
||||
/**
|
||||
* ویرایش نوبت: بازهٔ خودِ همین نوبت اشغال حساب نشود، وگرنه زمان فعلیاش در فهرست
|
||||
* نمیآید و کاربر نمیتواند «همان ساعت، سرویس متفاوت» را ثبت کند.
|
||||
*/
|
||||
excludeAppointmentUuid?: string;
|
||||
/** سرویسهای از قبل انتخابشده (ویرایش نوبت موجود). فقط یک بار هیدریت میشود. */
|
||||
initialSelection?: PickedService[];
|
||||
}) {
|
||||
const contextClinicUuid = useClinicContext();
|
||||
const clinicUuid = clinicUuidOverride === undefined ? contextClinicUuid : clinicUuidOverride;
|
||||
const [sectionUuid, setSectionUuid] = useState('');
|
||||
const [selected, setSelected] = useState<PickedService[]>([]);
|
||||
const [selected, setSelected] = useState<PickedService[]>(initialSelection ?? []);
|
||||
const [pickedSlot, setPickedSlot] = useState<ServiceSlot | null>(null);
|
||||
|
||||
// بخشهای یکتا از روی سرویسهای bookable (بدون endpoint اضافه — همه یکجا آمدهاند).
|
||||
@@ -45,8 +53,13 @@ export default function ServiceSlotPicker({
|
||||
[services, sectionUuid],
|
||||
);
|
||||
|
||||
// تعویض پزشک ⇒ لیست سرویسها عوض میشود؛ انتخابها ریست شوند.
|
||||
useEffect(() => { setSelected([]); setSectionUuid(''); }, [doctorUuid]);
|
||||
// تعویض پزشک ⇒ لیست سرویسها عوض میشود؛ انتخابها ریست شوند. اجرای نخست معاف است،
|
||||
// وگرنه initialSelection (ویرایش نوبت موجود) همان لحظه پاک میشد.
|
||||
const mounted = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!mounted.current) { mounted.current = true; return; }
|
||||
setSelected([]); setSectionUuid('');
|
||||
}, [doctorUuid]);
|
||||
useEffect(() => { setPickedSlot(null); }, [selected, date, doctorUuid]);
|
||||
|
||||
const serviceUuids = useMemo(() => selected.map(s => s.uuid), [selected]);
|
||||
@@ -59,12 +72,13 @@ export default function ServiceSlotPicker({
|
||||
|
||||
const durationsQs = selected.map(s => `&durations[${encodeURIComponent(s.uuid)}]=${s.duration}`).join('');
|
||||
const slotsQ = useQuery<ApiResponse<any>>({
|
||||
queryKey: ['service-slots-picker', doctorUuid, date, serviceUuids, durations, clinicUuid],
|
||||
queryKey: ['service-slots-picker', 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)}` : ''),
|
||||
+ (clinicUuid ? `&clinic_uuid=${encodeURIComponent(clinicUuid)}` : '')
|
||||
+ (excludeAppointmentUuid ? `&exclude_appointment_uuid=${encodeURIComponent(excludeAppointmentUuid)}` : ''),
|
||||
),
|
||||
enabled: !!doctorUuid && !!date && serviceUuids.length > 0,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user