// lazyflat — live time helpers. // Any element with [data-rel-utc=""] gets its text replaced every 5s // with a German relative-time string ("vor 3 min"). Elements with // [data-countdown-utc=""] show "in Xs" counting down each second. function fmtRelative(iso) { const ts = Date.parse(iso); if (!iso || Number.isNaN(ts)) return "—"; const diff = Math.max(0, Math.floor((Date.now() - ts) / 1000)); if (diff < 5) return "gerade eben"; if (diff < 60) return `vor ${diff} s`; if (diff < 3600) return `vor ${Math.floor(diff / 60)} min`; if (diff < 86400) return `vor ${Math.floor(diff / 3600)} h`; return `vor ${Math.floor(diff / 86400)} Tagen`; } function fmtCountdown(iso) { const ts = Date.parse(iso); if (!iso || Number.isNaN(ts)) return "—"; const secs = Math.floor((ts - Date.now()) / 1000); if (secs <= 0) return "aktualisiere…"; if (secs < 60) return `in ${secs} s`; if (secs < 3600) return `in ${Math.floor(secs / 60)} min`; return `in ${Math.floor(secs / 3600)} h`; } function updateRelativeTimes() { document.querySelectorAll("[data-rel-utc]").forEach((el) => { el.textContent = fmtRelative(el.dataset.relUtc); }); } function updateCountdowns() { document.querySelectorAll("[data-countdown-utc]").forEach((el) => { el.textContent = fmtCountdown(el.dataset.countdownUtc); }); } function tick() { updateRelativeTimes(); updateCountdowns(); } // Run immediately + on intervals. Also re-run after HTMX swaps so freshly // injected DOM gets formatted too. document.addEventListener("DOMContentLoaded", tick); document.body && document.body.addEventListener("htmx:afterSwap", tick); setInterval(updateCountdowns, 1000); setInterval(updateRelativeTimes, 5000); // Flat detail expand — lazily fetches /partials/wohnung/ into the sibling // .flat-detail container on first open, toggles visibility on subsequent clicks. // Event delegation survives HTMX swaps without re-binding on each poll. document.addEventListener("click", (ev) => { const btn = ev.target.closest(".flat-expand-btn"); if (!btn) return; const row = btn.closest(".flat-row"); if (!row) return; const pane = row.querySelector(".flat-detail"); if (!pane) return; if (btn.classList.contains("open")) { pane.style.display = "none"; btn.classList.remove("open"); return; } btn.classList.add("open"); pane.style.display = "block"; if (pane.dataset.loaded) return; pane.innerHTML = '
lädt…
'; const flatId = btn.dataset.flatId || ""; fetch("/partials/wohnung/" + encodeURIComponent(flatId), { headers: { "HX-Request": "true" } }) .then((r) => r.text()) .then((html) => { pane.innerHTML = html; pane.dataset.loaded = "1"; }) .catch(() => { pane.innerHTML = '
Detail konnte nicht geladen werden.
'; }); });