feat(admin): online-booking window UI in doctor weekly schedule

Add a نوبت‌دهی آنلاین card to the weekly-schedule tab: a toggle for
online_booking_enabled and a value+unit (هفته/ماه) booking window.
Hydrated from the schedule meta and sent in the weekly-schedule
POST/PATCH payload. Uses existing .input/.btn classes, RTL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-15 16:26:03 +03:30
co-authored by Claude Opus 4.8
parent f443beb615
commit 874125c4e1
+51 -3
View File
@@ -89,7 +89,17 @@ interface SessionConfig {
}
interface NewDayConfig { sessions: SessionConfig[]; }
type NewScheduleMap = Record<string, NewDayConfig>;
interface WeeklyScheduleData { uuid: string; doctor_uuid: string; schedule: NewScheduleMap; }
interface BookingMeta {
online_booking_enabled: boolean;
booking_window_value: number;
booking_window_unit: 'week' | 'month';
}
const DEFAULT_BOOKING_META: BookingMeta = {
online_booking_enabled: true,
booking_window_value: 1,
booking_window_unit: 'month',
};
interface WeeklyScheduleData { uuid: string; doctor_uuid: string; schedule: NewScheduleMap; meta?: BookingMeta; }
// ── Constants ──────────────────────────────────────────────────────────────
@@ -1191,6 +1201,7 @@ function WeeklyScheduleTab({ doctorUuid, addresses }: { doctorUuid: string; addr
const [scheduleMap, setScheduleMap] = useState<NewScheduleMap>(EMPTY_NEW_SCHEDULE);
const [scheduleUuid, setScheduleUuid] = useState<string | null>(null);
const [expandedDay, setExpandedDay] = useState<string | null>(null);
const [meta, setMeta] = useState<BookingMeta>(DEFAULT_BOOKING_META);
const scheduleQ = useQuery({
queryKey: ['doctor-schedule', doctorUuid],
@@ -1211,6 +1222,7 @@ function WeeklyScheduleTab({ doctorUuid, addresses }: { doctorUuid: string; addr
setScheduleMap(merged);
setScheduleUuid(d.uuid);
}
if (d?.meta) setMeta({ ...DEFAULT_BOOKING_META, ...d.meta });
} else if (scheduleQ.error instanceof ApiError && scheduleQ.error.status === 404) {
setScheduleMap(EMPTY_NEW_SCHEDULE); setScheduleUuid(null);
}
@@ -1237,8 +1249,8 @@ function WeeklyScheduleTab({ doctorUuid, addresses }: { doctorUuid: string; addr
if (hasAnyOverlap) throw new Error('تداخل زمانی در برنامه وجود دارد');
if (missingLocation) throw new Error('مکان مطب برای همه بازه‌های فعال الزامی است');
return scheduleUuid
? api.patch<ApiResponse<any>>(`/api/v1/appointment-settings/weekly-schedule/${doctorUuid}`, { schedule: scheduleMap })
: api.post<ApiResponse<any>>('/api/v1/appointment-settings/weekly-schedule', { doctor_uuid: doctorUuid, schedule: scheduleMap });
? api.patch<ApiResponse<any>>(`/api/v1/appointment-settings/weekly-schedule/${doctorUuid}`, { schedule: scheduleMap, meta })
: api.post<ApiResponse<any>>('/api/v1/appointment-settings/weekly-schedule', { doctor_uuid: doctorUuid, schedule: scheduleMap, meta });
},
onSuccess: (res) => {
const d: WeeklyScheduleData = res?.data?.data ?? res?.data;
@@ -1298,6 +1310,42 @@ function WeeklyScheduleTab({ doctorUuid, addresses }: { doctorUuid: string; addr
</div>
)}
{/* ─ نوبت‌دهی آنلاین */}
<div className="mb-3 p-3 rounded-xl border border-slate-200 dark:border-gray-700 space-y-3">
<label className="flex items-center justify-between gap-3 cursor-pointer">
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">نوبتدهی آنلاین</span>
<input
type="checkbox"
checked={meta.online_booking_enabled}
onChange={(e) => setMeta(m => ({ ...m, online_booking_enabled: e.target.checked }))}
/>
</label>
<div className="flex items-center gap-2">
<span className="text-sm text-slate-600 dark:text-slate-400">رزرو آنلاین تا</span>
<input
type="number"
min={1}
value={meta.booking_window_value}
disabled={!meta.online_booking_enabled}
onChange={(e) => setMeta(m => ({ ...m, booking_window_value: Math.max(1, Number(e.target.value) || 1) }))}
className="input w-16 text-center"
/>
<select
value={meta.booking_window_unit}
disabled={!meta.online_booking_enabled}
onChange={(e) => setMeta(m => ({ ...m, booking_window_unit: e.target.value as 'week' | 'month' }))}
className="input"
>
<option value="week">هفته</option>
<option value="month">ماه</option>
</select>
<span className="text-sm text-slate-600 dark:text-slate-400">آینده</span>
</div>
<p className="text-xs text-slate-400 dark:text-slate-500">
بیمار فقط تا این بازه میتواند آنلاین نوبت بگیرد؛ روزهای بعد از آن روی تقویم غیرفعالاند.
</p>
</div>
{SCHEDULE_DAYS.map(day => {
const sessions = scheduleMap[day.key]?.sessions ?? [];
const isOverlap = overlapDays[day.key];