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:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user