feat: edit request time blockers, today closing time, UI polish, and test fixes
- Add time blocker management for booking edit requests - Add closing_time field to admin today/current-next endpoint - Update UserBookingModal and CurrentAppointment UI components - Fix fmt import in bookings_test.go (was missing) - Fix created_by FK in TestAdminApproveEditRequest_TimeBlockerOverlap - Update test coverage for edit request time blocker overlap - Update gap backlog documentation
This commit is contained in:
@@ -351,6 +351,70 @@
|
||||
|
||||
async function submitReschedule() {
|
||||
if (!selectedBooking || !rescheduleDate || !rescheduleTime) return;
|
||||
|
||||
// Re-fetch available hours to confirm slot is still open
|
||||
try {
|
||||
const dateStr = rescheduleDate.toString();
|
||||
const monthKey = `${rescheduleDate.year}-${String(rescheduleDate.month).padStart(2, '0')}`;
|
||||
const startOfMonth = new CalendarDate(rescheduleDate.year, rescheduleDate.month, 1);
|
||||
const endOfMonth = new CalendarDate(rescheduleDate.year, rescheduleDate.month, rescheduleDate.calendar.getDaysInMonth(rescheduleDate));
|
||||
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}`),
|
||||
fetch(`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}`)
|
||||
]);
|
||||
|
||||
if (whRes.ok && ahRes.ok) {
|
||||
const whData: WorkingHoursDay[] = await whRes.json();
|
||||
const ahData: AvailableHoursDay[] = await ahRes.json();
|
||||
|
||||
const freshWH: Record<string, { isOpen: boolean; startTime: string; endTime: string }> = {};
|
||||
whData.forEach((d) => { freshWH[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime }; });
|
||||
const freshAH: Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }> = {};
|
||||
ahData.forEach((d) => { freshAH[d.date] = { isOpen: d.isOpen, slots: d.slots }; });
|
||||
|
||||
const dayWH = freshWH[dateStr];
|
||||
const dayAH = freshAH[dateStr];
|
||||
|
||||
if (!dayWH?.isOpen || !dayAH?.slots) {
|
||||
toast.error('This date is no longer available. Please select a different date.');
|
||||
rescheduleDate = undefined;
|
||||
rescheduleTime = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the selected time is still available
|
||||
const freshSlots: string[] = [];
|
||||
for (const slot of dayAH.slots) {
|
||||
const [sh, sm] = slot.startTime.split(':').map(Number);
|
||||
const [eh, em] = slot.endTime.split(':').map(Number);
|
||||
for (let m = sh * 60 + sm; m < eh * 60 + em; m += 15) {
|
||||
if (m + totalDuration <= eh * 60 + em) {
|
||||
freshSlots.push(`${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!freshSlots.includes(rescheduleTime)) {
|
||||
toast.error('This time slot is no longer available. Please choose a different time.');
|
||||
rescheduleTime = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check lunch protection
|
||||
const existingBookings = extractBookedSlots(dayWH.startTime, dayWH.endTime, dayAH.slots);
|
||||
const freshLunch = getLunchProtectionForSlots(dayWH.startTime, dayWH.endTime, existingBookings, totalDuration, 15, false);
|
||||
if (freshLunch.get(rescheduleTime)?.isBlocked) {
|
||||
toast.error('This time slot is no longer available. Please choose a different time.');
|
||||
rescheduleTime = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
toast.error('Could not verify slot availability. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
rescheduleSubmitting = true;
|
||||
try {
|
||||
const [hours, minutes] = rescheduleTime.split(':').map(Number);
|
||||
|
||||
Reference in New Issue
Block a user