feat(11-05): read/favorite triage toggles + Merklisten-Filter in the UI
Adds fetchTriage()/setTriage() to tender-radar-api.ts (plain fetch, consistent with the existing client). ResultsList batch-fetches the current user's triage state for the visible ids and merges it into a local per-tenderId map; a failed triage fetch never blocks rendering the list itself. Each row gets a Gelesen/Ungelesen and a Favorit toggle (optimistic update with revert-on-failure, event.stopPropagation() so the row's own click-to-open-detail doesn't fire); read rows render dimmed. FilterPanel gains a "Nur Favoriten/Merkliste" checkbox writing favOnly into the URL, which ResultsList already forwards generically to the backend. ResultsList.test.tsx extended (Rule 3 — required to keep the component test green with the new triage batch call) with coverage for batch-merge, both toggles, optimistic revert, and graceful degradation when the triage fetch fails. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -151,3 +151,56 @@ export async function getTender(id: string): Promise<Tender> {
|
||||
if (!res.ok) throw new Error('Failed to fetch tender detail');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* A single per-user triage row (Plan 11-05, UI-03/04). Per-user, NOT
|
||||
* tenant-wide (D-11) — the backend derives userId from the auth cookie,
|
||||
* this client never sends a userId.
|
||||
*/
|
||||
export interface TriageEntry {
|
||||
tenderId: string;
|
||||
isRead: boolean;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
/** Payload accepted by PUT /modules/tender-radar/triage. */
|
||||
export interface SetTriagePayload {
|
||||
tenderId: string;
|
||||
isRead?: boolean;
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-fetch this user's triage state (gelesen/ungelesen, Favorit) for a
|
||||
* set of tenderIds (Plan 11-05, UI-03/04) — used by ResultsList to merge
|
||||
* triage state into the visible page in one round-trip.
|
||||
* GET /modules/tender-radar/triage?ids=<csv>
|
||||
*/
|
||||
export async function fetchTriage(ids: string[]): Promise<TriageEntry[]> {
|
||||
if (!ids.length) return [];
|
||||
const params = new URLSearchParams({ ids: ids.join(',') });
|
||||
const res = await fetch(
|
||||
`${API_URL}/modules/tender-radar/triage?${params}`,
|
||||
{ credentials: 'include' },
|
||||
);
|
||||
if (!res.ok) throw new Error('Failed to fetch tender triage');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert this user's triage state for one tender (Plan 11-05, UI-03/04).
|
||||
* Idempotent on the backend (@@unique([userId,tenderId])).
|
||||
* PUT /modules/tender-radar/triage
|
||||
*/
|
||||
export async function setTriage(
|
||||
payload: SetTriagePayload,
|
||||
): Promise<TriageEntry> {
|
||||
const res = await fetch(`${API_URL}/modules/tender-radar/triage`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to save tender triage');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user