Files
Reservair/assets/js/elements/RsvTimeline.js
T

144 lines
4.6 KiB
JavaScript
Raw Normal View History

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();
}
// ---- 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;
if(occupancy.length === 0) {
this.replaceChildren(this._notice(s.no_blocks));
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(',', '');
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();
if (left === 0) cell.classList.add('rsv-slots-slot-full');
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);