Files
familienarchiv/frontend/src/routes/persons/[id]/edit/PersonEditForm.svelte
Marcel 9664a83dae feat(person): date + precision controls on person new/edit forms
New PersonLifeDateField (German date input + hidden ISO + DAY/MONTH/YEAR
precision select, min-h-44px, sm: side-by-side) used for birth and death
in both forms. Legacy APPROX precision seeds the select as YEAR so an
untouched save never claims DAY. Server actions send date+precision
pairs or omit both; obsolete year i18n keys removed, 9 form keys added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:20:18 +02:00

142 lines
4.2 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import PersonLifeDateField from '$lib/person/PersonLifeDateField.svelte';
import PersonTypeSelector from '$lib/person/PersonTypeSelector.svelte';
import {
PERSON_TYPES as TYPES,
type PersonType,
type PersonFormData
} from '$lib/person/person-validation';
let { person }: { person: PersonFormData } = $props();
let selectedType = $state<PersonType>(
untrack(() =>
TYPES.includes(person.personType as PersonType) ? (person.personType as PersonType) : 'PERSON'
)
);
// Match the selectedType initialiser pattern: untrack so a subsequent prop
// update (e.g. load() rerun) does not reset the user's in-progress edit.
let generationStr = $state(
untrack(() => (person.generation == null ? '' : String(person.generation)))
);
const isPerson = $derived(selectedType === 'PERSON');
const lastNameLabel = $derived(
selectedType === 'INSTITUTION' || selectedType === 'GROUP'
? m.form_label_name()
: m.form_label_last_name()
);
const labelCls = 'mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase';
const inputCls =
'block w-full rounded border border-line px-3 py-2 font-serif text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring';
</script>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="md:col-span-2">
<p class={labelCls}>
{m.form_label_person_type()}
</p>
<PersonTypeSelector
value={selectedType}
name="personType"
onchange={(type: PersonType) => (selectedType = type)}
/>
</div>
{#if isPerson}
<div>
<label for="title" class={labelCls}>{m.form_label_title()}</label>
<input
id="title"
name="title"
type="text"
maxlength="50"
value={person.title ?? ''}
class={inputCls}
/>
</div>
<div>
<label for="firstName" class={labelCls}>{m.form_label_first_name()} *</label>
<input
id="firstName"
name="firstName"
type="text"
required
value={person.firstName ?? ''}
class={inputCls}
/>
</div>
{/if}
<div class={!isPerson ? 'md:col-span-2' : ''}>
<label for="lastName" class={labelCls}>{lastNameLabel} *</label>
<input
id="lastName"
name="lastName"
type="text"
required
value={person.lastName}
class={inputCls}
/>
</div>
{#if isPerson}
<div class="md:col-span-2">
<label for="alias" class={labelCls}>{m.form_label_alias()}</label>
<input id="alias" name="alias" type="text" value={person.alias ?? ''} class={inputCls} />
</div>
<PersonLifeDateField
name="birthDate"
legend={m.person_label_birth_date()}
precisionLabel={m.person_label_birth_date_precision()}
initialIso={person.birthDate ?? ''}
initialPrecision={person.birthDatePrecision ?? null}
/>
<PersonLifeDateField
name="deathDate"
legend={m.person_label_death_date()}
precisionLabel={m.person_label_death_date_precision()}
initialIso={person.deathDate ?? ''}
initialPrecision={person.deathDatePrecision ?? null}
/>
<div class="md:col-span-2">
<label for="generation" class={labelCls}>{m.person_label_generation()}</label>
<select
id="generation"
name="generation"
bind:value={generationStr}
class="block min-h-[44px] w-full rounded border border-line bg-surface px-3 py-2 font-serif text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
aria-describedby="generation-hint"
>
<option value="">{m.person_option_generation_unset()}</option>
<option value="0">G 0</option>
<option value="1">G 1</option>
<option value="2">G 2</option>
<option value="3">G 3</option>
<option value="4">G 4</option>
<option value="5">G 5</option>
<option value="6">G 6</option>
</select>
<p id="generation-hint" class="mt-1 font-sans text-xs text-ink-3">
{m.person_hint_generation()}
</p>
</div>
{/if}
<div class="md:col-span-2">
<label for="notes" class={labelCls}>{m.person_label_notes()}</label>
<textarea
id="notes"
name="notes"
rows="4"
placeholder={m.person_placeholder_notes()}
class="block w-full resize-y rounded border border-line px-3 py-2 font-serif text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
>{person.notes ?? ''}</textarea
>
</div>
</div>