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>
|
||||||
<div class="cal-legend">
|
<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(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"><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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<form class="contact-form" id="bookingForm" novalidate>
|
<form class="contact-form" id="bookingForm" novalidate>
|
||||||
@@ -884,14 +886,57 @@
|
|||||||
let calMonth = new Date().getMonth() + 1; // 1-based
|
let calMonth = new Date().getMonth() + 1; // 1-based
|
||||||
let bookedSet = new Set();
|
let bookedSet = new Set();
|
||||||
let selectedDate = null;
|
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) {
|
async function loadCalendar(month, year) {
|
||||||
calTitle.textContent = MONTH_NAMES[month - 1] + ' ' + year;
|
calTitle.textContent = MONTH_NAMES[month - 1] + ' ' + year;
|
||||||
calGrid.innerHTML = '<div class="cal-loading" style="grid-column:1/-1">Checking availability…</div>';
|
calGrid.innerHTML = '<div class="cal-loading" style="grid-column:1/-1">Checking availability…</div>';
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/availability.php?month=' + month + '&year=' + year);
|
bookedSet = await fetchAvail(month, year);
|
||||||
const data = await res.json();
|
|
||||||
bookedSet = new Set(data.booked_dates || []);
|
|
||||||
renderCalendar(month, year);
|
renderCalendar(month, year);
|
||||||
} catch {
|
} catch {
|
||||||
calGrid.innerHTML = '<div class="cal-loading" style="grid-column:1/-1;color:rgba(239,68,68,0.6)">Could not load availability.</div>';
|
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 isBooked = bookedSet.has(dateStr);
|
||||||
const isToday = dateStr === todayStr;
|
const isToday = dateStr === todayStr;
|
||||||
const isSel = dateStr === selectedDate;
|
const isSel = dateStr === selectedDate;
|
||||||
|
const endDate = getEndDateStr(selectedDate, selectedPackage);
|
||||||
|
const isInRange = !isSel && selectedDate && endDate && endDate !== selectedDate
|
||||||
|
&& dateStr > selectedDate && dateStr <= endDate;
|
||||||
|
|
||||||
if (isSel) {
|
if (isSel) {
|
||||||
el.className = 'cal-day selected';
|
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) {
|
} else if (isPast) {
|
||||||
el.className = 'cal-day past';
|
el.className = 'cal-day past';
|
||||||
} else if (isBooked) {
|
} else if (isBooked) {
|
||||||
el.className = 'cal-day booked';
|
el.className = 'cal-day booked';
|
||||||
el.title = 'Already booked';
|
el.title = 'Unavailable';
|
||||||
} else {
|
} else {
|
||||||
el.className = 'cal-day available' + (isToday ? ' today' : '');
|
el.className = 'cal-day available' + (isToday ? ' today' : '');
|
||||||
el.addEventListener('click', () => selectDate(dateStr));
|
el.addEventListener('click', () => selectDate(dateStr));
|
||||||
@@ -948,14 +999,14 @@
|
|||||||
calGrid.appendChild(fragment);
|
calGrid.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectDate(dateStr) {
|
async function selectDate(dateStr) {
|
||||||
selectedDate = dateStr;
|
if (await hasRangeConflict(dateStr, selectedPackage)) {
|
||||||
if (dateInput) {
|
showDateUnavail();
|
||||||
dateInput.value = dateStr;
|
return;
|
||||||
dateInput.dispatchEvent(new Event('change'));
|
|
||||||
}
|
}
|
||||||
|
selectedDate = dateStr;
|
||||||
|
if (dateInput) dateInput.value = dateStr;
|
||||||
renderCalendar(calMonth, calYear);
|
renderCalendar(calMonth, calYear);
|
||||||
// Smooth-scroll form into view
|
|
||||||
document.getElementById('bookingForm').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
document.getElementById('bookingForm').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1002,8 +1053,17 @@
|
|||||||
const pkgSelect = document.querySelector('select[name="package"]');
|
const pkgSelect = document.querySelector('select[name="package"]');
|
||||||
const balLabel = document.getElementById('balance-due-label');
|
const balLabel = document.getElementById('balance-due-label');
|
||||||
const balAmt = document.getElementById('balance-due-amount');
|
const balAmt = document.getElementById('balance-due-amount');
|
||||||
|
if (pkgSelect) { selectedPackage = pkgSelect.value || 'half-day'; }
|
||||||
if (pkgSelect) {
|
if (pkgSelect) {
|
||||||
pkgSelect.addEventListener('change', function() {
|
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];
|
const price = PACKAGE_PRICES[this.value];
|
||||||
if (price && balLabel && balAmt) {
|
if (price && balLabel && balAmt) {
|
||||||
balAmt.textContent = '$' + (price - DEPOSIT).toFixed(2).replace(/\.00$/, '');
|
balAmt.textContent = '$' + (price - DEPOSIT).toFixed(2).replace(/\.00$/, '');
|
||||||
|
|||||||
Reference in New Issue
Block a user