#26 - Loading animation + success message fix

This commit was merged in pull request #31.
This commit is contained in:
Martin Slachta
2026-06-22 11:20:28 +02:00
parent c754e18a82
commit 97ee8fc991
32 changed files with 597 additions and 175 deletions
+7 -4
View File
@@ -64,15 +64,18 @@ class RsvReservationSelector extends HTMLElement {
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.
// Set up the calendar while cal_el is detached so the initial set_date()
// change event fires on a disconnected node and cannot reach any listener.
this._calendar = RsvCalendarPicker.create(cal_el, this.inputName);
// Set timetable-id and the initial date before connecting so connectedCallback
// sees both attributes and renders the correct date on first attach.
const time_el = document.createElement('rsv-timeline');
time_el.setAttribute('timetable-id', this.timetableId);
time_el.date = this._calendar.date;
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'));
+5 -42
View File
@@ -1,3 +1,5 @@
import { render_slot_items } from '../templating/RsvSlotItems.js';
class RsvReservationSummary extends HTMLElement {
// ---- Lifecycle ----------------------------------------------------------
@@ -25,31 +27,6 @@ class RsvReservationSummary extends HTMLElement {
}
}
// ---- Public API ---------------------------------------------------------
// Detached, static copy of the current selection for the success message.
// Mirrors the live layout minus the interactive "clear all" control.
snapshot() {
const s = ReservairStrings.summary;
const list = this.querySelector('.rsv-summary-list');
const count = this.querySelector('.rsv-summary-count');
const price = this.querySelector('.rsv-summary-price');
const node = document.createElement('div');
node.className = 'rsv-summary rsv-summary-snapshot';
node.innerHTML = `
<div class="rsv-summary-header">
<span class="rsv-summary-title">${s.title}</span>
</div>
<ul class="rsv-summary-list">${list ? list.innerHTML : ''}</ul>
<div class="rsv-summary-footer">
<span class="rsv-summary-count">${count ? count.textContent : ''}</span>
<div class="rsv-summary-price">${price ? price.textContent : ''}</div>
</div>
`;
return node;
}
// ---- Private ------------------------------------------------------------
_build() {
@@ -74,7 +51,7 @@ class RsvReservationSummary extends HTMLElement {
_render() {
const all_slots = [...this._all_slots.values()].flatMap(({ slots, price_per_block }) =>
slots.map(s => ({ ...s, price_per_block }))
slots.map(s => ({ ...s, price: price_per_block }))
);
const n = all_slots.length;
@@ -92,23 +69,9 @@ class RsvReservationSummary extends HTMLElement {
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;
}));
list.replaceChildren(...render_slot_items(all_slots, locale, s.currency));
const total = all_slots.reduce((sum, slot) => sum + slot.price_per_block, 0);
const total = all_slots.reduce((sum, slot) => sum + slot.price, 0);
count_el.textContent = this._fmt_count(n);
price_el.textContent = total > 0 ? `${total} ${s.currency}` : '';
}
+20
View File
@@ -53,6 +53,24 @@ class RsvTimeline extends HTMLElement {
}
}
_skeleton() {
const frag = document.createDocumentFragment();
const label = document.createElement('div');
label.className = 'rsv-slots-label rsv-slots-skeleton rsv-slots-skeleton-label';
frag.appendChild(label);
for (let i = 0; i < 4; i++) {
const row = document.createElement('div');
row.className = 'rsv-slots-slot rsv-slots-skeleton';
const time = document.createElement('span');
time.className = 'rsv-slots-slot-time rsv-slots-skeleton rsv-slots-skeleton-text';
const badge = document.createElement('span');
badge.className = 'rsv-slots-slot-badge rsv-slots-skeleton rsv-slots-skeleton-badge';
row.append(time, badge);
frag.appendChild(row);
}
return frag;
}
async _render() {
// Version guard: discard renders that were superseded by a newer call.
const v = ++this._version;
@@ -63,6 +81,8 @@ class RsvTimeline extends HTMLElement {
return;
}
this.replaceChildren(this._skeleton());
try {
const occupancy = await RsvTimetableService.get_availability_for_date(this.timetableId, this.date);
if (v !== this._version) return;