ui batch: admin tab, time filter, count-up, chevron sync, tidy
1. New /admin route with sub-tabs (Protokoll, Benutzer) for admins. Top nav: "Protokoll" dropped, "Admin" added right of Einstellungen. /logs and /einstellungen/benutzer issue 301 redirects to the new paths. Benutzer is no longer part of Einstellungen sub-nav. 2. User_filters.max_age_hours (migration v6) — new dropdown (1–10 h / beliebig) under Einstellungen → Filter; Wohnungen list drops flats older than the cutoff by discovered_at. 3. Header shows "aktualisiert vor X s" instead of a countdown. Template emits data-counter-up-utc with last_alert_heartbeat; app.js ticks up each second. When a scrape runs, the heartbeat updates and the HTMX swap resets the counter naturally. 4. Chevron state synced after HTMX swaps: panes preserved via hx-preserve keep the user's open/closed state, and the sibling button's .open class is re-applied by syncFlatExpandState() on afterSwap — previously a scroll-triggered poll would flip the chevron back to closed while the pane stayed open. 5. "Final absenden" footer removed from the profile page (functionality is unchanged, the switch still sits atop Wohnungen). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
83db8cd902
commit
da180bd7c7
10 changed files with 175 additions and 85 deletions
|
|
@ -1,7 +1,8 @@
|
|||
// lazyflat — live time helpers.
|
||||
// Any element with [data-rel-utc="<iso>"] gets its text replaced every 5s
|
||||
// with a German relative-time string ("vor 3 min"). Elements with
|
||||
// [data-countdown-utc="<iso>"] show "in Xs" counting down each second.
|
||||
// [data-rel-utc="<iso>"] → "vor 3 min" (relative-past, updated every 5 s)
|
||||
// [data-counter-up-utc="<iso>"] → "vor X s" counting up each second (used for
|
||||
// "aktualisiert vor X s"; resets when the
|
||||
// server ships a newer timestamp in the swap)
|
||||
|
||||
function fmtRelative(iso) {
|
||||
const ts = Date.parse(iso);
|
||||
|
|
@ -14,14 +15,13 @@ function fmtRelative(iso) {
|
|||
return `vor ${Math.floor(diff / 86400)} Tagen`;
|
||||
}
|
||||
|
||||
function fmtCountdown(iso) {
|
||||
function fmtCountUp(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`;
|
||||
const diff = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
||||
if (diff < 60) return `vor ${diff} s`;
|
||||
if (diff < 3600) return `vor ${Math.floor(diff / 60)} min`;
|
||||
return `vor ${Math.floor(diff / 3600)} h`;
|
||||
}
|
||||
|
||||
function updateRelativeTimes() {
|
||||
|
|
@ -30,22 +30,38 @@ function updateRelativeTimes() {
|
|||
});
|
||||
}
|
||||
|
||||
function updateCountdowns() {
|
||||
document.querySelectorAll("[data-countdown-utc]").forEach((el) => {
|
||||
el.textContent = fmtCountdown(el.dataset.countdownUtc);
|
||||
function updateCounterUps() {
|
||||
document.querySelectorAll("[data-counter-up-utc]").forEach((el) => {
|
||||
el.textContent = fmtCountUp(el.dataset.counterUpUtc);
|
||||
});
|
||||
}
|
||||
|
||||
// After the HTMX swap rebuilds the list, the open-chevron class is gone even
|
||||
// though the corresponding .flat-detail pane is preserved. Re-sync by looking
|
||||
// at pane visibility.
|
||||
function syncFlatExpandState() {
|
||||
document.querySelectorAll(".flat-detail").forEach((pane) => {
|
||||
const row = pane.closest(".flat-row");
|
||||
if (!row) return;
|
||||
const btn = row.querySelector(".flat-expand-btn");
|
||||
if (!btn) return;
|
||||
const open = pane.dataset.loaded === "1" && pane.style.display !== "none";
|
||||
btn.classList.toggle("open", open);
|
||||
});
|
||||
}
|
||||
|
||||
function tick() {
|
||||
updateRelativeTimes();
|
||||
updateCountdowns();
|
||||
updateCounterUps();
|
||||
}
|
||||
|
||||
// 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);
|
||||
document.addEventListener("DOMContentLoaded", syncFlatExpandState);
|
||||
if (document.body) {
|
||||
document.body.addEventListener("htmx:afterSwap", tick);
|
||||
document.body.addEventListener("htmx:afterSwap", syncFlatExpandState);
|
||||
}
|
||||
setInterval(updateCounterUps, 1000);
|
||||
setInterval(updateRelativeTimes, 5000);
|
||||
|
||||
// Flat detail expand — lazily fetches /partials/wohnung/<id> into the sibling
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue