98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
|
|
import { useState, useEffect, useCallback } from "react";
|
||
|
|
import { api } from "../../api";
|
||
|
|
import { components } from "../../generated/api";
|
||
|
|
import { formatDate } from "../../utils/date";
|
||
|
|
|
||
|
|
type BookableSlot = components["schemas"]["BookableSlot"];
|
||
|
|
type AvailableSlotsResponse = components["schemas"]["AvailableSlotsResponse"];
|
||
|
|
|
||
|
|
interface UseAvailableSlotsOptions {
|
||
|
|
/** Whether the user is authenticated and authorized */
|
||
|
|
enabled?: boolean;
|
||
|
|
/** Dates to check availability for */
|
||
|
|
dates: Date[];
|
||
|
|
/** Current wizard step - only fetch when in booking or confirmation step */
|
||
|
|
wizardStep?: "details" | "booking" | "confirmation";
|
||
|
|
}
|
||
|
|
|
||
|
|
interface UseAvailableSlotsResult {
|
||
|
|
/** Available slots for the selected date */
|
||
|
|
availableSlots: BookableSlot[];
|
||
|
|
/** Set of date strings that have availability */
|
||
|
|
datesWithAvailability: Set<string>;
|
||
|
|
/** Whether slots are currently being loaded for a specific date */
|
||
|
|
isLoadingSlots: boolean;
|
||
|
|
/** Whether availability is being checked for all dates */
|
||
|
|
isLoadingAvailability: boolean;
|
||
|
|
/** Fetch slots for a specific date */
|
||
|
|
fetchSlots: (date: Date) => Promise<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hook for managing available slots and date availability.
|
||
|
|
* Fetches availability for all dates when entering booking/confirmation steps.
|
||
|
|
*/
|
||
|
|
export function useAvailableSlots(options: UseAvailableSlotsOptions): UseAvailableSlotsResult {
|
||
|
|
const { enabled = true, dates, wizardStep } = options;
|
||
|
|
const [availableSlots, setAvailableSlots] = useState<BookableSlot[]>([]);
|
||
|
|
const [datesWithAvailability, setDatesWithAvailability] = useState<Set<string>>(new Set());
|
||
|
|
const [isLoadingSlots, setIsLoadingSlots] = useState(false);
|
||
|
|
const [isLoadingAvailability, setIsLoadingAvailability] = useState(true);
|
||
|
|
|
||
|
|
const fetchSlots = useCallback(
|
||
|
|
async (date: Date) => {
|
||
|
|
if (!enabled) return;
|
||
|
|
|
||
|
|
setIsLoadingSlots(true);
|
||
|
|
setAvailableSlots([]);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const dateStr = formatDate(date);
|
||
|
|
const data = await api.get<AvailableSlotsResponse>(`/api/exchange/slots?date=${dateStr}`);
|
||
|
|
setAvailableSlots(data.slots);
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Failed to fetch slots:", err);
|
||
|
|
} finally {
|
||
|
|
setIsLoadingSlots(false);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[enabled]
|
||
|
|
);
|
||
|
|
|
||
|
|
// Fetch availability for all dates when entering booking or confirmation step
|
||
|
|
useEffect(() => {
|
||
|
|
if (!enabled || (wizardStep !== "booking" && wizardStep !== "confirmation")) return;
|
||
|
|
|
||
|
|
const fetchAllAvailability = async () => {
|
||
|
|
setIsLoadingAvailability(true);
|
||
|
|
const availabilitySet = new Set<string>();
|
||
|
|
|
||
|
|
const promises = dates.map(async (date) => {
|
||
|
|
try {
|
||
|
|
const dateStr = formatDate(date);
|
||
|
|
const data = await api.get<AvailableSlotsResponse>(`/api/exchange/slots?date=${dateStr}`);
|
||
|
|
if (data.slots.length > 0) {
|
||
|
|
availabilitySet.add(dateStr);
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error(`Failed to fetch availability for ${formatDate(date)}:`, err);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
await Promise.all(promises);
|
||
|
|
setDatesWithAvailability(availabilitySet);
|
||
|
|
setIsLoadingAvailability(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
fetchAllAvailability();
|
||
|
|
}, [enabled, dates, wizardStep]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
availableSlots,
|
||
|
|
datesWithAvailability,
|
||
|
|
isLoadingSlots,
|
||
|
|
isLoadingAvailability,
|
||
|
|
fetchSlots,
|
||
|
|
};
|
||
|
|
}
|