From 364f7aacb771bd7008ddb33624fcb262197f652f Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 9 Apr 2026 00:44:31 -0700 Subject: [PATCH] feat(stats): expose 365d trends range in dashboard UI Add '365d' to the client TrendRange union, the TimeRange hook type, and the DateRangeSelector segmented control so users can select a 365-day window in the trends dashboard. --- .../components/trends/DateRangeSelector.tsx | 2 +- stats/src/hooks/useTrends.ts | 2 +- stats/src/lib/api-client.test.ts | 49 +++++++++++++++++++ stats/src/lib/api-client.ts | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/stats/src/components/trends/DateRangeSelector.tsx b/stats/src/components/trends/DateRangeSelector.tsx index 7d7352f3..c46c3979 100644 --- a/stats/src/components/trends/DateRangeSelector.tsx +++ b/stats/src/components/trends/DateRangeSelector.tsx @@ -53,7 +53,7 @@ export function DateRangeSelector({
(r === 'all' ? 'All' : r)} diff --git a/stats/src/hooks/useTrends.ts b/stats/src/hooks/useTrends.ts index 4f65a01c..907a2f28 100644 --- a/stats/src/hooks/useTrends.ts +++ b/stats/src/hooks/useTrends.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import { getStatsClient } from './useStatsApi'; import type { TrendsDashboardData } from '../types/stats'; -export type TimeRange = '7d' | '30d' | '90d' | 'all'; +export type TimeRange = '7d' | '30d' | '90d' | '365d' | 'all'; export type GroupBy = 'day' | 'month'; export function useTrends(range: TimeRange, groupBy: GroupBy) { diff --git a/stats/src/lib/api-client.test.ts b/stats/src/lib/api-client.test.ts index 85c794ee..c26c15c2 100644 --- a/stats/src/lib/api-client.test.ts +++ b/stats/src/lib/api-client.test.ts @@ -115,6 +115,55 @@ test('getTrendsDashboard requests the chart-ready trends endpoint with range and } }); +test('getTrendsDashboard accepts 365d range and builds correct URL', async () => { + const originalFetch = globalThis.fetch; + let seenUrl = ''; + globalThis.fetch = (async (input: RequestInfo | URL) => { + seenUrl = String(input); + return new Response( + JSON.stringify({ + activity: { watchTime: [], cards: [], words: [], sessions: [] }, + progress: { + watchTime: [], + sessions: [], + words: [], + newWords: [], + cards: [], + episodes: [], + lookups: [], + }, + ratios: { lookupsPerHundred: [] }, + animePerDay: { + episodes: [], + watchTime: [], + cards: [], + words: [], + lookups: [], + lookupsPerHundred: [], + }, + animeCumulative: { + watchTime: [], + episodes: [], + cards: [], + words: [], + }, + patterns: { + watchTimeByDayOfWeek: [], + watchTimeByHour: [], + }, + }), + { status: 200, headers: { 'Content-Type': 'application/json' } }, + ); + }) as typeof globalThis.fetch; + + try { + await apiClient.getTrendsDashboard('365d', 'day'); + assert.equal(seenUrl, `${BASE_URL}/api/stats/trends/dashboard?range=365d&groupBy=day`); + } finally { + globalThis.fetch = originalFetch; + } +}); + test('getSessionEvents can request only specific event types', async () => { const originalFetch = globalThis.fetch; let seenUrl = ''; diff --git a/stats/src/lib/api-client.ts b/stats/src/lib/api-client.ts index e4576e21..083d05f4 100644 --- a/stats/src/lib/api-client.ts +++ b/stats/src/lib/api-client.ts @@ -116,7 +116,7 @@ export const apiClient = { fetchJson(`/api/stats/trends/new-anime-per-day?limit=${limit}`), getWatchTimePerAnime: (limit = 90) => fetchJson(`/api/stats/trends/watch-time-per-anime?limit=${limit}`), - getTrendsDashboard: (range: '7d' | '30d' | '90d' | 'all', groupBy: 'day' | 'month') => + getTrendsDashboard: (range: '7d' | '30d' | '90d' | '365d' | 'all', groupBy: 'day' | 'month') => fetchJson( `/api/stats/trends/dashboard?range=${encodeURIComponent(range)}&groupBy=${encodeURIComponent(groupBy)}`, ),