From 08f28aeb9357d9710bc88f3de7319c488b863ac6 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 29 May 2026 23:28:15 +0200 Subject: [PATCH] localise chart + make daily data reachable on touch/keyboard --- src/components/dashboard/usage-display.tsx | 164 +++++++++++++++++---- src/messages/de.json | 10 +- src/messages/en.json | 10 +- src/messages/fr.json | 10 +- src/messages/it.json | 10 +- 5 files changed, 168 insertions(+), 36 deletions(-) diff --git a/src/components/dashboard/usage-display.tsx b/src/components/dashboard/usage-display.tsx index 9cbb081..4e4f040 100644 --- a/src/components/dashboard/usage-display.tsx +++ b/src/components/dashboard/usage-display.tsx @@ -1,6 +1,6 @@ "use client"; -import { useTranslations } from "next-intl"; +import { useTranslations, useLocale } from "next-intl"; import { useEffect, useState, useCallback } from "react"; import { BudgetEditableCard } from "@/components/dashboard/budget-editable-card"; @@ -84,42 +84,149 @@ function formatMonth(month: string, locale: string): string { } function UsageChart({ data }: { data: DailyUsage[] }) { + const t = useTranslations("usage"); + const locale = useLocale(); + // Which day's detail is shown in the readout. Defaults to the most + // recent day; hover (mouse), tap (touch) or focus (keyboard) all + // update it. The previous version put per-day numbers only in SVG + // hover tooltips, which are unreachable on touch devices and + // invisible to keyboard users — this readout fixes both. + const [selected, setSelected] = useState<number | null>(null); + if (!data.length) return null; - const maxTokens = Math.max(...data.map((d) => d.inputTokens + d.outputTokens), 1); + + const maxTokens = Math.max( + ...data.map((d) => d.inputTokens + d.outputTokens), + 1 + ); const barW = Math.max(4, Math.floor(600 / data.length) - 2); const h = 120; + const activeIndex = selected ?? data.length - 1; + const active = data[activeIndex]; + + const dayLabel = (iso: string) => { + const [y, m, dd] = iso.split("-").map(Number); + return new Date(y, m - 1, dd).toLocaleDateString(locale, { + month: "short", + day: "numeric", + }); + }; + + const barAria = (d: DailyUsage) => + `${dayLabel(d.date)}: ${fmt(d.inputTokens)} ${t("inputTokens")}, ${fmt( + d.outputTokens + )} ${t("outputTokens")}, ${chf(d.spend)}`; + return ( - <div className="overflow-x-auto"> - <svg - viewBox={`0 0 ${Math.max(data.length * (barW + 2), 600)} ${h + 24}`} - className="w-full h-36" - preserveAspectRatio="xMinYMid meet" - > - {data.map((d, i) => { - const total = d.inputTokens + d.outputTokens; - const totalH = (total / maxTokens) * h; - const inputH = (d.inputTokens / maxTokens) * h; - const x = i * (barW + 2); - return ( - <g key={d.date}> - <title>{d.date}: {fmt(d.inputTokens)} in / {fmt(d.outputTokens)} out — {chf(d.spend)} - - - {i % 7 === 0 && ( - {d.date.slice(8)} - )} - - ); - })} - +
+ {/* Readout — the touch/keyboard-accessible equivalent of the old + hover-only tooltip. Always reflects the active day. */} +
+ + {dayLabel(active.date)} + + + {fmt(active.inputTokens)} {t("inputTokens")} + + + {fmt(active.outputTokens)} {t("outputTokens")} + + {chf(active.spend)} +
+ +
+ + {data.map((d, i) => { + const total = d.inputTokens + d.outputTokens; + const totalH = (total / maxTokens) * h; + const inputH = (d.inputTokens / maxTokens) * h; + const x = i * (barW + 2); + const isActive = i === activeIndex; + return ( + setSelected(i)} + onMouseEnter={() => setSelected(i)} + onFocus={() => setSelected(i)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setSelected(i); + } + }} + > + {barAria(d)} + {/* Full-height transparent hit area so thin bars stay + easy to tap on touch screens. */} + + + + {isActive && ( + + )} + {i % 7 === 0 && ( + + {d.date.slice(8)} + + )} + + ); + })} + +
- Input + {" "} + {t("legendInput")} - Output + {" "} + {t("legendOutput")} + {t("chartHint")}
); @@ -161,6 +268,7 @@ export function UsageDisplay({ canEditBudget?: boolean; }) { const t = useTranslations("usage"); + const locale = useLocale(); const [month, setMonth] = useState(getCurrentMonth); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -202,7 +310,7 @@ export function UsageDisplay({ ← - {formatMonth(month, "en")} + {formatMonth(month, locale)}