2026-06-12 10:57:23 +00:00
|
|
|
|
import { RsvTimetableService } from '../services/RsvTimetableService.js';
|
|
|
|
|
|
|
2026-06-11 19:03:29 +02:00
|
|
|
|
class RsvTimeline extends HTMLElement {
|
|
|
|
|
|
static get observedAttributes() {
|
|
|
|
|
|
return ['timetable-id', 'date'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Attribute accessors ------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
get timetableId() { return parseInt(this.getAttribute('timetable-id')); }
|
|
|
|
|
|
|
|
|
|
|
|
get date() {
|
|
|
|
|
|
const attr = this.getAttribute('date');
|
|
|
|
|
|
// Parse as local midnight so setHours() in block rendering stays in local time.
|
|
|
|
|
|
return attr ? new Date(attr + 'T12:00:00') : new Date();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
set date(value) {
|
|
|
|
|
|
const d = new Date(value);
|
|
|
|
|
|
const str = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
|
|
|
|
this.setAttribute('date', str);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Lifecycle ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
connectedCallback() {
|
|
|
|
|
|
this._version = 0;
|
|
|
|
|
|
this.classList.add('rsv-slots-list');
|
|
|
|
|
|
this.addEventListener('click', this._on_click.bind(this));
|
|
|
|
|
|
this._render();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
attributeChangedCallback(_attr, oldVal, newVal) {
|
|
|
|
|
|
if (oldVal === newVal || !this.isConnected) return;
|
|
|
|
|
|
this._render();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-14 07:16:13 +02:00
|
|
|
|
// ---- 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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 19:03:29 +02:00
|
|
|
|
// ---- Private ------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
_on_click(event) {
|
|
|
|
|
|
const slot = event.target.closest('.rsv-slots-slot');
|
|
|
|
|
|
if (slot && !slot.classList.contains('rsv-slots-slot-full')) {
|
|
|
|
|
|
slot.classList.toggle('rsv-slots-slot-selected');
|
|
|
|
|
|
slot.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async _render() {
|
|
|
|
|
|
// Version guard: discard renders that were superseded by a newer call.
|
|
|
|
|
|
const v = ++this._version;
|
|
|
|
|
|
const s = ReservairStrings.timeline;
|
|
|
|
|
|
|
|
|
|
|
|
if (this.timetableId === null) {
|
|
|
|
|
|
this.replaceChildren(this._notice(s.not_reservable));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const occupancy = await RsvTimetableService.get_availability_for_date(this.timetableId, this.date);
|
|
|
|
|
|
if (v !== this._version) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const header = document.createElement('div');
|
|
|
|
|
|
header.classList.add('rsv-slots-label');
|
|
|
|
|
|
header.textContent = this.date.toLocaleDateString(navigator.language, {
|
|
|
|
|
|
weekday: 'long', day: 'numeric', month: 'long',
|
|
|
|
|
|
}).replace(',', '');
|
|
|
|
|
|
|
2026-06-14 07:16:13 +02:00
|
|
|
|
if(occupancy.length === 0) {
|
|
|
|
|
|
this.replaceChildren(header, this._notice(s.no_blocks));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-11 19:03:29 +02:00
|
|
|
|
const blocks = [];
|
|
|
|
|
|
|
|
|
|
|
|
for (const { from_minutes, to_minutes, block_size_in_minutes, occupancy: block_occ } of occupancy) {
|
|
|
|
|
|
if (from_minutes === to_minutes || block_occ.length === 0) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const from_block = parseInt(from_minutes) / block_size_in_minutes;
|
|
|
|
|
|
|
|
|
|
|
|
const time_slots = block_occ.map((occ, i) =>
|
|
|
|
|
|
this._block(this.date, occ, block_size_in_minutes, from_block + i)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const time_slot_group = document.createElement('div');
|
|
|
|
|
|
time_slot_group.classList.add('rsv-slots-group');
|
|
|
|
|
|
time_slot_group.replaceChildren(...time_slots);
|
|
|
|
|
|
blocks.push(time_slot_group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.replaceChildren(header, ...blocks);
|
|
|
|
|
|
} catch (_e) {
|
|
|
|
|
|
if (v !== this._version) return;
|
|
|
|
|
|
this.replaceChildren(this._notice(s.no_blocks));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_block(date, left, block_size, idx) {
|
|
|
|
|
|
const from = new Date(date);
|
|
|
|
|
|
from.setHours(0, idx * block_size, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const to = new Date(from);
|
|
|
|
|
|
to.setMinutes(to.getMinutes() + block_size);
|
|
|
|
|
|
|
|
|
|
|
|
const cell = document.createElement('div');
|
|
|
|
|
|
cell.classList.add('rsv-slots-slot', 'rsv-slots-slot-available');
|
|
|
|
|
|
cell.dataset.start_utc = from.toISOString();
|
|
|
|
|
|
cell.dataset.end_utc = to.toISOString();
|
2026-06-14 07:16:13 +02:00
|
|
|
|
if (left <= 0) cell.classList.add('rsv-slots-slot-full');
|
2026-06-11 19:03:29 +02:00
|
|
|
|
|
|
|
|
|
|
const time_el = document.createElement('span');
|
|
|
|
|
|
time_el.classList.add('rsv-slots-slot-time');
|
|
|
|
|
|
time_el.textContent = `${this._fmt(from)} – ${this._fmt(to)}`;
|
|
|
|
|
|
|
|
|
|
|
|
const badge = document.createElement('span');
|
|
|
|
|
|
badge.classList.add('rsv-slots-slot-badge');
|
|
|
|
|
|
const remaining_seats = left;
|
|
|
|
|
|
|
|
|
|
|
|
if (remaining_seats > 0) badge.classList.add('rsv-slots-slot-badge-available');
|
|
|
|
|
|
|
|
|
|
|
|
if (remaining_seats === 1) badge.textContent = `${remaining_seats} místo`;
|
|
|
|
|
|
else if (remaining_seats >= 2 && remaining_seats <= 4) badge.textContent = `${remaining_seats} místa`;
|
|
|
|
|
|
else badge.textContent = `${remaining_seats} míst`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cell.append(time_el, badge);
|
|
|
|
|
|
return cell;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_notice(text) {
|
|
|
|
|
|
const p = document.createElement('p');
|
|
|
|
|
|
p.classList.add('rsv-slots-notice');
|
|
|
|
|
|
p.textContent = text;
|
|
|
|
|
|
return p;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_fmt(dt) {
|
|
|
|
|
|
return dt.getHours() + ':' + String(dt.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
customElements.define('rsv-timeline', RsvTimeline);
|