d34f682c28
dashboard-api.ts: fuenf neue Abrufe fuer die Reiter-Wege, die vier bestehenden Aufrufe (Layout/Widgets lesen/schreiben) tragen jetzt die Reiter-Kennung (Abfrageparameter bzw. Rumpf). dashboard-store.ts: Zustand um dashboards/activeDashboardId/ isSwitchingDashboard erweitert. loadDashboard() holt zuerst die Reiter, macht den ersten aktiv, laedt erst danach dessen Inhalt; ein modul-globales Versprechen schuetzt gegen doppeltes Laden der Reiterliste bei doppeltem Einhaengen. selectDashboard() schreibt eine ungespeicherte Anordnung ZUERST fuer den alten Reiter (Kennung vor dem Wechsel gelesen) und ersetzt danach Kacheln/Anordnung vollstaendig. createDashboard/renameDashboard/ deleteDashboard pflegen Reiterliste und aktiven Reiter; Loeschen des aktiven Reiters macht den dann ersten Reiter aktiv. Die Marker-Umrechnung (quick-260916-bwo) laeuft unveraendert je Reiter mit, auch beim Wechsel. dashboard-tabs.tsx (neu): Reiterleiste, Klick wechselt immer; im Bearbeitungsmodus zusaetzlich Anlegen, Umbenennen (an Ort und Stelle, Enter/Escape) und Loeschen (mit Rueckfrage) - Loeschen-Knopf fehlt beim letzten verbleibenden Reiter (D-10). Fokus beim Umbenennen ueber einen Ref statt autoFocus (lint/a11y/noAutofocus). (portal)/page.tsx: Leiste ueber dem Raster, kurze Ladezeile waehrend eines Reiterwechsels statt des Rasters - die Leiste bleibt stehen. DashboardGrid selbst unveraendert (D-06). Uebersetzungen: neue Schluessel unter widgets.tabs.* in de.json/en.json, Dialog-Knoepfe nutzen die vorhandenen common.cancel/common.delete. Deviations (Rule 3 - blockierende Nachwirkung dieses Tasks, ausserhalb der files_modified-Liste, aber direkt durch die dashboardId-Pflicht verursacht): - settings/dashboard/page.tsx: fetchWidgets() verlangt jetzt eine Reiter-Kennung; die Seite ist nicht reiterbewusst (ausserhalb des Umfangs) und zeigt jetzt die Kacheln des ERSTEN Reiters - deckungsgleich mit dem bisherigen Verhalten fuer den haeufigen Fall genau eines Reiters. - (portal)/page.test.tsx: mockStore brauchte die neuen Reiter-Felder/ -Methoden, sonst waere DashboardTabs auf `dashboards.map` von undefined gescheitert. Tests: dashboard-store.test.ts 15 (6 alte angepasste Signaturen + 9 neue), dashboard-tabs.test.tsx 12 (neu). web gesamt 682 Tests in 82 Dateien, dashboard-grid.test.tsx unveraendert bei 12. type-check 4/4, lint 5/5 mit weiterhin genau 53 Warnungen in web. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
174 lines
5.2 KiB
TypeScript
174 lines
5.2 KiB
TypeScript
/**
|
|
* Dashboard API client functions.
|
|
* All calls use credentials: 'include' for cookie-based auth.
|
|
*/
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
|
|
|
// --- Reiter (quick-260923-ad9) ---------------------------------------------
|
|
|
|
export interface DashboardTab {
|
|
id: string;
|
|
name: string;
|
|
position: number;
|
|
}
|
|
|
|
export async function fetchDashboards(): Promise<DashboardTab[]> {
|
|
const res = await fetch(`${API_URL}/dashboard/tabs`, {
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to fetch dashboards');
|
|
return res.json();
|
|
}
|
|
|
|
export async function createDashboardTab(): Promise<DashboardTab> {
|
|
const res = await fetch(`${API_URL}/dashboard/tabs`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to create dashboard');
|
|
return res.json();
|
|
}
|
|
|
|
export async function renameDashboardTab(id: string, name: string): Promise<DashboardTab> {
|
|
const res = await fetch(`${API_URL}/dashboard/tabs/${id}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to rename dashboard');
|
|
return res.json();
|
|
}
|
|
|
|
export async function deleteDashboardTab(id: string): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/tabs/${id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to delete dashboard');
|
|
}
|
|
|
|
export async function reorderDashboardTabs(ids: string[]): Promise<DashboardTab[]> {
|
|
const res = await fetch(`${API_URL}/dashboard/tabs/order`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ ids }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to reorder dashboards');
|
|
return res.json();
|
|
}
|
|
|
|
// --- Anordnung und Kacheln — je Reiter (quick-260923-ad9) ------------------
|
|
|
|
export async function fetchLayout(dashboardId: string): Promise<Record<string, unknown>> {
|
|
const res = await fetch(
|
|
`${API_URL}/dashboard/layout?dashboardId=${encodeURIComponent(dashboardId)}`,
|
|
{ credentials: 'include' },
|
|
);
|
|
if (!res.ok) throw new Error('Failed to fetch layout');
|
|
return res.json();
|
|
}
|
|
|
|
export async function saveLayout(
|
|
dashboardId: string,
|
|
layouts: Record<string, unknown>,
|
|
): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/layout`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ dashboardId, layouts }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to save layout');
|
|
}
|
|
|
|
export async function fetchWidgets(
|
|
dashboardId: string,
|
|
): Promise<Array<{ id: string; widgetType: string; config: Record<string, unknown> }>> {
|
|
const res = await fetch(
|
|
`${API_URL}/dashboard/widgets?dashboardId=${encodeURIComponent(dashboardId)}`,
|
|
{ credentials: 'include' },
|
|
);
|
|
if (!res.ok) throw new Error('Failed to fetch widgets');
|
|
return res.json();
|
|
}
|
|
|
|
export async function addWidget(
|
|
dashboardId: string,
|
|
widgetType: string,
|
|
): Promise<{ id: string; widgetType: string; config: Record<string, unknown> }> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ dashboardId, widgetType }),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to add widget');
|
|
return res.json();
|
|
}
|
|
|
|
export async function removeWidget(id: string): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets/${id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to remove widget');
|
|
}
|
|
|
|
export async function updateWidgetConfig(
|
|
id: string,
|
|
config: Record<string, unknown>,
|
|
signal?: AbortSignal,
|
|
): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/widgets/${id}/config`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({ config }),
|
|
signal,
|
|
});
|
|
if (!res.ok) throw new Error('Failed to update widget config');
|
|
}
|
|
|
|
// --- Search Provider API (05-02) ---
|
|
|
|
export interface SearchProviderResponse {
|
|
id: string;
|
|
name: string;
|
|
urlTemplate: string;
|
|
isDefault: boolean;
|
|
userId?: string | null;
|
|
}
|
|
|
|
export async function fetchSearchProviders(): Promise<SearchProviderResponse[]> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to fetch search providers');
|
|
return res.json();
|
|
}
|
|
|
|
export async function addSearchProvider(payload: {
|
|
name: string;
|
|
urlTemplate: string;
|
|
}): Promise<SearchProviderResponse> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to add search provider');
|
|
return res.json();
|
|
}
|
|
|
|
export async function removeSearchProvider(id: string): Promise<void> {
|
|
const res = await fetch(`${API_URL}/dashboard/search-providers/${id}`, {
|
|
method: 'DELETE',
|
|
credentials: 'include',
|
|
});
|
|
if (!res.ok) throw new Error('Failed to remove search provider');
|
|
}
|