refactor: migrate all Svelte components from Svelte 4 to Svelte 5 runes
- Replace `export let` with `$props()` and `$bindable()` across all components
- Replace `$:` reactive statements with `$derived()` and `$effect()`
- Replace `createEventDispatcher` with callback props (e.g. `onchange`)
- Replace `on:event` directives with inline event handlers (`onclick`, `oninput`, etc.)
- Replace `<slot />` with `{@render children()}` in layout
- Use `untrack()` for SSR-safe $state initialization from reactive props
- Replace `blur` + `setTimeout` anti-pattern in TagInput with `clickOutside` action
- Fix `page` store usage in layout to use `$app/state` directly
- 0 errors, 0 warnings after svelte-check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,58 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
// Data & State
|
||||
let documents: typeof data.documents = [];
|
||||
let initialValues = { senderName: '', receiverName: '' };
|
||||
let senderId = $state(untrack(() => data.filters.senderId));
|
||||
let receiverId = $state(untrack(() => data.filters.receiverId));
|
||||
let fromDate = $state(untrack(() => data.filters.from));
|
||||
let toDate = $state(untrack(() => data.filters.to));
|
||||
let sortDir = $state(untrack(() => data.filters.dir));
|
||||
|
||||
// Filter State
|
||||
let senderId = '';
|
||||
let receiverId = '';
|
||||
let fromDate = '';
|
||||
let toDate = '';
|
||||
let sortDir = 'DESC';
|
||||
|
||||
// Reactive Update
|
||||
$: {
|
||||
documents = data.documents;
|
||||
initialValues = data.initialValues;
|
||||
// Sync with server data after navigation
|
||||
$effect(() => {
|
||||
senderId = data.filters.senderId;
|
||||
receiverId = data.filters.receiverId;
|
||||
fromDate = data.filters.from;
|
||||
toDate = data.filters.to;
|
||||
sortDir = data.filters.dir;
|
||||
}
|
||||
});
|
||||
|
||||
// Filter Logic
|
||||
function applyFilters() {
|
||||
setTimeout(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (senderId) params.set('senderId', senderId);
|
||||
if (receiverId) params.set('receiverId', receiverId);
|
||||
if (fromDate) params.set('from', fromDate);
|
||||
if (toDate) params.set('to', toDate);
|
||||
params.set('dir', sortDir);
|
||||
goto(`?${params.toString()}`, { keepFocus: true });
|
||||
}, 0);
|
||||
const params = new URLSearchParams();
|
||||
if (senderId) params.set('senderId', senderId);
|
||||
if (receiverId) params.set('receiverId', receiverId);
|
||||
if (fromDate) params.set('from', fromDate);
|
||||
if (toDate) params.set('to', toDate);
|
||||
params.set('dir', sortDir);
|
||||
goto(`?${params.toString()}`, { keepFocus: true });
|
||||
}
|
||||
|
||||
function toggleSort() {
|
||||
sortDir = sortDir === 'DESC' ? 'ASC' : 'DESC';
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function handleSenderChange(event: CustomEvent) {
|
||||
senderId = event.detail.value;
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function handleReceiverChange(event: CustomEvent) {
|
||||
receiverId = event.detail.value;
|
||||
applyFilters();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-w-5xl mx-auto py-10 px-4">
|
||||
@@ -74,9 +55,9 @@
|
||||
<PersonTypeahead
|
||||
name="senderId"
|
||||
label="Person A (Absender)"
|
||||
value={senderId}
|
||||
initialName={initialValues.senderName}
|
||||
on:change={handleSenderChange}
|
||||
bind:value={senderId}
|
||||
initialName={data.initialValues.senderName}
|
||||
onchange={() => applyFilters()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -87,9 +68,9 @@
|
||||
<PersonTypeahead
|
||||
name="receiverId"
|
||||
label="Person B (Empfänger)"
|
||||
value={receiverId}
|
||||
initialName={initialValues.receiverName}
|
||||
on:change={handleReceiverChange}
|
||||
bind:value={receiverId}
|
||||
initialName={data.initialValues.receiverName}
|
||||
onchange={() => applyFilters()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +87,7 @@
|
||||
id="dateFrom"
|
||||
type="date"
|
||||
bind:value={fromDate}
|
||||
on:change={() => applyFilters()}
|
||||
onchange={() => applyFilters()}
|
||||
class="block w-full border-gray-300 shadow-sm focus:border-brand-navy focus:ring-brand-navy text-sm py-2.5"
|
||||
/>
|
||||
</div>
|
||||
@@ -122,7 +103,7 @@
|
||||
id="dateTo"
|
||||
type="date"
|
||||
bind:value={toDate}
|
||||
on:change={() => applyFilters()}
|
||||
onchange={() => applyFilters()}
|
||||
class="block w-full border-gray-300 shadow-sm focus:border-brand-navy focus:ring-brand-navy text-sm py-2.5"
|
||||
/>
|
||||
</div>
|
||||
@@ -130,7 +111,7 @@
|
||||
<!-- Sort Toggle -->
|
||||
<div>
|
||||
<button
|
||||
on:click={toggleSort}
|
||||
onclick={toggleSort}
|
||||
class="w-full flex items-center justify-center h-[42px] border border-brand-sand text-xs font-bold uppercase tracking-wide text-brand-navy hover:bg-brand-navy hover:text-white transition-colors"
|
||||
>
|
||||
<span class="mr-2">Sortierung:</span>
|
||||
@@ -169,7 +150,7 @@
|
||||
<p class="text-brand-navy font-serif text-lg">Wählen Sie zwei Personen aus</p>
|
||||
<p class="text-gray-500 font-sans text-sm mt-1">Die Korrespondenz wird hier angezeigt.</p>
|
||||
</div>
|
||||
{:else if documents.length === 0}
|
||||
{:else if data.documents.length === 0}
|
||||
<div
|
||||
class="flex flex-col items-center justify-center py-24 bg-white border border-brand-sand rounded-sm text-center shadow-sm"
|
||||
>
|
||||
@@ -178,18 +159,15 @@
|
||||
</div>
|
||||
{:else}
|
||||
<!-- CHAT CONTAINER -->
|
||||
<!-- Added: White background, Border, Shadow to separate from page -->
|
||||
<div class="bg-white border border-brand-sand shadow-sm rounded-sm relative overflow-hidden">
|
||||
<!-- Decoration: Central Timeline Line -->
|
||||
<div
|
||||
class="absolute left-1/2 top-0 bottom-0 w-px bg-brand-sand/30 transform -translate-x-1/2 hidden md:block"
|
||||
></div>
|
||||
|
||||
<!-- Scrollable Area (optional, if you want max-height) -->
|
||||
<div class="p-6 md:p-8">
|
||||
<!-- TIGHTER GAP: Changed from gap-8 to gap-4 -->
|
||||
<div class="flex flex-col gap-4 relative z-10">
|
||||
{#each documents as doc}
|
||||
{#each data.documents as doc}
|
||||
{@const isRight = doc.sender?.id === senderId}
|
||||
|
||||
<!-- Message Row -->
|
||||
@@ -200,7 +178,7 @@
|
||||
? 'flex-row-reverse'
|
||||
: 'flex-row'}"
|
||||
>
|
||||
<!-- AVATAR (Small) -->
|
||||
<!-- AVATAR -->
|
||||
<div class="flex-shrink-0 mt-auto mb-1 hidden sm:block">
|
||||
<div
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center font-serif text-xs border shadow-sm
|
||||
@@ -217,7 +195,6 @@
|
||||
</div>
|
||||
|
||||
<!-- BUBBLE CARD -->
|
||||
<!-- Adjusted padding (p-4) and added light bg to left bubbles for contrast -->
|
||||
<a
|
||||
href="/documents/{doc.id}"
|
||||
class="group block p-4 rounded shadow-sm transition-all duration-200 transform hover:-translate-y-0.5 hover:shadow-md border
|
||||
|
||||
Reference in New Issue
Block a user