feat(frontend): new strip components, suggestions dropdown, empty state
CorrespondenzPersonBar (Row 1), CorrespondenzFilterControls (Row 2 with live count + sort), CorrespondentSuggestionsDropdown (fetch-on-focus, keyboard nav), SinglePersonHintBar, CorrespondenzEmptyState (recent persons from localStorage). New i18n shim in messages-extra.ts until root-owned paraglide files can be regenerated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
121
frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte
Normal file
121
frontend/src/routes/korrespondenz/CorrespondenzEmptyState.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { conv_empty_search_placeholder, conv_empty_recent_label } from '$lib/messages-extra';
|
||||
|
||||
interface RecentPerson {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onSelectPerson: (id: string) => void;
|
||||
}
|
||||
|
||||
const { onSelectPerson }: Props = $props();
|
||||
|
||||
let recentPersons = $state<RecentPerson[]>([]);
|
||||
|
||||
onMount(() => {
|
||||
try {
|
||||
const raw = localStorage.getItem('korrespondenz_recent_persons');
|
||||
if (raw) {
|
||||
// Svelte auto-escapes firstName/lastName — do not use {@html} with these values
|
||||
recentPersons = JSON.parse(raw) as RecentPerson[];
|
||||
}
|
||||
} catch {
|
||||
recentPersons = [];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="mx-auto flex max-w-sm flex-col items-center gap-4 py-16 text-center">
|
||||
<!-- Icon circle -->
|
||||
<div class="rounded-full bg-[#F0EDE6] p-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-[#002850]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="M2 7l10 7 10-7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Heading -->
|
||||
<h2 class="font-serif text-sm font-black text-[#0D2240]">Korrespondenz durchsuchen</h2>
|
||||
|
||||
<!-- Subtext -->
|
||||
<p class="max-w-[280px] text-xs text-[#888]">
|
||||
Wähle eine Person aus dem Archiv um deren Briefe zu sehen — mit oder ohne Korrespondent.
|
||||
</p>
|
||||
|
||||
<!-- Search input placeholder (visual only — clicking focuses Person A typeahead above) -->
|
||||
<button
|
||||
type="button"
|
||||
data-testid="conv-empty-search"
|
||||
aria-label={conv_empty_search_placeholder()}
|
||||
onclick={() => onSelectPerson('')}
|
||||
class="flex h-[28px] w-[260px] items-center rounded-sm border border-[#D1D5DB] bg-[#F9F8F6] px-3 text-xs text-[#AAA] italic transition-colors hover:border-[#002850]"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mr-1.5 shrink-0"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.35-4.35" />
|
||||
</svg>
|
||||
Person suchen…
|
||||
</button>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="flex w-full items-center gap-2">
|
||||
<div class="flex-1 border-t border-[#E0DDD6]"></div>
|
||||
<span class="text-[10px] font-bold tracking-wider text-[#AAA] uppercase">oder</span>
|
||||
<div class="flex-1 border-t border-[#E0DDD6]"></div>
|
||||
</div>
|
||||
|
||||
<!-- Recent persons -->
|
||||
{#if recentPersons.length > 0}
|
||||
<div class="flex w-full flex-col items-center gap-2">
|
||||
<span class="text-[10px] font-bold tracking-widest text-[#888] uppercase">
|
||||
{conv_empty_recent_label()}
|
||||
</span>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
{#each recentPersons as person (person.id)}
|
||||
<!-- TODO: allow clearing recent history -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => onSelectPerson(person.id)}
|
||||
class="flex items-center gap-1.5 rounded-full border border-[#D1D5DB] bg-white px-3 py-1.5 text-xs font-bold text-[#333] transition-colors hover:border-[#002850] hover:text-[#002850]"
|
||||
>
|
||||
<span
|
||||
class="flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-[#002850] text-[10px] text-white"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{person.firstName[0]}{person.lastName[0]}
|
||||
</span>
|
||||
<span class="hidden sm:inline">{person.lastName}, </span>{person.firstName}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user