feat(documents): add fetchDensity helper and /documents/+page.ts (#385)
The density data is fetched only on tablet/desktop (sm:+ breakpoint) and when ?view=calendar is not set — mobile users and the future calendar view (#386) skip the request entirely. Lives in +page.ts (client-side) so the matchMedia gate can run in the browser; +page.server.ts continues to handle the document search. Non-ok responses and network failures degrade to an empty bucket list rather than throwing, so the document list keeps rendering. 5 unit tests cover the gating + graceful degradation paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type MonthBucket = components['schemas']['MonthBucket'];
|
||||
type DocumentDensityResult = components['schemas']['DocumentDensityResult'];
|
||||
|
||||
export type DensityState = {
|
||||
density: MonthBucket[] | null;
|
||||
minDate: string | null;
|
||||
maxDate: string | null;
|
||||
};
|
||||
|
||||
const SKIP: DensityState = { density: null, minDate: null, maxDate: null };
|
||||
const EMPTY: DensityState = { density: [], minDate: null, maxDate: null };
|
||||
|
||||
export function monthBoundaryFrom(yearMonth: string): string {
|
||||
return `${yearMonth}-01`;
|
||||
@@ -45,3 +55,30 @@ export function fillDensityGaps(
|
||||
const counts = new Map(buckets.map((b) => [b.month, b.count]));
|
||||
return sequence.map((month) => ({ month, count: counts.get(month) ?? 0 }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the density data for the timeline widget. Mobile (sm: breakpoint and below)
|
||||
* and calendar view both skip the request entirely — the widget isn't rendered
|
||||
* there. A non-ok response or network failure degrades to an empty bucket list
|
||||
* instead of throwing, so the document list page keeps rendering.
|
||||
*/
|
||||
export async function fetchDensity(
|
||||
fetch: typeof globalThis.fetch,
|
||||
view: string | null,
|
||||
isDesktop: boolean
|
||||
): Promise<DensityState> {
|
||||
if (!isDesktop || view === 'calendar') return SKIP;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/documents/density');
|
||||
if (!response.ok) return EMPTY;
|
||||
const body = (await response.json()) as DocumentDensityResult;
|
||||
return {
|
||||
density: body.buckets,
|
||||
minDate: body.minDate ?? null,
|
||||
maxDate: body.maxDate ?? null
|
||||
};
|
||||
} catch {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user