feat(documents): timeline date-range filter with density bars (#385) #478

Merged
marcel merged 52 commits from feat/issue-385-timeline-density-filter into main 2026-05-08 12:27:17 +02:00
2 changed files with 58 additions and 0 deletions
Showing only changes of commit 6786c0112d - Show all commits

View File

@@ -7,6 +7,7 @@ import SearchFilterBar from '../SearchFilterBar.svelte';
import DocumentList from '../DocumentList.svelte';
import Pagination from '$lib/shared/primitives/Pagination.svelte';
import BulkSelectionBar from '$lib/document/BulkSelectionBar.svelte';
import TimelineDensityFilter from '$lib/document/TimelineDensityFilter.svelte';
import { bulkSelectionStore } from '$lib/document/bulkSelection.svelte';
import { getErrorMessage, parseBackendError } from '$lib/shared/errors';
import * as m from '$lib/paraglide/messages.js';
@@ -234,6 +235,21 @@ $effect(() => {
onblur={() => (qFocused = false)}
/>
<div class="mt-3 mb-4 hidden sm:block">
<TimelineDensityFilter
density={data.density}
minDate={data.minDate}
maxDate={data.maxDate}
from={from}
to={to}
onchange={(event) => {
from = event.from;
to = event.to;
triggerSearch();
}}
/>
</div>
<div class="mb-3 flex items-center justify-between gap-4">
<p class="font-sans text-base text-ink-2">
{#if data.totalElements > 0}{m.docs_result_count({ count: data.totalElements })}{/if}

View File

@@ -135,3 +135,45 @@ describe('documents page — URL building', () => {
expect(url).not.toContain('page=');
});
});
// ─── Timeline density widget wiring (#385) ────────────────────────────────────
describe('documents page — timeline density widget', () => {
it('renders the timeline widget when density data is present', async () => {
render(Page, {
data: makeData({
density: [{ month: '1915-08', count: 3 }],
minDate: '1915-08-01',
maxDate: '1915-08-31'
})
});
await expect.element(page.getByTestId('timeline-density-filter')).toBeInTheDocument();
});
it('hides the timeline widget when density is null (mobile / calendar view)', async () => {
render(Page, { data: makeData({ density: null, minDate: null, maxDate: null }) });
expect(document.querySelector('[data-testid="timeline-density-filter"]')).toBeNull();
});
it('clicking a timeline bar navigates with from/to set to that month boundary', async () => {
const { goto } = await import('$app/navigation');
vi.mocked(goto).mockClear();
render(Page, {
data: makeData({
density: [{ month: '1915-08', count: 3 }],
minDate: '1915-08-01',
maxDate: '1915-08-31'
})
});
const bar = document.querySelector('[data-testid="timeline-bar"]') as HTMLButtonElement;
bar.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(goto).toHaveBeenCalledOnce();
const [url] = vi.mocked(goto).mock.calls[0];
expect(url).toContain('from=1915-08-01');
expect(url).toContain('to=1915-08-31');
});
});