import { useState } from 'react'; import { useIntegrations, useOffice365CalendarRange } from '../crm/hooks'; import type { M365CalendarEvent } from '../crm/types'; import styles from './DashboardCalendarTab.module.css'; export type CalendarViewMode = 'month' | 'week' | 'agenda'; // ── Date Helpers ─────────────────────────────────────────────────────────────── function startOfWeekMonday(date: Date): Date { const d = new Date(date); const dow = (d.getDay() + 6) % 7; // Mon=0 … Sun=6 d.setDate(d.getDate() - dow); d.setHours(0, 0, 0, 0); return d; } function addDays(date: Date, n: number): Date { const d = new Date(date); d.setDate(d.getDate() + n); return d; } function isSameDay(a: Date, b: Date): boolean { return ( a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() ); } /** 6×7 Tageszellen für die Monatsansicht */ function getMonthCells(date: Date): Date[] { const firstDay = new Date(date.getFullYear(), date.getMonth(), 1); const gridStart = startOfWeekMonday(firstDay); return Array.from({ length: 42 }, (_, i) => addDays(gridStart, i)); } /** 5 Arbeitstage der Woche (Mo–Fr) */ function getWorkWeekDays(date: Date): Date[] { const monday = startOfWeekMonday(date); return Array.from({ length: 5 }, (_, i) => addDays(monday, i)); } function toISODate(date: Date): string { return date.toISOString().slice(0, 10); } function formatTime(iso: string): string { return new Date(iso).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', }); } function getEventsForDay(events: M365CalendarEvent[], day: Date): M365CalendarEvent[] { return events .filter((e) => isSameDay(new Date(e.start.dateTime), day)) .sort( (a, b) => new Date(a.start.dateTime).getTime() - new Date(b.start.dateTime).getTime(), ); } // Deterministische Farbe anhand Event-ID const EVENT_COLORS = ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#ef4444']; function eventColor(id: string): string { let h = 0; for (const ch of id) h = (h * 31 + ch.charCodeAt(0)) % EVENT_COLORS.length; return EVENT_COLORS[h]; } // ── Day Agenda (Tages-Detailansicht, auch für Home) ─────────────────────────── export function DayAgenda({ day, events, }: { day: Date; events: M365CalendarEvent[]; }) { const dayEvents = getEventsForDay(events, day); return ( ); } // ── Agenda-Listenansicht (eigene Ansicht, zeigt mehrere Tage) ───────────────── function AgendaView({ currentDate, events, selectedDay, onDayClick, }: { currentDate: Date; events: M365CalendarEvent[]; selectedDay: Date; onDayClick: (d: Date) => void; }) { const today = new Date(); // 14 Tage ab currentDate const days = Array.from({ length: 14 }, (_, i) => addDays(currentDate, i)); return (
{days.map((day) => { const dayEvents = getEventsForDay(events, day); const isToday = isSameDay(day, today); const isSelected = isSameDay(day, selectedDay); return (
onDayClick(day)} >
{day.toLocaleDateString('de-DE', { weekday: 'short' })} {day.getDate()} {day.toLocaleDateString('de-DE', { month: 'short' })}
{dayEvents.length === 0 ? ( Kein Termin ) : ( dayEvents.map((e) => ( ev.stopPropagation()} > {formatTime(e.start.dateTime)} {e.subject} {e.isOnlineMeeting && ( Online )} )) )}
); })}
); } // ── Monatsansicht ───────────────────────────────────────────────────────────── const WEEKDAY_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; function MonthView({ currentDate, selectedDay, events, onDayClick, }: { currentDate: Date; selectedDay: Date; events: M365CalendarEvent[]; onDayClick: (d: Date) => void; }) { const today = new Date(); const cells = getMonthCells(currentDate); const month = currentDate.getMonth(); return (
{/* Wochentag-Header */} {WEEKDAY_LABELS.map((d) => (
{d}
))} {/* Tageszellen */} {cells.map((day, i) => { const inMonth = day.getMonth() === month; const isToday = isSameDay(day, today); const isSelected = isSameDay(day, selectedDay); const dayEvents = getEventsForDay(events, day); return (
onDayClick(day)} > {day.getDate()}
{dayEvents.slice(0, 2).map((e) => (
{formatTime(e.start.dateTime)} {e.subject}
))} {dayEvents.length > 2 && (
+{dayEvents.length - 2}
)}
); })}
); } // ── Wochenansicht (Mo–Fr) ───────────────────────────────────────────────────── function WeekView({ currentDate, selectedDay, events, onDayClick, }: { currentDate: Date; selectedDay: Date; events: M365CalendarEvent[]; onDayClick: (d: Date) => void; }) { const today = new Date(); const weekDays = getWorkWeekDays(currentDate); // nur Mo–Fr return (
{weekDays.map((day) => { const isToday = isSameDay(day, today); const isSelected = isSameDay(day, selectedDay); const dayEvents = getEventsForDay(events, day); return (
onDayClick(day)} >
{day.toLocaleDateString('de-DE', { weekday: 'short' })} {day.getDate()}
{dayEvents.map((e) => (
{ ev.stopPropagation(); window.open(e.webLink, '_blank', 'noopener,noreferrer'); }} > {formatTime(e.start.dateTime)} {e.subject}
))}
); })}
); } // ── DashboardCalendarTab ────────────────────────────────────────────────────── export function DashboardCalendarTab() { const { data: integrationsData, isLoading: intLoading } = useIntegrations(); const isConnected = integrationsData?.data?.some( (i) => i.provider === 'MICROSOFT_365' && i.connected, ) ?? false; const [viewMode, setViewMode] = useState('month'); const [currentDate, setCurrentDate] = useState(() => new Date()); const [selectedDay, setSelectedDay] = useState(() => new Date()); // Datumsbereich berechnen const rangeStart = viewMode === 'month' ? startOfWeekMonday( new Date(currentDate.getFullYear(), currentDate.getMonth(), 1), ) : viewMode === 'week' ? startOfWeekMonday(currentDate) : currentDate; // agenda: ab currentDate const rangeEnd = viewMode === 'month' ? addDays(rangeStart, 42) : viewMode === 'week' ? addDays(startOfWeekMonday(currentDate), 5) // Mo–Fr : addDays(currentDate, 14); // 14 Tage Agenda const { data: eventsData, isLoading, error } = useOffice365CalendarRange( toISODate(rangeStart), toISODate(rangeEnd), ); const events: M365CalendarEvent[] = eventsData?.data ?? []; const navigate = (delta: number) => { const d = new Date(currentDate); if (viewMode === 'month') { d.setMonth(d.getMonth() + delta); } else if (viewMode === 'week') { d.setDate(d.getDate() + delta * 7); } else { d.setDate(d.getDate() + delta * 14); } setCurrentDate(d); }; const goToday = () => { const t = new Date(); setCurrentDate(t); setSelectedDay(t); }; // Anzeigebezeichnung const rangeLabel = (() => { if (viewMode === 'month') { return currentDate.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }); } if (viewMode === 'week') { const days = getWorkWeekDays(currentDate); const f = days[0]; const l = days[4]; const sm = f.getMonth() === l.getMonth(); return sm ? `${f.getDate()}. – ${l.getDate()}. ${l.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })}` : `${f.getDate()}. ${f.toLocaleDateString('de-DE', { month: 'short' })} – ${l.getDate()}. ${l.toLocaleDateString('de-DE', { month: 'short', year: 'numeric' })}`; } // agenda const end = addDays(currentDate, 13); const sm = currentDate.getMonth() === end.getMonth(); return sm ? `${currentDate.getDate()}. – ${end.getDate()}. ${end.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })}` : `${currentDate.getDate()}. ${currentDate.toLocaleDateString('de-DE', { month: 'short' })} – ${end.getDate()}. ${end.toLocaleDateString('de-DE', { month: 'short', year: 'numeric' })}`; })(); if (intLoading) { return

Verbindung wird geprüft…

; } if (!isConnected) { return (
📅

Microsoft 365 nicht verbunden

Verbinden Sie Ihr Konto unter{' '} CRM → Office 365 .

); } return (
{/* Toolbar */}

{rangeLabel}

{/* Status */} {isLoading && (

Termine werden geladen…

)} {error && (

Kalendertermine konnten nicht geladen werden.

)} {/* Hauptbereich */} {!isLoading && (
{viewMode === 'month' && ( )} {viewMode === 'week' && ( )} {viewMode === 'agenda' && ( )}
{/* Tages-Agenda nur bei Monat- und Wochenansicht */} {(viewMode === 'month' || viewMode === 'week') && ( )}
)}
); }