feat(documents): paginate search with a Pagination control
Frontend side of the /documents pagination work. The page.server.ts load reads ?page= from the URL, forwards page+size=50 to the backend, and exposes the new totalElements/pageNumber/pageSize/totalPages fields on `data`. +page.svelte renders a <Pagination> component below the result list; buildPageHref preserves every filter param and only updates page. The existing triggerSearch debounce flow intentionally drops `page` when any filter changes, so filter edits reset to page 0 automatically. <Pagination> uses plain <a href> links (not goto) so SvelteKit's default scroll restoration scrolls new pages to the top — the expected senior-UX behaviour. Decorative chevrons wrapped in aria-hidden spans, 44px touch targets, focus-visible ring, stacks vertically under 640px. The control hides itself when totalPages ≤ 1. Test coverage: 9 cases on Pagination (label, aria-current, prev/next enable/disable, makeHref invocation, decorative chevron, touch target), plus a filter-reset assertion on +page.svelte (page 5 → edit q → goto URL must drop page=). Adds i18n keys in de/en/es. Manual edit to api.ts pending a post-merge npm run generate:api against a rebuilt dev backend. (#315) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { untrack } from 'svelte';
|
||||
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import SearchFilterBar from '../SearchFilterBar.svelte';
|
||||
import DocumentList from '../DocumentList.svelte';
|
||||
import Pagination from '$lib/components/Pagination.svelte';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
let { data } = $props();
|
||||
@@ -35,6 +36,12 @@ let showAdvanced = $state(untrack(hasAdvancedFilters));
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
/**
|
||||
* Rebuilds the URL from the CURRENT local filter state. `page` is intentionally
|
||||
* not carried over — any filter change implicitly resets back to page 0, which
|
||||
* is the expected behaviour. For page-only navigation use {@link buildPageHref}
|
||||
* instead, which preserves every filter from the server `data`.
|
||||
*/
|
||||
function triggerSearch() {
|
||||
const params = new SvelteURLSearchParams();
|
||||
if (q) params.set('q', q);
|
||||
@@ -50,6 +57,29 @@ function triggerSearch() {
|
||||
goto(`/documents?${params.toString()}`, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the href for a Pagination prev/next link. Preserves every current
|
||||
* filter param and only updates `page`. Uses a normal <a href> (not goto)
|
||||
* so SvelteKit's default scroll restoration brings the user to the top of
|
||||
* the new slice — the expected behaviour for page navigation.
|
||||
*/
|
||||
function buildPageHref(targetPage: number): string {
|
||||
const params = new SvelteURLSearchParams();
|
||||
if (data.q) params.set('q', data.q);
|
||||
if (data.from) params.set('from', data.from);
|
||||
if (data.to) params.set('to', data.to);
|
||||
if (data.senderId) params.set('senderId', data.senderId);
|
||||
if (data.receiverId) params.set('receiverId', data.receiverId);
|
||||
(data.tags || []).forEach((t: string) => params.append('tag', t));
|
||||
if (data.sort) params.set('sort', data.sort);
|
||||
if (data.dir) params.set('dir', data.dir);
|
||||
if (data.tagQ) params.set('tagQ', data.tagQ);
|
||||
if (data.tagOp === 'OR') params.set('tagOp', 'OR');
|
||||
if (targetPage > 0) params.set('page', String(targetPage));
|
||||
const qs = params.toString();
|
||||
return qs ? `/documents?${qs}` : '/documents';
|
||||
}
|
||||
|
||||
function handleTextSearch() {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => triggerSearch(), 500);
|
||||
@@ -115,10 +145,12 @@ $effect(() => {
|
||||
|
||||
<DocumentList
|
||||
items={data.items}
|
||||
total={data.total}
|
||||
total={data.totalElements}
|
||||
q={data.q}
|
||||
canWrite={data.canWrite}
|
||||
error={data.error}
|
||||
sort={sort}
|
||||
/>
|
||||
|
||||
<Pagination page={data.pageNumber} totalPages={data.totalPages} makeHref={buildPageHref} />
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user