feat(persons): add birth/death year fields (issue #18)
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 1m48s
CI / Backend Unit Tests (pull_request) Successful in 2m3s
CI / E2E Tests (pull_request) Failing after 17m10s

V5 Flyway migration adds birth_year and death_year INTEGER columns.
Service validates birthYear <= deathYear (400 otherwise). Frontend edit
form adds year number inputs; view mode renders * year / † year. Backed
by 3 backend service tests and 1 E2E test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-19 21:45:02 +01:00
parent fe9b4a9569
commit b07391541b
12 changed files with 138 additions and 3 deletions

View File

@@ -307,6 +307,8 @@ export interface components {
firstName: string;
lastName: string;
alias?: string;
birthYear?: number;
deathYear?: number;
};
DocumentUpdateDTO: {
title?: string;

View File

@@ -28,6 +28,8 @@ export const actions = {
const firstName = formData.get('firstName')?.toString().trim();
const lastName = formData.get('lastName')?.toString().trim();
const alias = formData.get('alias')?.toString().trim() || undefined;
const birthYear = formData.get('birthYear')?.toString().trim() || undefined;
const deathYear = formData.get('deathYear')?.toString().trim() || undefined;
if (!firstName || !lastName) {
return fail(400, { updateError: 'Vor- und Nachname sind Pflichtfelder.' });
@@ -36,7 +38,12 @@ export const actions = {
const api = createApiClient(fetch);
const { error: apiError } = await api.PUT('/api/persons/{id}', {
params: { path: { id: params.id } },
body: { firstName, lastName, ...(alias ? { alias } : {}) }
body: {
firstName, lastName,
...(alias ? { alias } : {}),
...(birthYear ? { birthYear } : {}),
...(deathYear ? { deathYear } : {})
}
});
if (apiError) {

View File

@@ -82,6 +82,32 @@
class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy"
/>
</div>
<div>
<label for="birthYear" class="block text-xs font-bold uppercase tracking-widest text-gray-400 mb-1">{m.person_label_birth_year()}</label>
<input
id="birthYear"
name="birthYear"
type="number"
min="1"
max="2100"
placeholder={m.person_placeholder_year()}
value={person.birthYear ?? ''}
class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy"
/>
</div>
<div>
<label for="deathYear" class="block text-xs font-bold uppercase tracking-widest text-gray-400 mb-1">{m.person_label_death_year()}</label>
<input
id="deathYear"
name="deathYear"
type="number"
min="1"
max="2100"
placeholder={m.person_placeholder_year()}
value={person.deathYear ?? ''}
class="block w-full border border-gray-300 rounded px-3 py-2 font-serif text-brand-navy focus:outline-none focus:border-brand-navy"
/>
</div>
</div>
<div class="flex gap-3">
@@ -126,6 +152,17 @@
<span class="block text-lg font-serif text-brand-navy italic">"{person.alias}"</span>
</div>
{/if}
{#if person.birthYear || person.deathYear}
<div>
<span class="block text-xs font-bold font-sans text-gray-400 uppercase tracking-widest mb-1">
{#if person.birthYear && person.deathYear}{m.person_label_birth_year()} / {m.person_label_death_year()}{:else if person.birthYear}{m.person_label_birth_year()}{:else}{m.person_label_death_year()}{/if}
</span>
<span class="block text-lg font-serif text-brand-navy">
{#if person.birthYear}* {person.birthYear}{/if}{#if person.birthYear && person.deathYear} &nbsp;{/if}{#if person.deathYear}{person.deathYear}{/if}
</span>
</div>
{/if}
</div>
</div>
</div>