feat(web): UI component layer + dark-theme reskin

The TRM tokens were good but every screen hand-rolled inputs and buttons as
bare outlines on near-black panels, so fields, cards and buttons were
visually indistinguishable. Add a component layer (.input/.select/.textarea
as recessed slots, .btn family with a FILLED primary, .card scaffolding) and
adopt it across the booth/shift/login/tariff/site screens — several of which
were still light-theme inline styles dropped on a dark background.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-19 10:07:39 +02:00
parent ef0ecadff9
commit 808fb26ab6
8 changed files with 371 additions and 203 deletions
+36 -29
View File
@@ -62,44 +62,50 @@ export function SiteSettings({ canEdit }: { canEdit: boolean }) {
}
return (
<section style={{ marginTop: "1.5rem", padding: "0.75rem 1rem", border: "1px solid #ddd", borderRadius: 6, maxWidth: 460 }}>
<strong>{t("site.occupancy")}</strong>{" "}
{occ == null ? (
"…"
) : (
<>
<span style={{ fontWeight: 600 }}>{occ.count}</span>
{occ.capacity != null ? ` / ${occ.capacity}` : ` ${t("site.noCapacitySet")}`}
{occ.capacity != null && (
<span style={{ color: "#666" }}> · {occ.free} {t("site.free")}</span>
)}
{occ.full && <span style={{ color: "crimson", marginLeft: "0.5rem", fontWeight: 600 }}>{t("site.full")}</span>}{" "}
<button type="button" onClick={reload} style={{ marginLeft: "0.5rem" }}>↻</button>
</>
)}
<section className="card mt-6 max-w-md p-4">
<div className="flex flex-wrap items-center gap-1.5 text-[13px]">
<strong className="uppercase tracking-wider text-term-muted">{t("site.occupancy")}</strong>
{occ == null ? (
<span className="text-term-muted">…</span>
) : (
<>
<span className="text-h5 font-semibold tabular-nums text-term-text">{occ.count}</span>
<span className="tabular-nums text-term-muted">
{occ.capacity != null ? `/ ${occ.capacity}` : t("site.noCapacitySet")}
</span>
{occ.capacity != null && (
<span className="tabular-nums text-term-muted">· {occ.free} {t("site.free")}</span>
)}
{occ.full && <span className="font-semibold text-term-red">{t("site.full")}</span>}
<button type="button" className="btn btn-ghost btn-sm" onClick={reload}>↻</button>
</>
)}
</div>
{canEdit && (
<div style={{ marginTop: "0.6rem", display: "grid", gap: "0.5rem" }}>
<label>
{t("site.capacityLabel")}{" "}
<input value={capInput} onChange={(e) => setCapInput(e.target.value)} style={{ width: 80 }} placeholder={t("site.capacityPlaceholder")} />
</label>
<label style={{ display: "flex", alignItems: "center", gap: "0.4rem" }}>
<div className="mt-4 grid gap-3">
<div className="field">
<span className="label">{t("site.capacityLabel")}</span>
<input className="input w-32" value={capInput} onChange={(e) => setCapInput(e.target.value)} placeholder={t("site.capacityPlaceholder")} />
</div>
<label className="flex items-center gap-2 text-[12px] text-term-text">
<input
type="checkbox"
className="accent-term-amber"
checked={exitVoucherDefault}
onChange={(e) => setExitVoucherDefault(e.target.checked)}
/>
{t("site.printExitDefault")}
<span style={{ color: "#888", fontSize: "0.8rem" }}>{t("site.printExitHint")}</span>
<span className="hint">{t("site.printExitHint")}</span>
</label>
<div style={{ borderTop: "1px solid #eee", paddingTop: "0.5rem", color: "#666", fontSize: "0.85rem" }}>
<div className="border-t border-term-border pt-3 text-[11px] uppercase tracking-wider text-term-muted">
{t("site.parkDetails")}
</div>
{META_FIELDS.map(({ key, labelKey, phKey, multiline }) => (
<label key={key} style={{ display: "flex", flexDirection: "column", fontSize: "0.85rem" }}>
{t(labelKey)}
<div key={key} className="field">
<span className="label">{t(labelKey)}</span>
{multiline ? (
<textarea
className="textarea"
value={meta[key] ?? ""}
onChange={(e) => setMeta((m) => ({ ...m, [key]: e.target.value }))}
rows={2}
@@ -107,16 +113,17 @@ export function SiteSettings({ canEdit }: { canEdit: boolean }) {
/>
) : (
<input
className="input"
value={meta[key] ?? ""}
onChange={(e) => setMeta((m) => ({ ...m, [key]: e.target.value }))}
placeholder={phKey ? t(phKey) : undefined}
/>
)}
</label>
</div>
))}
<div>
<button type="button" onClick={save}>{t("site.save")}</button>
{msg && <span style={{ marginLeft: "0.5rem", color: "#555" }}>{msg}</span>}
<div className="flex items-center gap-3">
<button type="button" className="btn btn-primary btn-sm" onClick={save}>{t("site.save")}</button>
{msg && <span className="text-[12px] text-term-muted">{msg}</span>}
</div>
</div>
)}