diff --git a/apps/web/src/app/(portal)/page.tsx b/apps/web/src/app/(portal)/page.tsx index ad9523c..a4d6279 100644 --- a/apps/web/src/app/(portal)/page.tsx +++ b/apps/web/src/app/(portal)/page.tsx @@ -5,6 +5,8 @@ import { useTranslations } from 'next-intl'; import { DashboardGrid } from '@/components/dashboard/dashboard-grid'; import { DashboardTabs } from '@/components/dashboard/dashboard-tabs'; import { EditModeToggle } from '@/components/dashboard/edit-mode-toggle'; +import { BackgroundPicker, DashboardBackdrop, useDashboardBackground } from '@/components/dashboard/dashboard-background'; +import { useAuthStore } from '@/lib/stores/auth-store'; import { WidgetCatalogModal } from '@/components/dashboard/widget-catalog-modal'; import { registerWidget } from '@/components/dashboard/widget-registry'; import { ClockWidget } from '@/components/dashboard/widgets/clock-widget'; @@ -45,6 +47,10 @@ interface ActiveModule { export default function DashboardPage() { const t = useTranslations('widgets'); const [catalogOpen, setCatalogOpen] = useState(false); + // Hintergrund je Benutzer (Design „Mosaik“, Prototyp mit localStorage). + const userId = useAuthStore((s) => s.user?.id); + const { background, choose: chooseBackground } = useDashboardBackground(userId); + 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. @@ -115,7 +121,8 @@ export default function DashboardPage() { } 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 @@ -151,11 +158,12 @@ export default function DashboardPage() { Grid direkt unter der Kopfzeile beginnt (12 + 8 + 8 = 28 px statt 60 px). Im Bearbeitungsmodus steht "Widget hinzufuegen" links neben dem Haekchen. */}
+ {isEditMode && } {isEditMode && ( + + ); + }; + + return ( +
+ + + {open && ( +
+

{t('title')}

+

{t('hint')}

+ +

{t('builtIn')}

+
    + {option('none', t('none'), NO_BACKGROUND, { backgroundColor: 'var(--background)' }, + , + )} + {BACKGROUND_PRESETS.map((preset) => { + const css = dark ? preset.dark : preset.light; + return option( + preset.id, + t(`presets.${preset.labelKey}`), + { kind: 'preset', id: preset.id }, + css.startsWith('url(') + ? { backgroundImage: css, backgroundSize: 'cover', backgroundPosition: 'center' } + : { backgroundColor: css }, + ); + })} +
+ +

{t('ownImages')}

+
    + {(images ?? []).map((image) => + option( + image.id, + image.originalName, + { kind: 'image', imageId: image.id }, + { + backgroundImage: `url("${dashboardImageSrc(image.id)}")`, + backgroundSize: 'cover', + backgroundPosition: 'center', + }, + ), + )} +
  • + +
  • +
+ handleUpload(e.target.files?.[0])} + /> + {images !== null && images.length === 0 && ( +

{t('noImages')}

+ )} + {error && ( +

+ {error} +

+ )} +
+ )} +
+ ); +} diff --git a/apps/web/src/components/dashboard/dashboard-grid.test.tsx b/apps/web/src/components/dashboard/dashboard-grid.test.tsx index 2220bff..43fc340 100644 --- a/apps/web/src/components/dashboard/dashboard-grid.test.tsx +++ b/apps/web/src/components/dashboard/dashboard-grid.test.tsx @@ -165,7 +165,7 @@ describe('DashboardGrid', () => { expect(document.querySelector('.widget-drag-handle')).toBeInTheDocument(); }); - it('quick-260916-bwo Test 4: Grid-Props — 24/20/12/8/2 Spalten, rowHeight 20, margin 8, Breakpoints unveraendert, containerPadding folgt dem margin', async () => { + it('quick-260916-bwo Test 4: Grid-Props — 24/20/12/8/2 Spalten, rowHeight 20, margin 12, Breakpoints unveraendert, containerPadding folgt dem margin', async () => { captured.props = null; const { DashboardGrid } = await import('./dashboard-grid'); render( @@ -181,7 +181,7 @@ describe('DashboardGrid', () => { expect(captured.props).not.toBeNull(); expect(captured.props?.cols).toEqual({ lg: 24, md: 20, sm: 12, xs: 8, xxs: 2 }); expect(captured.props?.rowHeight).toBe(20); - expect(captured.props?.margin).toEqual([8, 8]); + expect(captured.props?.margin).toEqual([12, 12]); expect(captured.props?.breakpoints).toEqual({ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }); expect(captured.props?.containerPadding).toBeUndefined(); }); diff --git a/apps/web/src/components/dashboard/dashboard-grid.tsx b/apps/web/src/components/dashboard/dashboard-grid.tsx index 854921e..2d15867 100644 --- a/apps/web/src/components/dashboard/dashboard-grid.tsx +++ b/apps/web/src/components/dashboard/dashboard-grid.tsx @@ -229,7 +229,7 @@ export function DashboardGrid({ cols={COLS} layouts={effectiveLayouts as ResponsiveLayouts} rowHeight={20} - margin={[8, 8] as [number, number]} + margin={[12, 12] as [number, number]} // containerPadding bewusst nicht gesetzt: react-grid-layout 2.2.3 nimmt // dann das margin (gemessen: effectiveContainerPadding = containerPadding ?? margin). dragConfig={{ diff --git a/apps/web/src/components/dashboard/dashboard-tabs.tsx b/apps/web/src/components/dashboard/dashboard-tabs.tsx index a66068e..8a09a6d 100644 --- a/apps/web/src/components/dashboard/dashboard-tabs.tsx +++ b/apps/web/src/components/dashboard/dashboard-tabs.tsx @@ -507,7 +507,7 @@ export function DashboardTabs({ role="alertdialog" aria-modal="true" aria-label={t('tabs.deleteDialogTitle')} - className="relative z-[60] mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-xl" + className="relative z-[60] mx-4 max-w-md rounded-lg bg-card dark:border dark:border-border p-6 shadow-xl" >

{t('tabs.deleteDialogTitle')} diff --git a/apps/web/src/components/dashboard/edit-mode-toggle.tsx b/apps/web/src/components/dashboard/edit-mode-toggle.tsx index 54ab702..b730592 100644 --- a/apps/web/src/components/dashboard/edit-mode-toggle.tsx +++ b/apps/web/src/components/dashboard/edit-mode-toggle.tsx @@ -21,11 +21,7 @@ export function EditModeToggle({ isEditMode, onToggle }: EditModeToggleProps) {