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>
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { useTranslations } from 'next-intl';
|
|
import { WidgetSettingsPanel } from '@/components/settings/widget-settings-panel';
|
|
import { fetchDashboards, fetchWidgets } from '@/lib/dashboard-api';
|
|
|
|
/**
|
|
* Settings > Dashboard > Widgets page (D-03).
|
|
* Fetches the widget instances of the user's FIRST dashboard tab (the
|
|
* standard tab, quick-260923-ad9) and renders per-instance config forms.
|
|
*
|
|
* quick-260923-ad9 (Task 3, deviation Rule 3 — blocking compile issue):
|
|
* `fetchWidgets` now requires a `dashboardId` since widgets are scoped per
|
|
* tab. This page is not itself tab-aware (out of this plan's scope — see
|
|
* "Nicht im Umfang") and previously showed every widget the user had, back
|
|
* when there was exactly one dashboard per user; it now shows the first
|
|
* tab's widgets, which is the same set for the (overwhelmingly common)
|
|
* case of a user who has not yet created a second tab.
|
|
*/
|
|
export default function WidgetSettingsPage() {
|
|
const t = useTranslations('settings');
|
|
const tCommon = useTranslations('common');
|
|
const [widgets, setWidgets] = useState<
|
|
Array<{ id: string; widgetType: string; config: Record<string, unknown> }>
|
|
>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
try {
|
|
const dashboards = await fetchDashboards();
|
|
const first = dashboards[0];
|
|
setWidgets(first ? await fetchWidgets(first.id) : []);
|
|
} catch {
|
|
// Silent fail — empty widget list shown
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
})();
|
|
}, []);
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="mb-6 text-lg font-semibold text-foreground">
|
|
{t('categoryWidgets')}
|
|
</h1>
|
|
|
|
{isLoading ? (
|
|
<p className="text-sm text-muted-foreground">{tCommon('loading')}</p>
|
|
) : widgets.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">
|
|
{t('widgets.empty')}
|
|
</p>
|
|
) : (
|
|
<WidgetSettingsPanel
|
|
widgets={widgets}
|
|
onWidgetUpdate={(id, config) => {
|
|
setWidgets((prev) =>
|
|
prev.map((w) =>
|
|
w.id === id ? { ...w, config: { ...w.config, ...config } } : w,
|
|
),
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|