refactor(quick-260922-m1h): Widget-Typen an einer Stelle, Katalog aus der Registry, Kachel kennt ihr Modul
Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine, fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab. - WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in packages/shared; Registry, Katalog und die @IsIn-Whitelist der API leiten davon ab - neun wireXWidget()-Funktionen durch ein generisches registerWidget() ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung) - der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der Dashboard-Seite, nicht im Dialog - widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die oeffentliche Funktion bleibt unveraendert Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der serverseitige Filter in DashboardService.getWidgets. Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die Dockerfiles kopieren packages/shared bereits, der Produktionsbau von Next.js und der nest build laufen unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { DashboardGrid } from '@/components/dashboard/dashboard-grid';
|
||||
import { EditModeToggle } from '@/components/dashboard/edit-mode-toggle';
|
||||
import { WidgetCatalogModal } from '@/components/dashboard/widget-catalog-modal';
|
||||
import { wireClockWidget, wireSearchWidget, wireCalendarWidget, wireNoteWidget, wireCalculatorWidget, wireStopwatchWidget, wireFavoritesWidget, wirePictureFrameWidget, wireXframeWidget } from '@/components/dashboard/widget-registry';
|
||||
import { registerWidget } from '@/components/dashboard/widget-registry';
|
||||
import { ClockWidget } from '@/components/dashboard/widgets/clock-widget';
|
||||
import { SearchWidget } from '@/components/dashboard/widgets/search-widget';
|
||||
import { CalendarWidget } from '@/components/dashboard/widgets/calendar-widget';
|
||||
@@ -18,20 +18,34 @@ import { XframeWidget } from '@/components/dashboard/widgets/xframe-widget';
|
||||
import { useDashboardStore } from '@/lib/stores/dashboard-store';
|
||||
import type { WidgetType } from '@/components/dashboard/widget-registry';
|
||||
|
||||
// Wire widget components into the registry (deferred to avoid circular deps)
|
||||
wireClockWidget(ClockWidget);
|
||||
wireSearchWidget(SearchWidget);
|
||||
wireCalendarWidget(CalendarWidget);
|
||||
wireNoteWidget(NoteWidget);
|
||||
wireCalculatorWidget(CalculatorWidget);
|
||||
wireStopwatchWidget(StopwatchWidget);
|
||||
wireFavoritesWidget(FavoritesWidget);
|
||||
wirePictureFrameWidget(PictureFrameWidget);
|
||||
wireXframeWidget(XframeWidget);
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||
|
||||
// Anmeldung der Kachel-Komponenten an der Registry. Steht hier und nicht in
|
||||
// der Registry selbst, weil die Komponenten ueber den Wrapper wieder die
|
||||
// Registry importieren — ein Import aus der Registry heraus waere ein
|
||||
// Zirkelimport. Seit quick-260922-m1h EINE Funktion statt neun `wireXWidget`.
|
||||
registerWidget('clock', ClockWidget);
|
||||
registerWidget('search', SearchWidget);
|
||||
registerWidget('calendar', CalendarWidget);
|
||||
registerWidget('note', NoteWidget);
|
||||
registerWidget('calculator', CalculatorWidget);
|
||||
registerWidget('stopwatch', StopwatchWidget);
|
||||
registerWidget('favorites', FavoritesWidget);
|
||||
registerWidget('picture-frame', PictureFrameWidget);
|
||||
registerWidget('xframe', XframeWidget);
|
||||
|
||||
/** Modul-Eintrag aus `GET /modules/active` — hier zaehlt nur der Slug. */
|
||||
interface ActiveModule {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const t = useTranslations('widgets');
|
||||
const [catalogOpen, setCatalogOpen] = useState(false);
|
||||
// quick-260922-m1h: Slugs der Module, die dieser Benutzer nutzen darf —
|
||||
// der Katalog blendet Kacheln gesperrter Module damit aus. `null` heisst
|
||||
// "noch unbekannt oder Abruf fehlgeschlagen" und ist fail-closed.
|
||||
const [accessibleModuleSlugs, setAccessibleModuleSlugs] = useState<string[] | null>(null);
|
||||
|
||||
const {
|
||||
layouts,
|
||||
@@ -51,6 +65,28 @@ export default function DashboardPage() {
|
||||
loadDashboard();
|
||||
}, [loadDashboard]);
|
||||
|
||||
// Zugaengliche Module holen — gleiches Muster wie die Seitenleiste
|
||||
// (`components/layout/sidebar.tsx`): derselbe Endpunkt, derselbe
|
||||
// Sitzungs-Keks, Fehler still. Der Abruf steht hier und nicht im Dialog,
|
||||
// damit der Dialog ein reines Anzeige-Bauteil bleibt.
|
||||
const fetchAccessibleModules = useCallback(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/modules/active`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const modules: ActiveModule[] = await res.json();
|
||||
setAccessibleModuleSlugs(modules.map((m) => m.slug));
|
||||
} catch {
|
||||
// still: die Liste bleibt null, der Katalog zeigt dann nur
|
||||
// Plattform-Kacheln (fail-closed).
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccessibleModules();
|
||||
}, [fetchAccessibleModules]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex min-h-[60vh] items-center justify-center">
|
||||
@@ -117,6 +153,7 @@ export default function DashboardPage() {
|
||||
<WidgetCatalogModal
|
||||
isOpen={catalogOpen}
|
||||
onClose={() => setCatalogOpen(false)}
|
||||
accessibleModuleSlugs={accessibleModuleSlugs}
|
||||
onAddWidget={(type: WidgetType) => {
|
||||
addWidget(type);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user