mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-06-30 17:50:31 -05:00
Calendar multi-select: selectedDates Set, click-to-toggle, inRangeDates across all selections
This commit is contained in:
+49
-26
@@ -796,7 +796,7 @@
|
||||
<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>
|
||||
<p id="calHint" style="font-size:0.72rem;color:rgba(255,255,255,0.35);margin-top:0.5rem">Click a date to select it — click again to deselect. Weekend package shows both days automatically.</p>
|
||||
</div>
|
||||
|
||||
<form class="contact-form" id="bookingForm" novalidate>
|
||||
@@ -885,7 +885,8 @@
|
||||
let calYear = new Date().getFullYear();
|
||||
let calMonth = new Date().getMonth() + 1; // 1-based
|
||||
let bookedSet = new Set();
|
||||
let selectedDate = null;
|
||||
let selectedDates = new Set(); // all pinned start dates
|
||||
let lastSelectedDate = null; // what goes in the booking form
|
||||
let selectedPackage = 'half-day';
|
||||
const availCache = {};
|
||||
|
||||
@@ -964,6 +965,18 @@
|
||||
fragment.appendChild(el);
|
||||
}
|
||||
|
||||
// Pre-compute all in-range dates across every selected start date
|
||||
const inRangeDates = new Set();
|
||||
for (const start of selectedDates) {
|
||||
const endD = getEndDateStr(start, selectedPackage);
|
||||
if (endD && endD !== start) {
|
||||
const dd = new Date(start + 'T12:00:00'); dd.setDate(dd.getDate() + 1);
|
||||
while (dd.toISOString().split('T')[0] <= endD) {
|
||||
inRangeDates.add(dd.toISOString().split('T')[0]); dd.setDate(dd.getDate() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let d = 1; d <= daysInMo; d++) {
|
||||
const dateStr = year + '-' + String(month).padStart(2,'0') + '-' + String(d).padStart(2,'0');
|
||||
const el = document.createElement('div');
|
||||
@@ -972,13 +985,12 @@
|
||||
const isPast = dateStr < todayStr;
|
||||
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;
|
||||
const isSel = selectedDates.has(dateStr);
|
||||
const isInRange = !isSel && inRangeDates.has(dateStr);
|
||||
|
||||
if (isSel) {
|
||||
el.className = 'cal-day selected';
|
||||
el.addEventListener('click', () => selectDate(dateStr)); // click again to deselect
|
||||
} else if (isInRange) {
|
||||
el.className = 'cal-day in-range' + (isBooked ? ' booked' : '');
|
||||
el.title = isBooked ? 'Conflict — this day is unavailable' : 'In your booking range';
|
||||
@@ -1000,14 +1012,21 @@
|
||||
}
|
||||
|
||||
async function selectDate(dateStr) {
|
||||
if (await hasRangeConflict(dateStr, selectedPackage)) {
|
||||
showDateUnavail();
|
||||
return;
|
||||
if (selectedDates.has(dateStr)) {
|
||||
// Toggle off — deselect this date
|
||||
selectedDates.delete(dateStr);
|
||||
lastSelectedDate = selectedDates.size > 0
|
||||
? [...selectedDates].sort().reverse()[0] : null;
|
||||
} else {
|
||||
if (await hasRangeConflict(dateStr, selectedPackage)) {
|
||||
showDateUnavail(); return;
|
||||
}
|
||||
selectedDates.add(dateStr);
|
||||
lastSelectedDate = dateStr;
|
||||
document.getElementById('bookingForm').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
selectedDate = dateStr;
|
||||
if (dateInput) dateInput.value = dateStr;
|
||||
if (dateInput) dateInput.value = lastSelectedDate || '';
|
||||
renderCalendar(calMonth, calYear);
|
||||
document.getElementById('bookingForm').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
document.getElementById('calPrev').addEventListener('click', () => {
|
||||
@@ -1024,7 +1043,7 @@
|
||||
// Initial load
|
||||
loadCalendar(calMonth, calYear);
|
||||
|
||||
// Keep calendar in sync when user types a date manually; validate against bookedSet
|
||||
// Keep calendar in sync when user types a date manually; validate against availability
|
||||
if (dateInput) {
|
||||
dateInput.addEventListener('change', async () => {
|
||||
const val = dateInput.value;
|
||||
@@ -1034,14 +1053,14 @@
|
||||
calMonth = m; calYear = y;
|
||||
await loadCalendar(calMonth, calYear);
|
||||
}
|
||||
const unavailMsg = document.getElementById('date-unavail-msg');
|
||||
if (bookedSet.has(val)) {
|
||||
dateInput.value = '';
|
||||
selectedDate = null;
|
||||
if (unavailMsg) { unavailMsg.style.display = 'block'; setTimeout(() => unavailMsg.style.display = 'none', 5000); }
|
||||
if (await hasRangeConflict(val, selectedPackage)) {
|
||||
dateInput.value = lastSelectedDate || '';
|
||||
showDateUnavail();
|
||||
} else {
|
||||
selectedDate = val;
|
||||
if (unavailMsg) unavailMsg.style.display = 'none';
|
||||
selectedDates.add(val);
|
||||
lastSelectedDate = val;
|
||||
const msg = document.getElementById('date-unavail-msg');
|
||||
if (msg) msg.style.display = 'none';
|
||||
}
|
||||
renderCalendar(calMonth, calYear);
|
||||
});
|
||||
@@ -1058,11 +1077,15 @@
|
||||
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);
|
||||
});
|
||||
if (selectedDates.size > 0) {
|
||||
Promise.all([...selectedDates].map(d => hasRangeConflict(d, selectedPackage).then(c => c ? d : null)))
|
||||
.then(conflicts => {
|
||||
conflicts.filter(Boolean).forEach(d => selectedDates.delete(d));
|
||||
if (!selectedDates.has(lastSelectedDate)) lastSelectedDate = [...selectedDates].sort().reverse()[0] || null;
|
||||
if (dateInput) dateInput.value = lastSelectedDate || '';
|
||||
if (conflicts.some(Boolean)) showDateUnavail();
|
||||
renderCalendar(calMonth, calYear);
|
||||
});
|
||||
} else { renderCalendar(calMonth, calYear); }
|
||||
const price = PACKAGE_PRICES[this.value];
|
||||
if (price && balLabel && balAmt) {
|
||||
@@ -1171,7 +1194,7 @@
|
||||
msg.style.display = 'block';
|
||||
form.reset();
|
||||
if (squareCard) { squareCard.destroy(); squareCard = null; }
|
||||
selectedDate = null;
|
||||
selectedDates.clear(); lastSelectedDate = null;
|
||||
if (dateInput) dateInput.min = new Date().toISOString().split('T')[0];
|
||||
btn.textContent = 'Request Sent!';
|
||||
btn.style.background = '#16a34a';
|
||||
|
||||
Reference in New Issue
Block a user