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)}`,
),