refactor(persons): extract PersonCard, PersonMergePanel, CoCorrespondentsList, PersonDocumentList
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled

Split the 610-line person detail page into four focused co-located components:
- PersonCard: view/edit card with inline form (owns editMode)
- PersonMergePanel: merge target typeahead + two-step confirm (state reset via {#key})
- CoCorrespondentsList: frequency-ranked correspondent chips linking to conversations
- PersonDocumentList: reusable sorted/paginated document list (used for sent + received)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-26 12:32:01 +01:00
parent e831de4f85
commit 0db68da00c
5 changed files with 508 additions and 538 deletions

View File

@@ -0,0 +1,83 @@
<script lang="ts">
import { enhance } from '$app/forms';
import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
import { m } from '$lib/paraglide/messages.js';
let {
person,
form
}: {
person: { firstName: string; lastName: string };
form?: { mergeError?: string } | null;
} = $props();
let mergeTargetId = $state('');
let showMergeConfirm = $state(false);
</script>
<div class="mb-10 overflow-hidden rounded-sm border border-line bg-surface shadow-sm">
<div class="p-6 md:p-8">
<h2 class="mb-1 font-serif text-lg text-ink">{m.person_merge_heading()}</h2>
<p class="mb-5 font-sans text-sm text-ink-2">
{m.person_merge_description()}
</p>
{#if form?.mergeError}
<p class="mb-4 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{form.mergeError}
</p>
{/if}
<form method="POST" action="?/merge" use:enhance>
<input type="hidden" name="targetPersonId" bind:value={mergeTargetId} />
<div class="flex flex-col items-end gap-3 sm:flex-row">
<div class="flex-1">
<PersonTypeahead
name="_targetPersonDisplay"
label={m.person_merge_target_label()}
value={mergeTargetId}
onchange={(value) => {
mergeTargetId = value;
showMergeConfirm = false;
}}
/>
</div>
{#if !showMergeConfirm}
<button
type="button"
disabled={!mergeTargetId}
onclick={() => (showMergeConfirm = true)}
class="rounded border border-red-300 px-4 py-2 text-sm font-bold tracking-widest text-red-600 uppercase transition-colors hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-40"
>
{m.person_btn_merge()}
</button>
{:else}
<div class="flex gap-2">
<button
type="submit"
class="rounded bg-red-600 px-4 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-red-700"
>
{m.person_btn_merge_confirm()}
</button>
<button
type="button"
onclick={() => (showMergeConfirm = false)}
class="rounded border border-line px-4 py-2 text-sm font-bold tracking-widest text-ink-2 uppercase transition-colors hover:bg-muted"
>
{m.btn_cancel()}
</button>
</div>
{/if}
</div>
{#if showMergeConfirm}
<p class="mt-3 rounded border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
{m.person_merge_warning()} <strong>{person.firstName} {person.lastName}</strong>
{m.person_merge_will_be_deleted()}
</p>
{/if}
</form>
</div>
</div>