From 4f5f8255a1483835677ab4ccbcdf652402e100ea Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 30 Mar 2026 12:59:33 +0200 Subject: [PATCH] feat(korrespondenz): wire up +page.svelte orchestrator with new components Compose CorrespondenzPersonBar, CorrespondenzFilterControls, SinglePersonHintBar, CorrespondenzEmptyState, and updated ConversationTimeline. Add localStorage recent-persons persistence on applyFilters, single-person mode gate, and canWrite derived from user groups in load function. Co-Authored-By: Claude Sonnet 4.6 --- .../src/routes/korrespondenz/+page.server.ts | 8 +- .../src/routes/korrespondenz/+page.svelte | 105 ++++++++++++------ 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/frontend/src/routes/korrespondenz/+page.server.ts b/frontend/src/routes/korrespondenz/+page.server.ts index e61b3e13..c32c1afc 100644 --- a/frontend/src/routes/korrespondenz/+page.server.ts +++ b/frontend/src/routes/korrespondenz/+page.server.ts @@ -1,13 +1,18 @@ import type { components } from '$lib/generated/api'; import { createApiClient } from '$lib/api.server'; -export async function load({ url, fetch }) { +export async function load({ url, fetch, locals }) { const senderId = url.searchParams.get('senderId') || ''; const receiverId = url.searchParams.get('receiverId') || ''; const from = url.searchParams.get('from') || ''; const to = url.searchParams.get('to') || ''; const dir = url.searchParams.get('dir') || 'DESC'; + const canWrite = + (locals.user as { groups?: { permissions: string[] }[] } | undefined)?.groups?.some((g) => + g.permissions.includes('WRITE_ALL') + ) ?? false; + const api = createApiClient(fetch); let documents: components['schemas']['Document'][] = []; @@ -56,6 +61,7 @@ export async function load({ url, fetch }) { return { documents, + canWrite, initialValues: { senderName, receiverName }, filters: { senderId, receiverId, from, to, dir } }; diff --git a/frontend/src/routes/korrespondenz/+page.svelte b/frontend/src/routes/korrespondenz/+page.svelte index f151d4b2..d29e442d 100644 --- a/frontend/src/routes/korrespondenz/+page.svelte +++ b/frontend/src/routes/korrespondenz/+page.svelte @@ -2,9 +2,12 @@ import { goto } from '$app/navigation'; import { untrack } from 'svelte'; import { SvelteURLSearchParams } from 'svelte/reactivity'; -import { m } from '$lib/paraglide/messages.js'; -import ConversationFilterBar from './ConversationFilterBar.svelte'; +import CorrespondenzPersonBar from './CorrespondenzPersonBar.svelte'; +import CorrespondenzFilterControls from './CorrespondenzFilterControls.svelte'; +import SinglePersonHintBar from './SinglePersonHintBar.svelte'; import ConversationTimeline from './ConversationTimeline.svelte'; +import CorrespondenzEmptyState from './CorrespondenzEmptyState.svelte'; +import { m } from '$lib/paraglide/messages.js'; let { data } = $props(); @@ -14,6 +17,10 @@ let fromDate = $state(untrack(() => data.filters.from)); let toDate = $state(untrack(() => data.filters.to)); let sortDir = $state(untrack(() => data.filters.dir)); +// Derived name states — kept as reactive copies so ConversationTimeline always has current names +let senderName = $state(untrack(() => data.initialValues.senderName)); +let receiverName = $state(untrack(() => data.initialValues.receiverName)); + // Sync with server data after navigation $effect(() => { senderId = data.filters.senderId; @@ -21,9 +28,32 @@ $effect(() => { fromDate = data.filters.from; toDate = data.filters.to; sortDir = data.filters.dir; + senderName = data.initialValues.senderName; + receiverName = data.initialValues.receiverName; }); +const isSinglePerson = $derived(!!senderId && !receiverId); + +const RECENT_STORAGE_KEY = 'korrespondenz_recent_persons'; +const MAX_RECENT = 5; + +function persistRecentPerson(id: string, name: string) { + if (!id) return; + try { + const raw = localStorage.getItem(RECENT_STORAGE_KEY); + const existing: { id: string; name: string }[] = raw ? JSON.parse(raw) : []; + const filtered = existing.filter((p) => p.id !== id); + const updated = [{ id, name }, ...filtered].slice(0, MAX_RECENT); + localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(updated)); + } catch { + // localStorage unavailable — silently ignore + } +} + function applyFilters() { + // Persist to recent persons when a person is selected + if (senderId && senderName) persistRecentPerson(senderId, senderName); + const params = new SvelteURLSearchParams(); if (senderId) params.set('senderId', senderId); if (receiverId) params.set('receiverId', receiverId); @@ -44,54 +74,63 @@ function swapPersons() { receiverId = tmp; applyFilters(); } + +function selectPerson(id: string) { + senderId = id; + receiverId = ''; + applyFilters(); +} -
+
-
-

{m.conv_heading()}

-

+

+

{m.conv_heading()}

+

{m.conv_subtitle()}

- + - - {#if !senderId || !receiverId} -
-
- -
-

{m.conv_empty_heading()}

-

{m.conv_empty_text()}

-
+ + + + + {#if isSinglePerson} + + {/if} + + + {#if !senderId} + {:else if data.documents.length === 0}
-

{m.conv_no_results_heading()}

-

{m.conv_no_results_text()}

+

{m.conv_no_results_heading()}

+

{m.conv_no_results_text()}

{:else} {/if}