diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d048aa..d0566d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T ## Unveröffentlicht +## 1.5.2 – 2026-09-28 + +### Neu + +- Dashboard: Den Titel eines Widgets können Sie jetzt ausblenden. Im Bearbeitungsmodus sitzt dafür oben in der Mitte jedes Widgets mit Titel ein kleines „T“; ein Klick blendet den Titel aus, ein zweiter wieder ein. Die Einstellung gilt je Widget und bleibt gespeichert. Im Bearbeitungsmodus sehen Sie den Titel weiterhin, damit Sie ihn ändern können; Knöpfe wie der Stift der Notiz bleiben auch ohne Titel oben rechts erreichbar. + +### Geändert + +- Seitenleiste: Die Kategorien (etwa „Fuhrpark“ oder „Infrastruktur“) sind etwas größer beschriftet, die Module darunter etwas kleiner – so ist die Gliederung auf einen Blick erkennbar. + ## 1.5.1 – 2026-09-28 ### Geändert diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 29f1eef..e6746ae 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -474,6 +474,26 @@ input[type="checkbox"], input[type="radio"] { } } +/* Titel ausgeblendet (quick-260928-wtt, `hideTitle` in der Widget- + Konfiguration, nur in der Ansicht): die Titelzeile entfaellt. Traegt sie + Aktionen (Notiz-Stift, XFrame „In neuem Tab“), bleiben nur diese als + kleine Schwebeleiste oben rechts. */ +[data-hide-title] .widget-head { + display: none; +} +[data-hide-title] .widget-head:has(.widget-action) { + display: flex; + position: absolute; + top: 0.25rem; + right: 0.25rem; + z-index: 5; + min-height: 0; + padding: 0; +} +[data-hide-title] .widget-head:has(.widget-action) > :not(.widget-action) { + display: none; +} + /* Beim Laden blenden die Kacheln gestaffelt ein (je 80 ms, --enter-delay). */ @media (prefers-reduced-motion: no-preference) { .widget-card { diff --git a/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx b/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx index 88a3b86..ac37b62 100644 --- a/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx +++ b/apps/web/src/components/dashboard/widgets/widget-wrapper.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; // quick-260916-iex: Link-Widget entfernt — unbekannte Widget-Typen (z. B. @@ -14,6 +14,12 @@ vi.mock('next-intl', () => ({ : key, })); +const setWidgetConfig = vi.fn(); +vi.mock('@/lib/stores/dashboard-store', () => ({ + useDashboardStore: (selector: (state: { setWidgetConfig: typeof setWidgetConfig }) => unknown) => + selector({ setWidgetConfig }), +})); + import { WidgetWrapper } from './widget-wrapper'; describe('WidgetWrapper', () => { @@ -54,4 +60,53 @@ describe('WidgetWrapper', () => { ), ).toBeNull(); }); + + // quick-260928-wtt: Titel je Widget ein-/ausblenden + it('Bearbeitungsmodus: Schalter blendet den Titel aus und speichert hideTitle', () => { + setWidgetConfig.mockClear(); + render( + , + ); + fireEvent.click(screen.getByRole('button', { name: 'hideTitle' })); + expect(setWidgetConfig).toHaveBeenCalledWith('w-uhr', { hideTitle: true }); + // im Bearbeitungsmodus bleibt der Titel sichtbar + expect(screen.getByRole('article')).not.toHaveAttribute('data-hide-title'); + }); + + it('ausgeblendeter Titel: Schalter blendet wieder ein, Ansicht markiert die Karte', () => { + setWidgetConfig.mockClear(); + const { rerender } = render( + , + ); + fireEvent.click(screen.getByRole('button', { name: 'showTitle' })); + expect(setWidgetConfig).toHaveBeenCalledWith('w-uhr', { hideTitle: false }); + + rerender( + , + ); + expect(screen.getByRole('article')).toHaveAttribute('data-hide-title'); + }); + + it('Widgets ohne Titelzeile (Suche) bekommen keinen Schalter', () => { + render( + , + ); + expect(screen.queryByRole('button', { name: 'hideTitle' })).toBeNull(); + }); }); diff --git a/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx b/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx index 073aea1..1265181 100644 --- a/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx +++ b/apps/web/src/components/dashboard/widgets/widget-wrapper.tsx @@ -2,6 +2,7 @@ import { useTranslations } from 'next-intl'; import type { CSSProperties } from 'react'; +import { useDashboardStore } from '@/lib/stores/dashboard-store'; import { WIDGET_REGISTRY, type WidgetType } from '../widget-registry'; import { WidgetIcon } from './widget-icon'; @@ -14,6 +15,14 @@ import { WidgetIcon } from './widget-icon'; */ const FRAME_HEADER_TYPES = new Set(['calendar', 'clock', 'calculator', 'stopwatch']); +/** + * Kacheltypen mit Titelzeile — nur diese bekommen im Bearbeitungsmodus den + * Schalter „Titel ausblenden“ (quick-260928-wtt). Die Wahl steht als + * `hideTitle: true` in der Widget-Konfiguration und gilt nur in der Ansicht; + * im Bearbeitungsmodus bleibt der Titel sichtbar (und editierbar). + */ +const TITLE_TYPES = new Set([...FRAME_HEADER_TYPES, 'note', 'favorites', 'proxmox', 'xframe']); + interface WidgetWrapperProps { widget: { id: string; @@ -42,6 +51,9 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }: const t = useTranslations('widgets'); const definition = WIDGET_REGISTRY[widget.widgetType as WidgetType]; const WidgetComponent = definition?.component; + const setWidgetConfig = useDashboardStore((state) => state.setWidgetConfig); + const hasTitle = TITLE_TYPES.has(widget.widgetType); + const titleHidden = widget.config?.hideTitle === true; // Design „Mosaik“: in der Ansicht eine ruhige Fluent-Karte (hell Schatten, // dunkel Haarlinie), die Akzent-Umrandung nur im Bearbeitungsmodus. @@ -54,6 +66,7 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }: className={cardClassName} style={{ '--enter-delay': `${Math.min(enterIndex, 8) * 80}ms` } as CSSProperties} data-widget-id={widget.id} + data-hide-title={!isEditMode && hasTitle && titleHidden ? '' : undefined} aria-label={definition ? t(definition.nameKey) : widget.widgetType} > {/* Edit mode: Overlay-Kopfleiste (Griff-Hinweis) mit Loesch-Knopf rechts. @@ -81,6 +94,39 @@ export function WidgetWrapper({ widget, isEditMode, onRemove, enterIndex = 0 }: + {hasTitle && ( + + )} @@ -219,7 +225,7 @@ export function Sidebar() { const active = isActive(href) || isActive(`/modules/${mod.slug}`); return (
  • - + {active && } {mod.name} diff --git a/apps/web/src/lib/stores/dashboard-store.ts b/apps/web/src/lib/stores/dashboard-store.ts index 1a49cb0..02ffcd9 100644 --- a/apps/web/src/lib/stores/dashboard-store.ts +++ b/apps/web/src/lib/stores/dashboard-store.ts @@ -26,6 +26,8 @@ interface DashboardState { updateLayouts: (layouts: Record) => void; addWidget: (widgetType: WidgetType) => Promise; removeWidget: (id: string) => Promise; + /** Teil-Konfiguration eines Widgets setzen — sofort sichtbar, bei Fehler zurueck. */ + setWidgetConfig: (id: string, partial: Record) => Promise; loadDashboard: () => Promise; saveLayout: () => Promise; selectDashboard: (id: string) => Promise; @@ -159,6 +161,22 @@ export const useDashboardStore = create()((set, get) => ({ } }, + setWidgetConfig: async (id: string, partial: Record) => { + const before = get().widgets.find((w) => w.id === id)?.config; + if (!before) return; + const patch = (config: Record) => + set((state) => ({ + widgets: state.widgets.map((w) => (w.id === id ? { ...w, config } : w)), + })); + patch({ ...before, ...partial }); + try { + await api.updateWidgetConfig(id, partial); + } catch (err) { + console.error('Failed to update widget config:', err); + patch(before); + } + }, + loadDashboard: async () => { set({ isLoading: true, error: null }); try { diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json index e4a8adb..46c4fbb 100644 --- a/apps/web/src/messages/de.json +++ b/apps/web/src/messages/de.json @@ -236,6 +236,8 @@ "catalogTitle": "Widget hinzufügen", "catalogClose": "Dialog schließen", "deleteTooltip": "Widget entfernen", + "hideTitle": "Titel ausblenden", + "showTitle": "Titel einblenden", "dragHint": "Ziehen Sie die Kachel, um sie zu verschieben", "editMode": "Dashboard bearbeiten", "saveChanges": "Änderungen speichern", diff --git a/apps/web/src/messages/en.json b/apps/web/src/messages/en.json index f4dad94..e4bda8a 100644 --- a/apps/web/src/messages/en.json +++ b/apps/web/src/messages/en.json @@ -236,6 +236,8 @@ "catalogTitle": "Add widget", "catalogClose": "Close dialog", "deleteTooltip": "Remove widget", + "hideTitle": "Hide title", + "showTitle": "Show title", "dragHint": "Drag the tile to move it", "editMode": "Edit dashboard", "saveChanges": "Save changes",