mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
refactor(workouts): render workout dates through useFormatters
The workout list, the workout detail header and the dashboard's recent workouts tile each rebuilt their own date formatter from the shared cache, with a private copy of the current-year check, and so ignored the reader's date-order preference. They now use useFormatters(), and the copies are gone. The timezone formatter guard now refuses any two-argument getDateTimeFormat call in src/components, so a component cannot build its own display formatter again. The medication card's day bucketing, the one other such call, reads userDayKey instead.
This commit is contained in:
1 parent
862361a224
commit
9fe6f98fdb
8 files changed
+121
-116
No files matched your search
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { makeFormatters } from "@/lib/format-locale";
|
||||
import type { WorkoutListEntry } from "@/hooks/use-workouts";
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,13 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({ isAuthenticated: true }),
|
||||
}));
|
||||
|
||||
// The day label follows the reader's date order through `useFormatters()`.
|
||||
const READER_FORMATTERS = makeFormatters("en", "UTC", "H24", "YMD");
|
||||
vi.mock("@/lib/i18n/context", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/i18n/context")>()),
|
||||
useFormatters: () => READER_FORMATTERS,
|
||||
}));
|
||||
|
||||
let mockResult: {
|
||||
data:
|
||||
| {
|
||||
@@ -147,5 +155,8 @@ describe("<RecentWorkoutsTile>", () => {
|
||||
expect(html).toContain("Cycling");
|
||||
expect(html).toContain("30m");
|
||||
expect(html).toContain("1h 0m");
|
||||
expect(html).toContain(
|
||||
READER_FORMATTERS.dateWithWeekdaySmart("2026-05-15T07:00:00Z"),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -4,12 +4,11 @@ import Link from "next/link";
|
||||
import { Activity } from "lucide-react";
|
||||
|
||||
import { formatDurationMinutes } from "@/lib/i18n/duration";
|
||||
import { useDisplayTimezone, useTranslations } from "@/lib/i18n/context";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import { useWorkouts, type WorkoutListEntry } from "@/hooks/use-workouts";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { TileHeader } from "@/components/insights/tile-header";
|
||||
import { getDateTimeFormat } from "@/lib/intl/formatter-cache";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { QueryErrorCard } from "@/components/ui/query-error-card";
|
||||
import { iconForSport } from "@/lib/workouts/sport-icons";
|
||||
@@ -32,16 +31,6 @@ import { iconForSport } from "@/lib/workouts/sport-icons";
|
||||
* round-trip per session.
|
||||
*/
|
||||
|
||||
function formatDayLabel(iso: string, locale: string, timeZone: string): string {
|
||||
const d = new Date(iso);
|
||||
return getDateTimeFormat(locale, {
|
||||
timeZone,
|
||||
weekday: "short",
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
}).format(d);
|
||||
}
|
||||
|
||||
function renderRow(workout: WorkoutListEntry, sportName: string) {
|
||||
const Icon = iconForSport(workout.sportType);
|
||||
return (
|
||||
@@ -61,8 +50,8 @@ function renderRow(workout: WorkoutListEntry, sportName: string) {
|
||||
}
|
||||
|
||||
export function RecentWorkoutsTile() {
|
||||
const { t, locale } = useTranslations();
|
||||
const timeZone = useDisplayTimezone();
|
||||
const { t } = useTranslations();
|
||||
const fmt = useFormatters();
|
||||
const { data, isError, error, refetch } = useWorkouts({ limit: 3 });
|
||||
|
||||
// A query's `isLoading` is not a hydration-safe branch: TanStack reports the
|
||||
@@ -148,7 +137,7 @@ export function RecentWorkoutsTile() {
|
||||
>
|
||||
{renderRow(workout, sportName)}
|
||||
<span className="text-muted-foreground shrink-0 text-xs tabular-nums">
|
||||
{formatDayLabel(workout.startedAt, locale, timeZone)}
|
||||
{fmt.dateWithWeekdaySmart(workout.startedAt)}
|
||||
</span>
|
||||
<span className="ml-1 shrink-0 text-xs font-medium tabular-nums">
|
||||
{formatDurationMinutes(
|
||||
|
||||
Reference in new issue
Block a user