feat(documents): explain that a date range excludes undated documents

DocumentList gains from/to props; when a date range is active and yields no
results, the empty state shows the localized docs_range_excludes_undated
note instead of the generic copy, so the reader understands undated letters
aren't part of a range. Person-grouped modes keep undated letters under
their sender/receiver (badge-on-row, no synthetic sub-group).

Refs #668
This commit is contained in:
Marcel
2026-05-27 18:50:18 +02:00
parent bca3f34cec
commit 5d8bb70255
3 changed files with 102 additions and 2 deletions

View File

@@ -15,7 +15,9 @@ let {
error,
total = 0,
q = '',
sort = 'DATE'
sort = 'DATE',
from = '',
to = ''
}: {
items: DocumentListItem[];
canWrite: boolean;
@@ -23,8 +25,15 @@ let {
total?: number;
q?: string;
sort?: SortMode;
from?: string;
to?: string;
} = $props();
// A from/to range excludes undated documents — when it yields nothing, the
// empty state must say so explicitly (a localized constant, never a reflected
// backend string). Issue #668.
const hasDateRange = $derived(!!from || !!to);
const groups = $derived.by(() => {
if (sort === 'SENDER') return groupBySender(items);
if (sort === 'RECEIVER') return groupByReceiver(items);
@@ -119,7 +128,13 @@ function groupByReceiver(docItems: DocumentListItem[]) {
</div>
<h3 class="font-serif text-lg font-medium text-ink">{m.docs_empty_heading()}</h3>
<p class="mt-1 font-sans text-sm text-ink-2">
{q ? m.docs_empty_for_term({ term: q }) : m.docs_empty_text()}
{#if hasDateRange}
{m.docs_range_excludes_undated()}
{:else if q}
{m.docs_empty_for_term({ term: q })}
{:else}
{m.docs_empty_text()}
{/if}
</p>
<button
onclick={() => goto('/documents')}

View File

@@ -16,6 +16,7 @@ function makeItem(overrides: Partial<DocumentListItem> = {}): DocumentListItem {
title: 'Testbrief',
originalFilename: 'testbrief.pdf',
documentDate: '2024-03-15',
metaDatePrecision: 'DAY',
sender: undefined,
receivers: [],
tags: [],
@@ -278,3 +279,85 @@ describe('DocumentList DocumentRow delegation', () => {
await expect.element(mark).toHaveTextContent('Brief');
});
});
// ─── Undated badge in person-grouped modes (#668) ────────────────────────────
describe('DocumentList undated badge in person grouping', () => {
const sender = {
id: 's1',
lastName: 'Mustermann',
displayName: 'Max Mustermann',
personType: 'PERSON' as const,
familyMember: false,
provisional: false
};
const receiver = {
id: 'r1',
lastName: 'Brandt',
displayName: 'Felix Brandt',
personType: 'PERSON' as const,
familyMember: false,
provisional: false
};
it('shows the undated badge on a row under SENDER grouping', async () => {
const items = [
makeItem({ id: '1', documentDate: undefined, metaDatePrecision: 'UNKNOWN', sender })
];
render(DocumentList, { ...baseProps, items, total: 1, sort: 'SENDER' });
await expect.element(page.getByTestId('undated-badge').first()).toBeInTheDocument();
});
it('shows the undated badge on a row under RECEIVER grouping', async () => {
const items = [
makeItem({
id: '1',
documentDate: undefined,
metaDatePrecision: 'UNKNOWN',
receivers: [receiver]
})
];
render(DocumentList, { ...baseProps, items, total: 1, sort: 'RECEIVER' });
await expect.element(page.getByTestId('undated-badge').first()).toBeInTheDocument();
});
it('keeps an undated letter under its sender, not in a synthetic undated sub-group', async () => {
const items = [
makeItem({ id: '1', documentDate: '1916-06-15', metaDatePrecision: 'DAY', sender }),
makeItem({ id: '2', documentDate: undefined, metaDatePrecision: 'UNKNOWN', sender })
];
render(DocumentList, { ...baseProps, items, total: 2, sort: 'SENDER' });
// One sender card; no "Undatiert" group header inside person-grouped mode.
await expect
.element(page.getByTestId('group-header').filter({ hasText: 'Max Mustermann' }))
.toBeInTheDocument();
await expect.element(page.getByText('Undatiert')).not.toBeInTheDocument();
const cards = page.getByTestId('group-card');
await expect.element(cards.nth(1)).not.toBeInTheDocument();
});
});
// ─── Date-range / undated empty state (#668) ─────────────────────────────────
describe('DocumentList range-excludes-undated empty state', () => {
it('explains that a date range excludes undated documents when from/to active and no results', async () => {
render(DocumentList, {
...baseProps,
items: [],
total: 0,
from: '1920-01-01',
to: '1930-12-31'
});
await expect
.element(page.getByText(/Datumsfilter schließt undatierte Dokumente aus/))
.toBeInTheDocument();
});
it('shows the generic empty state when no date range is active', async () => {
render(DocumentList, { ...baseProps, items: [], total: 0 });
await expect.element(page.getByText(/Keine Dokumente/)).toBeInTheDocument();
await expect
.element(page.getByText(/Datumsfilter schließt undatierte Dokumente aus/))
.not.toBeInTheDocument();
});
});

View File

@@ -343,6 +343,8 @@ $effect(() => {
canWrite={data.canWrite}
error={data.error}
sort={sort}
from={data.from}
to={data.to}
/>
<Pagination page={data.pageNumber} totalPages={data.totalPages} makeHref={buildPageHref} />