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>
130 lines
4.0 KiB
Svelte
130 lines
4.0 KiB
Svelte
<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>
|