969fd01f5c
useButtonType: "Widget hinzufuegen"-, Bearbeiten-Modus- und Katalog-Buttons
(kein Formular in diesen Dateien) erhalten type="button".
noSvgWithoutTitle: Widget-Icons in der Katalog-Kachel (immer neben dem
Widget-Namen) und Buttons mit bestehendem aria-label/title werden dekorativ
(aria-hidden="true"); der bislang unbeschriftete Such-Button im Such-Widget
erhaelt aria-label={t('search.searchButton')} (neuer Schluessel, siehe
vorherige i18n-Festschreibung).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import { 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 } 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';
|
|
import { NoteWidget } from '@/components/dashboard/widgets/note-widget';
|
|
import { CalculatorWidget } from '@/components/dashboard/widgets/calculator-widget';
|
|
import { StopwatchWidget } from '@/components/dashboard/widgets/stopwatch-widget';
|
|
import { FavoritesWidget } from '@/components/dashboard/widgets/favorites-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);
|
|
|
|
export default function DashboardPage() {
|
|
const t = useTranslations('widgets');
|
|
const [catalogOpen, setCatalogOpen] = useState(false);
|
|
|
|
const {
|
|
layouts,
|
|
widgets,
|
|
isEditMode,
|
|
isLoading,
|
|
error,
|
|
setEditMode,
|
|
updateLayouts,
|
|
addWidget,
|
|
removeWidget,
|
|
loadDashboard,
|
|
} = useDashboardStore();
|
|
|
|
// Load dashboard data on mount
|
|
useEffect(() => {
|
|
loadDashboard();
|
|
}, [loadDashboard]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex min-h-[60vh] items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">Loading...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex min-h-[60vh] items-center justify-center">
|
|
<p className="text-sm text-destructive">{t('layoutLoadError')}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="relative p-2">
|
|
{/* Dashboard grid — direkt im Container, ohne Abstands-Wrapper. */}
|
|
<DashboardGrid
|
|
layouts={layouts}
|
|
widgets={widgets}
|
|
isEditMode={isEditMode}
|
|
onLayoutChange={updateLayouts}
|
|
onRemoveWidget={removeWidget}
|
|
/>
|
|
|
|
{/* Feste Aktionsleiste unten rechts.
|
|
quick-260916-dyv: Umschalter unten rechts statt oben rechts, damit das
|
|
Grid direkt unter der Kopfzeile beginnt (12 + 8 + 8 = 28 px statt 60 px).
|
|
Im Bearbeitungsmodus steht "Widget hinzufuegen" links neben dem Haekchen. */}
|
|
<div className="fixed bottom-6 right-6 z-20 flex items-center gap-2">
|
|
{isEditMode && (
|
|
<button
|
|
type="button"
|
|
onClick={() => setCatalogOpen(true)}
|
|
className="flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-lg hover:opacity-90 transition-opacity"
|
|
>
|
|
<svg
|
|
aria-hidden="true"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<line x1="12" y1="5" x2="12" y2="19" />
|
|
<line x1="5" y1="12" x2="19" y2="12" />
|
|
</svg>
|
|
{t('addWidget')}
|
|
</button>
|
|
)}
|
|
<EditModeToggle
|
|
isEditMode={isEditMode}
|
|
onToggle={() => setEditMode(!isEditMode)}
|
|
/>
|
|
</div>
|
|
|
|
{/* Widget catalog modal */}
|
|
<WidgetCatalogModal
|
|
isOpen={catalogOpen}
|
|
onClose={() => setCatalogOpen(false)}
|
|
onAddWidget={(type: WidgetType) => {
|
|
addWidget(type);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|