chore: apply project-wide formatting and UI library updates
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -33,7 +33,11 @@
|
||||
|
||||
let saving = $state(false);
|
||||
let placeholder = $state<CalendarDate>(
|
||||
new CalendarDate(new SvelteDate().getFullYear(), new SvelteDate().getMonth() + 1, new SvelteDate().getDate())
|
||||
new CalendarDate(
|
||||
new SvelteDate().getFullYear(),
|
||||
new SvelteDate().getMonth() + 1,
|
||||
new SvelteDate().getDate()
|
||||
)
|
||||
);
|
||||
let selectedDate = $state<CalendarDate | undefined>(undefined);
|
||||
let selectedTime = $state<string | null>(null);
|
||||
@@ -55,21 +59,42 @@
|
||||
const minDate = new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate());
|
||||
const maxDate = new SvelteDate();
|
||||
maxDate.setMonth(today.getMonth() + 6);
|
||||
const maxCalendarDate = new CalendarDate(maxDate.getFullYear(), maxDate.getMonth() + 1, maxDate.getDate());
|
||||
const maxCalendarDate = new CalendarDate(
|
||||
maxDate.getFullYear(),
|
||||
maxDate.getMonth() + 1,
|
||||
maxDate.getDate()
|
||||
);
|
||||
|
||||
let bookingDuration = $derived(
|
||||
booking.services?.reduce((sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0), 0) ?? booking.duration_minutes ?? 0
|
||||
booking.services?.reduce(
|
||||
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
||||
0
|
||||
) ??
|
||||
booking.duration_minutes ??
|
||||
0
|
||||
);
|
||||
|
||||
const lunchProtection = $derived(
|
||||
selectedDate ? buildLunchProtection(selectedDate, workingHours, availableHours, bookingDuration, true) : new Map()
|
||||
selectedDate
|
||||
? buildLunchProtection(selectedDate, workingHours, availableHours, bookingDuration, true)
|
||||
: new Map()
|
||||
);
|
||||
|
||||
const groupedTimeSlots = $derived(
|
||||
selectedDate ? generateGroupedTimeSlots(selectedDate, workingHours, availableHours, bookingDuration, lunchProtection) : []
|
||||
selectedDate
|
||||
? generateGroupedTimeSlots(
|
||||
selectedDate,
|
||||
workingHours,
|
||||
availableHours,
|
||||
bookingDuration,
|
||||
lunchProtection
|
||||
)
|
||||
: []
|
||||
);
|
||||
|
||||
const formattedSelectedDate = $derived(selectedDate ? getDayWithOrdinal(selectedDate) : undefined);
|
||||
const formattedSelectedDate = $derived(
|
||||
selectedDate ? getDayWithOrdinal(selectedDate) : undefined
|
||||
);
|
||||
|
||||
function isDateUnavailable(date: DateValue): boolean {
|
||||
if (!(date instanceof CalendarDate)) return true;
|
||||
@@ -84,8 +109,20 @@
|
||||
// If booking duration is 0 (data missing), no valid slots can exist — mark unavailable
|
||||
if (bookingDuration <= 0) return true;
|
||||
// Build lunch protection specifically for the date being checked (not the selected date)
|
||||
const dayProtection = buildLunchProtection(date as CalendarDate, workingHours, availableHours, bookingDuration, true);
|
||||
const slots = generateAvailableTimeSlots(date as CalendarDate, workingHours, availableHours, bookingDuration, dayProtection);
|
||||
const dayProtection = buildLunchProtection(
|
||||
date as CalendarDate,
|
||||
workingHours,
|
||||
availableHours,
|
||||
bookingDuration,
|
||||
true
|
||||
);
|
||||
const slots = generateAvailableTimeSlots(
|
||||
date as CalendarDate,
|
||||
workingHours,
|
||||
availableHours,
|
||||
bookingDuration,
|
||||
dayProtection
|
||||
);
|
||||
if (slots.length === 0) return true;
|
||||
return false;
|
||||
}
|
||||
@@ -130,7 +167,9 @@
|
||||
const whMap: Record<string, DayHours> = {};
|
||||
const ahMap: Record<string, DayAvailability> = {};
|
||||
|
||||
whData.forEach((d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime }));
|
||||
whData.forEach(
|
||||
(d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime })
|
||||
);
|
||||
ahData.forEach((d) => (ahMap[d.date] = { isOpen: d.isOpen, slots: d.slots }));
|
||||
|
||||
// Cache by month key
|
||||
@@ -191,7 +230,11 @@
|
||||
|
||||
try {
|
||||
const startOfMonth = new CalendarDate(date.year, date.month, 1);
|
||||
const endOfMonth = new CalendarDate(date.year, date.month, date.calendar.getDaysInMonth(date));
|
||||
const endOfMonth = new CalendarDate(
|
||||
date.year,
|
||||
date.month,
|
||||
date.calendar.getDaysInMonth(date)
|
||||
);
|
||||
const [whRes, ahRes] = await Promise.all([
|
||||
fetch(`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
@@ -206,7 +249,9 @@
|
||||
if (gen !== hoursMonthGeneration) return;
|
||||
const whMap: Record<string, DayHours> = {};
|
||||
const ahMap: Record<string, DayAvailability> = {};
|
||||
whData.forEach((d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime }));
|
||||
whData.forEach(
|
||||
(d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime })
|
||||
);
|
||||
ahData.forEach((d) => (ahMap[d.date] = { isOpen: d.isOpen, slots: d.slots }));
|
||||
|
||||
workingHoursCache[monthKey] = whMap;
|
||||
@@ -220,7 +265,7 @@
|
||||
toast.error('Failed to load availability');
|
||||
} finally {
|
||||
delete loadingMonths[monthKey];
|
||||
loadingMonthKeys = new Set([...loadingMonthKeys].filter(k => k !== monthKey));
|
||||
loadingMonthKeys = new Set([...loadingMonthKeys].filter((k) => k !== monthKey));
|
||||
loadingHours = false;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +282,11 @@
|
||||
loadingMonths = {};
|
||||
loadingMonthKeys = new Set();
|
||||
rescheduleAutoSelectDone = false;
|
||||
placeholder = new CalendarDate(new SvelteDate().getFullYear(), new SvelteDate().getMonth() + 1, new SvelteDate().getDate());
|
||||
placeholder = new CalendarDate(
|
||||
new SvelteDate().getFullYear(),
|
||||
new SvelteDate().getMonth() + 1,
|
||||
new SvelteDate().getDate()
|
||||
);
|
||||
}
|
||||
|
||||
async function reschedule() {
|
||||
@@ -318,7 +367,14 @@
|
||||
|
||||
// Auto-select first available date once data loads (timing-safe, data-driven)
|
||||
$effect(() => {
|
||||
if (open && workingHours && availableHours && !selectedDate && !userNavigatedCalendar && !rescheduleAutoSelectDone) {
|
||||
if (
|
||||
open &&
|
||||
workingHours &&
|
||||
availableHours &&
|
||||
!selectedDate &&
|
||||
!userNavigatedCalendar &&
|
||||
!rescheduleAutoSelectDone
|
||||
) {
|
||||
rescheduleAutoSelectDone = true;
|
||||
const now = new SvelteDate();
|
||||
const maxDateJs = new SvelteDate(
|
||||
@@ -334,7 +390,11 @@
|
||||
const checkDate = new SvelteDate(now);
|
||||
checkDate.setDate(now.getDate() + i);
|
||||
const dateStr = checkDate.toISOString().split('T')[0];
|
||||
const calDate = new CalendarDate(checkDate.getFullYear(), checkDate.getMonth() + 1, checkDate.getDate());
|
||||
const calDate = new CalendarDate(
|
||||
checkDate.getFullYear(),
|
||||
checkDate.getMonth() + 1,
|
||||
checkDate.getDate()
|
||||
);
|
||||
if (workingHours[dateStr]?.isOpen && !isDateUnavailable(calDate)) {
|
||||
selectedDate = calDate;
|
||||
if (!userNavigatedCalendar) {
|
||||
@@ -360,10 +420,21 @@
|
||||
<Card.Root class="mb-4">
|
||||
<Card.Content class="pt-4">
|
||||
<div class="text-sm font-medium">
|
||||
Current: {new SvelteDate(booking.start_time).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' })} at {new SvelteDate(booking.start_time).toLocaleTimeString('en-GB', { hour: 'numeric', minute: '2-digit', hour12: true })}
|
||||
Current: {new SvelteDate(booking.start_time).toLocaleDateString('en-GB', {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})} at {new SvelteDate(booking.start_time).toLocaleTimeString('en-GB', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
})}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mt-1">
|
||||
{booking.user?.full_name || 'Unknown'} · {booking.services?.map((s) => s.service_name).join(', ') || 'No services'} · {bookingDuration} min
|
||||
<div class="mt-1 text-sm text-gray-500">
|
||||
{booking.user?.full_name || 'Unknown'} · {booking.services
|
||||
?.map((s) => s.service_name)
|
||||
.join(', ') || 'No services'} · {bookingDuration} min
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -381,10 +452,10 @@
|
||||
selectedDate = newDate;
|
||||
selectedTime = null;
|
||||
}}
|
||||
onPlaceholderChange={(newPlaceholder) => {
|
||||
placeholder = newPlaceholder;
|
||||
userNavigatedCalendar = true;
|
||||
}}
|
||||
onPlaceholderChange={(newPlaceholder) => {
|
||||
placeholder = newPlaceholder;
|
||||
userNavigatedCalendar = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -393,14 +464,18 @@
|
||||
<div class="border-t">
|
||||
<div class="max-h-64 overflow-y-auto p-6">
|
||||
{#if formattedSelectedDate}
|
||||
<div class="mb-3 grid justify-center gap-2 text-sm font-medium">{formattedSelectedDate}</div>
|
||||
<div class="mb-3 grid justify-center gap-2 text-sm font-medium">
|
||||
{formattedSelectedDate}
|
||||
</div>
|
||||
{/if}
|
||||
<TimeSlotList
|
||||
slots={groupedTimeSlots}
|
||||
{selectedTime}
|
||||
duration={bookingDuration}
|
||||
protection={lunchProtection}
|
||||
onSelect={(time) => { selectedTime = time; }}
|
||||
onSelect={(time) => {
|
||||
selectedTime = time;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -423,7 +498,14 @@
|
||||
</div>
|
||||
|
||||
<Modal.Footer class="flex items-center justify-end gap-2">
|
||||
<Button variant="outline" onclick={() => { open = false; resetForm(); }} disabled={saving}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={() => {
|
||||
open = false;
|
||||
resetForm();
|
||||
}}
|
||||
disabled={saving}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onclick={reschedule} disabled={saving || !selectedDate || !selectedTime}>
|
||||
|
||||
Reference in New Issue
Block a user