import { useState, useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { useAuth } from '../auth/AuthContext'; import { UserAvatar } from '../components/UserAvatar'; import { useTheme } from '../theme/ThemeContext'; import { WeatherWidget } from '../components/WeatherWidget'; import { AnalogClock } from '../components/AnalogClock'; import { DashboardEmailTab } from './DashboardEmailTab'; import { DashboardCalendarTab, DayAgenda } from './DashboardCalendarTab'; import { DashboardTasksTab } from './DashboardTasksTab'; import { DashboardContactsTab } from './DashboardContactsTab'; import { useIntegrations, useOffice365CalendarRange, useActiveTradeEvents, useOffice365Emails, useOffice365TasksFlat, useCrmOpenTasks, useCompleteO365Task, useCompleteCrmTask, } from '../crm/hooks'; import { useEventCountdown } from '../hooks/useEventCountdown'; import { useWeather } from '../hooks/useWeather'; import type { M365CalendarEvent, TradeEvent, M365Email } from '../crm/types'; import type { M365TaskFlat, CrmOpenTask } from '../crm/types'; import styles from './DashboardPage.module.css'; // ── Spruch des Tages ────────────────────────────────────────────────────────── const QUOTES: { text: string; author: string }[] = [ { text: 'Der Weg ist das Ziel.', author: 'Konfuzius' }, { text: 'Erfolg hat drei Buchstaben: Tun.', author: 'J. W. von Goethe' }, { text: 'Wer aufhört, besser zu werden, hat aufgehört, gut zu sein.', author: 'Philip Rosenthal' }, { text: 'Die Fantasie ist wichtiger als das Wissen.', author: 'Albert Einstein' }, { text: 'Wer kämpft, kann verlieren. Wer nicht kämpft, hat schon verloren.', author: 'Bertolt Brecht' }, { text: 'Sei du selbst die Veränderung, die du dir wünschst für diese Welt.', author: 'Mahatma Gandhi' }, { text: 'Wenn der Wind der Veränderung weht, bauen die einen Mauern und die anderen Windmühlen.', author: 'Chinesisches Sprichwort' }, { text: 'Das Geheimnis des Erfolgs ist, den Standpunkt des anderen zu verstehen.', author: 'Henry Ford' }, { text: 'Tu erst das Notwendige, dann das Mögliche, und plötzlich schaffst du das Unmögliche.', author: 'Franz von Assisi' }, { text: 'Man sieht nur mit dem Herzen gut. Das Wesentliche ist für die Augen unsichtbar.', author: 'Antoine de Saint-Exupéry' }, { text: 'Nicht die Stärksten überleben, sondern die Anpassungsfähigsten.', author: 'Charles Darwin' }, { text: 'Ein langer Marsch beginnt mit dem ersten Schritt.', author: 'Laotse' }, { text: 'Wer wagt, gewinnt.', author: 'Volksweisheit' }, { text: 'Jeder Experte war einmal ein Anfänger.', author: 'Unbekannt' }, { text: 'In der Mitte liegt die Kraft.', author: 'Sprichwort' }, { text: 'Nicht wer wenig hat, sondern wer viel begehrt, ist arm.', author: 'Seneca' }, { text: 'Kreativität ist Intelligenz, die Spaß hat.', author: 'Albert Einstein' }, { text: 'Stärke zeigt sich nicht darin, niemals zu fallen, sondern darin, nach jedem Fall aufzustehen.', author: 'Nelson Mandela' }, { text: 'Der beste Zeitpunkt, einen Baum zu pflanzen, war vor zwanzig Jahren. Der zweitbeste ist jetzt.', author: 'Chinesisches Sprichwort' }, { text: 'Übung macht den Meister.', author: 'Volksweisheit' }, { text: 'Es ist nicht genug zu wissen — man muss auch anwenden.', author: 'J. W. von Goethe' }, { text: 'Optimismus ist die Grundlage des Mutes.', author: 'Nick Butler' }, { text: 'Wo ein Wille ist, ist auch ein Weg.', author: 'Volksweisheit' }, { text: 'Die größte Entdeckungsreise liegt nicht darin, neue Länder zu suchen, sondern darin, die Dinge mit neuen Augen zu sehen.', author: 'Marcel Proust' }, { text: 'Der Klügere gibt nach — eine traurige Wahrheit, sie begründet die Weltherrschaft der Dummheit.', author: 'Marie von Ebner-Eschenbach' }, { text: 'Heute ist der erste Tag vom Rest deines Lebens.', author: 'Sprichwort' }, { text: 'Ein gutes Gewissen ist ein sanftes Ruhekissen.', author: 'Volksweisheit' }, { text: 'Kein Mensch hat das Recht, das zu unterlassen, was er kann.', author: 'Albert Schweitzer' }, { text: 'Wissen ist Macht.', author: 'Francis Bacon' }, { text: 'Lächle und die Welt lächelt zurück.', author: 'Volksweisheit' }, { text: 'Der Mut macht erst den Mann.', author: 'Friedrich Schiller' }, { text: 'Ein Lächeln kostet nichts, bringt aber viel.', author: 'Unbekannt' }, { text: 'Aus Fehlern lernt man — außer man macht sie nicht.', author: 'Sprichwort' }, { text: 'Denke nicht daran, was du tun könntest, sondern was du tust.', author: 'Unbekannt' }, { text: 'Die Zeit heilt alle Wunden.', author: 'Volksweisheit' }, // Weitere 30 Sprüche { text: 'Das Leben ist zu kurz, um schlechten Wein zu trinken.', author: 'Johann Wolfgang von Goethe' }, { text: 'Zwei Dinge sind unendlich: das Universum und die menschliche Dummheit. Beim Universum bin ich mir nicht ganz sicher.', author: 'Albert Einstein' }, { text: 'Wer mit Ungeheuern kämpft, mag zusehen, dass er nicht dabei zum Ungeheuer wird.', author: 'Friedrich Nietzsche' }, { text: 'Die Kunst des Lebens liegt darin, den richtigen Augenblick zu erkennen.', author: 'Unbekannt' }, { text: 'Tue jeden Tag etwas, das dich ein kleines Stück voranbringt.', author: 'Unbekannt' }, { text: 'Es ist leichter, einen Fehler zu gestehen als ihn zu rechtfertigen.', author: 'Unbekannt' }, { text: 'Wer andere kennt, ist klug. Wer sich selbst kennt, ist weise.', author: 'Laotse' }, { text: 'Wer immer tut, was er schon kann, bleibt immer das, was er schon ist.', author: 'Henry Ford' }, { text: 'Eine Reise von tausend Meilen beginnt mit einem einzigen Schritt.', author: 'Laotse' }, { text: 'Niemand kann dir das Gefühl geben, minderwertig zu sein, ohne deine Zustimmung.', author: 'Eleanor Roosevelt' }, { text: 'In jeder Schwierigkeit steckt eine Möglichkeit.', author: 'Albert Einstein' }, { text: 'Charakter zeigt sich nicht in Ausnahmesituationen des Lebens, sondern in seiner Alltäglichkeit.', author: 'Aristoteles' }, { text: 'Rede nicht darüber, was du tun wirst. Tu es.', author: 'Unbekannt' }, { text: 'Die größte Schwäche liegt darin aufzugeben. Der sicherste Weg zum Erfolg ist, es noch einmal zu versuchen.', author: 'Thomas Edison' }, { text: 'Gib jedem Tag die Chance, der schönste deines Lebens zu werden.', author: 'Mark Twain' }, { text: 'Wenn der Schüler bereit ist, erscheint der Lehrer.', author: 'Buddhistisches Sprichwort' }, { text: 'Der einzige Weg, gute Arbeit zu leisten, ist zu lieben, was man tut.', author: 'Steve Jobs' }, { text: 'Träume nicht dein Leben, sondern lebe deinen Traum.', author: 'Unbekannt' }, { text: 'Das Leben ist das, was passiert, während du eifrig dabei bist, andere Pläne zu machen.', author: 'John Lennon' }, { text: 'Manchmal muss man einen Schritt zurückgehen, um zwei nach vorne machen zu können.', author: 'Unbekannt' }, { text: 'Das Herz sieht schärfer als das Auge.', author: 'Russisches Sprichwort' }, { text: 'Was du nicht willst, das man dir tu, das füg auch keinem andern zu.', author: 'Goldene Regel' }, { text: 'Der Erfolg hat viele Väter, aber die Niederlage ist eine Waise.', author: 'John F. Kennedy' }, { text: 'Qualität ist keine Handlung, sie ist eine Gewohnheit.', author: 'Aristoteles' }, { text: 'Das Ziel des Lebens ist Selbstentfaltung.', author: 'Oscar Wilde' }, { text: 'Die Natur eilt nie und bringt doch alles zustande.', author: 'Laotse' }, { text: 'Wir sind, was wir wiederholt tun. Herausragend sein ist keine Handlung, sondern eine Gewohnheit.', author: 'Aristoteles' }, { text: 'Mut ist nicht die Abwesenheit von Angst, sondern die Überzeugung, dass es etwas Wichtigeres gibt als Angst.', author: 'Nelson Mandela' }, { text: 'Vergiss nicht: Du bist der Architekt deines eigenen Schicksals.', author: 'Alfred A. Montapert' }, { text: 'Jeder, der aufgehört hat zu lernen, ist alt geworden — ob mit zwanzig oder mit achtzig.', author: 'Henry Ford' }, ]; function getDayOfYear(date: Date): number { const start = new Date(date.getFullYear(), 0, 0); return Math.floor((date.getTime() - start.getTime()) / 86_400_000); } function QuoteOfTheDay() { const q = QUOTES[getDayOfYear(new Date()) % QUOTES.length]; return (
„{q.text}" — {q.author}
); } // ── Messe-Ticker: Hilfsfunktionen ───────────────────────────────────────────── function isStillRelevant(event: TradeEvent): boolean { const endDay = new Date(event.endDate); endDay.setHours(23, 59, 59, 999); const cutoff = new Date(endDay.getTime() + 24 * 60 * 60 * 1000); return new Date() < cutoff; } function formatDateRange(start: string, end: string): string { const opts: Intl.DateTimeFormatOptions = { day: '2-digit', month: '2-digit', year: 'numeric' }; return `${new Date(start).toLocaleDateString('de-DE', opts)} – ${new Date(end).toLocaleDateString('de-DE', opts)}`; } // ── Kompakte Messe-Zeile (klickbar) ────────────────────────────────────────── function CompactMesseRow({ event, onClick, }: { event: TradeEvent; onClick: () => void; }) { const countdown = useEventCountdown(event); const color = countdown.status === 'upcoming' ? '#3b82f6' : countdown.status === 'ongoing' ? '#22c55e' : '#9ca3af'; return ( ); } // ── Messe-Detail-Popup ──────────────────────────────────────────────────────── function MesseDetailModal({ event, onClose, }: { event: TradeEvent; onClose: () => void; }) { const countdown = useEventCountdown(event); const color = countdown.status === 'upcoming' ? '#3b82f6' : countdown.status === 'ongoing' ? '#22c55e' : '#9ca3af'; return (
e.stopPropagation()} > {/* Header */}

{event.name}

{countdown.status === 'upcoming' ? 'Bevorstehend' : countdown.status === 'ongoing' ? 'Läuft' : 'Beendet'}
{/* Countdown */}
{countdown.label}
{countdown.status === 'ongoing' && (
)} {/* Daten */}
{formatDateRange(event.startDate, event.endDate)}
{event.location && (
{event.location}{event.boothInfo ? ` · Stand: ${event.boothInfo}` : ''}
)} {!event.location && event.boothInfo && (
Stand: {event.boothInfo}
)}
{event.description && (

{event.description}

)} {event.websiteUrl && ( Website öffnen ↗ )}
); } // ── Kompakter Messe-Ticker für Sidebar ─────────────────────────────────────── function CompactMesseTicker() { const { data } = useActiveTradeEvents(); const [selected, setSelected] = useState(null); const events = (data?.data ?? []).filter(isStillRelevant); if (events.length === 0) return null; return (

Messen

{events.map((ev) => ( setSelected(ev)} /> ))}
{selected && ( setSelected(null)} /> )}
); } // ── Linke Spalte: Uhr + Wetter + 3-Tage-Prognose ───────────────────────────── function HomeLeftColumn({ city }: { city?: string | null }) { const { data: weatherData } = useWeather(city ?? undefined); return (
{/* Analoge Uhr */} {/* Aktuelles Wetter */}
{/* 3-Tage-Prognose */} {weatherData?.forecast && weatherData.forecast.length > 0 && (

3-Tage-Prognose

{weatherData.forecast.map((day) => { const d = new Date(day.date); const dayLabel = d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit', }); return (
{dayLabel} {day.icon} {day.tempMax}°{' '} {day.tempMin}°
); })}
)}
); } // ── Mittlere Spalte: Kompakte Aufgaben ──────────────────────────────────────── const CRM_MARKER_RE = /\[INSIGHT_CRM:([^\]]+)\]/; function extractCrmId(body: string | null | undefined): string | null { if (!body) return null; const m = CRM_MARKER_RE.exec(body); return m ? m[1] : null; } function formatDue(isoOrDateTime: string | null): string | null { if (!isoOrDateTime) return null; try { const d = new Date(isoOrDateTime); const today = new Date(); const todayStr = today.toISOString().slice(0, 10); const dStr = d.toISOString().slice(0, 10); const tomorrow = new Date(today.getTime() + 86_400_000).toISOString().slice(0, 10); if (dStr === todayStr) return 'Heute'; if (dStr === tomorrow) return 'Morgen'; if (dStr < todayStr) { const days = Math.round((today.getTime() - d.getTime()) / 86_400_000); return `Überfällig (${days}d)`; } return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); } catch { return null; } } function isDue(iso: string | null): boolean { if (!iso) return false; try { return new Date(iso) < new Date(); } catch { return false; } } function HomeTasksWidget({ onSeeAll }: { onSeeAll?: () => void }) { const { data: integrationsData } = useIntegrations(); const isO365Connected = integrationsData?.data?.some( (i) => i.provider === 'MICROSOFT_365' && i.connected, ) ?? false; const { data: o365Data, isLoading: o365Loading } = useOffice365TasksFlat(); const { data: crmData, isLoading: crmLoading } = useCrmOpenTasks(); const completeO365 = useCompleteO365Task(); const completeCrm = useCompleteCrmTask(); const [pendingKey, setPendingKey] = useState(null); const o365Tasks: M365TaskFlat[] = o365Data?.data ?? []; const crmTasks: CrmOpenTask[] = crmData?.data ?? []; // CRM-IDs die bereits in O365 sind const syncedCrmIds = new Set(); for (const t of o365Tasks) { const id = extractCrmId(t.bodyContent); if (id) syncedCrmIds.add(id); } // Unified list (gleiche Logik wie DashboardTasksTab) type TaskSource = 'o365' | 'crm' | 'synced'; interface HomeTask { key: string; source: TaskSource; title: string; dueDate: string | null; o365ListId?: string; o365TaskId?: string; crmActivityId?: string; } const unified: HomeTask[] = []; for (const t of o365Tasks) { const crmId = extractCrmId(t.bodyContent); unified.push({ key: `o365-${t.id}`, source: crmId ? 'synced' : 'o365', title: t.title, dueDate: t.dueDateTime?.dateTime ?? null, o365ListId: t.listId, o365TaskId: t.id, crmActivityId: crmId ?? undefined, }); } for (const c of crmTasks) { if (syncedCrmIds.has(c.id)) continue; unified.push({ key: `crm-${c.id}`, source: 'crm', title: c.subject, dueDate: c.scheduledAt, crmActivityId: c.id, }); } unified.sort((a, b) => { const aO = a.dueDate && isDue(a.dueDate); const bO = b.dueDate && isDue(b.dueDate); if (aO && !bO) return -1; if (!aO && bO) return 1; if (a.dueDate && b.dueDate) return a.dueDate.localeCompare(b.dueDate); if (a.dueDate) return -1; if (b.dueDate) return 1; return 0; }); const visible = unified.slice(0, 8); const isLoading = (isO365Connected && o365Loading) || crmLoading; function handleComplete(task: HomeTask) { setPendingKey(task.key); const ps: Promise[] = []; if (task.o365TaskId && task.o365ListId) { ps.push(completeO365.mutateAsync({ listId: task.o365ListId, taskId: task.o365TaskId })); } if (task.crmActivityId) { ps.push(completeCrm.mutateAsync(task.crmActivityId)); } Promise.allSettled(ps).finally(() => setPendingKey(null)); } return (

Aufgaben

{onSeeAll && ( )}
{isLoading && (

Lädt…

)} {!isLoading && visible.length === 0 && (

✅ Keine offenen Aufgaben

)} {!isLoading && visible.length > 0 && (
{visible.map((task) => { const overdue = isDue(task.dueDate); const dueFmt = formatDue(task.dueDate); return (
{/* Source-Badge */} {(task.source === 'o365' || task.source === 'synced') && ( O )} {task.source === 'crm' && ( C )}
{task.title} {dueFmt && ( {dueFmt} )}
); })}
)} {unified.length > 8 && (
{unified.length - 8} weitere Aufgaben
)}
); } // ── Mittlere Spalte: Kompakte E-Mails (letzte 3 Tage) ──────────────────────── function formatEmailTime(iso: string): string { const d = new Date(iso); const now = new Date(); if (d.toDateString() === now.toDateString()) { return d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); } return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); } function HomeEmailsWidget({ onSeeAll }: { onSeeAll?: () => void }) { const { data: integrationsData } = useIntegrations(); const isConnected = integrationsData?.data?.some( (i) => i.provider === 'MICROSOFT_365' && i.connected, ) ?? false; const { data: emailData, isLoading } = useOffice365Emails(); if (!isConnected) return null; // Letzte 3 Tage filtern const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - 3); const emails: M365Email[] = (emailData?.data ?? []).filter( (e) => new Date(e.receivedDateTime) >= cutoff, ); return (

E-Mails (3 Tage)

{onSeeAll && ( )}
{isLoading &&

Lädt…

} {!isLoading && emails.length === 0 && (

Keine E-Mails in den letzten 3 Tagen

)} {!isLoading && emails.length > 0 && (
{emails.slice(0, 8).map((email) => { const sender = email.from?.emailAddress?.name ?? email.from?.emailAddress?.address ?? 'Unbekannt'; const subject = email.subject ?? '(kein Betreff)'; const timeStr = formatEmailTime(email.receivedDateTime); return ( {!email.isRead ? ( ) : ( )}
{sender}
{subject}
{timeStr}
); })}
)} {emails.length > 8 && (
{emails.length - 8} weitere E-Mails
)}
); } // ── Sidebar: Messe-Ticker + Tages-Agenda ───────────────────────────────────── function HomeSidebar() { const today = new Date(); const todayISO = today.toISOString().slice(0, 10); const tomorrowISO = new Date(today.getTime() + 86_400_000).toISOString().slice(0, 10); const { data: integrationsData } = useIntegrations(); const isConnected = integrationsData?.data?.some( (i) => i.provider === 'MICROSOFT_365' && i.connected, ) ?? false; const { data: eventsData, isLoading } = useOffice365CalendarRange(todayISO, tomorrowISO); const events: M365CalendarEvent[] = eventsData?.data ?? []; return (
{isConnected && ( isLoading ?

Kalender wird geladen…

: )}
); } // ── Tab-Definitionen ────────────────────────────────────────────────────────── type DashboardTab = 'home' | 'emails' | 'calendar' | 'tasks' | 'contacts'; const TABS: { id: DashboardTab; label: string }[] = [ { id: 'home', label: 'Home' }, { id: 'emails', label: 'E-Mail' }, { id: 'calendar', label: 'Kalender' }, { id: 'tasks', label: 'Aufgaben' }, { id: 'contacts', label: 'Kontakte' }, ]; // ── HomeTab ─────────────────────────────────────────────────────────────────── function HomeTab({ firstName, lastName, city, onSwitchTab, }: { firstName?: string; lastName?: string; city?: string | null; onSwitchTab: (tab: DashboardTab) => void; }) { return ( <> {/* Header: Name links, Spruch rechts */}

Willkommen, {firstName} {lastName}

{/* 3-Spalten-Layout */}
{/* Links: Uhr + Wetter + Prognose */} {/* Mitte: Aufgaben + E-Mails */}
onSwitchTab('tasks')} /> onSwitchTab('emails')} />
{/* Rechts: Messe-Ticker + Tagesagenda */}
); } // ── Main ────────────────────────────────────────────────────────────────────── const THEME_OPTIONS_DASH = [ { value: 'light' as const, icon: '☀' }, { value: 'dark' as const, icon: '☾' }, { value: 'system' as const, icon: '⚙' }, ]; export function DashboardPage() { const { user, logout } = useAuth(); const location = useLocation(); const navigate = useNavigate(); const { mode, setMode } = useTheme(); const [activeTab, setActiveTab] = useState('home'); const handleLogout = async () => { await logout(); navigate('/login'); }; // Immer auf Home-Tab springen wenn Dashboard-NavLink geklickt wird useEffect(() => { setActiveTab('home'); }, [location.key]); return (
{/* Tab-Leiste */}
{TABS.map((tab) => ( ))} {/* Profil-Bereich rechts in der Tab-Leiste */}
{/* Theme-Schalter */}
{THEME_OPTIONS_DASH.map((opt) => ( ))}
{/* Benutzer (→ Profil) */}
navigate('/profile')} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') navigate('/profile'); }} title="Profil bearbeiten" > {user?.firstName} {user?.lastName}
{/* Abmelden */}
{/* Tab-Inhalt */}
{activeTab === 'home' && ( )} {activeTab === 'emails' && } {activeTab === 'calendar' && } {activeTab === 'tasks' && } {activeTab === 'contacts' && }
); }