feat: human + relative dates; fix language/theme toggle stale-context
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
This commit is contained in:
+21
-7
@@ -99,10 +99,17 @@ function LanguageToggle({
|
||||
user: SessionUser;
|
||||
setUser: (u: SessionUser | null) => void;
|
||||
}) {
|
||||
// The ACTIVE language is i18n's own state, not the router-context `user` — the
|
||||
// latter is captured at route-resolution time and does NOT re-render when we call
|
||||
// setUser, so reading `user.language` here goes stale after the first switch (the
|
||||
// highlight froze and the equality guard blocked switching back until a refresh).
|
||||
// useTranslation() subscribes to i18n's languageChanged, so this stays live.
|
||||
const { i18n } = useTranslation();
|
||||
const active = i18n.language as Lang;
|
||||
async function pick(lang: Lang) {
|
||||
if (lang === user.language) return;
|
||||
setLanguage(lang); // instant UI
|
||||
setUser({ ...user, language: lang });
|
||||
if (lang === active) return;
|
||||
setLanguage(lang); // instant UI (fires i18n languageChanged → re-render)
|
||||
setUser({ ...user, language: lang }); // keep context eventually-consistent + persisted state
|
||||
try {
|
||||
await setLanguagePref(lang); // persist
|
||||
} catch {
|
||||
@@ -117,7 +124,7 @@ function LanguageToggle({
|
||||
type="button"
|
||||
onClick={() => pick(l)}
|
||||
className={`rounded-term px-1.5 py-0.5 ${
|
||||
user.language === l ? "bg-term-panel-2 text-term-amber" : "text-term-muted hover:text-term-text"
|
||||
active === l ? "bg-term-panel-2 text-term-amber" : "text-term-muted hover:text-term-text"
|
||||
}`}
|
||||
>
|
||||
{l}
|
||||
@@ -138,10 +145,17 @@ function ThemeToggle({
|
||||
setUser: (u: SessionUser | null) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
// Local state for the ACTIVE theme — same reason as LanguageToggle: the router
|
||||
// context `user` doesn't re-render on setUser, so reading `user.theme` here froze
|
||||
// the highlight after one switch and blocked toggling back until a refresh. Seed
|
||||
// from the prop; update optimistically on pick. App's effect keeps the DOM in sync
|
||||
// with the persisted user on (re)login.
|
||||
const [active, setActive] = useState<Theme>(user.theme);
|
||||
async function pick(theme: Theme) {
|
||||
if (theme === user.theme) return;
|
||||
if (theme === active) return;
|
||||
setActive(theme);
|
||||
applyTheme(theme); // instant UI
|
||||
setUser({ ...user, theme });
|
||||
setUser({ ...user, theme }); // keep context eventually-consistent + persisted state
|
||||
try {
|
||||
await setThemePref(theme); // persist
|
||||
} catch {
|
||||
@@ -156,7 +170,7 @@ function ThemeToggle({
|
||||
type="button"
|
||||
onClick={() => pick(th)}
|
||||
className={`rounded-term px-1.5 py-0.5 ${
|
||||
user.theme === th ? "bg-term-panel-2 text-term-amber" : "text-term-muted hover:text-term-text"
|
||||
active === th ? "bg-term-panel-2 text-term-amber" : "text-term-muted hover:text-term-text"
|
||||
}`}
|
||||
>
|
||||
{t(th === "dark" ? "common.themeDark" : "common.themeLight")}
|
||||
|
||||
Reference in New Issue
Block a user