diff --git a/frontend/src/lib/components/DocumentMetadataDrawer.svelte b/frontend/src/lib/components/DocumentMetadataDrawer.svelte index a24a27d1..fd896f1c 100644 --- a/frontend/src/lib/components/DocumentMetadataDrawer.svelte +++ b/frontend/src/lib/components/DocumentMetadataDrawer.svelte @@ -3,6 +3,7 @@ import { m } from '$lib/paraglide/messages.js'; import { formatDate } from '$lib/utils/date'; import { formatDocumentStatus } from '$lib/utils/documentStatusLabel'; import { getInitials, personAvatarColor } from '$lib/utils/personFormat'; +import RelationshipBadge from '$lib/components/RelationshipBadge.svelte'; type Person = { id: string; firstName?: string | null; lastName: string; displayName: string }; type Tag = { id: string; name: string }; @@ -14,9 +15,18 @@ type Props = { sender: Person | null; receivers: Person[]; tags: Tag[]; + inferredRelationship?: { labelFromA: string; labelFromB: string } | null; }; -let { documentDate, location, status, sender, receivers, tags }: Props = $props(); +let { + documentDate, + location, + status, + sender, + receivers, + tags, + inferredRelationship = null +}: Props = $props(); const VISIBLE_RECEIVER_LIMIT = 5; @@ -112,6 +122,12 @@ function getFullName(person: Person): string { {/if} {/if} + {#if inferredRelationship} + + {/if} {:else}

{m.doc_details_no_persons()}

diff --git a/frontend/src/lib/components/DocumentTopBar.svelte b/frontend/src/lib/components/DocumentTopBar.svelte index f352364a..15cadd6b 100644 --- a/frontend/src/lib/components/DocumentTopBar.svelte +++ b/frontend/src/lib/components/DocumentTopBar.svelte @@ -30,9 +30,16 @@ type Props = { canWrite: boolean; fileUrl: string; transcribeMode: boolean; + inferredRelationship?: { labelFromA: string; labelFromB: string } | null; }; -let { doc, canWrite, fileUrl, transcribeMode = $bindable() }: Props = $props(); +let { + doc, + canWrite, + fileUrl, + transcribeMode = $bindable(), + inferredRelationship = null +}: Props = $props(); let detailsOpen = $state(false); @@ -275,6 +282,7 @@ let mobileMenuOpen = $state(false); sender={doc.sender ?? null} receivers={doc.receivers ? [...doc.receivers] : []} tags={doc.tags ? [...doc.tags] : []} + inferredRelationship={inferredRelationship} /> {/if} diff --git a/frontend/src/lib/components/RelationshipBadge.svelte b/frontend/src/lib/components/RelationshipBadge.svelte new file mode 100644 index 00000000..8de6473f --- /dev/null +++ b/frontend/src/lib/components/RelationshipBadge.svelte @@ -0,0 +1,26 @@ + + +
+

+ {m.doc_details_field_relationship()} +

+
+ {labelFromA} + + {labelFromB} +
+
diff --git a/frontend/src/lib/relationshipLabels.ts b/frontend/src/lib/relationshipLabels.ts new file mode 100644 index 00000000..ff2edd44 --- /dev/null +++ b/frontend/src/lib/relationshipLabels.ts @@ -0,0 +1,44 @@ +import * as m from '$lib/paraglide/messages.js'; + +/** + * Maps a backend inferred-label key (parent, uncle_aunt, ...) to its + * localised string. Unknown keys fall back to "distant". + */ +export function inferredRelationshipLabel(key: string): string { + switch (key) { + case 'parent': + return m.relation_inferred_parent(); + case 'child': + return m.relation_inferred_child(); + case 'spouse': + return m.relation_inferred_spouse(); + case 'sibling': + return m.relation_inferred_sibling(); + case 'grandparent': + return m.relation_inferred_grandparent(); + case 'grandchild': + return m.relation_inferred_grandchild(); + case 'great_grandparent': + return m.relation_inferred_great_grandparent(); + case 'great_grandchild': + return m.relation_inferred_great_grandchild(); + case 'uncle_aunt': + return m.relation_inferred_uncle_aunt(); + case 'niece_nephew': + return m.relation_inferred_niece_nephew(); + case 'great_uncle_aunt': + return m.relation_inferred_great_uncle_aunt(); + case 'great_niece_nephew': + return m.relation_inferred_great_niece_nephew(); + case 'inlaw_parent': + return m.relation_inferred_inlaw_parent(); + case 'inlaw_child': + return m.relation_inferred_inlaw_child(); + case 'sibling_inlaw': + return m.relation_inferred_sibling_inlaw(); + case 'cousin_1': + return m.relation_inferred_cousin_1(); + default: + return m.relation_inferred_distant(); + } +} diff --git a/frontend/src/routes/documents/[id]/+page.server.ts b/frontend/src/routes/documents/[id]/+page.server.ts index 5fb27114..e601dd44 100644 --- a/frontend/src/routes/documents/[id]/+page.server.ts +++ b/frontend/src/routes/documents/[id]/+page.server.ts @@ -1,6 +1,7 @@ import { error, redirect } from '@sveltejs/kit'; import { createApiClient } from '$lib/api.server'; import { getErrorMessage } from '$lib/errors'; +import { inferredRelationshipLabel } from '$lib/relationshipLabels'; export async function load({ params, fetch }) { const { id } = params; @@ -15,5 +16,38 @@ export async function load({ params, fetch }) { throw error(docResult.response.status, getErrorMessage(code)); } - return { document: docResult.data! }; + const document = docResult.data!; + const inferredRelationship = await loadInferredRelationship(api, document); + + return { document, inferredRelationship }; +} + +async function loadInferredRelationship( + api: ReturnType, + document: { + sender?: { id: string; familyMember?: boolean } | null; + receivers?: { id: string; familyMember?: boolean }[]; + } +): Promise<{ labelFromA: string; labelFromB: string } | null> { + const sender = document.sender; + const receivers = document.receivers ?? []; + + // The badge is shown only when both endpoints are family members and the + // document has exactly one receiver. + if (!sender?.familyMember) return null; + if (receivers.length !== 1) return null; + const receiver = receivers[0]; + if (!receiver?.familyMember) return null; + + const result = await api.GET('/api/persons/{aId}/relationship-to/{bId}', { + params: { path: { aId: sender.id, bId: receiver.id } } + }); + + if (result.response.status === 404) return null; + if (!result.response.ok || !result.data) return null; + + return { + labelFromA: inferredRelationshipLabel(result.data.labelFromA), + labelFromB: inferredRelationshipLabel(result.data.labelFromB) + }; } diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index 9c52faa6..5c780a54 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -395,6 +395,7 @@ onMount(() => { canWrite={canWrite} fileUrl={fileLoader.fileUrl} bind:transcribeMode={transcribeMode} + inferredRelationship={data.inferredRelationship} />