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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 }
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-5xl px-4 py-10">
|
||||
<div class="mx-auto max-w-3xl px-4 py-8">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8 border-b border-ink/10 pb-4">
|
||||
<h1 class="font-serif text-3xl font-medium text-ink">{m.conv_heading()}</h1>
|
||||
<p class="mt-2 font-sans text-sm text-ink-2">
|
||||
<div class="mb-6 border-b border-[#E0DDD6] pb-4">
|
||||
<h1 class="font-serif text-2xl font-semibold text-[#002850]">{m.conv_heading()}</h1>
|
||||
<p class="mt-1 font-sans text-sm text-[#666]">
|
||||
{m.conv_subtitle()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ConversationFilterBar
|
||||
<!-- Filter strip: Row 1 — persons -->
|
||||
<CorrespondenzPersonBar
|
||||
bind:senderId={senderId}
|
||||
bind:receiverId={receiverId}
|
||||
bind:fromDate={fromDate}
|
||||
bind:toDate={toDate}
|
||||
bind:sortDir={sortDir}
|
||||
initialSenderName={data.initialValues.senderName}
|
||||
initialReceiverName={data.initialValues.receiverName}
|
||||
onapplyFilters={applyFilters}
|
||||
ontoggleSort={toggleSort}
|
||||
onswapPersons={swapPersons}
|
||||
/>
|
||||
|
||||
<!-- RESULTS LIST SECTION -->
|
||||
{#if !senderId || !receiverId}
|
||||
<div
|
||||
class="flex flex-col items-center justify-center rounded-sm border border-dashed border-line bg-surface py-24 text-center"
|
||||
>
|
||||
<div class="mb-4 rounded-full bg-muted p-4 text-ink">
|
||||
<svg class="h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/></svg
|
||||
>
|
||||
</div>
|
||||
<p class="font-serif text-lg text-ink">{m.conv_empty_heading()}</p>
|
||||
<p class="mt-1 font-sans text-sm text-ink-2">{m.conv_empty_text()}</p>
|
||||
</div>
|
||||
<!-- Filter strip: Row 2 — date/sort/count -->
|
||||
<CorrespondenzFilterControls
|
||||
senderId={senderId}
|
||||
bind:fromDate={fromDate}
|
||||
bind:toDate={toDate}
|
||||
bind:sortDir={sortDir}
|
||||
documentCount={data.documents.length}
|
||||
onapplyFilters={applyFilters}
|
||||
ontoggleSort={toggleSort}
|
||||
/>
|
||||
|
||||
<!-- Single-person hint bar -->
|
||||
{#if isSinglePerson}
|
||||
<SinglePersonHintBar
|
||||
senderName={senderName}
|
||||
fromDate={fromDate || undefined}
|
||||
toDate={toDate || undefined}
|
||||
sortDir={sortDir}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Results -->
|
||||
{#if !senderId}
|
||||
<CorrespondenzEmptyState onSelectPerson={selectPerson} />
|
||||
{:else if data.documents.length === 0}
|
||||
<div
|
||||
class="flex flex-col items-center justify-center rounded-sm border border-line bg-surface py-24 text-center shadow-sm"
|
||||
class="flex flex-col items-center justify-center rounded-sm border border-[#E0DDD6] bg-[#F7F5F2] py-24 text-center shadow-sm"
|
||||
>
|
||||
<p class="font-serif text-ink">{m.conv_no_results_heading()}</p>
|
||||
<p class="mt-2 text-sm text-ink-3">{m.conv_no_results_text()}</p>
|
||||
<p class="font-serif text-[#333]">{m.conv_no_results_heading()}</p>
|
||||
<p class="mt-2 text-sm text-[#888]">{m.conv_no_results_text()}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<ConversationTimeline
|
||||
@@ -99,6 +138,8 @@ function swapPersons() {
|
||||
senderId={senderId}
|
||||
receiverId={receiverId}
|
||||
canWrite={data.canWrite}
|
||||
senderName={senderName}
|
||||
receiverName={receiverName}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user