feat(persons): add formatLifeDateRange + formatDocumentStatus utility functions

Unit tests for both; i18n keys for doc status and person stats bar;
PERSON_NOT_FOUND added to frontend ErrorCode type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-29 19:50:30 +02:00
parent 7b03aada3b
commit 3abdf9bb68
8 changed files with 151 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import * as m from '$lib/paraglide/messages.js';
* Keep in sync with backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java
*/
export type ErrorCode =
| 'PERSON_NOT_FOUND'
| 'DOCUMENT_NOT_FOUND'
| 'DOCUMENT_NO_FILE'
| 'FILE_NOT_FOUND'
@@ -47,6 +48,8 @@ export async function parseBackendError(res: Response): Promise<BackendError | n
/** Returns a localised message for the given error code via Paraglide. Falls back to INTERNAL_ERROR. */
export function getErrorMessage(code: ErrorCode | string | undefined): string {
switch (code) {
case 'PERSON_NOT_FOUND':
return m.error_person_not_found();
case 'DOCUMENT_NOT_FOUND':
return m.error_document_not_found();
case 'DOCUMENT_NO_FILE':

View File

@@ -0,0 +1,28 @@
import { describe, it, expect } from 'vitest';
import { formatDocumentStatus } from './documentStatusLabel';
describe('formatDocumentStatus', () => {
it('maps PLACEHOLDER to correct label', () => {
expect(formatDocumentStatus('PLACEHOLDER')).toBe('Platzhalter');
});
it('maps UPLOADED to correct label', () => {
expect(formatDocumentStatus('UPLOADED')).toBe('Hochgeladen');
});
it('maps TRANSCRIBED to correct label', () => {
expect(formatDocumentStatus('TRANSCRIBED')).toBe('Transkribiert');
});
it('maps REVIEWED to correct label', () => {
expect(formatDocumentStatus('REVIEWED')).toBe('Geprüft');
});
it('maps ARCHIVED to correct label', () => {
expect(formatDocumentStatus('ARCHIVED')).toBe('Archiviert');
});
it('returns fallback for unknown status', () => {
expect(formatDocumentStatus('SOMETHING_NEW')).toBe('Unbekannt');
});
});

View File

@@ -0,0 +1,22 @@
import { m } from '$lib/paraglide/messages.js';
/**
* Maps a document status string to a localised human-readable label.
* Falls back to "Unknown" for unrecognised values.
*/
export function formatDocumentStatus(status: string): string {
switch (status) {
case 'PLACEHOLDER':
return m.doc_status_placeholder();
case 'UPLOADED':
return m.doc_status_uploaded();
case 'TRANSCRIBED':
return m.doc_status_transcribed();
case 'REVIEWED':
return m.doc_status_reviewed();
case 'ARCHIVED':
return m.doc_status_archived();
default:
return m.doc_status_unknown();
}
}

View File

@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';
import { formatLifeDateRange } from './personLifeDates';
describe('formatLifeDateRange', () => {
it('returns both dates when birth and death year are given', () => {
expect(formatLifeDateRange(1882, 1944)).toBe('* 1882 † 1944');
});
it('returns only birth year when only birthYear is given', () => {
expect(formatLifeDateRange(1882, undefined)).toBe('* 1882');
});
it('returns only death year when only deathYear is given', () => {
expect(formatLifeDateRange(undefined, 1944)).toBe('† 1944');
});
it('returns empty string when neither year is given', () => {
expect(formatLifeDateRange(undefined, undefined)).toBe('');
});
it('returns empty string when both are null', () => {
expect(formatLifeDateRange(null, null)).toBe('');
});
});

View File

@@ -0,0 +1,20 @@
/**
* Formats the life date range for a person.
* Examples:
* * 1882 † 1944 (both)
* * 1882 (birth only)
* † 1944 (death only)
* "" (neither)
*/
export function formatLifeDateRange(birthYear?: number | null, deathYear?: number | null): string {
if (birthYear && deathYear) {
return `* ${birthYear} ${deathYear}`;
}
if (birthYear) {
return `* ${birthYear}`;
}
if (deathYear) {
return `${deathYear}`;
}
return '';
}