From 3cfaae06da483d7006cadcd965593c5af41e918b Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 28 Apr 2026 19:22:10 +0200 Subject: [PATCH] feat(stammbaum): AddRelationshipForm accepts onSubmit callback prop for fetch-based submission When onSubmit is provided the form has no server action and calls the callback with typed RelFormData instead. Uses a shared {#snippet} for the form body so the two submission paths share one template. Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/AddRelationshipForm.svelte | 211 ++++++++++-------- .../AddRelationshipForm.svelte.spec.ts | 9 + 2 files changed, 133 insertions(+), 87 deletions(-) diff --git a/frontend/src/lib/components/AddRelationshipForm.svelte b/frontend/src/lib/components/AddRelationshipForm.svelte index b7a82583..7c5ee2e4 100644 --- a/frontend/src/lib/components/AddRelationshipForm.svelte +++ b/frontend/src/lib/components/AddRelationshipForm.svelte @@ -7,11 +7,19 @@ import type { components } from '$lib/generated/api'; type RelationshipDTO = components['schemas']['RelationshipDTO']; type RelationType = NonNullable; +export type RelFormData = { + relatedPersonId: string; + relationType: RelationType; + fromYear?: number; + toYear?: number; +}; + interface Props { personId: string; + onSubmit?: (data: RelFormData) => Promise; } -let { personId }: Props = $props(); +let { personId, onSubmit }: Props = $props(); let open = $state(false); let addType = $state('PARENT_OF'); @@ -19,6 +27,7 @@ let addRelatedPersonId = $state(''); let addRelatedPersonName = $state(''); let addFromYear = $state(''); let addToYear = $state(''); +let callbackError = $state(null); const yearError = $derived.by(() => { const from = addFromYear.trim(); @@ -44,14 +53,123 @@ function reset() { addRelatedPersonName = ''; addFromYear = ''; addToYear = ''; + callbackError = null; } function cancel() { open = false; reset(); } + +async function handleCallbackSubmit(event: Event) { + event.preventDefault(); + if (submitDisabled || !onSubmit) return; + const data: RelFormData = { relatedPersonId: addRelatedPersonId, relationType: addType }; + const from = parseInt(addFromYear.trim(), 10); + if (!Number.isNaN(from)) data.fromYear = from; + const to = parseInt(addToYear.trim(), 10); + if (!Number.isNaN(to)) data.toYear = to; + try { + await onSubmit(data); + open = false; + reset(); + } catch { + callbackError = m.error_internal_error(); + } +} +{#snippet formFields()} +
+ +
+ +
+ + +
+ {#if selfError} + + {/if} + {#if callbackError} + + {/if} +
+ + +
+{/snippet} + {#if !open} +{:else if onSubmit} +
+ {@render formFields()} +
{:else}
-
- -
- -
- - -
- {#if selfError} - - {/if} -
- - -
+ {@render formFields()}
{/if} diff --git a/frontend/src/lib/components/AddRelationshipForm.svelte.spec.ts b/frontend/src/lib/components/AddRelationshipForm.svelte.spec.ts index 9ddfd8a2..f542d7de 100644 --- a/frontend/src/lib/components/AddRelationshipForm.svelte.spec.ts +++ b/frontend/src/lib/components/AddRelationshipForm.svelte.spec.ts @@ -38,6 +38,15 @@ describe('AddRelationshipForm', () => { await expect.element(page.getByRole('button', { name: /^Hinzufügen$/i })).toBeDisabled(); }); + it('form has no server action when onSubmit prop is provided', async () => { + const onSubmit = vi.fn().mockResolvedValue(undefined); + render(AddRelationshipForm, { personId: 'person-1', onSubmit }); + document.querySelector('button')!.click(); + await expect.element(page.getByRole('combobox')).toBeInTheDocument(); + const form = document.querySelector('form'); + expect(form?.hasAttribute('action')).toBe(false); + }); + it('shows year-range error when toYear is before fromYear', async () => { render(AddRelationshipForm, { personId: 'person-1' }); document.querySelector('button')!.click();