From 01e72611f04429ddc92f5520d825492ce2244d38 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 20 Apr 2026 21:34:23 +0200 Subject: [PATCH] feat(dashboard): redesign needs-metadata with row anatomy + totalCount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches to two props — topDocs (max 5, capped by caller) and totalCount — so the footer link can surface "Alle 12 anzeigen →" even when only 5 items are shown. Each row gets a generic document icon, title, relative upload time and a chevron, wrapped in a single per the issue spec. Still returns null when topDocs is empty, keeping the empty dashboard clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/DashboardNeedsMetadata.svelte | 66 ++++++++++++++----- .../DashboardNeedsMetadata.svelte.spec.ts | 56 +++++++++------- 2 files changed, 83 insertions(+), 39 deletions(-) diff --git a/frontend/src/lib/components/DashboardNeedsMetadata.svelte b/frontend/src/lib/components/DashboardNeedsMetadata.svelte index cb197418..d7203a8b 100644 --- a/frontend/src/lib/components/DashboardNeedsMetadata.svelte +++ b/frontend/src/lib/components/DashboardNeedsMetadata.svelte @@ -1,37 +1,73 @@ -{#if incompleteDocs.length > 0} +{#if topDocs.length > 0}

{m.dashboard_needs_metadata_heading()}

- {#each incompleteDocs as doc (doc.id)} -
{/if} diff --git a/frontend/src/lib/components/DashboardNeedsMetadata.svelte.spec.ts b/frontend/src/lib/components/DashboardNeedsMetadata.svelte.spec.ts index ee2fdeb8..b08e32ff 100644 --- a/frontend/src/lib/components/DashboardNeedsMetadata.svelte.spec.ts +++ b/frontend/src/lib/components/DashboardNeedsMetadata.svelte.spec.ts @@ -6,44 +6,52 @@ import DashboardNeedsMetadata from './DashboardNeedsMetadata.svelte'; afterEach(cleanup); -type IncompleteDocumentDTO = { - id: string; - title: string; -}; +type IncompleteDoc = { id: string; title: string; uploadedAt: string }; -function makeDoc(id: string, title: string): IncompleteDocumentDTO { - return { id, title }; +function makeDoc(id: string, title: string, uploadedAt = '2026-04-20T12:00:00'): IncompleteDoc { + return { id, title, uploadedAt }; } describe('DashboardNeedsMetadata', () => { - it('renders nothing when incompleteDocs is empty', async () => { - render(DashboardNeedsMetadata, { incompleteDocs: [] }); + it('renders nothing when topDocs is empty', async () => { + render(DashboardNeedsMetadata, { topDocs: [], totalCount: 0 }); const widget = page.getByTestId('dashboard-needs-metadata'); await expect.element(widget).not.toBeInTheDocument(); }); - it('shows the widget when incompleteDocs are present', async () => { - render(DashboardNeedsMetadata, { incompleteDocs: [makeDoc('d1', 'Taufschein')] }); - const widget = page.getByTestId('dashboard-needs-metadata'); - await expect.element(widget).toBeInTheDocument(); + it('shows the widget when topDocs is present', async () => { + render(DashboardNeedsMetadata, { topDocs: [makeDoc('d1', 'Taufschein')], totalCount: 1 }); + await expect.element(page.getByTestId('dashboard-needs-metadata')).toBeInTheDocument(); }); - it('renders a link to /enrich/{id} for each document', async () => { + it('renders one link per row pointing at /enrich/{id}', async () => { const docs = [makeDoc('d1', 'Taufschein'), makeDoc('d2', 'Heiratsurkunde')]; - render(DashboardNeedsMetadata, { incompleteDocs: docs }); - const links = page.getByRole('link'); - await expect.element(links.nth(0)).toHaveAttribute('href', '/enrich/d1'); - await expect.element(links.nth(1)).toHaveAttribute('href', '/enrich/d2'); + render(DashboardNeedsMetadata, { topDocs: docs, totalCount: 2 }); + await expect + .element(page.getByRole('link', { name: /Taufschein/ })) + .toHaveAttribute('href', '/enrich/d1'); + await expect + .element(page.getByRole('link', { name: /Heiratsurkunde/ })) + .toHaveAttribute('href', '/enrich/d2'); }); - it('shows the document title in each row', async () => { - render(DashboardNeedsMetadata, { incompleteDocs: [makeDoc('d1', 'Sterbeurkunde 1930')] }); - await expect.element(page.getByText('Sterbeurkunde 1930')).toBeInTheDocument(); + it('hides the footer link when totalCount is 5 or fewer', async () => { + const docs = Array.from({ length: 5 }, (_, i) => makeDoc(`d${i}`, `Dok ${i}`)); + render(DashboardNeedsMetadata, { topDocs: docs, totalCount: 5 }); + const footer = page.getByRole('link', { name: /Alle/i }); + await expect.element(footer).not.toBeInTheDocument(); }); - it('shows a "Alle anzeigen" link to /enrich', async () => { - render(DashboardNeedsMetadata, { incompleteDocs: [makeDoc('d1', 'Dok')] }); - const allLink = page.getByRole('link', { name: /Alle anzeigen/i }); - await expect.element(allLink).toHaveAttribute('href', '/enrich'); + it('shows the footer link with totalCount when totalCount > 5', async () => { + const docs = Array.from({ length: 5 }, (_, i) => makeDoc(`d${i}`, `Dok ${i}`)); + render(DashboardNeedsMetadata, { topDocs: docs, totalCount: 12 }); + const footer = page.getByRole('link', { name: /12/ }); + await expect.element(footer).toHaveAttribute('href', '/enrich'); + }); + + it('uses totalCount in the footer even when topDocs has fewer items', async () => { + const docs = [makeDoc('d1', 'Only one')]; + render(DashboardNeedsMetadata, { topDocs: docs, totalCount: 50 }); + await expect.element(page.getByRole('link', { name: /50/ })).toBeInTheDocument(); }); });