refactor(dates): type DocumentMultiSelect options without double-cast

The search results were mapped to a partial object then forced with
`as unknown as Document[]`. DocumentListItem already carries every field
the picker reads (id, title, documentDate, metaDatePrecision REQUIRED,
metaDateEnd), so introduce a DocumentOption Pick type and drop the
double-cast — the mapped objects are now honestly typed.

Refs #666

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-27 12:22:06 +02:00
parent 4169373693
commit 6cc622b4db

View File

@@ -5,11 +5,20 @@ import { clickOutside } from '$lib/shared/actions/clickOutside';
import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate'; import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate';
import { getLocale } from '$lib/paraglide/runtime.js'; import { getLocale } from '$lib/paraglide/runtime.js';
type Document = components['schemas']['Document'];
type DocumentListItem = components['schemas']['DocumentListItem']; type DocumentListItem = components['schemas']['DocumentListItem'];
/**
* Exactly the fields this picker reads — id for selection/dedup, the rest for
* the honest date label. A full `Document` and a `DocumentListItem` are both
* structurally assignable, so the search results need no cast.
*/
type DocumentOption = Pick<
DocumentListItem,
'id' | 'title' | 'documentDate' | 'metaDatePrecision' | 'metaDateEnd'
>;
interface Props { interface Props {
selectedDocuments?: Document[]; selectedDocuments?: DocumentOption[];
placeholder?: string; placeholder?: string;
hiddenInputName?: string; hiddenInputName?: string;
} }
@@ -21,7 +30,7 @@ let {
}: Props = $props(); }: Props = $props();
let searchTerm = $state(''); let searchTerm = $state('');
let results: Document[] = $state([]); let results: DocumentOption[] = $state([]);
let showDropdown = $state(false); let showDropdown = $state(false);
let loading = $state(false); let loading = $state(false);
let debounceTimer: ReturnType<typeof setTimeout>; let debounceTimer: ReturnType<typeof setTimeout>;
@@ -47,13 +56,13 @@ function handleInput() {
const res = await fetch(`/api/documents/search?q=${encodeURIComponent(searchTerm)}&size=10`); const res = await fetch(`/api/documents/search?q=${encodeURIComponent(searchTerm)}&size=10`);
if (res.ok) { if (res.ok) {
const body: { items: DocumentListItem[] } = await res.json(); const body: { items: DocumentListItem[] } = await res.json();
const docs = body.items.map((it) => ({ const docs: DocumentOption[] = body.items.map((it) => ({
id: it.id, id: it.id,
title: it.title, title: it.title,
documentDate: it.documentDate, documentDate: it.documentDate,
metaDatePrecision: it.metaDatePrecision, metaDatePrecision: it.metaDatePrecision,
metaDateEnd: it.metaDateEnd metaDateEnd: it.metaDateEnd
})) as unknown as Document[]; }));
results = docs.filter((d) => !selectedDocuments.some((s) => s.id === d.id)); results = docs.filter((d) => !selectedDocuments.some((s) => s.id === d.id));
} }
} catch { } catch {
@@ -64,7 +73,7 @@ function handleInput() {
}, 300); }, 300);
} }
function selectDocument(doc: Document) { function selectDocument(doc: DocumentOption) {
selectedDocuments = [...selectedDocuments, doc]; selectedDocuments = [...selectedDocuments, doc];
searchTerm = ''; searchTerm = '';
showDropdown = false; showDropdown = false;
@@ -75,10 +84,15 @@ function removeDocument(id: string | undefined) {
selectedDocuments = selectedDocuments.filter((d) => d.id !== id); selectedDocuments = selectedDocuments.filter((d) => d.id !== id);
} }
function formatDocLabel(doc: Document): string { function formatDocLabel(doc: DocumentOption): string {
if (!doc.documentDate) return doc.title; if (!doc.documentDate) return doc.title;
const precision = (doc.metaDatePrecision as DatePrecision | undefined) ?? 'DAY'; const label = formatDocumentDate(
const label = formatDocumentDate(doc.documentDate, precision, doc.metaDateEnd, null, getLocale()); doc.documentDate,
doc.metaDatePrecision as DatePrecision,
doc.metaDateEnd,
null,
getLocale()
);
return `${doc.title} · ${label}`; return `${doc.title} · ${label}`;
} }
</script> </script>