118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
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();
|
|
}
|
|
|
|
// ---- 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);
|