'use client'; import { useTranslations } from 'next-intl'; import { useCallback, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { BackgroundPicker, DashboardBackdrop, useDashboardBackground, } from '@/components/dashboard/dashboard-background'; import { DashboardGrid } from '@/components/dashboard/dashboard-grid'; import { DashboardTabs } from '@/components/dashboard/dashboard-tabs'; import { EditModeToggle } from '@/components/dashboard/edit-mode-toggle'; import { WidgetCatalogModal } from '@/components/dashboard/widget-catalog-modal'; import type { WidgetType } from '@/components/dashboard/widget-registry'; import { registerWidget } from '@/components/dashboard/widget-registry'; import { CalculatorWidget } from '@/components/dashboard/widgets/calculator-widget'; import { CalendarWidget } from '@/components/dashboard/widgets/calendar-widget'; import { ClockWidget } from '@/components/dashboard/widgets/clock-widget'; import { FavoritesWidget } from '@/components/dashboard/widgets/favorites-widget'; import { NoteWidget } from '@/components/dashboard/widgets/note-widget'; import { PictureFrameWidget } from '@/components/dashboard/widgets/picture-frame-widget'; import { ProxmoxWidget } from '@/components/dashboard/widgets/proxmox-widget'; import { ReminderWidget } from '@/components/dashboard/widgets/reminder-widget'; import { SearchWidget } from '@/components/dashboard/widgets/search-widget'; import { StopwatchWidget } from '@/components/dashboard/widgets/stopwatch-widget'; import { XframeWidget } from '@/components/dashboard/widgets/xframe-widget'; import { HEADER_ACTIONS_SLOT_ID } from '@/components/layout/header-slot'; import { useDashboardStore } from '@/lib/stores/dashboard-store'; 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); registerWidget('proxmox', ProxmoxWidget); registerWidget('reminder', ReminderWidget); /** 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); // Seitlicher Versatz der zentrierten Kacheln — die Zeile mit Begruessung // und Befehlsleiste rueckt mit, damit sie buendig ueber den Kacheln steht. // Einhaengepunkt der Kopfzeile fuer die Dashboard-Aktionen (quick-260928-vxe) const [actionsSlot, setActionsSlot] = useState(null); useEffect(() => { setActionsSlot(document.getElementById(HEADER_ACTIONS_SLOT_ID)); }, []); // Hintergrund je Benutzer (Design „Mosaik“), seit quick-260928-ujj in der // Datenbank gespeichert und mit der Sitzung geladen. const { background, choose: chooseBackground } = useDashboardBackground(); const hasBackground = background.kind !== 'none'; // 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(null); const { dashboards, activeDashboardId, isSwitchingDashboard, layouts, canvas, widgets, isEditMode, isLoading, error, setEditMode, updateLayouts, addWidget, removeWidget, loadDashboard, selectDashboard, createDashboard, renameDashboard, deleteDashboard, reorderDashboards, captureCanvas, } = useDashboardStore(); // Load dashboard data on mount useEffect(() => { 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 (

Loading...

); } if (error) { return (

{t('layoutLoadError')}

); } const actions = (
{isEditMode && } {isEditMode && ( )} setEditMode(!isEditMode)} />
); return (
{/* Reiterleiste (quick-260923-ad9) — seit quick-260924-h7x per Portal in der Kopfzeile, hier nimmt sie keinen Platz mehr ein. Sie bleibt waehrend eines Reiterwechsels stehen, nur das Raster zeigt eine kurze Ladezeile. */} {/* Dashboard-Aktionen (quick-260928-vxe): per Portal rechts in der App-Leiste statt als eigene Zeile ueber dem Raster. Fehlt der Einhaengepunkt (etwa in Tests ohne Kopfzeile), stehen sie hier. */} {actionsSlot ? createPortal(actions, actionsSlot) : actions} {isSwitchingDashboard ? (

{t('tabs.switching')}

) : ( /* Dashboard grid — direkt im Container, ohne Abstands-Wrapper. */ setCatalogOpen(true)} onQuickAdd={(type) => addWidget(type)} canvas={canvas} onCaptureCanvas={captureCanvas} /> )} {/* Widget catalog modal */} setCatalogOpen(false)} accessibleModuleSlugs={accessibleModuleSlugs} onAddWidget={(type: WidgetType) => { addWidget(type); }} />
); }