Files
familienarchiv/frontend/src/lib/components/DashboardActivityFeed.svelte
Marcel fd93f1a4da
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m48s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m43s
feat(chronik): rename route and heading to Aktivitäten
/chronik → /aktivitaeten; heading updated in all three locales.
Component folder (lib/components/chronik/) stays unchanged — internal
implementation detail, not user-facing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 09:28:09 +02:00

113 lines
3.4 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 * as m from '$lib/paraglide/messages.js';
import type { components } from '$lib/generated/api';
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
interface Props {
feed: ActivityFeedItemDTO[];
}
const { feed }: Props = $props();
const verbMap: Record<string, string> = {
TEXT_SAVED: m.audit_action_text_saved(),
FILE_UPLOADED: m.audit_action_file_uploaded(),
ANNOTATION_CREATED: m.audit_action_annotation_created(),
BLOCK_REVIEWED: m.audit_action_annotation_created(),
COMMENT_ADDED: m.audit_action_comment_added(),
MENTION_CREATED: m.audit_action_mention_created()
};
function verb(kind: string): string {
return verbMap[kind] ?? kind;
}
function formatDate(iso: string): string {
return new Intl.DateTimeFormat('de-DE', {
day: 'numeric',
month: 'short',
year: 'numeric'
}).format(new Date(iso));
}
function formatTime(iso: string): string {
return new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit' }).format(
new Date(iso)
);
}
// Rollup rows get "14. Apr. · 14:0214:32"; singletons stay "14. Apr. 2026".
function timestamp(item: ActivityFeedItemDTO): string {
if (item.happenedAtUntil && item.count > 1) {
const short = new Intl.DateTimeFormat('de-DE', {
day: 'numeric',
month: 'short'
}).format(new Date(item.happenedAt));
return `${short} \u00b7 ${formatTime(item.happenedAt)}\u2013${formatTime(item.happenedAtUntil)}`;
}
return formatDate(item.happenedAt);
}
</script>
<section class="rounded-sm border border-line bg-surface p-5">
<div class="mb-3 flex items-center justify-between">
<h2 class="font-sans text-xs font-bold tracking-widest text-ink-3 uppercase">
{m.feed_caption()}
</h2>
<a
href="/aktivitaeten"
aria-label={m.feed_show_all()}
class="font-sans text-xs text-ink-3 transition-colors hover:text-ink">{m.feed_show_all()}</a
>
</div>
{#if feed.length > 0}
<ul class="flex flex-col gap-3">
{#each feed as item (item.happenedAt + item.documentId + item.kind)}
<li class="flex items-start gap-3">
{#if item.actor}
<span
class="mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full font-sans text-sm font-bold text-white"
style="background:{item.actor.color}">{item.actor.initials}</span
>
{:else}
<span
class="mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-line font-sans text-sm text-ink-3"
>?</span
>
{/if}
<div class="min-w-0 flex-1">
<p class="font-sans text-sm leading-snug text-ink">
{#if item.actor}
<strong>{item.actor.name ?? item.actor.initials}</strong>
{/if}
{verb(item.kind)}
<a href="/documents/{item.documentId}" class="underline hover:text-ink">
{item.documentTitle}
</a>
{#if item.count > 1}
<span
data-testid="feed-rollup-count"
class="ml-1.5 inline-block rounded-sm bg-primary px-2 py-0.5 font-sans text-[10px] font-bold text-primary-fg"
>
{item.count}
</span>
{/if}
{#if item.youMentioned}
<span
class="ml-1.5 inline-block rounded-full border border-accent px-2 py-px font-sans text-[10px] font-bold text-accent"
>
{m.feed_for_you()}
</span>
{/if}
</p>
<p class="mt-0.5 font-sans text-xs text-ink-3">{timestamp(item)}</p>
</div>
</li>
{/each}
</ul>
{/if}
</section>