fix(web): booth UI/UX pass — readable font scaling + booth layout/report clarity
A round of operator-facing fixes on the booth screen, shift views, and the
font-scale control. (Follows the font-scale feature in f706726, which used CSS
`zoom` — reverted here for the rem approach below.)
Font scaling (the A−/A+ control now actually works without breaking layout):
- The control scaled via CSS `zoom`, which also scaled viewport-locked containers
(h-screen frame, max-h-[90vh] modals) so at 130% modal headers/footers were
pushed off-screen. Reworked to scale TEXT only: converted every `text-[Npx]`
font utility to rem across the web app (~230 sites in 25 files + the
.label/.hint/.btn component classes + body in index.css; 16px root, so 100% is
visually identical), and applyFontScale now sets the ROOT font-size. vh/h-screen
layout stays put, so chrome never clips; tall content scrolls its own container.
Verified at 130%: text 12px→15.6px while the frame stayed viewport-height.
Live feed (event rows):
- Plate, badges and reason now flow inline after the identity and wrap only when
the row runs out of width — no more forced second line when there's empty space.
- Dropped the redundant TARGË via-badge (the plate chip already conveys it).
- Removed the Direction filter group (Hyrje/Dalje) — it duplicated the entry/exit
options already in the Type filter.
Active sessions:
- Rebuilt as a real table (Ticket/subscriber · Plate · Entry · Elapsed) so columns
align and long values (subscriber names, ticket ids) no longer truncate.
- Dropped the status column (an unpaid transient is normal; a subscriber shows ★ +
name; overstay keeps a row tint). Removed the now-redundant status filter; only
the Transient/Subscriber filter remains. Plate is now searchable (uses s.plate).
Shift report (close-shift modal + Shift History + printed Z-report slip):
- Removed the confusing `shitje` (subscription-sales) sub-line — Abonime is the
total; only the out-of-window part is broken out. subscriptionSalesMinor stays in
the signed payload (audit data), just not displayed/printed.
- Show the inherited opening cash ("Arka fillestare") above the expected drawer, so
opening + cash-taken = expected reads clearly. Money values no longer line-wrap.
Subscription edit modal:
- Fixed the 2-col grid alignment: a lone "only one version" cell was shifting every
following row by one column — it now emits a full label+value pair.
Removed orphaned i18n keys (fStatus*, fDir*, srcSubSales) from sq+en (parity kept).
Full workspace build/lint/test green.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -226,7 +226,7 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-term-border bg-term-panel-2 px-4 py-2">
|
||||
<Dialog.Title className="m-0 text-[12px] font-semibold uppercase tracking-wider text-term-amber">
|
||||
<Dialog.Title className="m-0 text-[0.75rem] font-semibold uppercase tracking-wider text-term-amber">
|
||||
{isSubscription
|
||||
? `${t("pay.subscription")} · ${s?.subscriptionHolder ?? t("subs.unnamed")}`
|
||||
: `${t("pay.ticket")} ${identity}`}
|
||||
@@ -244,19 +244,19 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
<div className="rounded-term border border-term-amber bg-term-amber/5 px-3 py-2">
|
||||
{blockedByOther ? (
|
||||
<>
|
||||
<div className="text-[12px] font-semibold uppercase tracking-wider text-term-amber">
|
||||
<div className="text-[0.75rem] font-semibold uppercase tracking-wider text-term-amber">
|
||||
{t("shift.gateOtherTitle")}
|
||||
</div>
|
||||
<div className="mt-1 text-[12px] text-term-text">
|
||||
<div className="mt-1 text-[0.75rem] text-term-text">
|
||||
{t("shift.gateOtherBody", { operator: heldBy ?? "?" })}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-[12px] font-semibold uppercase tracking-wider text-term-amber">
|
||||
<div className="text-[0.75rem] font-semibold uppercase tracking-wider text-term-amber">
|
||||
{t("shift.gateTitle")}
|
||||
</div>
|
||||
<div className="mt-1 text-[12px] text-term-text">{t("shift.gateBody")}</div>
|
||||
<div className="mt-1 text-[0.75rem] text-term-text">{t("shift.gateBody")}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenShift}
|
||||
@@ -321,7 +321,7 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
out-of-window window charge; then show that amount. For an overstay the
|
||||
amount is the TOP-UP delta, not the whole stay. */}
|
||||
<div className="flex items-end justify-between rounded-term bg-term-panel-2 px-3 py-2">
|
||||
<span className="text-[11px] uppercase tracking-wider text-term-muted">
|
||||
<span className="text-[0.6875rem] uppercase tracking-wider text-term-muted">
|
||||
{subWindowDue ? t("pay.windowCharge") : isSubscription ? t("pay.plan") : isOverstay ? t("pay.topUp") : t("pay.total")}
|
||||
</span>
|
||||
<span className="text-3xl font-bold text-term-cyan">
|
||||
@@ -341,22 +341,22 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
gate; once paid, prompt the operator to open the barrier; a prepaid
|
||||
subscriber sees the assist explanation only after revealing it. */}
|
||||
{subWindowDue && !windowPaid ? (
|
||||
<div className="rounded-term border border-term-amber/40 bg-term-amber/5 px-3 py-2 text-[12px] text-term-text">
|
||||
<div className="rounded-term border border-term-amber/40 bg-term-amber/5 px-3 py-2 text-[0.75rem] text-term-text">
|
||||
{t("pay.windowChargeHint")}
|
||||
</div>
|
||||
) : isSubscription && windowPaid ? (
|
||||
<div className="rounded-term border border-term-green/40 bg-term-green/5 px-3 py-2 text-[12px] text-term-text">
|
||||
<div className="rounded-term border border-term-green/40 bg-term-green/5 px-3 py-2 text-[0.75rem] text-term-text">
|
||||
{t("pay.windowPaidHint")}
|
||||
</div>
|
||||
) : isSubscription && assistRevealed ? (
|
||||
<div className="rounded-term border border-term-cyan/40 bg-term-cyan/5 px-3 py-2 text-[12px] text-term-text">
|
||||
<div className="rounded-term border border-term-cyan/40 bg-term-cyan/5 px-3 py-2 text-[0.75rem] text-term-text">
|
||||
{t("pay.subAssistHint")}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* For an overstay, explain why a top-up is required (no free exit). */}
|
||||
{isOverstay && (
|
||||
<div className="rounded-term border border-term-red/40 bg-term-red/5 px-3 py-2 text-[12px] text-term-text">
|
||||
<div className="rounded-term border border-term-red/40 bg-term-red/5 px-3 py-2 text-[0.75rem] text-term-text">
|
||||
{t("pay.overstayHint")}
|
||||
</div>
|
||||
)}
|
||||
@@ -368,7 +368,7 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
subscriber window charge that's still unpaid). */}
|
||||
{phase !== "done" && canPay && !(subWindowDue && windowPaid) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] uppercase tracking-wider text-term-muted">{t("pay.tender")}</span>
|
||||
<span className="text-[0.6875rem] uppercase tracking-wider text-term-muted">{t("pay.tender")}</span>
|
||||
{(["cash", "card"] as const).map((tn) => (
|
||||
<button
|
||||
key={tn}
|
||||
@@ -384,7 +384,7 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
|
||||
{/* Voucher checkbox (transient only; a subscriber doesn't self-exit). */}
|
||||
{phase !== "done" && !isSubscription && (
|
||||
<label className="flex items-center gap-2 text-[12px]">
|
||||
<label className="flex items-center gap-2 text-[0.75rem]">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-term-amber"
|
||||
@@ -401,10 +401,10 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
signed `void` event — the entry is never edited. */}
|
||||
{voiding && phase !== "done" && (
|
||||
<div className="rounded-term border border-term-amber/50 bg-term-amber/5 px-3 py-2">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wider text-term-amber">
|
||||
<div className="text-[0.6875rem] font-semibold uppercase tracking-wider text-term-amber">
|
||||
{t("pay.cancelTicketTitle")}
|
||||
</div>
|
||||
<div className="mt-1 text-[12px] text-term-text">{t("pay.cancelTicketHint")}</div>
|
||||
<div className="mt-1 text-[0.75rem] text-term-text">{t("pay.cancelTicketHint")}</div>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{(["misprint", "test", "wrongVehicle"] as const).map((k) => (
|
||||
<button
|
||||
@@ -560,7 +560,7 @@ export function BoothPayModal({ identity, onClose }: { identity: string; onClose
|
||||
function Row({ label, value, valueClass = "" }: { label: string; value: string; valueClass?: string }) {
|
||||
return (
|
||||
<div className="flex items-baseline justify-between">
|
||||
<span className="text-[11px] uppercase tracking-wider text-term-muted">{label}</span>
|
||||
<span className="text-[0.6875rem] uppercase tracking-wider text-term-muted">{label}</span>
|
||||
<span className={`text-sm ${valueClass}`}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user