Files
familienarchiv/frontend/src/routes/briefwechsel/ConversationTimeline.svelte
2026-05-05 14:40:14 +02:00

118 lines
3.3 KiB
Svelte

<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import DistributionBar from '$lib/shared/primitives/DistributionBar.svelte';
import ThumbnailRow from '$lib/document/ThumbnailRow.svelte';
type Person = { id: string; firstName?: string | null; lastName: string; displayName: string };
type Tag = { id: string; name: string };
interface Props {
documents: {
id: string;
title?: string;
originalFilename: string;
documentDate?: string;
location?: string;
summary?: string;
contentType?: string;
thumbnailKey?: string;
thumbnailGeneratedAt?: string;
thumbnailAspect?: 'PORTRAIT' | 'LANDSCAPE';
pageCount?: number;
sender?: Person | null;
receivers?: Person[];
tags?: Tag[];
}[];
senderId: string;
receiverId?: string;
canWrite: boolean;
senderName?: string;
receiverName?: string;
}
let { documents, senderId, receiverId, canWrite, senderName, receiverName }: Props = $props();
const enrichedDocuments = $derived(
documents.map((doc, i) => {
const year = doc.documentDate ? new Date(doc.documentDate).getFullYear() : null;
const prevYear =
i > 0 && documents[i - 1].documentDate
? new Date(documents[i - 1].documentDate!).getFullYear()
: null;
const isOut = doc.sender?.id === senderId;
return { doc, year, showYearDivider: year !== null && year !== prevYear, isOut };
})
);
const countsByYear = $derived(
documents.reduce((acc, d) => {
if (d.documentDate) {
const y = new Date(d.documentDate).getFullYear();
acc.set(y, (acc.get(y) ?? 0) + 1);
}
return acc;
}, new Map<number, number>())
);
const outCount = $derived(documents.filter((d) => d.sender?.id === senderId).length);
const inCount = $derived(documents.length - outCount);
const isBilateral = $derived(!!senderId && !!receiverId);
const showOtherParty = $derived(!receiverId);
const newDocUrl = $derived(
`/documents/new?senderId=${encodeURIComponent(senderId)}${receiverId ? `&receiverId=${encodeURIComponent(receiverId)}` : ''}`
);
</script>
{#if isBilateral && documents.length > 0}
<DistributionBar
outCount={outCount}
inCount={inCount}
senderName={senderName ?? ''}
receiverName={receiverName ?? ''}
/>
{/if}
<div class="overflow-hidden rounded-sm border border-line bg-surface">
{#each enrichedDocuments as { doc, year, showYearDivider, isOut } (doc.id)}
{#if showYearDivider && year !== null}
<div
data-testid="year-divider"
class="flex items-baseline gap-3 border-t-2 border-b border-line bg-muted px-[14px] py-[8px]"
>
<span class="text-2xl font-black tracking-tight text-primary">{year}</span>
<span class="text-sm font-bold text-ink-3">{countsByYear.get(year) ?? 0} Briefe</span>
</div>
{/if}
<ThumbnailRow doc={doc} isOut={isOut} showOtherParty={showOtherParty} />
{/each}
{#if canWrite}
<div class="flex justify-end border-t border-line px-[14px] py-[6px]">
<a
href={newDocUrl}
data-testid="conv-new-doc-link"
class="inline-flex items-center gap-1 text-xs font-bold text-primary/50 transition-colors hover:text-primary"
>
<svg
class="h-3 w-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
{m.conv_new_doc_link()}
</a>
</div>
{/if}
</div>