Files
familienarchiv/frontend/src/routes/persons/[id]/PersonDocumentList.svelte
Marcel e7f8aa5894 refactor: move document domain core to lib/document/
Moves ~25 components, utils (search, filename, groupDocuments,
documentStatusLabel, validateFile), bulkSelection store, and
TranscriptionSection sub-component. Fixes broken relative imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 13:56:36 +02:00

130 lines
4.0 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { formatDate } from '$lib/utils/date';
import { formatDocumentStatus } from '$lib/document/documentStatusLabel';
import { sortDocumentsByDate, type SortDir } from '$lib/utils/sort';
import DocumentThumbnail from '$lib/document/DocumentThumbnail.svelte';
const DOCS_PREVIEW_LIMIT = 5;
let {
documents,
heading,
emptyMessage
}: {
documents: {
id: string;
title?: string | null;
originalFilename: string;
documentDate?: string | null;
location?: string | null;
status: string;
contentType?: string;
thumbnailUrl?: string;
}[];
heading: string;
emptyMessage: string;
} = $props();
const yearRange = $derived.by(() => {
const years = documents
.filter((d) => d.documentDate)
.map((d) => parseInt(d.documentDate!.substring(0, 4)));
if (!years.length) return null;
const min = Math.min(...years);
const max = Math.max(...years);
return min === max ? `${min}` : `${min} ${max}`;
});
let sortDir = $state<SortDir>('DESC');
let showAll = $state(false);
const sortedDocuments = $derived(sortDocumentsByDate(documents, sortDir));
const visibleDocuments = $derived(
showAll ? sortedDocuments : sortedDocuments.slice(0, DOCS_PREVIEW_LIMIT)
);
</script>
<div class="mb-4 rounded-sm border border-line bg-surface">
<!-- Section header -->
<div class="flex items-center gap-3 border-b border-line px-4 py-3">
<h2 class="text-[10px] font-bold tracking-widest text-ink-3 uppercase">{heading}</h2>
<span
class="rounded-full bg-primary/10 px-2 py-0.5 font-sans text-[10px] font-bold text-primary"
>
{documents.length}
</span>
{#if yearRange}
<span class="font-sans text-[10px] text-ink-3">{yearRange}</span>
{/if}
{#if documents.length > 1}
<button
onclick={() => (sortDir = sortDir === 'DESC' ? 'ASC' : 'DESC')}
class="ml-auto text-[10px] font-bold tracking-widest text-ink-3 uppercase transition-colors hover:text-ink"
>
{sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}
</button>
{/if}
</div>
{#if documents.length === 0}
<p class="px-4 py-6 text-center font-sans text-sm text-ink-3">{emptyMessage}</p>
{:else}
<ul>
{#each visibleDocuments as doc (doc.id)}
<li>
<a
href="/documents/{doc.id}"
class="group flex items-center gap-3 border-b border-line px-4 py-3 transition-colors last:border-b-0 hover:bg-muted"
>
<!-- Thumbnail tile -->
<DocumentThumbnail doc={doc} />
<!-- Title + meta -->
<div class="min-w-0 flex-1">
<div class="truncate font-serif text-sm font-medium text-ink group-hover:underline">
{doc.title || doc.originalFilename}
</div>
<div class="mt-0.5 flex items-center gap-1.5 font-sans text-[11px] text-ink-3">
<span>{doc.documentDate ? formatDate(doc.documentDate) : m.doc_no_date()}</span>
{#if doc.location}
<span>·</span>
<span>{doc.location}</span>
{/if}
</div>
</div>
<!-- Status chip -->
<span
class="hidden flex-shrink-0 rounded-full border px-2 py-0.5 font-sans text-[10px] font-bold sm:inline-block
{doc.status === 'UPLOADED'
? 'border-accent/50 bg-accent/20 text-ink'
: doc.status === 'ARCHIVED'
? 'border-green-200 bg-green-50 text-green-800'
: 'border-yellow-200 bg-yellow-50 text-yellow-800'}"
>
{formatDocumentStatus(doc.status)}
</span>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Right-MD.svg"
alt=""
aria-hidden="true"
class="h-4 w-4 flex-shrink-0 opacity-30 transition-opacity group-hover:opacity-80"
/>
</a>
</li>
{/each}
</ul>
{#if documents.length > DOCS_PREVIEW_LIMIT && !showAll}
<button
onclick={() => (showAll = true)}
class="w-full border-t border-line py-2.5 text-center font-sans text-[11px] font-bold tracking-widest text-ink/50 uppercase transition-colors hover:text-ink"
>
{m.person_show_more({ count: documents.length - DOCS_PREVIEW_LIMIT })}
</button>
{/if}
{/if}
</div>