diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ba9bd..69c1e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T ### Geändert - Kalender-Einstellungen: Das Feld „Adresse (URL)“ im Formular für Kalenderquellen zeigt jetzt je nach Typ ein passendes Beispiel (z. B. `https://mail.firma.de/EWS/Exchange.asmx` für Exchange EWS) und bei Exchange EWS einen Hinweis, dass die vollständige Adresse nötig ist – der Servername allein reicht nicht. +- Kalender-Widget neu gestaltet: Monatsübersicht mit Terminanzahl je Tag (Termine beim Überfahren sichtbar) und darunter die nächsten Termine. In den Widget-Einstellungen lässt sich die Monatsansicht ein-/ausblenden sowie Anzahl und Zeitraum der angezeigten Termine wählen. ## 1.1.0 – 2026-09-16 diff --git a/apps/web/src/components/settings/widget-settings-panel.test.tsx b/apps/web/src/components/settings/widget-settings-panel.test.tsx index e4792b0..c869c28 100644 --- a/apps/web/src/components/settings/widget-settings-panel.test.tsx +++ b/apps/web/src/components/settings/widget-settings-panel.test.tsx @@ -19,7 +19,12 @@ vi.mock('next-intl', async () => { | string | undefined; return { - useTranslations: (ns?: string) => (key: string) => lookup(ns ? `${ns}.${key}` : key) ?? key, + useTranslations: + (ns?: string) => + (key: string, values?: Record) => { + const raw = lookup(ns ? `${ns}.${key}` : key) ?? key; + return values ? raw.replace(/\{(\w+)\}/g, (_: string, n: string) => String(values[n] ?? '')) : raw; + }, }; }); @@ -36,6 +41,7 @@ vi.mock('@/components/settings/search-provider-form', () => ({ })); const clockTexts = (de as { widgets: { clock: Record } }).widgets.clock; +const cal = (de as { widgets: { calendar: Record } }).widgets.calendar; const WIDGET = { id: 'c1', widgetType: 'clock', config: { timezone: 'Europe/Berlin', timeFontSizePt: 24 } }; @@ -105,3 +111,74 @@ describe('WidgetSettingsPanel — Schriftgroesse der Uhrzeit (quick-260916-bwo)' expect(screen.getByRole('alert')).toBeInTheDocument(); }); }); + +describe('WidgetSettingsPanel — Kalender-Einstellungen (quick-260916-htc)', () => { + function renderCalendarExpanded(config: Record = {}) { + const onWidgetUpdate = vi.fn(); + const widget = { id: 'k1', widgetType: 'calendar', config }; + render(); + fireEvent.click(screen.getByRole('button', { name: /Kalender #1/ })); + return { onWidgetUpdate }; + } + + it('Test 5: Vorbelegung aus der Konfiguration, Optionslisten und uebersetzte Link-Zeile', () => { + renderCalendarExpanded(); + + const showMonth = screen.getByLabelText(cal.configShowMonth) as HTMLInputElement; + expect(showMonth.checked).toBe(true); + + const maxEventsSelect = screen.getByLabelText(cal.configMaxEvents) as HTMLSelectElement; + expect(maxEventsSelect.value).toBe('3'); + const maxEventsOptionTexts = Array.from(maxEventsSelect.options).map((o) => o.textContent); + expect(maxEventsOptionTexts).toEqual([ + cal.configMaxEventsNone, + cal.configMaxEventsOne, + ...Array.from({ length: 9 }, (_, i) => cal.configMaxEventsMany.replace('{count}', String(i + 2))), + ]); + + const lookaheadSelect = screen.getByLabelText(cal.configLookahead) as HTMLSelectElement; + expect(lookaheadSelect.value).toBe('30'); + const lookaheadOptionTexts = Array.from(lookaheadSelect.options).map((o) => o.textContent); + expect(lookaheadOptionTexts).toEqual( + [7, 14, 30, 60, 90].map((days) => cal.configLookaheadOption.replace('{days}', String(days))), + ); + + expect(screen.getByText(cal.configSourcesHint)).toBeInTheDocument(); + const link = screen.getByRole('link', { name: cal.configSourcesLink }); + expect(link).toHaveAttribute('href', '/settings/dashboard/calendar'); + + expect(screen.queryByText(/managed under/)).not.toBeInTheDocument(); + }); + + it('Test 6: jede Aenderung ruft updateWidgetConfig/onWidgetUpdate nur mit dem geaenderten Feld auf', async () => { + const { onWidgetUpdate } = renderCalendarExpanded(); + + fireEvent.change(screen.getByLabelText(cal.configMaxEvents), { target: { value: '5' } }); + await vi.waitFor(() => expect(onWidgetUpdate).toHaveBeenCalledWith('k1', { maxEvents: 5 })); + expect(updateWidgetConfig).toHaveBeenCalledWith('k1', { maxEvents: 5 }); + + fireEvent.change(screen.getByLabelText(cal.configLookahead), { target: { value: '14' } }); + await vi.waitFor(() => expect(onWidgetUpdate).toHaveBeenCalledWith('k1', { lookaheadDays: 14 })); + expect(updateWidgetConfig).toHaveBeenCalledWith('k1', { lookaheadDays: 14 }); + + fireEvent.click(screen.getByLabelText(cal.configShowMonth)); + await vi.waitFor(() => expect(onWidgetUpdate).toHaveBeenCalledWith('k1', { showMonth: false })); + expect(updateWidgetConfig).toHaveBeenCalledWith('k1', { showMonth: false }); + + expect(updateWidgetConfig).toHaveBeenCalledTimes(3); + }); + + it('Test 7: bestehende Werte werden angezeigt, ungueltige Werte geklemmt', () => { + renderCalendarExpanded({ showMonth: false, maxEvents: 7, lookaheadDays: 60 }); + + expect((screen.getByLabelText(cal.configShowMonth) as HTMLInputElement).checked).toBe(false); + expect((screen.getByLabelText(cal.configMaxEvents) as HTMLSelectElement).value).toBe('7'); + expect((screen.getByLabelText(cal.configLookahead) as HTMLSelectElement).value).toBe('60'); + + cleanup(); + + renderCalendarExpanded({ maxEvents: 42, lookaheadDays: 45 }); + expect((screen.getByLabelText(cal.configMaxEvents) as HTMLSelectElement).value).toBe('10'); + expect((screen.getByLabelText(cal.configLookahead) as HTMLSelectElement).value).toBe('30'); + }); +}); diff --git a/apps/web/src/components/settings/widget-settings-panel.tsx b/apps/web/src/components/settings/widget-settings-panel.tsx index 33fd54e..7b14f3d 100644 --- a/apps/web/src/components/settings/widget-settings-panel.tsx +++ b/apps/web/src/components/settings/widget-settings-panel.tsx @@ -11,6 +11,11 @@ import { CLOCK_FONT_SIZE_MIN_PT, resolveClockTimeFontSizePt, } from '@/components/dashboard/widgets/clock-font-size'; +import { + CALENDAR_LOOKAHEAD_OPTIONS, + CALENDAR_MAX_EVENTS_LIMIT, + resolveCalendarConfig, +} from '@/components/dashboard/widgets/calendar-month'; /** * IANA timezones offered for clock configuration (D-12). @@ -170,20 +175,14 @@ export function WidgetSettingsPanel({ /> )} - {/* Calendar config — managed elsewhere */} + {/* Calendar config (quick-260916-htc) */} {widget.widgetType === 'calendar' && ( -
-

- Calendar sources are managed under{' '} - - Settings > Dashboard > Calendar - - . -

-
+ + handleConfigChange(widget.id, cfg) + } + /> )} )} @@ -317,6 +316,90 @@ function ClockConfig({ ); } +function CalendarConfig({ + config, + onChange, +}: { + config: Record; + onChange: (cfg: Record) => void; +}) { + const t = useTranslations('widgets'); + const { showMonth, maxEvents, lookaheadDays } = resolveCalendarConfig(config); + + const maxEventsOptions = Array.from({ length: CALENDAR_MAX_EVENTS_LIMIT + 1 }, (_, n) => n); + + return ( +
+ {/* Monatsansicht ein-/ausblenden */} +
+ onChange({ showMonth: e.target.checked })} + /> + +
+ + {/* Anzahl der angezeigten Termine */} +
+ + +
+ + {/* Zeitraum der angezeigten Termine */} +
+ + +
+ + {/* Hinweis auf die Kalenderquellenverwaltung */} +

+ {t('calendar.configSourcesHint')}{' '} + + {t('calendar.configSourcesLink')} + +

+
+ ); +} + function NoteConfig({ config, onChange, diff --git a/docs/anleitung-anwender.md b/docs/anleitung-anwender.md index 53c396a..09eeb4e 100644 --- a/docs/anleitung-anwender.md +++ b/docs/anleitung-anwender.md @@ -73,7 +73,7 @@ Ihre Änderungen werden über **„Änderungen speichern"** übernommen. Verlass |---|---| | Uhr | Zeigt die aktuelle Uhrzeit an (optional mit Datum). Die Uhrzeit wächst und schrumpft mit der Kachel; wer eine feste Größe möchte, stellt sie unter Einstellungen > Dashboard als Schriftgröße in Punkt ein | | Suchleiste | Schnellsuche im Web über frei konfigurierbare Suchanbieter | -| Kalender | Zeigt kommende Termine aus Ihren verbundenen Kalenderquellen | +| Kalender | Monatsübersicht mit der Anzahl der Termine je Tag (die Termine eines Tages erscheinen, wenn Sie mit der Maus darüberfahren) und darunter die nächsten Termine aus Ihren verbundenen Kalenderquellen. Ob die Monatsansicht erscheint, wie viele Termine und welcher Zeitraum gezeigt werden, stellen Sie unter Einstellungen > Dashboard > Widgets ein | | Notizen | Freitext-Notizen mit Markdown-Formatierung | | Taschenrechner | Grundrechenarten, auch per Tastatur bedienbar | | Favoriten | Schnellzugriff auf mehrere selbst gepflegte Links, als Liste oder Kachelansicht | @@ -150,7 +150,7 @@ Ein einfaches Werkzeug, um zu prüfen, ob eine Internet-Domain verfügbar ist. G - **Akzentfarbe:** Passt die Hauptfarbe der Oberfläche an Ihren Geschmack an; über „Zurücksetzen" kehren Sie zur Standardfarbe zurück. - **Passwort ändern:** Nur sichtbar und nutzbar, wenn Ihr Konto **lokal** in Tessera verwaltet wird. Wird Ihr Konto stattdessen über das Verzeichnis (LDAP/Active Directory) verwaltet, zeigt Tessera stattdessen den Hinweis „Ihr Passwort wird über das Verzeichnis (LDAP) verwaltet. Eine Änderung ist hier nicht möglich." — in diesem Fall ändern Sie Ihr Passwort über die üblichen Firmenwege (z. B. Windows-Anmeldung), nicht in Tessera. -**Dashboard > Widgets:** Hier finden Sie für jedes auf Ihrem Dashboard platzierte Widget die zugehörigen Einstellungen, zum Beispiel eigene Suchanbieter für die Suchleiste. +**Dashboard > Widgets:** Hier finden Sie für jedes auf Ihrem Dashboard platzierte Widget die zugehörigen Einstellungen, zum Beispiel eigene Suchanbieter für die Suchleiste oder beim Kalender die Monatsansicht (ein/aus), die Anzahl der angezeigten Termine (bis zu zehn, oder ausgeblendet) und den Zeitraum (7 bis 90 Tage). **Dashboard > Kalender:** Hier verwalten Sie die Kalenderquellen, aus denen das Kalender-Widget seine Termine bezieht — Quellen hinzufügen, die Verbindung testen und nicht mehr benötigte Quellen wieder entfernen.