refactor(conversations): migrate ConversationTimeline to groupDocuments
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 1s
CI / Backend Unit Tests (pull_request) Failing after 2s
CI / Unit & Component Tests (push) Failing after 3s
CI / Backend Unit Tests (push) Failing after 2s

Replace hand-rolled enrichedDocuments year-divider logic with the shared
groupDocuments utility. Also fixes a timezone bug in documentYears: adds
'T12:00:00' to date strings so getFullYear() doesn't drift on UTC boundaries.
No behavior change — year dividers render the same way as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #236.
This commit is contained in:
Marcel
2026-04-15 09:41:40 +02:00
parent 25aa05411f
commit 38d558182a

View File

@@ -2,6 +2,7 @@
import { m } from '$lib/paraglide/messages.js'; import { m } from '$lib/paraglide/messages.js';
import { formatDate } from '$lib/utils/date'; import { formatDate } from '$lib/utils/date';
import GroupDivider from '$lib/components/GroupDivider.svelte'; import GroupDivider from '$lib/components/GroupDivider.svelte';
import { groupDocuments } from '$lib/utils/groupDocuments';
let { let {
documents, documents,
@@ -30,22 +31,15 @@ let {
const documentYears = $derived( const documentYears = $derived(
documents documents
.map((doc) => (doc.documentDate ? new Date(doc.documentDate).getFullYear() : null)) .map((doc) =>
doc.documentDate ? new Date(doc.documentDate + 'T12:00:00').getFullYear() : null
)
.filter((y): y is number => y !== null) .filter((y): y is number => y !== null)
); );
const yearFrom = $derived(documentYears.length > 0 ? Math.min(...documentYears) : null); const yearFrom = $derived(documentYears.length > 0 ? Math.min(...documentYears) : null);
const yearTo = $derived(documentYears.length > 0 ? Math.max(...documentYears) : null); const yearTo = $derived(documentYears.length > 0 ? Math.max(...documentYears) : null);
const enrichedDocuments = $derived( const documentGroups = $derived.by(() => groupDocuments(documents, 'DATE', ''));
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;
return { doc, year, showYearDivider: year !== null && year !== prevYear };
})
);
</script> </script>
<!-- Summary bar --> <!-- Summary bar -->
@@ -83,81 +77,83 @@ const enrichedDocuments = $derived(
<div class="p-6 md:p-8"> <div class="p-6 md:p-8">
<div class="relative z-10 flex flex-col gap-4"> <div class="relative z-10 flex flex-col gap-4">
{#each enrichedDocuments as { doc, year, showYearDivider } (doc.id)} {#each documentGroups as group (group.label)}
{#if showYearDivider} {#if group.label}
<GroupDivider label={String(year)} /> <GroupDivider label={group.label} />
{/if} {/if}
{@const isRight = doc.sender?.id === senderId} {#each group.documents as doc (doc.id)}
{@const isRight = doc.sender?.id === senderId}
<!-- Message Row --> <!-- Message Row -->
<div class="flex w-full {isRight ? 'justify-end' : 'justify-start'}"> <div class="flex w-full {isRight ? 'justify-end' : 'justify-start'}">
<!-- Bubble Group --> <!-- Bubble Group -->
<div <div
class="flex max-w-[90%] gap-3 md:max-w-[70%] {isRight class="flex max-w-[90%] gap-3 md:max-w-[70%] {isRight
? 'flex-row-reverse' ? 'flex-row-reverse'
: 'flex-row'}" : 'flex-row'}"
> >
<!-- AVATAR --> <!-- AVATAR -->
<div class="mt-auto mb-1 hidden flex-shrink-0 sm:block"> <div class="mt-auto mb-1 hidden flex-shrink-0 sm:block">
<div <div
class="flex h-8 w-8 items-center justify-center rounded-full border font-serif text-xs shadow-sm class="flex h-8 w-8 items-center justify-center rounded-full border font-serif text-xs shadow-sm
{isRight {isRight
? 'border-primary bg-primary text-primary-fg' ? 'border-primary bg-primary text-primary-fg'
: 'border-line bg-surface text-ink'}" : 'border-line bg-surface text-ink'}"
> >
{#if doc.sender} {#if doc.sender}
{doc.sender.firstName ? doc.sender.firstName[0] : doc.sender.lastName[0]}{doc.sender.lastName[0]} {doc.sender.firstName ? doc.sender.firstName[0] : doc.sender.lastName[0]}{doc.sender.lastName[0]}
{:else} {:else}
? ?
{/if} {/if}
</div>
</div> </div>
</div>
<!-- BUBBLE CARD --> <!-- BUBBLE CARD -->
<a <a
href="/documents/{doc.id}" href="/documents/{doc.id}"
class="group block transform rounded border p-4 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md class="group block transform rounded border p-4 shadow-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md
{isRight {isRight
? 'rounded-br-none border-primary bg-primary text-primary-fg' ? 'rounded-br-none border-primary bg-primary text-primary-fg'
: 'rounded-bl-none border-line bg-muted/50 text-ink'}" : 'rounded-bl-none border-line bg-muted/50 text-ink'}"
> >
<!-- Header --> <!-- Header -->
<div class="mb-2 flex items-start justify-between gap-4"> <div class="mb-2 flex items-start justify-between gap-4">
<h3 <h3
class="font-serif text-sm leading-snug font-medium {isRight class="font-serif text-sm leading-snug font-medium {isRight
? 'text-primary-fg' ? 'text-primary-fg'
: 'text-ink'}" : 'text-ink'}"
> >
{doc.title || doc.originalFilename} {doc.title || doc.originalFilename}
</h3> </h3>
<!-- Status Dot --> <!-- Status Dot -->
<span <span
class="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full class="mt-1.5 h-1.5 w-1.5 flex-shrink-0 rounded-full
{doc.status === 'UPLOADED' ? 'bg-accent' : 'bg-yellow-400'}" {doc.status === 'UPLOADED' ? 'bg-accent' : 'bg-yellow-400'}"
title={doc.status} title={doc.status}
> >
</span> </span>
</div> </div>
<!-- Metadata --> <!-- Metadata -->
<div <div
class="flex flex-wrap gap-3 font-sans text-[10px] tracking-wider uppercase opacity-80 {isRight class="flex flex-wrap gap-3 font-sans text-[10px] tracking-wider uppercase opacity-80 {isRight
? 'text-primary-fg/70' ? 'text-primary-fg/70'
: 'text-ink-2'}" : 'text-ink-2'}"
> >
<span class="flex items-center">
{doc.documentDate ? formatDate(doc.documentDate) : '—'}
</span>
{#if doc.location}
<span class="flex items-center"> <span class="flex items-center">
{doc.location} {doc.documentDate ? formatDate(doc.documentDate) : '—'}
</span> </span>
{/if} {#if doc.location}
</div> <span class="flex items-center">
</a> {doc.location}
</span>
{/if}
</div>
</a>
</div>
</div> </div>
</div> {/each}
{/each} {/each}
</div> </div>
</div> </div>