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>
127 lines
3.9 KiB
Svelte
127 lines
3.9 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { m } from '$lib/paraglide/messages.js';
|
|
import DocumentRow from '$lib/document/DocumentRow.svelte';
|
|
import { SvelteMap } from 'svelte/reactivity';
|
|
import type { components } from '$lib/generated/api';
|
|
|
|
type DocumentSearchItem = components['schemas']['DocumentSearchItem'];
|
|
|
|
type SortMode = 'DATE' | 'TITLE' | 'SENDER' | 'RECEIVER' | 'UPLOAD_DATE' | 'RELEVANCE';
|
|
|
|
let {
|
|
items,
|
|
canWrite,
|
|
error,
|
|
q = '',
|
|
sort = 'DATE'
|
|
}: {
|
|
items: DocumentSearchItem[];
|
|
canWrite: boolean;
|
|
error?: string | null;
|
|
q?: string;
|
|
sort?: SortMode;
|
|
} = $props();
|
|
|
|
const groups = $derived.by(() => {
|
|
if (sort === 'SENDER') return groupBySender(items);
|
|
if (sort === 'RECEIVER') return groupByReceiver(items);
|
|
return groupByYear(items);
|
|
});
|
|
|
|
function groupByYear(docItems: DocumentSearchItem[]) {
|
|
const map = new SvelteMap<string, DocumentSearchItem[]>();
|
|
for (const item of docItems) {
|
|
const label = item.document.documentDate?.substring(0, 4) ?? m.docs_group_undated();
|
|
const bucket = map.get(label);
|
|
if (bucket) bucket.push(item);
|
|
else map.set(label, [item]);
|
|
}
|
|
return Array.from(map.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
|
|
}
|
|
|
|
function groupBySender(docItems: DocumentSearchItem[]) {
|
|
const map = new SvelteMap<string, DocumentSearchItem[]>();
|
|
for (const item of docItems) {
|
|
const label = item.document.sender?.displayName ?? m.docs_group_unknown_sender();
|
|
const bucket = map.get(label);
|
|
if (bucket) bucket.push(item);
|
|
else map.set(label, [item]);
|
|
}
|
|
return Array.from(map.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
|
|
}
|
|
|
|
function groupByReceiver(docItems: DocumentSearchItem[]) {
|
|
const map = new SvelteMap<string, DocumentSearchItem[]>();
|
|
for (const item of docItems) {
|
|
const receivers = item.document.receivers ?? [];
|
|
const labels =
|
|
receivers.length > 0
|
|
? receivers.map((r) => r.displayName)
|
|
: [m.docs_group_unknown_receiver()];
|
|
for (const label of labels) {
|
|
const bucket = map.get(label);
|
|
if (bucket) bucket.push(item);
|
|
else map.set(label, [item]);
|
|
}
|
|
}
|
|
return Array.from(map.entries()).map(([label, groupItems]) => ({ label, items: groupItems }));
|
|
}
|
|
</script>
|
|
|
|
<!-- ERROR -->
|
|
{#if error}
|
|
<div class="border border-line bg-surface shadow-sm">
|
|
<div class="bg-red-50 p-8 text-center text-red-600">
|
|
{error}
|
|
</div>
|
|
</div>
|
|
{:else if items.length > 0}
|
|
<!-- GROUP CARDS -->
|
|
{#each groups as group (group.label)}
|
|
<div
|
|
data-testid="group-card"
|
|
class="mb-4 overflow-hidden border border-line bg-surface shadow-sm"
|
|
>
|
|
<div class="border-b border-line bg-muted px-5 py-2">
|
|
<span
|
|
data-testid="group-header"
|
|
class="font-sans text-xs font-bold text-ink-3"
|
|
class:uppercase={sort !== 'SENDER' && sort !== 'RECEIVER'}
|
|
class:tracking-widest={sort !== 'SENDER' && sort !== 'RECEIVER'}
|
|
class:tracking-wide={sort === 'SENDER' || sort === 'RECEIVER'}>{group.label}</span
|
|
>
|
|
</div>
|
|
<ul class="divide-y divide-line">
|
|
{#each group.items as item (group.label + '-' + item.document.id)}
|
|
<DocumentRow item={item} canWrite={canWrite} />
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
{/each}
|
|
{:else}
|
|
<!-- EMPTY STATE -->
|
|
<div class="border border-line bg-surface shadow-sm">
|
|
<div class="p-16 text-center">
|
|
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
|
|
<img
|
|
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Mag-Glass-MD.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
class="h-6 w-6"
|
|
/>
|
|
</div>
|
|
<h3 class="font-serif text-lg font-medium text-ink">{m.docs_empty_heading()}</h3>
|
|
<p class="mt-1 font-sans text-sm text-ink-2">
|
|
{q ? m.docs_empty_for_term({ term: q }) : m.docs_empty_text()}
|
|
</p>
|
|
<button
|
|
onclick={() => goto('/documents')}
|
|
class="mt-6 text-sm font-bold tracking-wide text-primary uppercase transition hover:text-ink-2"
|
|
>
|
|
{m.docs_empty_btn_clear()}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|