feat(web): Kalender-Widget zeigt je Kalender einen kleinen Kreis am Tag; Changelog
- Plaketten-Wrapper calendar-day-badges rendert buildDayBadges(day.events) statt der alten Einzel-Plakette - Ein Kalender: unveraendert eine Plakette in Kalenderfarbe/Akzentfarbe (Tests 3/3b/3c bleiben gruen) - Zwei/drei Kalender: je ein kleiner Kreis pro Kalender in Startreihenfolge (Test 3d) - Vier oder mehr Kalender: zwei Kreise plus grauer Restkreis bg-muted-foreground/text-background mit Summe (Test 3e) - CHANGELOG-Stichpunkt erweitert (mehrere Kalender am selben Tag) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
||||
### Geändert
|
||||
|
||||
- Kalenderquellen: Adressfeld zeigt je Typ ein Beispiel; bei Exchange EWS Hinweis auf `/EWS/Exchange.asmx`
|
||||
- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders
|
||||
- Kalender-Widget: Plakette am Tag in der Farbe des Kalenders; mehrere Kalender am selben Tag zeigen je einen kleinen Kreis
|
||||
|
||||
### Entfernt
|
||||
|
||||
|
||||
@@ -207,6 +207,102 @@ describe('CalendarWidget', () => {
|
||||
expect(within(tooltip).getAllByTestId('tooltip-color-dot')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('Test 3d: zwei Kalender am selben Tag zeigen zwei kleine Kreise in Startreihenfolge', async () => {
|
||||
mockFetchEvents.mockResolvedValue([
|
||||
ev('Lunch', new Date(2026, 6, 20, 10, 0), new Date(2026, 6, 20, 11, 0), {
|
||||
sourceId: 's2',
|
||||
color: '#4060c4',
|
||||
}),
|
||||
ev('Kaffee', new Date(2026, 6, 20, 14, 0), new Date(2026, 6, 20, 15, 0), {
|
||||
sourceId: 's2',
|
||||
color: '#4060c4',
|
||||
}),
|
||||
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0), {
|
||||
sourceId: 's1',
|
||||
color: '#c44040',
|
||||
}),
|
||||
]);
|
||||
|
||||
const { CalendarWidget } = await import('./calendar-widget');
|
||||
render(<CalendarWidget instanceId="cal-3d" config={{}} isEditMode={false} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const day20 = document.querySelector('[data-date="2026-07-20"]') as HTMLElement;
|
||||
const badges = within(day20).getAllByTestId('calendar-day-count');
|
||||
expect(badges).toHaveLength(2);
|
||||
expect(badges[0]).toHaveTextContent('1');
|
||||
expect(badges[0]).toHaveStyle({ backgroundColor: '#c44040' });
|
||||
expect(badges[1]).toHaveTextContent('2');
|
||||
expect(badges[1]).toHaveStyle({ backgroundColor: '#4060c4' });
|
||||
for (const badge of badges) {
|
||||
expect(badge).toHaveClass('text-white');
|
||||
expect(badge).not.toHaveClass('bg-primary');
|
||||
expect(badge).toHaveClass('h-[clamp(8px,2.4cqw,12px)]');
|
||||
}
|
||||
expect(within(day20).queryByTestId('calendar-day-count-rest')).toBeNull();
|
||||
expect(within(day20).getByTestId('calendar-day-badges').childElementCount).toBe(2);
|
||||
});
|
||||
|
||||
it('Test 3e: vier Kalender am selben Tag zeigen zwei Kreise plus grauen Restkreis mit Summe', async () => {
|
||||
mockFetchEvents.mockResolvedValue([
|
||||
ev('e1', new Date(2026, 6, 21, 8, 0), new Date(2026, 6, 21, 9, 0), {
|
||||
sourceId: 's1',
|
||||
color: '#111111',
|
||||
}),
|
||||
ev('e2a', new Date(2026, 6, 21, 9, 0), new Date(2026, 6, 21, 9, 30), {
|
||||
sourceId: 's2',
|
||||
color: '#222222',
|
||||
}),
|
||||
ev('e2b', new Date(2026, 6, 21, 9, 30), new Date(2026, 6, 21, 10, 0), {
|
||||
sourceId: 's2',
|
||||
color: '#222222',
|
||||
}),
|
||||
ev('e3', new Date(2026, 6, 21, 10, 0), new Date(2026, 6, 21, 11, 0), {
|
||||
sourceId: 's3',
|
||||
color: undefined,
|
||||
}),
|
||||
ev('e4a', new Date(2026, 6, 21, 11, 0), new Date(2026, 6, 21, 12, 0), {
|
||||
sourceId: 's4',
|
||||
color: '#444444',
|
||||
}),
|
||||
ev('e4b', new Date(2026, 6, 21, 12, 0), new Date(2026, 6, 21, 13, 0), {
|
||||
sourceId: 's4',
|
||||
color: '#444444',
|
||||
}),
|
||||
ev('e4c', new Date(2026, 6, 21, 13, 0), new Date(2026, 6, 21, 14, 0), {
|
||||
sourceId: 's4',
|
||||
color: '#444444',
|
||||
}),
|
||||
]);
|
||||
|
||||
const { CalendarWidget } = await import('./calendar-widget');
|
||||
render(<CalendarWidget instanceId="cal-3e" config={{}} isEditMode={false} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const day21 = document.querySelector('[data-date="2026-07-21"]') as HTMLElement;
|
||||
expect(within(day21).getByTestId('calendar-day-badges').childElementCount).toBe(3);
|
||||
|
||||
const badges = within(day21).getAllByTestId('calendar-day-count');
|
||||
expect(badges).toHaveLength(2);
|
||||
expect(badges[0]).toHaveTextContent('1');
|
||||
expect(badges[0]).toHaveStyle({ backgroundColor: '#111111' });
|
||||
expect(badges[1]).toHaveTextContent('2');
|
||||
expect(badges[1]).toHaveStyle({ backgroundColor: '#222222' });
|
||||
|
||||
const restBadge = within(day21).getByTestId('calendar-day-count-rest');
|
||||
expect(restBadge).toHaveTextContent('4');
|
||||
expect(restBadge).toHaveClass('bg-muted-foreground');
|
||||
expect(restBadge).toHaveClass('text-background');
|
||||
expect((restBadge as HTMLElement).style.backgroundColor).toBe('');
|
||||
expect(restBadge).not.toHaveClass('text-white');
|
||||
});
|
||||
|
||||
it('Test 4: Tooltip erscheint per Portal im body und zeigt bis zu 5 Termine plus Hinweis', async () => {
|
||||
mockFetchEvents.mockResolvedValue([
|
||||
ev('Team Meeting', new Date(2026, 6, 20, 9, 0), new Date(2026, 6, 20, 10, 0)),
|
||||
|
||||
@@ -8,6 +8,7 @@ import { fetchEvents, fetchSources } from '@/lib/calendar-api';
|
||||
import type { CalendarEvent } from '@/lib/calendar-api';
|
||||
import {
|
||||
buildCalendarDays,
|
||||
buildDayBadges,
|
||||
computeFetchWindow,
|
||||
formatEventDate,
|
||||
formatEventTime,
|
||||
@@ -30,9 +31,11 @@ const TOOLTIP_EDGE_PX = 4;
|
||||
*
|
||||
* Oben ein Monatsraster (Nav-Zeile Zurueck/Monat/Weiter, Wochentagskopf,
|
||||
* 42 Zellen ab Montag, Faehrmonatstage gedaempft, heutiger Tag hervorgehoben,
|
||||
* Zaehl-Plakette an Tagen mit Terminen in der Farbe des Kalenders des
|
||||
* fruehesten Termins des Tages (sonst Akzentfarbe), Tooltip beim Ueberfahren
|
||||
* mit Farbpunkt je Zeile (quick-260916-jvj)), darunter
|
||||
* Zaehl-Plakette an Tagen mit Terminen in der Farbe des Kalenders (ein
|
||||
* Kalender am Tag); bei mehreren Kalendern am selben Tag je ein kleiner
|
||||
* Kreis pro Kalender (max. drei, danach zwei plus grauer Restkreis mit
|
||||
* Summe, Logik in `buildDayBadges`, quick-260916-k2z), Tooltip beim
|
||||
* Ueberfahren mit Farbpunkt je Zeile (quick-260916-jvj)), darunter
|
||||
* der Block "Naechste Termine". Beide Bloecke lassen sich unabhaengig
|
||||
* ein-/ausblenden (Config `showMonth`/`maxEvents`, siehe calendar-month.ts).
|
||||
*
|
||||
@@ -219,6 +222,8 @@ export function CalendarWidget({ config }: WidgetProps) {
|
||||
<div className="grid grid-cols-7 gap-px">
|
||||
{days.map((day) => {
|
||||
const hasEvents = day.events.length > 0;
|
||||
const badges = buildDayBadges(day.events);
|
||||
const single = badges.length === 1;
|
||||
const cellClass = [
|
||||
'widgetNoDrag relative flex min-h-[clamp(16px,5.5cqh,40px)] items-start rounded bg-muted/50 px-1 py-0.5 text-[clamp(9px,2.4cqw,13px)] leading-none',
|
||||
day.inCurrentMonth ? 'text-foreground' : 'text-muted-foreground/60',
|
||||
@@ -243,25 +248,40 @@ export function CalendarWidget({ config }: WidgetProps) {
|
||||
onMouseLeave={() => setHover(null)}
|
||||
>
|
||||
<span>{day.date.getDate()}</span>
|
||||
{hasEvents &&
|
||||
(() => {
|
||||
const badgeColor = day.events[0]?.color;
|
||||
{hasEvents && (
|
||||
// Wrapper traegt die Positionierung, die einzelnen Kreise nicht
|
||||
// mehr (mehrere Kreise stehen sonst uebereinander).
|
||||
<span
|
||||
data-testid="calendar-day-badges"
|
||||
className="absolute bottom-px right-px flex items-end gap-px"
|
||||
>
|
||||
{badges.map((badge) => {
|
||||
const badgeClass = [
|
||||
'absolute bottom-px right-px flex h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] items-center justify-center rounded-full px-0.5 text-[clamp(7px,1.8cqw,10px)] font-semibold leading-none',
|
||||
badgeColor ? 'text-white' : 'bg-primary text-primary-foreground',
|
||||
'flex items-center justify-center rounded-full px-0.5 font-semibold leading-none',
|
||||
single
|
||||
? 'h-[clamp(10px,3cqw,16px)] min-w-[clamp(10px,3cqw,16px)] text-[clamp(7px,1.8cqw,10px)]'
|
||||
: 'h-[clamp(8px,2.4cqw,12px)] min-w-[clamp(8px,2.4cqw,12px)] text-[clamp(6px,1.5cqw,8px)]',
|
||||
badge.rest
|
||||
? 'bg-muted-foreground text-background'
|
||||
: badge.color
|
||||
? 'text-white'
|
||||
: 'bg-primary text-primary-foreground',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
return (
|
||||
<span
|
||||
data-testid="calendar-day-count"
|
||||
key={badge.key}
|
||||
data-testid={badge.rest ? 'calendar-day-count-rest' : 'calendar-day-count'}
|
||||
className={badgeClass}
|
||||
style={badgeColor ? { backgroundColor: badgeColor } : undefined}
|
||||
style={badge.color && !badge.rest ? { backgroundColor: badge.color } : undefined}
|
||||
>
|
||||
{day.events.length}
|
||||
{badge.count}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user