feat(web): one date standard across the UI — "25 Qer 14:30"
Dates were a mix: catalog-formatted "25 Qershor 20:01" where screens used
formatRelativeDateTime, and browser-locale "7/6/2026, 9:34 AM" in ~20
places that called raw toLocaleString/-Date-/-Time-String. Unified:
- common.monthsShort in both catalogs (Jan/Shk/…/Qer/Korr/…/Dhj);
formatDate ("25 Qer", year only when not current), formatDateTime
("25 Qer 14:30", optional seconds), formatClock ("HH:mm", 24h) in
lib/format.ts. formatRelativeDateTime keeps Sot/Dje and switches its
older-dates branch to the same short months.
- Every raw toLocale* DATE call swept: shifts X-report line, plan
effective dates, sub version labels, drawer today feed, snapshot
tooltips, device footer checkedAt, event-detail timestamp (keeps
seconds — chain evidence), tariff composer active-since + version
sidebar. Number toLocaleString (thousand separators) untouched.
The catalogs in this commit also carry the keys for the two follow-up
commits (fee breakdown, composer increment labels).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchDeviceStatus, type DeviceStatus } from "../api.js";
|
||||
import { formatClock } from "../lib/format.js";
|
||||
import { qk } from "../lib/query.js";
|
||||
import { useLiveStore } from "../lib/live-store.js";
|
||||
|
||||
@@ -184,7 +185,7 @@ export function DeviceFooter() {
|
||||
</div>
|
||||
{d.detail && <div className="mt-0.5 break-words text-[0.6875rem] text-term-muted">{d.detail}</div>}
|
||||
<div className="mt-0.5 text-[0.625rem] tabular-nums text-term-muted/70">
|
||||
{t("devices.checkedAt", { time: new Date(d.checkedAt).toLocaleTimeString() })}
|
||||
{t("devices.checkedAt", { time: formatClock(d.checkedAt) })}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateTime } from "../lib/format.js";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchSnapshots, snapshotImageUrl, type PlateRead } from "../api.js";
|
||||
|
||||
@@ -52,7 +53,7 @@ export function SnapshotStrip({ identity }: { identity: string }) {
|
||||
key={`${p.plate}-${p.direction}-${i}`}
|
||||
className="inline-flex items-center gap-1.5 rounded-term border border-term-cyan/40 bg-term-cyan/10 px-2 py-0.5 text-[0.6875rem]"
|
||||
title={`${dirLabel(p.direction)}${p.region ? ` · ${p.region}` : ""}${
|
||||
p.at ? ` · ${new Date(p.at).toLocaleString()}` : ""
|
||||
p.at ? ` · ${formatDateTime(p.at, t)}` : ""
|
||||
}`}
|
||||
>
|
||||
<span className="text-[0.5625rem] uppercase tracking-wider text-term-muted">{t("pay.plate")}</span>
|
||||
@@ -72,7 +73,7 @@ export function SnapshotStrip({ identity }: { identity: string }) {
|
||||
type="button"
|
||||
onClick={() => setZoom(s.id)}
|
||||
className="group flex flex-col items-center gap-1 rounded-term border border-term-border bg-term-panel-2 p-1 hover:border-term-amber"
|
||||
title={`${dirLabel(s.direction)} · ${new Date(s.capturedAt).toLocaleString()}`}
|
||||
title={`${dirLabel(s.direction)} · ${formatDateTime(s.capturedAt, t)}`}
|
||||
>
|
||||
<img
|
||||
src={snapshotImageUrl(s.id)}
|
||||
@@ -97,7 +98,7 @@ export function SnapshotStrip({ identity }: { identity: string }) {
|
||||
<div
|
||||
key={`fail-${f.direction ?? "both"}-${i}`}
|
||||
className="flex h-[6.75rem] w-28 flex-col items-center justify-center gap-1 rounded-term border border-dashed border-term-amber/60 bg-term-amber/5 p-1 text-center"
|
||||
title={`${dirLabel(f.direction)} · ${f.error}${f.occurredAt ? ` · ${new Date(f.occurredAt).toLocaleString()}` : ""}`}
|
||||
title={`${dirLabel(f.direction)} · ${f.error}${f.occurredAt ? ` · ${formatDateTime(f.occurredAt, t)}` : ""}`}
|
||||
>
|
||||
<span className="text-lg leading-none text-term-amber">⚠</span>
|
||||
<span className="text-[0.5625rem] uppercase tracking-wider text-term-amber">{dirLabel(f.direction)}</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type ReactNode } from "react";
|
||||
import { type LedgerEvent } from "../api.js";
|
||||
import { formatMoney } from "../lib/format.js";
|
||||
import { formatMoney, formatDateTime } from "../lib/format.js";
|
||||
import { renderReason } from "../lib/reason.js";
|
||||
import { Modal } from "./Modal.js";
|
||||
import { SnapshotStrip } from "./SnapshotStrip.js";
|
||||
@@ -226,7 +226,7 @@ export function EventDetailModal({ e, onClose }: { e: LedgerEvent; onClose: () =
|
||||
|
||||
{/* Humanized fields — labelled rows, not raw JSON. Only what applies renders. */}
|
||||
<div>
|
||||
<DetailRow label={t("booth.edTime")}>{new Date(e.occurredAt).toLocaleString()}</DetailRow>
|
||||
<DetailRow label={t("booth.edTime")}>{formatDateTime(e.occurredAt, t, { seconds: true })}</DetailRow>
|
||||
<DetailRow label={t("booth.edIndex")}>#{e.index}</DetailRow>
|
||||
{e.direction && <DetailRow label={t("booth.edDirection")}>{e.direction}</DetailRow>}
|
||||
{e.source && <DetailRow label={t("booth.edSource")}>{e.source}</DetailRow>}
|
||||
|
||||
Reference in New Issue
Block a user