refactor(documents): extract buildSearchParams — address @felixbrandt

triggerSearch (local state, filter change) and buildPageHref (server data,
page nav) were each iterating over the same ~10 filter params. Any new
filter would have had to land in two places. buildSearchParams is now the
single source of truth for which params the /documents URL understands;
both callers just pass their snapshot and an optional targetPage. (#316)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-24 10:48:34 +02:00
parent 6808d9fb03
commit baf7ae3420

View File

@@ -36,46 +36,84 @@ let showAdvanced = $state(untrack(hasAdvancedFilters));
let searchTimer: ReturnType<typeof setTimeout>;
type FilterSnapshot = {
q: string;
from: string;
to: string;
senderId: string;
receiverId: string;
tags: string[];
sort: string;
dir: string;
tagQ: string;
tagOp: 'AND' | 'OR';
};
/**
* Builds a URLSearchParams from a filter snapshot. Single source of truth for
* which params the `/documents` URL understands — add a filter here and both
* filter-change nav (triggerSearch) and page nav (buildPageHref) will pick it
* up. `page` is appended only when > 0 so the default page 0 stays out of the
* URL, keeping the filter-change-resets-to-page-0 behaviour implicit.
*/
function buildSearchParams(filters: FilterSnapshot, targetPage?: number): SvelteURLSearchParams {
const params = new SvelteURLSearchParams();
if (filters.q) params.set('q', filters.q);
if (filters.from) params.set('from', filters.from);
if (filters.to) params.set('to', filters.to);
if (filters.senderId) params.set('senderId', filters.senderId);
if (filters.receiverId) params.set('receiverId', filters.receiverId);
filters.tags.forEach((tag) => params.append('tag', tag));
if (filters.sort) params.set('sort', filters.sort);
if (filters.dir) params.set('dir', filters.dir);
if (filters.tagQ) params.set('tagQ', filters.tagQ);
if (filters.tagOp === 'OR') params.set('tagOp', 'OR');
if (targetPage !== undefined && targetPage > 0) params.set('page', String(targetPage));
return params;
}
/**
* 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`.
* not carried over — any filter change implicitly resets back to page 0.
*/
function triggerSearch() {
const params = new SvelteURLSearchParams();
if (q) params.set('q', q);
if (from) params.set('from', from);
if (to) params.set('to', to);
if (senderId) params.set('senderId', senderId);
if (receiverId) params.set('receiverId', receiverId);
tagNames.forEach((tag) => params.append('tag', tag.name));
if (sort) params.set('sort', sort);
if (dir) params.set('dir', dir);
if (tagQ) params.set('tagQ', tagQ);
if (tagOperator === 'OR') params.set('tagOp', 'OR');
const params = buildSearchParams({
q,
from,
to,
senderId,
receiverId,
tags: tagNames.map((t) => t.name),
sort,
dir,
tagQ,
tagOp: tagOperator
});
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.
* Builds the href for a Pagination prev/next link. Preserves every filter
* param from server `data` and 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 params = buildSearchParams(
{
q: data.q || '',
from: data.from || '',
to: data.to || '',
senderId: data.senderId || '',
receiverId: data.receiverId || '',
tags: data.tags || [],
sort: data.sort || '',
dir: data.dir || '',
tagQ: data.tagQ || '',
tagOp: (data.tagOp as 'AND' | 'OR') || 'AND'
},
targetPage
);
const qs = params.toString();
return qs ? `/documents?${qs}` : '/documents';
}