feat(stammbaum): inline add-relationship form in side panel

Implements the inline-edit affordance from
docs/specs/stammbaum-tree-spec.html (section 3): a low-opacity
"+ Beziehung hinzufügen" button below the direct relationships list
expands into a compact form (type select, person typeahead,
optional Von/Bis Jahr inputs, Abbrechen + Speichern). On save the
form POSTs to /api/persons/{id}/relationships, reloads the panel's
own data, and calls invalidateAll() so the tree picks up the new
edge without a hard refresh.

The panel takes a new canWrite prop, plumbed through from the
+layout.server.ts data already exposed on page.data.

Also pins the /stammbaum canvas to the viewport (-my-6 cancels
<main>'s py-6, h-[calc(100dvh-4.25rem)] subtracts the navbar) so
the page no longer overflows below the fold.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-27 21:44:51 +02:00
committed by marcel
parent c40cc05f68
commit ccbcbca0e8
2 changed files with 177 additions and 4 deletions

View File

@@ -15,6 +15,7 @@ interface Props {
let { data }: Props = $props();
const focusId = $derived(page.url.searchParams.get('focus'));
const canWrite = $derived<boolean>(page.data.canWrite ?? false);
let selectedId = $state<string | null>(null);
$effect(() => {
@@ -34,7 +35,10 @@ function zoomOut() {
}
</script>
<div class="-mt-6 flex h-full flex-col">
<!-- 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"
>
@@ -102,7 +106,11 @@ function zoomOut() {
</div>
{#if selectedNode}
<aside class="w-[268px] shrink-0 overflow-y-auto border-l border-line bg-surface">
<StammbaumSidePanel node={selectedNode} onClose={() => (selectedId = null)} />
<StammbaumSidePanel
node={selectedNode}
canWrite={canWrite}
onClose={() => (selectedId = null)}
/>
</aside>
{/if}
</div>