Move the audited barrier re-open out of the inline Active-Sessions row button
and into the modal, and turn the modal's dead-ends into useful views.
- Remove the inline per-row "Open barrier" button. Clicking a row opens the
modal, which carries the action.
- Modal recognizes a closed-within-grace transient (found && !open &&
withinGrace) and shows the session view + Open barrier instead of dead-ending
on "already closed" — the exact case (paid, barrier unconfirmed) that needs a
re-pulse. Server reopenBarrier guard unchanged.
- Active-Sessions rows show a live grace-remaining countdown badge
(exited - M:SS, 1s tick off graceExpiresAt) via new formatCountdown helper.
- Settled sessions show the ACTUAL sum paid (new SessionLookup.paidMinor,
summed across payment events) instead of a flat "PAID" badge.
- A fully-closed (grace-expired) session's modal is no longer a dead-end: it
shows a read-only review view (figures + paid amount + entry/exit snapshot
strip) for dispute/audit review, with no pay/exit/open controls.
i18n sq+en parity kept; web build/lint/test green.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Adds an admin Reports screen (/setup/reports, gated report:read) — an
on-demand dashboard over the signed event log.
Server (ledger-first): GET /api/reports/summary?from&to&bucket aggregates
in one call — entry/exit counts + all money summed straight from
ledger_events (same source the shift Z-report reconciles, so totals tie
out to the drawer); revenue split into ticket / subscription-sale /
out-of-window mirrors the Z-report. Duration stats come from the sessions
cache (flagged). All bucketing is in the SITE timezone (siteTz). A .csv
export of the per-bucket series. reports.ts + routes/reports.ts.
Web: Reports.tsx — date-range presets (today/7d/30d/90d), hour/day/month
grain, KPI cards, entry/exit line, revenue bar + cash/card split,
revenue-mix pie, peak-hours histogram, numeric breakdown, subscription
stats. Charts via Recharts (MIT), lazy-loaded into its own chunk
(~111KB gz) so the booth bundle is untouched. New Setup tab + nav + i18n
(sq + en parity). asc() exported from @parking/db; formatMinutes helper.
Tests: reports.test.ts (10) pin the sums, tz bucketing, money split,
duration stats, subscription counts. server 90/90; build+lint 14/14.
Wiki: reporting-analytics.md "Built v1" section + log entry.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Dates were raw ISO on printed slips and time-only in the UI (a session from
two days ago showed just "10:48"). Make them human and day-relative. Also fix
a latent toggle bug surfaced while testing.
Dates:
- Printed tickets/receipts/subscription cards now show "19 Qershor 2026
10:48:25" (Albanian month, 24h with seconds) instead of YYYY-MM-DD HH:MM.
stamp() exported as formatStampSq so the shift Z-report shares it.
- Shift Z-report is now Albanian (Operatori/Nga/Deri/Para në dorë/Arka…),
was English-only with ISO dates.
- Web sessions/logs/history show relative days: "Sot 10:48" / "Dje 17:33" /
"17 Qershor 10:48" via formatRelativeDateTime(). Month names come from the
i18n catalog (common.months), NOT Intl — the appliance browser's ICU lacks
Albanian locale data and Intl silently falls back to English month names.
Toggle fix:
- The language + theme toggles read the active value from the TanStack Router
context `user`, which is captured at route-resolution time and does not
re-render on setUser. After one switch the highlight froze and the equality
guard blocked switching back until a page refresh. Drive them off live state
instead: language from i18n.language (useTranslation subscribes to
languageChanged), theme from local useState. (Bug dated to 040c0ff.)
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V