fix(korrespondenz): address 10 visual and functional regressions
- Strip full-bleed: remove max-w container, put strips at page level - Remove page heading/subtitle above strip (not in spec) - Swap button always visible (drop opacity-0, keep pointer-events-none) - Korrespondent placeholder "Alle Korrespondenten" + label "— optional" - Add placeholder prop to PersonTypeahead; add onfocused callback prop - "Person suchen" button now focuses #senderId-search instead of no-op navigate - Wire CorrespondentSuggestionsDropdown on correspondent field focus - Hint bar: bold name via <strong>, year-only dates (no ISO strings) - Asymmetry bar: use first name only to prevent label overflow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,10 @@ interface Props {
|
||||
value?: string;
|
||||
initialName?: string;
|
||||
suggestedName?: string;
|
||||
placeholder?: string;
|
||||
restrictToCorrespondentsOf?: string;
|
||||
onchange?: (value: string) => void;
|
||||
onfocused?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -20,8 +22,10 @@ let {
|
||||
value = $bindable(''),
|
||||
initialName = '',
|
||||
suggestedName = '',
|
||||
placeholder,
|
||||
restrictToCorrespondentsOf,
|
||||
onchange
|
||||
onchange,
|
||||
onfocused
|
||||
}: Props = $props();
|
||||
|
||||
let searchTerm = $state(initialName);
|
||||
@@ -79,6 +83,7 @@ function handleInput() {
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
onfocused?.();
|
||||
showDropdown = true;
|
||||
if (restrictToCorrespondentsOf) {
|
||||
const personId = untrack(() => restrictToCorrespondentsOf)!;
|
||||
@@ -131,7 +136,7 @@ function clickOutside(node: HTMLElement) {
|
||||
bind:value={searchTerm}
|
||||
oninput={handleInput}
|
||||
onfocus={handleFocus}
|
||||
placeholder={m.comp_typeahead_placeholder()}
|
||||
placeholder={placeholder ?? m.comp_typeahead_placeholder()}
|
||||
class="mt-1 block w-full rounded-md border border-line p-2 shadow-sm focus:border-accent focus:ring-accent"
|
||||
/>
|
||||
|
||||
|
||||
@@ -76,53 +76,49 @@ function swapPersons() {
|
||||
}
|
||||
|
||||
function selectPerson(id: string) {
|
||||
if (!id) {
|
||||
document.querySelector<HTMLInputElement>('#senderId-search')?.focus();
|
||||
return;
|
||||
}
|
||||
senderId = id;
|
||||
receiverId = '';
|
||||
applyFilters();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-3xl px-4 py-8">
|
||||
<!-- Page Header -->
|
||||
<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>
|
||||
<!-- Strip: Row 1 — full width, no container -->
|
||||
<CorrespondenzPersonBar
|
||||
bind:senderId={senderId}
|
||||
bind:receiverId={receiverId}
|
||||
initialSenderName={data.initialValues.senderName}
|
||||
initialReceiverName={data.initialValues.receiverName}
|
||||
onapplyFilters={applyFilters}
|
||||
onswapPersons={swapPersons}
|
||||
/>
|
||||
|
||||
<!-- Filter strip: Row 1 — persons -->
|
||||
<CorrespondenzPersonBar
|
||||
bind:senderId={senderId}
|
||||
bind:receiverId={receiverId}
|
||||
initialSenderName={data.initialValues.senderName}
|
||||
initialReceiverName={data.initialValues.receiverName}
|
||||
onapplyFilters={applyFilters}
|
||||
onswapPersons={swapPersons}
|
||||
<!-- Strip: Row 2 — full width -->
|
||||
<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}
|
||||
|
||||
<!-- 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 -->
|
||||
<!-- Content area with padding -->
|
||||
<div class="px-[18px] py-[14px]">
|
||||
{#if !senderId}
|
||||
<CorrespondenzEmptyState onSelectPerson={selectPerson} />
|
||||
{:else if data.documents.length === 0}
|
||||
|
||||
@@ -51,6 +51,9 @@ const outPct = $derived(documents.length > 0 ? (outCount / documents.length) * 1
|
||||
|
||||
const isBilateral = $derived(!!senderId && !!receiverId);
|
||||
|
||||
const shortSenderName = $derived(senderName?.split(' ')[0] ?? senderName ?? '');
|
||||
const shortReceiverName = $derived(receiverName?.split(' ')[0] ?? receiverName ?? '');
|
||||
|
||||
function statusDotClass(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
PLACEHOLDER: 'bg-yellow-400',
|
||||
@@ -82,8 +85,8 @@ const newDocUrl = $derived(
|
||||
aria-label="Briefverteilung in diesem Zeitraum: {outCount} von {senderName ?? ''}, {inCount} von {receiverName ?? ''}"
|
||||
>
|
||||
<div class="flex justify-between text-[10px] font-bold">
|
||||
<span class="text-[#002850]">{outCount} von {senderName} →</span>
|
||||
<span class="text-[#0F5755]">{inCount} von {receiverName} ←</span>
|
||||
<span class="text-[#002850]">{outCount} von {shortSenderName} →</span>
|
||||
<span class="text-[#0F5755]">{inCount} von {shortReceiverName} ←</span>
|
||||
</div>
|
||||
<div class="flex h-[5px] overflow-hidden rounded-full bg-[#E0DDD6]">
|
||||
<div class="h-full bg-[#002850] transition-all" style="width: {outPct}%"></div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
|
||||
import CorrespondentSuggestionsDropdown from './CorrespondentSuggestionsDropdown.svelte';
|
||||
|
||||
interface Props {
|
||||
senderId?: string;
|
||||
@@ -20,6 +21,18 @@ let {
|
||||
}: Props = $props();
|
||||
|
||||
let swapVisible = $derived(!!(senderId && receiverId));
|
||||
|
||||
let showSuggestions = $state(false);
|
||||
|
||||
function handleCorrespondentFocused() {
|
||||
if (senderId) showSuggestions = true;
|
||||
}
|
||||
|
||||
function handleSuggestionSelect(id: string) {
|
||||
receiverId = id;
|
||||
showSuggestions = false;
|
||||
onapplyFilters();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-end gap-[9px] border-b border-[#EAE7E0] bg-white px-4 py-[9px] sm:px-[18px]">
|
||||
@@ -42,7 +55,6 @@ let swapVisible = $derived(!!(senderId && receiverId));
|
||||
aria-label="Personen tauschen"
|
||||
onclick={onswapPersons}
|
||||
class="mb-1 flex h-7 w-7 shrink-0 items-center justify-center rounded border border-[#D1D5DB] bg-white text-[#888] transition-colors hover:border-[#002850] hover:text-[#002850]"
|
||||
class:opacity-0={!swapVisible}
|
||||
class:pointer-events-none={!swapVisible}
|
||||
tabindex={swapVisible ? 0 : -1}
|
||||
>
|
||||
@@ -65,23 +77,31 @@ let swapVisible = $derived(!!(senderId && receiverId));
|
||||
|
||||
<!-- Korrespondent field -->
|
||||
<div
|
||||
class="min-w-0 flex-1"
|
||||
class="relative min-w-0 flex-1"
|
||||
class:[&_input]:border-dashed={!receiverId}
|
||||
class:[&_input]:border-solid={!!receiverId}
|
||||
class:[&_input]:bg-[#F9F8F6]={!receiverId}
|
||||
>
|
||||
<PersonTypeahead
|
||||
name="receiverId"
|
||||
label={receiverId ? 'Korrespondent' : 'Korrespondent'}
|
||||
label={receiverId ? 'Korrespondent' : 'Korrespondent — optional'}
|
||||
bind:value={receiverId}
|
||||
initialName={initialReceiverName}
|
||||
placeholder="Alle Korrespondenten"
|
||||
restrictToCorrespondentsOf={senderId || undefined}
|
||||
onchange={() => onapplyFilters()}
|
||||
onchange={() => {
|
||||
showSuggestions = false;
|
||||
onapplyFilters();
|
||||
}}
|
||||
onfocused={handleCorrespondentFocused}
|
||||
/>
|
||||
{#if !receiverId}
|
||||
<span class="pointer-events-none absolute -mt-[1px] text-[11px] text-[#AAA] italic">
|
||||
— optional
|
||||
</span>
|
||||
{#if showSuggestions && senderId && !receiverId}
|
||||
<CorrespondentSuggestionsDropdown
|
||||
senderId={senderId}
|
||||
senderName=""
|
||||
onselect={handleSuggestionSelect}
|
||||
onclose={() => (showSuggestions = false)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
conv_hint_single_person,
|
||||
conv_hint_single_person_filtered,
|
||||
conv_strip_sort_newest,
|
||||
conv_strip_sort_oldest
|
||||
} from '$lib/messages-extra';
|
||||
import { conv_strip_sort_newest, conv_strip_sort_oldest } from '$lib/messages-extra';
|
||||
|
||||
interface Props {
|
||||
senderName: string;
|
||||
@@ -16,8 +11,9 @@ interface Props {
|
||||
let { senderName, fromDate = '', toDate = '', sortDir = 'DESC' }: Props = $props();
|
||||
|
||||
let hasDateFilter = $derived(!!(fromDate || toDate));
|
||||
|
||||
let sortLabel = $derived(sortDir === 'ASC' ? conv_strip_sort_oldest() : conv_strip_sort_newest());
|
||||
let fromYear = $derived(fromDate ? fromDate.substring(0, 4) : '');
|
||||
let toYear = $derived(toDate ? toDate.substring(0, 4) : '');
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -26,13 +22,12 @@ let sortLabel = $derived(sortDir === 'ASC' ? conv_strip_sort_oldest() : conv_str
|
||||
<span class="text-sm" aria-hidden="true">📋</span>
|
||||
|
||||
{#if hasDateFilter}
|
||||
{conv_hint_single_person_filtered({
|
||||
name: senderName,
|
||||
from: fromDate ?? '',
|
||||
to: toDate ?? '',
|
||||
sortLabel
|
||||
})}
|
||||
<strong>{senderName}</strong>
|
||||
<span>·</span>
|
||||
<span>{fromYear}–{toYear}</span>
|
||||
<span>·</span>
|
||||
<span>{sortLabel}</span>
|
||||
{:else}
|
||||
{conv_hint_single_person({ name: senderName })}
|
||||
Alle Briefe von <strong>{senderName}</strong> — wähle einen Korrespondenten oben um einzugrenzen
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user