mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
fix(ui): print every displayed decimal in the reader's notation
toFixed() prints a full stop in every language and a locale-less toLocaleString() follows the browser, so a German reader saw 37.5 next to 70,5 on the same screen. Fever in the illness log, the cycle phase crosstab, custom metric values, nutrient day totals, the mood chart and heatmap tooltips, the mood takeaways, the elevation chart axis, the BP 30-day average, the host metrics clock and the encryption row counts now go through useFormatters(). The new healthlog/no-unlocalised-decimal rule refuses toFixed(), toPrecision(), a locale-less toLocaleString() or Intl constructor, and a quotient rendered straight into text under src/components and src/app. Rounding through Number(...) stays legal; SVG path data and number-input values carry a reasoned next-line directive.
This commit is contained in:
1 parent
56daa57350
commit
537bad4212
24 files changed
+596
-60
No files matched your search
@@ -229,11 +229,11 @@ export function EncryptionSection() {
|
||||
/>
|
||||
<Stat
|
||||
label={t("admin.section.encryption.totalRows")}
|
||||
value={s.totalRows.toLocaleString()}
|
||||
value={fmt.integer(s.totalRows)}
|
||||
/>
|
||||
<Stat
|
||||
label={t("admin.section.encryption.staleRows")}
|
||||
value={s.staleRows.toLocaleString()}
|
||||
value={fmt.integer(s.staleRows)}
|
||||
/>
|
||||
</div>
|
||||
{/* "Safe to drop the legacy key" needs a legacy key to drop. With a
|
||||
@@ -414,6 +414,7 @@ export function ColumnCoverage({
|
||||
initiallyOpen?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
const [open, setOpen] = useState(initiallyOpen);
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const withRows = columns.filter((c) => c.total > 0);
|
||||
@@ -484,16 +485,12 @@ export function ColumnCoverage({
|
||||
<td className="py-2 pr-3 font-mono text-xs">
|
||||
{c.model}.{c.field}
|
||||
</td>
|
||||
<td className="py-2 pr-3">
|
||||
{c.total.toLocaleString()}
|
||||
</td>
|
||||
<td className="py-2 pr-3">
|
||||
{active.toLocaleString()}
|
||||
</td>
|
||||
<td className="py-2 pr-3">{fmt.integer(c.total)}</td>
|
||||
<td className="py-2 pr-3">{fmt.integer(active)}</td>
|
||||
<td className="py-2">
|
||||
{stale > 0 ? (
|
||||
<Badge variant="secondary">
|
||||
{stale.toLocaleString()}
|
||||
{fmt.integer(stale)}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-muted-foreground">0</span>
|
||||
|
||||
@@ -54,7 +54,8 @@ export function GeneralSettingsSection() {
|
||||
: "";
|
||||
const storedQuotaGb =
|
||||
settings?.documentQuotaBytes != null
|
||||
? String(Math.round((settings.documentQuotaBytes / GIB) * 10) / 10)
|
||||
? // eslint-disable-next-line healthlog/no-unlocalised-decimal -- value of a type="number" input, which only takes a full stop
|
||||
String(Math.round((settings.documentQuotaBytes / GIB) * 10) / 10)
|
||||
: "";
|
||||
|
||||
const commitMaxFile = () => {
|
||||
|
||||
@@ -47,6 +47,7 @@ import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { readStoredTimezone } from "@/lib/timezone-mirror";
|
||||
import { resolveIntlLocale } from "@/lib/format-locale";
|
||||
|
||||
interface HostMetricsApiSample {
|
||||
capturedAt: string;
|
||||
@@ -89,7 +90,10 @@ const COLOR_DISK = "var(--dracula-purple)";
|
||||
* + the absolute peak BPS used to scale `diskBusyPercent`. Kept as a
|
||||
* separate export so the test suite doesn't need to render Recharts.
|
||||
*/
|
||||
export function buildChartRows(samples: HostMetricsApiSample[]): {
|
||||
export function buildChartRows(
|
||||
samples: HostMetricsApiSample[],
|
||||
intlLocale?: string,
|
||||
): {
|
||||
rows: ChartRow[];
|
||||
peakDiskBps: number;
|
||||
hasDiskData: boolean;
|
||||
@@ -104,7 +108,7 @@ export function buildChartRows(samples: HostMetricsApiSample[]): {
|
||||
// samples), and an operator reading their own instance should see the same
|
||||
// clock here as on every other timestamp in the admin area — a spike at
|
||||
// "14:20" must not be 14:20 here and 15:20 two screens away.
|
||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||
const formatter = new Intl.DateTimeFormat(intlLocale ?? "en-US", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
@@ -141,7 +145,7 @@ export function buildChartRows(samples: HostMetricsApiSample[]): {
|
||||
}
|
||||
|
||||
export function HostMetricsChart() {
|
||||
const { t } = useTranslations();
|
||||
const { t, locale } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
|
||||
const { data, isLoading, isError, error, refetch } = useQuery({
|
||||
@@ -156,8 +160,8 @@ export function HostMetricsChart() {
|
||||
});
|
||||
|
||||
const { rows, peakDiskBps, hasDiskData } = useMemo(
|
||||
() => buildChartRows(data?.samples ?? []),
|
||||
[data?.samples],
|
||||
() => buildChartRows(data?.samples ?? [], resolveIntlLocale(locale)),
|
||||
[data?.samples, locale],
|
||||
);
|
||||
|
||||
// Total physical memory (latest sample) — lets the tooltip pair the
|
||||
|
||||
@@ -162,7 +162,8 @@ export function UserManagementSection() {
|
||||
setEditQuotaGb(
|
||||
u.documentQuotaBytes === null
|
||||
? ""
|
||||
: String(Math.round((u.documentQuotaBytes / 1_073_741_824) * 10) / 10),
|
||||
: // eslint-disable-next-line healthlog/no-unlocalised-decimal -- value of a type="number" input, which only takes a full stop
|
||||
String(Math.round((u.documentQuotaBytes / 1_073_741_824) * 10) / 10),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user