Compare commits
3 Commits
main
...
feat/issue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f887f12f5 | ||
|
|
33a1db5d77 | ||
|
|
649b6b447c |
@@ -193,6 +193,8 @@
|
|||||||
"person_no_docs": "Diese Person ist noch nicht als Absender verknüpft.",
|
"person_no_docs": "Diese Person ist noch nicht als Absender verknüpft.",
|
||||||
"person_received_docs_heading": "Empfangene Dokumente",
|
"person_received_docs_heading": "Empfangene Dokumente",
|
||||||
"person_no_received_docs": "Diese Person ist noch nicht als Empfänger verknüpft.",
|
"person_no_received_docs": "Diese Person ist noch nicht als Empfänger verknüpft.",
|
||||||
|
"person_meta_doc_count": "{count} Dokumente",
|
||||||
|
"person_meta_rel_count": "{count} Beziehungen",
|
||||||
"person_role_sender": "Gesendet",
|
"person_role_sender": "Gesendet",
|
||||||
"person_role_receiver": "Empfangen",
|
"person_role_receiver": "Empfangen",
|
||||||
"person_co_correspondents_heading": "Häufige Korrespondenten",
|
"person_co_correspondents_heading": "Häufige Korrespondenten",
|
||||||
|
|||||||
@@ -193,6 +193,8 @@
|
|||||||
"person_no_docs": "This person has not yet been linked as a sender.",
|
"person_no_docs": "This person has not yet been linked as a sender.",
|
||||||
"person_received_docs_heading": "Received documents",
|
"person_received_docs_heading": "Received documents",
|
||||||
"person_no_received_docs": "This person has not yet been linked as a receiver.",
|
"person_no_received_docs": "This person has not yet been linked as a receiver.",
|
||||||
|
"person_meta_doc_count": "{count} documents",
|
||||||
|
"person_meta_rel_count": "{count} relationships",
|
||||||
"person_role_sender": "Sent",
|
"person_role_sender": "Sent",
|
||||||
"person_role_receiver": "Received",
|
"person_role_receiver": "Received",
|
||||||
"person_co_correspondents_heading": "Frequent correspondents",
|
"person_co_correspondents_heading": "Frequent correspondents",
|
||||||
|
|||||||
@@ -193,6 +193,8 @@
|
|||||||
"person_no_docs": "Esta persona aún no está vinculada como remitente.",
|
"person_no_docs": "Esta persona aún no está vinculada como remitente.",
|
||||||
"person_received_docs_heading": "Documentos recibidos",
|
"person_received_docs_heading": "Documentos recibidos",
|
||||||
"person_no_received_docs": "Esta persona aún no está vinculada como receptor.",
|
"person_no_received_docs": "Esta persona aún no está vinculada como receptor.",
|
||||||
|
"person_meta_doc_count": "{count} documentos",
|
||||||
|
"person_meta_rel_count": "{count} relaciones",
|
||||||
"person_role_sender": "Enviado",
|
"person_role_sender": "Enviado",
|
||||||
"person_role_receiver": "Recibido",
|
"person_role_receiver": "Recibido",
|
||||||
"person_co_correspondents_heading": "Corresponsales frecuentes",
|
"person_co_correspondents_heading": "Corresponsales frecuentes",
|
||||||
|
|||||||
26
frontend/src/lib/shared/primitives/MetaLine.svelte
Normal file
26
frontend/src/lib/shared/primitives/MetaLine.svelte
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
items,
|
||||||
|
iconSrc
|
||||||
|
}: {
|
||||||
|
items: string[];
|
||||||
|
iconSrc?: string;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if items.length > 0}
|
||||||
|
<div
|
||||||
|
data-testid="meta-line"
|
||||||
|
style="display:flex; align-items:center; flex-wrap:wrap; gap:8px; font-family:var(--font-sans); font-size:12px; color:var(--c-ink-2);"
|
||||||
|
>
|
||||||
|
{#if iconSrc}
|
||||||
|
<img src={iconSrc} alt="" style="width:14px; height:14px; opacity:0.5; flex-shrink:0;" />
|
||||||
|
{/if}
|
||||||
|
{#each items as item, i (i)}
|
||||||
|
{#if i > 0}
|
||||||
|
<span data-testid="meta-sep" aria-hidden="true">·</span>
|
||||||
|
{/if}
|
||||||
|
<span data-testid="meta-item">{item}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
74
frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts
Normal file
74
frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
import { cleanup, render } from 'vitest-browser-svelte';
|
||||||
|
import MetaLine from './MetaLine.svelte';
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
describe('MetaLine', () => {
|
||||||
|
it('renders N item spans when given N items', async () => {
|
||||||
|
render(MetaLine, { items: ['14. März 1923', '14 Dokumente', '4 Personen'] });
|
||||||
|
const spans = document.querySelectorAll('[data-testid="meta-item"]');
|
||||||
|
expect(spans).toHaveLength(3);
|
||||||
|
expect(spans[0].textContent).toBe('14. März 1923');
|
||||||
|
expect(spans[1].textContent).toBe('14 Dokumente');
|
||||||
|
expect(spans[2].textContent).toBe('4 Personen');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders separator spans between items', async () => {
|
||||||
|
render(MetaLine, { items: ['A', 'B', 'C'] });
|
||||||
|
const seps = document.querySelectorAll('[data-testid="meta-sep"]');
|
||||||
|
// N items → N-1 separators
|
||||||
|
expect(seps).toHaveLength(2);
|
||||||
|
expect(seps[0].textContent).toBe('·');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders nothing when items is empty', async () => {
|
||||||
|
const { container } = render(MetaLine, { items: [] });
|
||||||
|
// No element children — Svelte may leave an empty comment node but no DOM elements
|
||||||
|
expect(container.querySelectorAll('[data-testid]')).toHaveLength(0);
|
||||||
|
expect(container.querySelectorAll('div, span, img')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders nothing when items has one element (no separator)', async () => {
|
||||||
|
render(MetaLine, { items: ['Nur eines'] });
|
||||||
|
const seps = document.querySelectorAll('[data-testid="meta-sep"]');
|
||||||
|
expect(seps).toHaveLength(0);
|
||||||
|
const spans = document.querySelectorAll('[data-testid="meta-item"]');
|
||||||
|
expect(spans).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the leading img when iconSrc is supplied', async () => {
|
||||||
|
render(MetaLine, {
|
||||||
|
items: ['Datum'],
|
||||||
|
iconSrc: '/degruyter-icons/Simple/Small-16px/SVG/Action/Calendar-Add-SM.svg'
|
||||||
|
});
|
||||||
|
const img = document.querySelector('img');
|
||||||
|
expect(img).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT render an img when iconSrc is omitted', async () => {
|
||||||
|
render(MetaLine, { items: ['Datum'] });
|
||||||
|
const img = document.querySelector('img');
|
||||||
|
expect(img).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('icon has width 14px, height 14px, opacity 0.5, and alt=""', async () => {
|
||||||
|
render(MetaLine, {
|
||||||
|
items: ['Datum'],
|
||||||
|
iconSrc: '/degruyter-icons/Simple/Small-16px/SVG/Action/Calendar-Add-SM.svg'
|
||||||
|
});
|
||||||
|
const img = document.querySelector('img') as HTMLImageElement;
|
||||||
|
expect(img.alt).toBe('');
|
||||||
|
// Inline style values (set directly on the element, not via getComputedStyle)
|
||||||
|
expect(img.style.width).toBe('14px');
|
||||||
|
expect(img.style.height).toBe('14px');
|
||||||
|
expect(img.style.opacity).toBe('0.5');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies font-size 12px to the wrapper', async () => {
|
||||||
|
render(MetaLine, { items: ['Test'] });
|
||||||
|
const wrapper = document.querySelector('[data-testid="meta-line"]') as HTMLElement;
|
||||||
|
expect(wrapper).not.toBeNull();
|
||||||
|
expect(wrapper.style.fontSize).toBe('12px');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,6 +3,7 @@ import { m } from '$lib/paraglide/messages.js';
|
|||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
import BackButton from '$lib/shared/primitives/BackButton.svelte';
|
import BackButton from '$lib/shared/primitives/BackButton.svelte';
|
||||||
import PersonCard from './PersonCard.svelte';
|
import PersonCard from './PersonCard.svelte';
|
||||||
|
import MetaLine from '$lib/shared/primitives/MetaLine.svelte';
|
||||||
import NameHistoryCard from './NameHistoryCard.svelte';
|
import NameHistoryCard from './NameHistoryCard.svelte';
|
||||||
import CoCorrespondentsList from './CoCorrespondentsList.svelte';
|
import CoCorrespondentsList from './CoCorrespondentsList.svelte';
|
||||||
import PersonDocumentList from './PersonDocumentList.svelte';
|
import PersonDocumentList from './PersonDocumentList.svelte';
|
||||||
@@ -15,6 +16,16 @@ const person = $derived(data.person);
|
|||||||
const sentDocuments = $derived(data.sentDocuments);
|
const sentDocuments = $derived(data.sentDocuments);
|
||||||
const receivedDocuments = $derived(data.receivedDocuments);
|
const receivedDocuments = $derived(data.receivedDocuments);
|
||||||
|
|
||||||
|
const totalDocCount = $derived(sentDocuments.length + receivedDocuments.length);
|
||||||
|
const relCount = $derived(data.relationships.length + data.inferredRelationships.length);
|
||||||
|
|
||||||
|
const personMetaItems = $derived.by(() => {
|
||||||
|
const items: string[] = [];
|
||||||
|
if (totalDocCount > 0) items.push(m.person_meta_doc_count({ count: totalDocCount }));
|
||||||
|
if (relCount > 0) items.push(m.person_meta_rel_count({ count: relCount }));
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
const coCorrespondents = $derived.by(() => {
|
const coCorrespondents = $derived.by(() => {
|
||||||
const freq = new SvelteMap<string, { id: string; name: string; count: number }>();
|
const freq = new SvelteMap<string, { id: string; name: string; count: number }>();
|
||||||
|
|
||||||
@@ -61,6 +72,11 @@ const coCorrespondents = $derived.by(() => {
|
|||||||
<!-- Left column: Person card + name history -->
|
<!-- Left column: Person card + name history -->
|
||||||
<div>
|
<div>
|
||||||
<PersonCard person={person} canWrite={data.canWrite} />
|
<PersonCard person={person} canWrite={data.canWrite} />
|
||||||
|
{#if personMetaItems.length > 0}
|
||||||
|
<div class="mt-3">
|
||||||
|
<MetaLine items={personMetaItems} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<NameHistoryCard aliases={data.aliases} personFirstName={person.firstName} />
|
<NameHistoryCard aliases={data.aliases} personFirstName={person.firstName} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user