feat(frontend): add out_of_hours booking type and timeSlots utils

Add optional out_of_hours field to Booking interface and TimeSlotGroup. Add debug logging to timeSlot generation utilities for easier development.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-22 12:55:32 +01:00
co-authored by Sisyphus
parent 34aba319f1
commit 08d62080c1
2 changed files with 55 additions and 3 deletions
+1
View File
@@ -118,6 +118,7 @@ export interface Booking {
updated_at: string; updated_at: string;
created_by?: string; created_by?: string;
created_by_name?: string; created_by_name?: string;
out_of_hours?: boolean;
// Deposit fields // Deposit fields
deposit_required: boolean; deposit_required: boolean;
+54 -3
View File
@@ -11,6 +11,7 @@ export interface TimeSlotGroup {
startTime: string; startTime: string;
endTime: string; endTime: string;
isGrouped?: boolean; isGrouped?: boolean;
outOfHours?: boolean;
} }
export interface DayHours { export interface DayHours {
@@ -111,7 +112,16 @@ export function generateAvailableTimeSlots(
const dateStr = date.toString(); const dateStr = date.toString();
const dayWH = workingHours[dateStr]; const dayWH = workingHours[dateStr];
const dayAH = availableHours[dateStr]; const dayAH = availableHours[dateStr];
if (!dayWH?.isOpen || !dayAH?.slots) return []; if (!dayWH?.isOpen || !dayAH?.slots) {
console.log('[DEBUG] generateAvailableTimeSlots - early return', {
dateStr,
hasDayWH: !!dayWH,
dayWH_isOpen: dayWH?.isOpen,
hasDayAH: !!dayAH,
dayAH_slots: dayAH?.slots?.length
});
return [];
}
const slots: string[] = []; const slots: string[] = [];
const now = new SvelteDate(); const now = new SvelteDate();
@@ -140,6 +150,16 @@ export function generateAvailableTimeSlots(
} }
} }
} }
console.log('[DEBUG] generateAvailableTimeSlots result', {
dateStr,
duration,
dayWH_startTime: dayWH.startTime,
dayWH_endTime: dayWH.endTime,
dayAH_slots_raw: dayAH.slots,
isToday,
generatedSlotsCount: slots.length,
generatedSlots: slots.slice(0, 20) + (slots.length > 20 ? `... (${slots.length} total)` : '')
});
return slots; return slots;
} }
@@ -150,10 +170,25 @@ export function generateGroupedTimeSlots(
duration: number, duration: number,
protection: Map<string, LunchProtectionResult> protection: Map<string, LunchProtectionResult>
): TimeSlotGroup[] { ): TimeSlotGroup[] {
if (!workingHours) return []; if (!workingHours) {
console.log('[DEBUG] generateGroupedTimeSlots - no workingHours');
return [];
}
const dateStr = date.toString(); const dateStr = date.toString();
const dayWH = workingHours[dateStr]; const dayWH = workingHours[dateStr];
if (!dayWH || !dayWH.isOpen) return []; if (!dayWH || !dayWH.isOpen) {
console.log('[DEBUG] generateGroupedTimeSlots - day not open', { dateStr, dayWH });
return [];
}
console.log('[DEBUG] generateGroupedTimeSlots - inputs', {
dateStr,
duration,
startTime: dayWH.startTime,
endTime: dayWH.endTime,
isOpen: dayWH.isOpen,
source: (dayWH as any).source
});
const grouped: TimeSlotGroup[] = []; const grouped: TimeSlotGroup[] = [];
const [startHour, startMinute] = dayWH.startTime.split(':').map(Number); const [startHour, startMinute] = dayWH.startTime.split(':').map(Number);
@@ -175,6 +210,15 @@ export function generateGroupedTimeSlots(
duration, duration,
protection protection
); );
console.log('[DEBUG] generateGroupedTimeSlots - availableSlots', {
availableSlotsCount: availableSlots.length,
availableSlots: availableSlots.slice(0, 30) + (availableSlots.length > 30 ? `... (${availableSlots.length} total)` : ''),
dayRange: `${dayWH.startTime}-${dayWH.endTime}`,
startTotalMinutes,
endTotalMinutes
});
let currentUnavailableStart: string | null = null; let currentUnavailableStart: string | null = null;
let lastAvailableEndTime: string | null = null; let lastAvailableEndTime: string | null = null;
@@ -222,5 +266,12 @@ export function generateGroupedTimeSlots(
} }
} }
console.log('[DEBUG] generateGroupedTimeSlots - final result', {
availableCount: grouped.filter(s => s.type === 'available').length,
unavailableCount: grouped.filter(s => s.type === 'unavailable').length,
availableTimes: grouped.filter(s => s.type === 'available').map(s => s.startTime + '-' + s.endTime).slice(0, 20),
unavailableRanges: grouped.filter(s => s.type === 'unavailable').map(s => s.startTime + '-' + s.endTime)
});
return grouped; return grouped;
} }