From 2a7911184c66ee94a14849e26724cc5f231fb8ab Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Fri, 22 May 2026 22:25:02 +0000 Subject: [PATCH] Calendar: range highlight, cross-month conflict check, dynamic legend + instructions --- index.html | 82 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index d3490c6..284cf91 100644 --- a/index.html +++ b/index.html @@ -792,9 +792,11 @@
Available
-
Booked
Selected
+ +
Unavailable
+

Click any available date to select it. Weekend rentals automatically highlight both days.

@@ -884,14 +886,57 @@ let calMonth = new Date().getMonth() + 1; // 1-based let bookedSet = new Set(); let selectedDate = null; + let selectedPackage = 'half-day'; + const availCache = {}; + + function getEndDateStr(startDate, pkg) { + if (!startDate || pkg !== 'weekend') return startDate; + const d = new Date(startDate + 'T12:00:00'); + d.setDate(d.getDate() + 1); + return d.toISOString().split('T')[0]; + } + + async function fetchAvail(month, year) { + const key = year + '-' + String(month).padStart(2, '0'); + if (!availCache[key]) { + const res = await fetch('/availability.php?month=' + month + '&year=' + year); + const data = await res.json(); + availCache[key] = new Set(data.booked_dates || []); + } + return availCache[key]; + } + + async function hasRangeConflict(startDate, pkg) { + const endDate = getEndDateStr(startDate, pkg); + const dates = (endDate && endDate !== startDate) ? [startDate, endDate] : [startDate]; + for (const date of dates) { + const [y, m] = date.split('-').map(Number); + const set = await fetchAvail(m, y); + if (set.has(date)) return true; + } + return false; + } + + function showDateUnavail() { + const msg = document.getElementById('date-unavail-msg'); + if (msg) { msg.style.display = 'block'; setTimeout(() => msg.style.display = 'none', 5000); } + } + + function updateLegend() { + const rangeItem = document.getElementById('legend-range'); + const hint = document.getElementById('calHint'); + const isWknd = selectedPackage === 'weekend'; + if (rangeItem) rangeItem.style.display = isWknd ? 'flex' : 'none'; + if (hint) hint.textContent = isWknd + ? 'Click your start date — both weekend days highlight automatically.' + : 'Click any available date to select it.'; + } async function loadCalendar(month, year) { calTitle.textContent = MONTH_NAMES[month - 1] + ' ' + year; calGrid.innerHTML = '
Checking availability…
'; try { - const res = await fetch('/availability.php?month=' + month + '&year=' + year); - const data = await res.json(); - bookedSet = new Set(data.booked_dates || []); + bookedSet = await fetchAvail(month, year); renderCalendar(month, year); } catch { calGrid.innerHTML = '
Could not load availability.
'; @@ -928,14 +973,20 @@ const isBooked = bookedSet.has(dateStr); const isToday = dateStr === todayStr; const isSel = dateStr === selectedDate; + const endDate = getEndDateStr(selectedDate, selectedPackage); + const isInRange = !isSel && selectedDate && endDate && endDate !== selectedDate + && dateStr > selectedDate && dateStr <= endDate; if (isSel) { el.className = 'cal-day selected'; + } else if (isInRange) { + el.className = 'cal-day in-range' + (isBooked ? ' booked' : ''); + el.title = isBooked ? 'Conflict — this day is unavailable' : 'In your booking range'; } else if (isPast) { el.className = 'cal-day past'; } else if (isBooked) { el.className = 'cal-day booked'; - el.title = 'Already booked'; + el.title = 'Unavailable'; } else { el.className = 'cal-day available' + (isToday ? ' today' : ''); el.addEventListener('click', () => selectDate(dateStr)); @@ -948,14 +999,14 @@ calGrid.appendChild(fragment); } - function selectDate(dateStr) { - selectedDate = dateStr; - if (dateInput) { - dateInput.value = dateStr; - dateInput.dispatchEvent(new Event('change')); + async function selectDate(dateStr) { + if (await hasRangeConflict(dateStr, selectedPackage)) { + showDateUnavail(); + return; } + selectedDate = dateStr; + if (dateInput) dateInput.value = dateStr; renderCalendar(calMonth, calYear); - // Smooth-scroll form into view document.getElementById('bookingForm').scrollIntoView({ behavior: 'smooth', block: 'start' }); } @@ -1002,8 +1053,17 @@ const pkgSelect = document.querySelector('select[name="package"]'); const balLabel = document.getElementById('balance-due-label'); const balAmt = document.getElementById('balance-due-amount'); + if (pkgSelect) { selectedPackage = pkgSelect.value || 'half-day'; } if (pkgSelect) { pkgSelect.addEventListener('change', function() { + selectedPackage = this.value; + updateLegend(); + if (selectedDate) { + hasRangeConflict(selectedDate, selectedPackage).then(conflict => { + if (conflict) { if (dateInput) dateInput.value = ''; selectedDate = null; showDateUnavail(); } + renderCalendar(calMonth, calYear); + }); + } else { renderCalendar(calMonth, calYear); } const price = PACKAGE_PRICES[this.value]; if (price && balLabel && balAmt) { balAmt.textContent = '$' + (price - DEPOSIT).toFixed(2).replace(/\.00$/, '');