@@ -179,6 +179,14 @@ export const RsvCalendarPicker = (() => {
|
||||
new InputEvent('change', { bubbles: true, cancelable: true, composed: true })
|
||||
);
|
||||
},
|
||||
|
||||
// Re-check the radio for the current date. The date lives in JS state, so
|
||||
// this restores the visual selection after a native form.reset() clears it.
|
||||
reselect() {
|
||||
if (this.date === null) return;
|
||||
const radio = this.body.querySelector(`input[id="${this.date.toISOString()}"]`);
|
||||
if (radio) radio.checked = true;
|
||||
},
|
||||
};
|
||||
|
||||
container.classList.add('rsv-calendar');
|
||||
|
||||
@@ -37,6 +37,16 @@ class RsvReservationSelector extends HTMLElement {
|
||||
this.querySelectorAll('.rsv-slots-slot-selected').forEach(s => s.classList.remove('rsv-slots-slot-selected'));
|
||||
this._slots = [];
|
||||
this._commit();
|
||||
// _commit clears only the slots; keep the picked date selected, re-asserting
|
||||
// it in case a surrounding form.reset() just unchecked the calendar radio.
|
||||
this._calendar?.reselect();
|
||||
}
|
||||
|
||||
// Reset for a new reservation: drop the local selection (keeping the date) and
|
||||
// reload availability so slots booked by the previous submission show as full.
|
||||
reset() {
|
||||
this.clear();
|
||||
this.querySelector('rsv-timeline')?.refresh();
|
||||
}
|
||||
|
||||
// ---- Private ------------------------------------------------------------
|
||||
|
||||
@@ -25,6 +25,31 @@ class RsvReservationSummary extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Public API ---------------------------------------------------------
|
||||
|
||||
// Detached, static copy of the current selection for the success message.
|
||||
// Mirrors the live layout minus the interactive "clear all" control.
|
||||
snapshot() {
|
||||
const s = ReservairStrings.summary;
|
||||
const list = this.querySelector('.rsv-summary-list');
|
||||
const count = this.querySelector('.rsv-summary-count');
|
||||
const price = this.querySelector('.rsv-summary-price');
|
||||
|
||||
const node = document.createElement('div');
|
||||
node.className = 'rsv-summary rsv-summary-snapshot';
|
||||
node.innerHTML = `
|
||||
<div class="rsv-summary-header">
|
||||
<span class="rsv-summary-title">${s.title}</span>
|
||||
</div>
|
||||
<ul class="rsv-summary-list">${list ? list.innerHTML : ''}</ul>
|
||||
<div class="rsv-summary-footer">
|
||||
<span class="rsv-summary-count">${count ? count.textContent : ''}</span>
|
||||
<div class="rsv-summary-price">${price ? price.textContent : ''}</div>
|
||||
</div>
|
||||
`;
|
||||
return node;
|
||||
}
|
||||
|
||||
// ---- Private ------------------------------------------------------------
|
||||
|
||||
_build() {
|
||||
@@ -51,7 +76,6 @@ class RsvReservationSummary extends HTMLElement {
|
||||
const all_slots = [...this._all_slots.values()].flatMap(({ slots, price_per_block }) =>
|
||||
slots.map(s => ({ ...s, price_per_block }))
|
||||
);
|
||||
console.log(all_slots);
|
||||
|
||||
const n = all_slots.length;
|
||||
const list = this.querySelector('.rsv-summary-list');
|
||||
|
||||
@@ -35,6 +35,14 @@ class RsvTimeline extends HTMLElement {
|
||||
this._render();
|
||||
}
|
||||
|
||||
// ---- Public API ---------------------------------------------------------
|
||||
|
||||
// Re-fetch availability for the current date, e.g. after a booking occupied
|
||||
// some slots. The date is unchanged, so attributeChangedCallback won't fire.
|
||||
refresh() {
|
||||
this._render();
|
||||
}
|
||||
|
||||
// ---- Private ------------------------------------------------------------
|
||||
|
||||
_on_click(event) {
|
||||
@@ -59,10 +67,6 @@ class RsvTimeline extends HTMLElement {
|
||||
const occupancy = await RsvTimetableService.get_availability_for_date(this.timetableId, this.date);
|
||||
if (v !== this._version) return;
|
||||
|
||||
if(occupancy.length === 0) {
|
||||
this.replaceChildren(this._notice(s.no_blocks));
|
||||
return;
|
||||
}
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.classList.add('rsv-slots-label');
|
||||
@@ -70,6 +74,11 @@ class RsvTimeline extends HTMLElement {
|
||||
weekday: 'long', day: 'numeric', month: 'long',
|
||||
}).replace(',', '');
|
||||
|
||||
if(occupancy.length === 0) {
|
||||
this.replaceChildren(header, this._notice(s.no_blocks));
|
||||
return;
|
||||
}
|
||||
|
||||
const blocks = [];
|
||||
|
||||
for (const { from_minutes, to_minutes, block_size_in_minutes, occupancy: block_occ } of occupancy) {
|
||||
@@ -107,7 +116,7 @@ class RsvTimeline extends HTMLElement {
|
||||
cell.classList.add('rsv-slots-slot', 'rsv-slots-slot-available');
|
||||
cell.dataset.start_utc = from.toISOString();
|
||||
cell.dataset.end_utc = to.toISOString();
|
||||
if (left === 0) cell.classList.add('rsv-slots-slot-full');
|
||||
if (left <= 0) cell.classList.add('rsv-slots-slot-full');
|
||||
|
||||
const time_el = document.createElement('span');
|
||||
time_el.classList.add('rsv-slots-slot-time');
|
||||
|
||||
@@ -56,24 +56,14 @@ export const RsvFormSender = {
|
||||
svg.appendChild(path);
|
||||
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'success-icon';
|
||||
icon.className = 'rsv-success-icon';
|
||||
icon.appendChild(svg);
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'success-title';
|
||||
title.textContent = s.success_title;
|
||||
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'success-msg';
|
||||
subtitle.textContent = s.success_subtitle;
|
||||
|
||||
const reset_btn = document.createElement('button');
|
||||
reset_btn.className = 'reset-btn';
|
||||
reset_btn.textContent = s.new_reservation;
|
||||
const body = this.build_success_body(form, s);
|
||||
|
||||
const state = document.createElement('div');
|
||||
state.className = 'success-state';
|
||||
state.append(icon, title, subtitle, reset_btn);
|
||||
state.className = 'rsv-success-state';
|
||||
state.append(icon, body);
|
||||
|
||||
const msg = document.createElement('div');
|
||||
msg.appendChild(state);
|
||||
@@ -81,12 +71,50 @@ export const RsvFormSender = {
|
||||
existing.forEach(child => child.style.display = 'none');
|
||||
wrapper.appendChild(msg);
|
||||
|
||||
reset_btn.addEventListener('click', () => {
|
||||
// The form catches every data-rsv-reset button in the card and links it to
|
||||
// its cleanup — so new buttons just need the marker, no wiring here.
|
||||
const reset = () => {
|
||||
msg.remove();
|
||||
form.reset();
|
||||
// Native reset leaves custom controls untouched; reset the reservation
|
||||
// selectors so their slots, hidden inputs and the summary clear, the date
|
||||
// stays selected and availability reloads with the just-booked slots.
|
||||
form.querySelectorAll('rsv-reservation-selector').forEach(sel => sel.reset());
|
||||
this.clear_feedback(form);
|
||||
existing.forEach(child => child.style.display = '');
|
||||
});
|
||||
};
|
||||
state.querySelectorAll('[data-rsv-reset]').forEach(btn => btn.addEventListener('click', reset));
|
||||
},
|
||||
|
||||
// Body of the success card. Uses the admin-configured template when the form
|
||||
// ships one, filling the .rsv-success-summary placeholder (expanded server-side
|
||||
// from <reservation-summary>) with a snapshot of the selected slots; otherwise
|
||||
// falls back to the default text.
|
||||
build_success_body(form, strings) {
|
||||
const tpl = form.parentElement?.querySelector('template.rsv-form-success');
|
||||
|
||||
if (!tpl) {
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'rsv-success-msg';
|
||||
subtitle.textContent = strings.success_subtitle;
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.className = 'rsv-success-msg';
|
||||
body.appendChild(tpl.content.cloneNode(true));
|
||||
|
||||
const placeholder = body.querySelector('.rsv-success-summary');
|
||||
if (placeholder) {
|
||||
const summary = form.querySelector('rsv-reservation-summary');
|
||||
if (summary && typeof summary.snapshot === 'function') {
|
||||
placeholder.replaceWith(summary.snapshot());
|
||||
} else {
|
||||
placeholder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
},
|
||||
|
||||
set_loading(form, is_loading) {
|
||||
|
||||
Reference in New Issue
Block a user