diff --git a/.changelog/unreleased/9997-referral-chart-hourly-labels.md b/.changelog/unreleased/9997-referral-chart-hourly-labels.md new file mode 100644 index 00000000..e6dd9bb1 --- /dev/null +++ b/.changelog/unreleased/9997-referral-chart-hourly-labels.md @@ -0,0 +1,8 @@ +--- +type: fixed +area: referrals +pr: 9997 +breaking: false +--- + +Referral charts now display distinct hourly labels for the 24-hour view without duplicate chart keys or repeated dates. diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx index 4fb7d007..339d5432 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx @@ -1,9 +1,39 @@ -import { afterEach, describe, expect, it } from "vitest" +import { afterEach, describe, expect, it, vi } from "vitest" import { cleanup, render, screen } from "@testing-library/react" import { StatChartCard } from "./stat-chart-card" describe("StatChartCard", () => { - afterEach(cleanup) + afterEach(() => { + cleanup() + vi.useRealTimers() + }) + + it.each(["24h", "7d", "30d", "90d", "total"] as const)( + "renders five distinct labels for the %s period", + (period) => { + vi.useFakeTimers() + vi.setSystemTime(new Date("2026-10-15T12:30:00")) + + const { container } = render( + , + ) + const labels = Array.from(container.querySelectorAll('text[y="100"]')).map( + (element) => element.textContent, + ) + + expect(labels).toHaveLength(5) + expect(new Set(labels).size).toBe(labels.length) + if (period === "24h") { + expect(labels).toEqual( + expect.arrayContaining([expect.stringMatching(/^\d{2}:\d{2}$/)]), + ) + expect(labels.every((label) => /^\d{2}:\d{2}$/.test(label))).toBe(true) + } else { + expect(labels.every((label) => /^\d{2}\/\d{2}$/.test(label))).toBe(true) + } + }, + ) + it("renders the title", () => { render( , diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx index 490f42b1..bfbb49e6 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx @@ -5,8 +5,11 @@ import { formatUsd } from "@/shared/lib/format" function xAxisLabels(period: TimePeriod): Array { const now = new Date() + const pad = (value: number) => String(value).padStart(2, "0") const fmt = (d: Date) => - `${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}` + period === "24h" + ? `${pad(d.getHours())}:${pad(d.getMinutes())}` + : `${pad(d.getMonth() + 1)}/${pad(d.getDate())}` const msAgo = (ms: number) => new Date(now.getTime() - ms) const day = 86_400_000 @@ -18,7 +21,9 @@ function xAxisLabels(period: TimePeriod): Array { total: 365 * day, } - const total = ranges[period] + // Exact 24-hour endpoints share a clock time. Exclude the first minute + // so five HH:mm labels are meaningful and distinct without adding a date. + const total = period === "24h" ? ranges[period] - 60_000 : ranges[period] return Array.from({ length: 5 }, (_, i) => fmt(msAgo(total - (i / 4) * total))) } @@ -118,7 +123,7 @@ export function StatChartCard({ title, tooltip, value, period, accent = "blue" } const x = 28 + (i / (labels.length - 1)) * 368 return (