diff --git a/frontend/src/lib/components/account/EditRequestModal.svelte b/frontend/src/lib/components/account/EditRequestModal.svelte index 06bedd8..c139412 100644 --- a/frontend/src/lib/components/account/EditRequestModal.svelte +++ b/frontend/src/lib/components/account/EditRequestModal.svelte @@ -485,6 +485,21 @@ } }); + // Clear selection when navigating to a month that doesn't contain the selected date. + // Runs AFTER all synchronous state changes settle, so clicking a date in a different + // month (fires both onPlaceholderChange and onValueChange) keeps the new selection, + // while clicking prev/next arrows without picking a date clears it. + $effect(() => { + if ( + newDate && + placeholderDate && + (newDate.month !== placeholderDate.month || newDate.year !== placeholderDate.year) + ) { + newDate = undefined; + newTime = ''; + } + }); + // ─── API calls ────────────────────────────────────────── async function fetchHoursRange(startDate: CalendarDate, months: number) { loadingHours = true; diff --git a/frontend/src/lib/components/admin/BookingCreateModal.svelte b/frontend/src/lib/components/admin/BookingCreateModal.svelte index a6818f5..2c8fe52 100644 --- a/frontend/src/lib/components/admin/BookingCreateModal.svelte +++ b/frontend/src/lib/components/admin/BookingCreateModal.svelte @@ -437,6 +437,21 @@ } }); + // Clear selection when navigating to a month that doesn't contain the selected date. + // Runs AFTER all synchronous state changes settle, so clicking a date in a different + // month (fires both onPlaceholderChange and onValueChange) keeps the new selection, + // while clicking prev/next arrows without picking a date clears it. + $effect(() => { + if ( + selectedDate && + placeholder && + (selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year) + ) { + selectedDate = undefined; + selectedTime = null; + } + }); + // =============== Reset State =============== function resetState() { currentStep = 1; diff --git a/frontend/src/lib/components/admin/BookingModal.svelte b/frontend/src/lib/components/admin/BookingModal.svelte index 2b48470..8baa30b 100644 --- a/frontend/src/lib/components/admin/BookingModal.svelte +++ b/frontend/src/lib/components/admin/BookingModal.svelte @@ -21,9 +21,10 @@ open: boolean; bookingId: string; onReschedule?: () => void; + onChanged?: () => void; } - let { open = $bindable(), bookingId, onReschedule }: Props = $props(); + let { open = $bindable(), bookingId, onReschedule, onChanged }: Props = $props(); let selectedBooking = $state(null); let showApprovalModal = $state(false); @@ -83,6 +84,7 @@ toast.success('Booking cancelled'); showCancelModal = false; fetchBookingDetails(); + onChanged?.(); } else { const text = await response.text(); toast.error('Failed to cancel: ' + extractErrorMessage(text)); @@ -770,6 +772,7 @@ onApproved={() => { showApprovalModal = false; fetchBookingDetails(); + onChanged?.(); }} /> {/if} @@ -783,6 +786,7 @@ showRescheduleModal = false; fetchBookingDetails(); onReschedule?.(); + onChanged?.(); }} /> {/if} diff --git a/frontend/src/lib/components/admin/RescheduleModal.svelte b/frontend/src/lib/components/admin/RescheduleModal.svelte index 09b35a8..28b0bd4 100644 --- a/frontend/src/lib/components/admin/RescheduleModal.svelte +++ b/frontend/src/lib/components/admin/RescheduleModal.svelte @@ -409,6 +409,21 @@ } } }); + + // Clear selection when navigating to a month that doesn't contain the selected date. + // Runs AFTER all synchronous state changes settle, so clicking a date in a different + // month (fires both onPlaceholderChange and onValueChange) keeps the new selection, + // while clicking prev/next arrows without picking a date clears it. + $effect(() => { + if ( + selectedDate && + placeholder && + (selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year) + ) { + selectedDate = undefined; + selectedTime = null; + } + }); diff --git a/frontend/src/lib/components/booking/BookingFlow.svelte b/frontend/src/lib/components/booking/BookingFlow.svelte index 2a6d67e..2d04d79 100644 --- a/frontend/src/lib/components/booking/BookingFlow.svelte +++ b/frontend/src/lib/components/booking/BookingFlow.svelte @@ -678,6 +678,21 @@ } }); + // Clear selection when navigating to a month that doesn't contain the selected date. + // Runs AFTER all synchronous state changes settle, so clicking a date in a different + // month (fires both onPlaceholderChange and onValueChange) keeps the new selection, + // while clicking prev/next arrows without picking a date clears it. + $effect(() => { + if ( + selectedDate && + placeholder && + (selectedDate.month !== placeholder.month || selectedDate.year !== placeholder.year) + ) { + selectedDate = undefined; + selectedTime = null; + } + }); + async function fetchHoursRange(startDate: CalendarDate, months: number) { // Calculate end month manually (CalendarDate is immutable) let endYear = startDate.year; diff --git a/frontend/src/lib/components/booking/DatePicker.svelte b/frontend/src/lib/components/booking/DatePicker.svelte index af533dd..08c2cde 100644 --- a/frontend/src/lib/components/booking/DatePicker.svelte +++ b/frontend/src/lib/components/booking/DatePicker.svelte @@ -54,5 +54,14 @@ onPlaceholderChange(p as CalendarDate); } }} + // Detect clicks on unavailable dates (bits-ui blocks onValueChange for these, + // so the parent's selection stays stale). Clear the selection so the time + // picker hides and the user knows they didn't pick anything. + onclick={(e: MouseEvent) => { + const target = e.target as HTMLElement; + if (target.closest('[data-unavailable]')) { + onchange?.(undefined); + } + }} /> diff --git a/frontend/src/routes/admin/schedule/+page.svelte b/frontend/src/routes/admin/schedule/+page.svelte index 035d4df..bd8be22 100644 --- a/frontend/src/routes/admin/schedule/+page.svelte +++ b/frontend/src/routes/admin/schedule/+page.svelte @@ -766,7 +766,7 @@ {/if} - + fetchWeekData()} /> {/if}