feat(person): add a curator "Ereignis für diese Person" link to PersonCard

A curator on a person's page can now seed a timeline event from that
person: a gated link to #781's /zeitstrahl/events/new?personId={id},
which prefills the person and returns to /persons/{id} on save. Hidden
for a Reader. New i18n key person_add_event in de/en/es.

Refs #842
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-15 08:16:10 +02:00
parent 9118a10e4b
commit 446611e3cc
5 changed files with 46 additions and 1 deletions

View File

@@ -188,6 +188,7 @@
"person_hint_generation": "Generation in der Familie (G 0 = älteste Generation)",
"person_year_error": "Bitte eine vierstellige Jahreszahl eingeben",
"person_years_error_order": "Geburtsjahr muss vor dem Todesjahr liegen",
"person_add_event": "Ereignis für diese Person",
"person_docs_heading": "Gesendete Dokumente",
"person_no_docs": "Diese Person ist noch nicht als Absender verknüpft.",
"person_received_docs_heading": "Empfangene Dokumente",

View File

@@ -188,6 +188,7 @@
"person_hint_generation": "Generation within the family (G 0 = oldest generation)",
"person_year_error": "Please enter a four-digit year",
"person_years_error_order": "Birth year must be before death year",
"person_add_event": "Add event for this person",
"person_docs_heading": "Sent documents",
"person_no_docs": "This person has not yet been linked as a sender.",
"person_received_docs_heading": "Received documents",

View File

@@ -188,6 +188,7 @@
"person_hint_generation": "Generación dentro de la familia (G 0 = generación más antigua)",
"person_year_error": "Introduzca un año de cuatro dígitos",
"person_years_error_order": "El año de nacimiento debe ser anterior al año de fallecimiento",
"person_add_event": "Añadir evento para esta persona",
"person_docs_heading": "Documentos enviados",
"person_no_docs": "Esta persona aún no está vinculada como remitente.",
"person_received_docs_heading": "Documentos recibidos",

View File

@@ -128,7 +128,8 @@ const deathText = $derived(formatLifeDate(person.deathDate, person.deathDatePrec
</div>
{/if}
<!-- Edit button — full width, outlined -->
<!-- Curator actions — full width, outlined. Both links are gated to
WRITE_ALL; the gate is UX only (the #781 route guard is the boundary). -->
{#if canWrite}
<a
href="/persons/{person.id}/edit"
@@ -142,6 +143,15 @@ const deathText = $derived(formatLifeDate(person.deathDate, person.deathDatePrec
/>
{m.btn_edit()}
</a>
<!-- Opens #781's create form pre-seeded with this person; on save it
returns to /persons/{id} (originPersonId). #842 REQ-003/010. -->
<a
data-testid="person-add-event"
href="/zeitstrahl/events/new?personId={person.id}"
class="mt-2 flex min-h-[44px] w-full items-center justify-center gap-1.5 rounded border border-line px-3 py-2 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:border-primary hover:text-ink"
>
{m.person_add_event()}
</a>
{/if}
</div>
</div>

View File

@@ -0,0 +1,32 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import PersonCard from './PersonCard.svelte';
afterEach(() => cleanup());
const PERSON_ID = '55555555-5555-5555-5555-555555555555';
const makePerson = (overrides = {}) => ({
id: PERSON_ID,
firstName: 'Karl',
lastName: 'Raddatz',
displayName: 'Karl Raddatz',
personType: 'PERSON',
...overrides
});
describe('PersonCard add-event affordance (#842)', () => {
it('shows an add-event link pre-seeded with the person to a curator (REQ-003)', () => {
render(PersonCard, { person: makePerson(), canWrite: true });
const add = document.querySelector(
'[data-testid="person-add-event"]'
) as HTMLAnchorElement | null;
expect(add).not.toBeNull();
expect(add?.getAttribute('href')).toBe(`/zeitstrahl/events/new?personId=${PERSON_ID}`);
});
it('renders no add-event link to a reader (REQ-004)', () => {
render(PersonCard, { person: makePerson(), canWrite: false });
expect(document.querySelector('[data-testid="person-add-event"]')).toBeNull();
});
});