feat(documents): show global undated count chip on the filter toggle
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m50s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Failing after 4m3s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 21s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m3s
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m50s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Failing after 4m3s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 21s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m3s
Surface the backend's global undatedCount on the "Nur undatierte" toggle as a count chip — the total undated documents matching the current filter across all pages, not the page slice. The loader forwards undatedCount straight through (defaulting to 0); the chip hides at 0 and stays visible regardless of the toggle state so it advertises the triage backlog size. generate:api was hand-edited (undatedCount added to DocumentSearchResult) — CI must re-run npm run generate:api to confirm parity. Refs #668 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2471,6 +2471,8 @@ export interface components {
|
||||
pageSize: number;
|
||||
/** Format: int32 */
|
||||
totalPages: number;
|
||||
/** Format: int64 */
|
||||
undatedCount: number;
|
||||
};
|
||||
MatchOffset: {
|
||||
/** Format: int32 */
|
||||
|
||||
@@ -16,6 +16,7 @@ let {
|
||||
tagQ = $bindable(''),
|
||||
tagOperator = $bindable<'AND' | 'OR'>('AND'),
|
||||
undated = $bindable(false),
|
||||
undatedCount = 0,
|
||||
sort = $bindable('DATE'),
|
||||
dir = $bindable('desc'),
|
||||
showAdvanced = $bindable(false),
|
||||
@@ -37,6 +38,7 @@ let {
|
||||
tagQ?: string;
|
||||
tagOperator?: 'AND' | 'OR';
|
||||
undated?: boolean;
|
||||
undatedCount?: number;
|
||||
sort?: string;
|
||||
dir?: string;
|
||||
showAdvanced?: boolean;
|
||||
@@ -275,6 +277,18 @@ $effect(() => {
|
||||
{#if undated}✓{/if}
|
||||
</span>
|
||||
{m.docs_filter_undated_only()}
|
||||
<!-- Global count of undated docs matching the current filter across ALL
|
||||
pages (not the page slice). Stays visible regardless of the toggle
|
||||
state so it advertises the triage backlog size (issue #668). -->
|
||||
{#if undatedCount > 0}
|
||||
<span
|
||||
data-testid="undated-count"
|
||||
class="inline-flex min-w-[1.5rem] items-center justify-center rounded-full px-1.5 py-0.5 text-[0.65rem] leading-none tabular-nums {undated
|
||||
? 'bg-primary-fg/20 text-primary-fg'
|
||||
: 'bg-line text-ink-2'}"
|
||||
>{undatedCount}</span
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -162,6 +162,20 @@ describe('SearchFilterBar – undated-only toggle (#668)', () => {
|
||||
await page.getByTestId('undated-only-toggle').click();
|
||||
await expect.poll(() => onSearchImmediate.mock.calls.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('shows the global undated count chip when undatedCount > 0', async () => {
|
||||
// The count is the backend's global filtered total (#668), passed straight
|
||||
// through — the chip must render it verbatim, not a page-derived number.
|
||||
render(SearchFilterBar, { ...defaultProps, sort: 'DATE', dir: 'desc', undatedCount: 42 });
|
||||
await openAdvanced();
|
||||
await expect.element(page.getByTestId('undated-count')).toHaveTextContent('42');
|
||||
});
|
||||
|
||||
it('hides the undated count chip when undatedCount is 0', async () => {
|
||||
render(SearchFilterBar, { ...defaultProps, sort: 'DATE', dir: 'desc', undatedCount: 0 });
|
||||
await openAdvanced();
|
||||
await expect.element(page.getByTestId('undated-count')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SearchFilterBar – tagQ live filter', () => {
|
||||
|
||||
@@ -85,6 +85,7 @@ export async function load({ url, fetch }) {
|
||||
pageNumber: 0,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalPages: 0,
|
||||
undatedCount: 0,
|
||||
q,
|
||||
from,
|
||||
to,
|
||||
@@ -116,6 +117,8 @@ export async function load({ url, fetch }) {
|
||||
pageNumber: result.data?.pageNumber ?? page,
|
||||
pageSize: result.data?.pageSize ?? PAGE_SIZE,
|
||||
totalPages: result.data?.totalPages ?? 0,
|
||||
// Global undated count for the active filter, across all pages (issue #668).
|
||||
undatedCount: result.data?.undatedCount ?? 0,
|
||||
q,
|
||||
from,
|
||||
to,
|
||||
|
||||
@@ -268,6 +268,7 @@ $effect(() => {
|
||||
bind:tagQ={tagQ}
|
||||
bind:tagOperator={tagOperator}
|
||||
bind:undated={undated}
|
||||
undatedCount={data.undatedCount ?? 0}
|
||||
initialSenderName={initialSenderName}
|
||||
initialReceiverName={initialReceiverName}
|
||||
navKey={navKey}
|
||||
|
||||
@@ -225,6 +225,51 @@ describe('documents page load — search params', () => {
|
||||
expect(result.totalElements).toBe(42);
|
||||
});
|
||||
|
||||
it('forwards the global undatedCount from the search result (#668)', async () => {
|
||||
// The backend returns the global undated total for the active filter across
|
||||
// ALL pages; the loader must pass it straight through, not recompute it locally.
|
||||
const mockGet = vi.fn().mockResolvedValue({
|
||||
response: { ok: true, status: 200 },
|
||||
data: {
|
||||
items: [],
|
||||
totalElements: 200,
|
||||
pageNumber: 0,
|
||||
pageSize: 50,
|
||||
totalPages: 4,
|
||||
undatedCount: 73
|
||||
}
|
||||
});
|
||||
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
||||
typeof createApiClient
|
||||
>);
|
||||
|
||||
const result = await load({
|
||||
url: makeUrl({ q: 'test' }),
|
||||
request: new Request('http://localhost/documents'),
|
||||
fetch: vi.fn() as unknown as typeof fetch
|
||||
});
|
||||
|
||||
expect(result.undatedCount).toBe(73);
|
||||
});
|
||||
|
||||
it('defaults undatedCount to 0 when the search result omits it', async () => {
|
||||
const mockGet = vi.fn().mockResolvedValue({
|
||||
response: { ok: true, status: 200 },
|
||||
data: { items: [], totalElements: 0, pageNumber: 0, pageSize: 50, totalPages: 0 }
|
||||
});
|
||||
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
|
||||
typeof createApiClient
|
||||
>);
|
||||
|
||||
const result = await load({
|
||||
url: makeUrl(),
|
||||
request: new Request('http://localhost/documents'),
|
||||
fetch: vi.fn() as unknown as typeof fetch
|
||||
});
|
||||
|
||||
expect(result.undatedCount).toBe(0);
|
||||
});
|
||||
|
||||
it('returns filter values in the result for pre-filling the UI', async () => {
|
||||
const mockGet = vi.fn().mockResolvedValue({
|
||||
response: { ok: true, status: 200 },
|
||||
|
||||
Reference in New Issue
Block a user