101 lines
3.4 KiB
JavaScript
101 lines
3.4 KiB
JavaScript
|
|
class RsvReservationSummary extends HTMLElement {
|
|||
|
|
|
|||
|
|
// ---- Lifecycle ----------------------------------------------------------
|
|||
|
|
|
|||
|
|
connectedCallback() {
|
|||
|
|
this._all_slots = new Map(); // name → { slots, price_per_block }
|
|||
|
|
this._form = this.closest('form');
|
|||
|
|
this._build();
|
|||
|
|
|
|||
|
|
if (this._form) {
|
|||
|
|
this._handler = e => {
|
|||
|
|
this._all_slots.set(e.detail.name, {
|
|||
|
|
slots: e.detail.slots,
|
|||
|
|
price_per_block: e.detail.price_per_block,
|
|||
|
|
});
|
|||
|
|
this._render();
|
|||
|
|
};
|
|||
|
|
this._form.addEventListener('rsv:slots-changed', this._handler);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
disconnectedCallback() {
|
|||
|
|
if (this._form && this._handler) {
|
|||
|
|
this._form.removeEventListener('rsv:slots-changed', this._handler);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---- Private ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
_build() {
|
|||
|
|
const s = ReservairStrings.summary;
|
|||
|
|
this.innerHTML = `
|
|||
|
|
<div class="rsv-summary-header">
|
|||
|
|
<span class="rsv-summary-title">${s.title}</span>
|
|||
|
|
<button type="button" class="rsv-summary-clear">${s.clear_all}</button>
|
|||
|
|
</div>
|
|||
|
|
<ul class="rsv-summary-list"></ul>
|
|||
|
|
<div class="rsv-summary-footer">
|
|||
|
|
<span class="rsv-summary-count"></span>
|
|||
|
|
<div class="rsv-summary-price"></div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
this.hidden = true;
|
|||
|
|
|
|||
|
|
this.querySelector('.rsv-summary-clear').addEventListener('click', () => {
|
|||
|
|
this._form?.querySelectorAll('rsv-reservation-selector').forEach(sel => sel.clear());
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_render() {
|
|||
|
|
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');
|
|||
|
|
const count_el = this.querySelector('.rsv-summary-count');
|
|||
|
|
const price_el = this.querySelector('.rsv-summary-price');
|
|||
|
|
const s = ReservairStrings.summary;
|
|||
|
|
const locale = navigator.language;
|
|||
|
|
|
|||
|
|
this.hidden = n === 0;
|
|||
|
|
if (n === 0) {
|
|||
|
|
list.replaceChildren();
|
|||
|
|
count_el.textContent = '';
|
|||
|
|
price_el.textContent = '';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const time_opts = { hour: '2-digit', minute: '2-digit' };
|
|||
|
|
list.replaceChildren(...all_slots.map(slot => {
|
|||
|
|
const start = new Date(slot.start_utc);
|
|||
|
|
const end = new Date(slot.end_utc);
|
|||
|
|
const li = document.createElement('li');
|
|||
|
|
li.className = 'rsv-summary-item';
|
|||
|
|
li.innerHTML = `
|
|||
|
|
<div class="rsv-summary-item-info">
|
|||
|
|
<span class="rsv-summary-item-date">${start.toLocaleDateString(locale, { weekday: 'long', day: 'numeric', month: 'long' })}</span>
|
|||
|
|
<span class="rsv-summary-item-time">${start.toLocaleTimeString(locale, time_opts)} – ${end.toLocaleTimeString(locale, time_opts)}</span>
|
|||
|
|
</div>
|
|||
|
|
${slot.price_per_block > 0 ? `<span class="rsv-summary-item-price">${slot.price_per_block} ${s.currency}</span>` : ''}
|
|||
|
|
`;
|
|||
|
|
return li;
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const total = all_slots.reduce((sum, slot) => sum + slot.price_per_block, 0);
|
|||
|
|
count_el.textContent = this._fmt_count(n);
|
|||
|
|
price_el.textContent = total > 0 ? `${total} ${s.currency}` : '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_fmt_count(n) {
|
|||
|
|
const s = ReservairStrings.summary;
|
|||
|
|
if (n === 1) return s.count_one;
|
|||
|
|
if (n < 5) return s.count_few.replace('%d', n);
|
|||
|
|
return s.count_many.replace('%d', n);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
customElements.define('rsv-reservation-summary', RsvReservationSummary);
|