diff --git a/packages/frontend/src/shell/DashboardPage.module.css b/packages/frontend/src/shell/DashboardPage.module.css index 5593613..37f9fdb 100644 --- a/packages/frontend/src/shell/DashboardPage.module.css +++ b/packages/frontend/src/shell/DashboardPage.module.css @@ -72,8 +72,228 @@ } .homeSidebar { - width: 280px; + width: 300px; flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* ── Kompakter Messe-Ticker ── */ + +.compactMesse { + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + padding: 0.625rem 0.75rem; + box-shadow: var(--shadow-sm); +} + +.compactMesseTitle { + font-size: 0.6875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-text-muted); + margin: 0 0 0.5rem 0; +} + +.compactMesseList { + display: flex; + flex-direction: column; + gap: 2px; +} + +.messeRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + width: 100%; + background: none; + border: none; + border-left: 3px solid transparent; + padding: 0.3125rem 0.5rem; + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + cursor: pointer; + text-align: left; + transition: background 0.12s; +} + +.messeRow:hover { + background: var(--color-bg-subtle); +} + +.messeRowName { + font-size: 0.8125rem; + font-weight: 500; + color: var(--color-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} + +.messeRowCountdown { + font-size: 0.75rem; + color: var(--color-text-muted); + white-space: nowrap; + flex-shrink: 0; +} + +/* ── Messe-Detail-Modal ── */ + +.messeModalBackdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 500; + padding: 1rem; +} + +.messeModal { + background: var(--color-bg-card); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + border-top: 4px solid #3b82f6; + width: 100%; + max-width: 440px; + padding: 1.25rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.875rem; +} + +.messeModalHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.75rem; +} + +.messeModalTitle { + font-size: 1.0625rem; + font-weight: 700; + color: var(--color-text); + margin: 0 0 0.25rem 0; + line-height: 1.3; +} + +.messeStatusChip { + display: inline-flex; + padding: 0.125rem 0.5rem; + border-radius: 999px; + font-size: 0.625rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.messeChip_upcoming { + background: #dbeafe; + color: #1e40af; +} + +.messeChip_ongoing { + background: #dcfce7; + color: #166534; +} + +.messeChip_ended { + background: var(--color-bg-subtle); + color: var(--color-text-muted); +} + +.messeModalClose { + background: none; + border: none; + font-size: 1.375rem; + line-height: 1; + color: var(--color-text-muted); + cursor: pointer; + padding: 0; + flex-shrink: 0; +} + +.messeModalClose:hover { + color: var(--color-text); +} + +.messeModalCountdown { + font-size: 1.25rem; + font-weight: 700; + color: var(--color-text); + margin: 0; +} + +.messeProgressBar { + width: 100%; + height: 6px; + background: var(--color-bg-subtle); + border-radius: 3px; + overflow: hidden; +} + +.messeProgressFill { + height: 100%; + border-radius: 3px; + transition: width 0.4s ease; +} + +.messeModalMeta { + display: flex; + flex-direction: column; + gap: 0.375rem; +} + +.messeModalMetaRow { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--color-text-secondary); +} + +.messeModalMetaRow svg { + flex-shrink: 0; + color: var(--color-text-muted); +} + +.messeModalDesc { + font-size: 0.875rem; + color: var(--color-text-secondary); + margin: 0; + line-height: 1.55; + padding-top: 0.25rem; + border-top: 1px solid var(--color-border); +} + +.messeModalLink { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--color-primary); + text-decoration: none; +} + +.messeModalLink:hover { + text-decoration: underline; +} + +/* dark mode Messe-Modal */ +:global([data-theme='dark']) .messeChip_upcoming { + background: rgba(59, 130, 246, 0.15); + color: #93c5fd; +} + +:global([data-theme='dark']) .messeChip_ongoing { + background: rgba(34, 197, 94, 0.15); + color: #86efac; } /* ── Platzhalter für bestehenden Home-Inhalt ── */ diff --git a/packages/frontend/src/shell/DashboardPage.tsx b/packages/frontend/src/shell/DashboardPage.tsx index 43d45ab..0171d46 100644 --- a/packages/frontend/src/shell/DashboardPage.tsx +++ b/packages/frontend/src/shell/DashboardPage.tsx @@ -1,14 +1,185 @@ import { useState } from 'react'; import { useAuth } from '../auth/AuthContext'; import { WeatherWidget } from '../components/WeatherWidget'; -import { EventCountdownTiles } from '../components/EventCountdownTiles'; import { DashboardEmailTab } from './DashboardEmailTab'; import { DashboardCalendarTab, DayAgenda } from './DashboardCalendarTab'; import { DashboardTasksTab } from './DashboardTasksTab'; -import { useIntegrations, useOffice365CalendarRange } from '../crm/hooks'; -import type { M365CalendarEvent } from '../crm/types'; +import { useIntegrations, useOffice365CalendarRange, useActiveTradeEvents } from '../crm/hooks'; +import { useEventCountdown } from '../hooks/useEventCountdown'; +import type { M365CalendarEvent, TradeEvent } from '../crm/types'; import styles from './DashboardPage.module.css'; +// ── 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)} /> + )} +
+ ); +} + type DashboardTab = 'home' | 'emails' | 'calendar' | 'tasks' | 'contacts'; const TABS: { id: DashboardTab; label: string }[] = [ @@ -19,9 +190,9 @@ const TABS: { id: DashboardTab; label: string }[] = [ { id: 'contacts', label: 'Kontakte' }, ]; -// ── Tages-Agenda Widget für Home-Tab ───────────────────────────────────────── +// ── Sidebar: Messe-Ticker + Tages-Agenda ───────────────────────────────────── -function HomeDayAgendaWidget() { +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); @@ -35,16 +206,19 @@ function HomeDayAgendaWidget() { const { data: eventsData, isLoading } = useOffice365CalendarRange(todayISO, tomorrowISO); const events: M365CalendarEvent[] = eventsData?.data ?? []; - if (!isConnected) return null; - if (isLoading) return ( -
-

Kalender wird geladen…

-
- ); - return (
- + {/* Kompakter Messe-Ticker immer oben */} + + + {/* Tages-Agenda nur wenn O365 verbunden */} + {isConnected && ( + isLoading + ?

+ Kalender wird geladen… +

+ : + )}
); } @@ -67,7 +241,6 @@ function HomeTab({ firstName, lastName, city, role }: {
-

INSIGHT Platform - Sprint 1 Alpha @@ -77,7 +250,7 @@ function HomeTab({ firstName, lastName, city, role }: {

- +
);