mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-06-30 17:50:31 -05:00
Calendar: range highlight, cross-month conflict check, dynamic legend + instructions
This commit is contained in:
+71
-11
@@ -792,9 +792,11 @@
|
||||
</div>
|
||||
<div class="cal-legend">
|
||||
<div class="cal-legend-item"><div class="cal-legend-dot" style="background:rgba(255,255,255,0.12)"></div> Available</div>
|
||||
<div class="cal-legend-item"><div class="cal-legend-dot" style="background:rgba(239,68,68,0.35)"></div> Booked</div>
|
||||
<div class="cal-legend-item"><div class="cal-legend-dot" style="background:var(--orange)"></div> Selected</div>
|
||||
<div class="cal-legend-item" id="legend-range" style="display:none"><div class="cal-legend-dot" style="background:rgba(249,115,22,0.35)"></div> In Range</div>
|
||||
<div class="cal-legend-item"><div class="cal-legend-dot" style="background:rgba(239,68,68,0.35)"></div> Unavailable</div>
|
||||
</div>
|
||||
<p id="calHint" style="font-size:0.72rem;color:rgba(255,255,255,0.35);margin-top:0.5rem">Click any available date to select it. Weekend rentals automatically highlight both days.</p>
|
||||
</div>
|
||||
|
||||
<form class="contact-form" id="bookingForm" novalidate>
|
||||
@@ -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 = '<div class="cal-loading" style="grid-column:1/-1">Checking availability…</div>';
|
||||
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 = '<div class="cal-loading" style="grid-column:1/-1;color:rgba(239,68,68,0.6)">Could not load availability.</div>';
|
||||
@@ -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$/, '');
|
||||
|
||||
Reference in New Issue
Block a user