Files
Reservair/assets/js/elements/RsvReservationSelector.js
T
Martin Slachta 7b3d9f0ece (#3) - templating
2026-06-14 07:16:13 +02:00

130 lines
4.1 KiB
JavaScript

import { RsvCalendarPicker } from './RsvCalendar.js';
class RsvReservationSelector extends HTMLElement {
static get observedAttributes() {
return ['timetable-id', 'name', 'price-per-block'];
}
// ---- Attribute accessors ------------------------------------------------
get timetableId() { return parseInt(this.getAttribute('timetable-id')); }
get inputName() { return this.getAttribute('name') ?? 'reservation'; }
get pricePerBlock() { return parseFloat(this.getAttribute('price-per-block')) || 0; }
// ---- Lifecycle ----------------------------------------------------------
connectedCallback() {
this._slots = [];
this.classList.add('rsv-timetable-selector');
this._build();
}
attributeChangedCallback(_attr, oldVal, newVal) {
if (oldVal === null || oldVal === newVal || !this.isConnected) return;
this._build();
}
// ---- Public API ---------------------------------------------------------
getValue() {
return {
timetable_id: this.timetableId,
timetable_reservations: this._slots.map(s => s.start_utc),
};
}
clear() {
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 ------------------------------------------------------------
_build() {
this._slots = [];
this.replaceChildren();
const tid = document.createElement('input');
tid.type = 'hidden';
tid.name = `${this.inputName}.timetable_id`;
tid.value = this.timetableId;
this.appendChild(tid);
const cal_el = document.createElement('div');
cal_el.classList.add('rsv-calendar');
// Create rsv-timeline with timetable-id set before appending so
// connectedCallback sees the correct attribute on first render.
const time_el = document.createElement('rsv-timeline');
time_el.setAttribute('timetable-id', this.timetableId);
this.append(cal_el, time_el);
this._calendar = RsvCalendarPicker.create(cal_el, this.inputName);
// Date change: clear selection, then push new date to the timeline element.
cal_el.addEventListener('change', () => {
this.querySelectorAll('.rsv-slots-slot-selected').forEach(s => s.classList.remove('rsv-slots-slot-selected'));
this._slots = [];
this._commit();
time_el.date = this._calendar.date;
});
// Slot toggle: read selected slots from timeline, then commit.
time_el.addEventListener('input', e => {
e.stopPropagation();
this._slots = Array.from(time_el.querySelectorAll('.rsv-slots-slot-selected')).map(s => ({
start_utc: s.dataset.start_utc,
end_utc: s.dataset.end_utc,
}));
this._commit();
});
this._commit();
}
_commit() {
const name = this.inputName;
this.querySelectorAll(`input[name="${name}.timetable_reservations[]"]`).forEach(i => i.remove());
let json = [];
this._slots.forEach(slot => {
const inp = document.createElement('input');
inp.type = 'hidden';
inp.name = `${name}.timetable_reservations[]`;
inp.value = slot.start_utc;
this.appendChild(inp);
json.push(slot.start_utc);
});
this.value = JSON.stringify({
"timetable_id": this.timetableId,
"timetable_reservations": json
});
this.dispatchEvent(new CustomEvent('rsv:slots-changed', {
bubbles: true,
detail: {
name,
slots: this._slots,
price_per_block: this.pricePerBlock,
value: this.getValue(),
},
}));
}
}
customElements.define('rsv-reservation-selector', RsvReservationSelector);