Files
familienarchiv/frontend/src/routes/stammbaum/+page.svelte

129 lines
4.0 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { m } from '$lib/paraglide/messages.js';
import { page } from '$app/state';
import StammbaumTree from '$lib/components/StammbaumTree.svelte';
import StammbaumSidePanel from '$lib/components/StammbaumSidePanel.svelte';
import type { components } from '$lib/generated/api';
type PersonNodeDTO = components['schemas']['PersonNodeDTO'];
type RelationshipDTO = components['schemas']['RelationshipDTO'];
interface Props {
data: { nodes: PersonNodeDTO[]; edges: RelationshipDTO[] };
}
let { data }: Props = $props();
const canWrite = $derived<boolean>(page.data.canWrite ?? false);
const focusId = page.url.searchParams.get('focus');
let selectedId = $state<string | null>(
focusId && data.nodes.some((n) => n.id === focusId) ? focusId : null
);
const selectedNode = $derived(data.nodes.find((n) => n.id === selectedId) ?? null);
let zoom = $state(1);
function zoomIn() {
zoom = Math.min(2, zoom + 0.1);
}
function zoomOut() {
zoom = Math.max(0.4, zoom - 0.1);
}
</script>
<!-- 4.25rem = 4rem navbar (h-16) + 0.25rem accent strip (h-1).
-my-6 cancels <main>'s py-6 so the canvas sits flush against the navbar
on top and the viewport edge on the bottom. -->
<div class="-my-6 flex h-[calc(100dvh-4.25rem)] flex-col">
<header
class="flex shrink-0 items-center justify-between border-b border-line bg-surface px-6 py-4"
>
<h1 class="font-serif text-2xl text-ink">{m.nav_stammbaum()}</h1>
{#if data.nodes.length > 0}
<div class="flex items-center gap-2">
<button
type="button"
onclick={zoomOut}
aria-label={m.stammbaum_zoom_out()}
class="inline-flex h-11 w-11 items-center justify-center rounded-sm border border-line bg-surface text-ink-2 transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
>
</button>
<button
type="button"
onclick={zoomIn}
aria-label={m.stammbaum_zoom_in()}
class="inline-flex h-11 w-11 items-center justify-center rounded-sm border border-line bg-surface text-ink-2 transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
>
+
</button>
</div>
{/if}
</header>
{#if data.nodes.length === 0}
<div class="flex flex-1 items-center justify-center p-8">
<div
class="mx-auto max-w-md rounded-sm border border-line bg-surface p-10 text-center shadow-sm"
>
<svg
class="mx-auto mb-4 h-12 w-12 text-ink-3"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
aria-hidden="true"
>
<circle cx="12" cy="5" r="2.5" />
<circle cx="6" cy="14" r="2.5" />
<circle cx="18" cy="14" r="2.5" />
<path stroke-linecap="round" d="M12 7.5v3M9.5 12.5L9 14M14.5 12.5l.5 1.5" />
</svg>
<h2 class="mb-2 font-serif text-xl text-ink">{m.stammbaum_empty_heading()}</h2>
<p class="mb-4 font-serif text-sm text-ink-2">{m.stammbaum_empty_body()}</p>
<a
href="/persons"
class="inline-block font-sans text-sm font-medium text-primary hover:underline"
>
{m.stammbaum_empty_link()}
</a>
</div>
</div>
{:else}
<div class="flex flex-1 overflow-hidden">
<div class="flex-1 overflow-auto bg-muted/20">
<StammbaumTree
nodes={data.nodes}
edges={data.edges}
selectedId={selectedId}
zoom={zoom}
onSelect={(id) => (selectedId = id)}
/>
</div>
{#if selectedNode}
<!-- Desktop: side panel on the right -->
<aside
class="hidden w-[320px] shrink-0 overflow-y-auto border-l border-line bg-surface md:block"
>
<StammbaumSidePanel
node={selectedNode}
canWrite={canWrite}
onClose={() => (selectedId = null)}
/>
</aside>
<!-- Mobile: fixed bottom sheet -->
<div
class="fixed inset-x-0 bottom-0 z-40 max-h-[60dvh] overflow-y-auto border-t border-line bg-surface shadow-lg md:hidden"
>
<StammbaumSidePanel
node={selectedNode}
canWrite={canWrite}
onClose={() => (selectedId = null)}
/>
</div>
{/if}
</div>
{/if}
</div>