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:
@@ -2,28 +2,30 @@
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { WIDGET_REGISTRY, type WidgetType } from './widget-registry';
|
||||
import { WIDGET_REGISTRY, type WidgetType, visibleWidgetTypes } from './widget-registry';
|
||||
|
||||
interface WidgetCatalogModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onAddWidget: (type: WidgetType) => void;
|
||||
/**
|
||||
* Slugs der Module, die der angemeldete Benutzer nutzen darf — geholt von
|
||||
* der Dashboard-Seite ueber `GET /modules/active` (quick-260922-m1h).
|
||||
* `null` heisst "noch unbekannt oder Abruf fehlgeschlagen": dann bleiben
|
||||
* Kacheln MIT `moduleSlug` ausgeblendet (fail-closed).
|
||||
*
|
||||
* Der Abruf steht bewusst NICHT in diesem Dialog, damit er ein reines
|
||||
* Anzeige-Bauteil bleibt und ohne Netzwerk-Attrappe testbar ist.
|
||||
*/
|
||||
accessibleModuleSlugs: readonly string[] | null;
|
||||
}
|
||||
|
||||
const WIDGET_TYPES: WidgetType[] = [
|
||||
'clock',
|
||||
'search',
|
||||
'calendar',
|
||||
'note',
|
||||
'calculator',
|
||||
'favorites',
|
||||
'stopwatch',
|
||||
'picture-frame',
|
||||
'xframe',
|
||||
];
|
||||
|
||||
/**
|
||||
* Modal dialog showing available widget types as selectable cards.
|
||||
*
|
||||
* quick-260922-m1h: Die Liste kommt aus WIDGET_REGISTRY (Reihenfolge der
|
||||
* Registry-Definition) statt aus einer zweiten, hier gepflegten Liste — eine
|
||||
* neue Kachel musste sonst an zwei Stellen eingetragen werden.
|
||||
* Click on a card adds the widget to the dashboard and closes the modal.
|
||||
* Escape to close, click outside to close, focus trap (D-01 flow).
|
||||
*/
|
||||
@@ -31,6 +33,7 @@ export function WidgetCatalogModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onAddWidget,
|
||||
accessibleModuleSlugs,
|
||||
}: WidgetCatalogModalProps) {
|
||||
const t = useTranslations('widgets');
|
||||
const tCommon = useTranslations('common');
|
||||
@@ -107,13 +110,14 @@ export function WidgetCatalogModal({
|
||||
|
||||
{/* 2x2 grid of widget type cards */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{WIDGET_TYPES.map((type) => {
|
||||
{visibleWidgetTypes(WIDGET_REGISTRY, accessibleModuleSlugs).map((type) => {
|
||||
const def = WIDGET_REGISTRY[type];
|
||||
const Icon = def.icon;
|
||||
return (
|
||||
<button
|
||||
key={type}
|
||||
type="button"
|
||||
data-widget-type={type}
|
||||
onClick={() => {
|
||||
onAddWidget(type);
|
||||
onClose();
|
||||
|
||||
Reference in New Issue
Block a user