Disable dates with no availability on booking page
- Fetch availability for all dates on page load - Track which dates have available slots in state - Disable date buttons that have no availability - Add visual styling for disabled dates (reduced opacity, not-allowed cursor) - Prevent clicking on dates with no availability - Improves UX by showing which dates are bookable at a glance
This commit is contained in:
parent
7926e3ae4c
commit
63f40433cc
1 changed files with 52 additions and 4 deletions
|
|
@ -92,6 +92,12 @@ const pageStyles: Record<string, React.CSSProperties> = {
|
||||||
background: "rgba(167, 139, 250, 0.15)",
|
background: "rgba(167, 139, 250, 0.15)",
|
||||||
border: "1px solid #a78bfa",
|
border: "1px solid #a78bfa",
|
||||||
},
|
},
|
||||||
|
dateButtonDisabled: {
|
||||||
|
opacity: 0.4,
|
||||||
|
cursor: "not-allowed",
|
||||||
|
background: "rgba(255, 255, 255, 0.01)",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.04)",
|
||||||
|
},
|
||||||
dateWeekday: {
|
dateWeekday: {
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
|
@ -224,6 +230,8 @@ export default function BookingPage() {
|
||||||
const [isBooking, setIsBooking] = useState(false);
|
const [isBooking, setIsBooking] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
const [datesWithAvailability, setDatesWithAvailability] = useState<Set<string>>(new Set());
|
||||||
|
const [isLoadingAvailability, setIsLoadingAvailability] = useState(true);
|
||||||
|
|
||||||
const dates = getDateRange(minAdvanceDays, maxAdvanceDays);
|
const dates = getDateRange(minAdvanceDays, maxAdvanceDays);
|
||||||
|
|
||||||
|
|
@ -245,6 +253,36 @@ export default function BookingPage() {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Fetch availability for all dates on mount
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user || !isAuthorized) return;
|
||||||
|
|
||||||
|
const fetchAllAvailability = async () => {
|
||||||
|
setIsLoadingAvailability(true);
|
||||||
|
const availabilitySet = new Set<string>();
|
||||||
|
|
||||||
|
// Fetch availability for all dates in parallel
|
||||||
|
const promises = dates.map(async (date) => {
|
||||||
|
try {
|
||||||
|
const dateStr = formatDate(date);
|
||||||
|
const data = await api.get<AvailableSlotsResponse>(`/api/booking/slots?date=${dateStr}`);
|
||||||
|
if (data.slots.length > 0) {
|
||||||
|
availabilitySet.add(dateStr);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Silently fail for individual dates - they'll just be marked as unavailable
|
||||||
|
console.error(`Failed to fetch availability for ${formatDate(date)}:`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
setDatesWithAvailability(availabilitySet);
|
||||||
|
setIsLoadingAvailability(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAllAvailability();
|
||||||
|
}, [user, isAuthorized, dates]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedDate && user && isAuthorized) {
|
if (selectedDate && user && isAuthorized) {
|
||||||
fetchSlots(selectedDate);
|
fetchSlots(selectedDate);
|
||||||
|
|
@ -252,8 +290,12 @@ export default function BookingPage() {
|
||||||
}, [selectedDate, user, isAuthorized, fetchSlots]);
|
}, [selectedDate, user, isAuthorized, fetchSlots]);
|
||||||
|
|
||||||
const handleDateSelect = (date: Date) => {
|
const handleDateSelect = (date: Date) => {
|
||||||
setSelectedDate(date);
|
const dateStr = formatDate(date);
|
||||||
setSuccessMessage(null);
|
// Only allow selection if date has availability
|
||||||
|
if (datesWithAvailability.has(dateStr)) {
|
||||||
|
setSelectedDate(date);
|
||||||
|
setSuccessMessage(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSlotSelect = (slot: BookableSlot) => {
|
const handleSlotSelect = (slot: BookableSlot) => {
|
||||||
|
|
@ -329,14 +371,20 @@ export default function BookingPage() {
|
||||||
<h2 style={styles.sectionTitle}>Select a Date</h2>
|
<h2 style={styles.sectionTitle}>Select a Date</h2>
|
||||||
<div style={styles.dateGrid}>
|
<div style={styles.dateGrid}>
|
||||||
{dates.map((date) => {
|
{dates.map((date) => {
|
||||||
const isSelected = selectedDate && formatDate(selectedDate) === formatDate(date);
|
const dateStr = formatDate(date);
|
||||||
|
const isSelected = selectedDate && formatDate(selectedDate) === dateStr;
|
||||||
|
const hasAvailability = datesWithAvailability.has(dateStr);
|
||||||
|
const isDisabled = !hasAvailability || isLoadingAvailability;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={formatDate(date)}
|
key={dateStr}
|
||||||
onClick={() => handleDateSelect(date)}
|
onClick={() => handleDateSelect(date)}
|
||||||
|
disabled={isDisabled}
|
||||||
style={{
|
style={{
|
||||||
...styles.dateButton,
|
...styles.dateButton,
|
||||||
...(isSelected ? styles.dateButtonSelected : {}),
|
...(isSelected ? styles.dateButtonSelected : {}),
|
||||||
|
...(isDisabled ? styles.dateButtonDisabled : {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={styles.dateWeekday}>
|
<div style={styles.dateWeekday}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue