From fbf0b33a1f57c0581c4894c553505be494645c95 Mon Sep 17 00:00:00 2001
From: Thomas Reitz
Date: Fri, 13 Mar 2026 11:36:30 +0100
Subject: [PATCH] feat(dashboard): E-Mail Lesefenster + Kalender Umbau
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
E-Mail Tab:
- Klick auf E-Mail öffnet Detail-Modal (Lesefenster wie Outlook)
- Modal zeigt: Betreff, Absender, Datum, Anhang-Info, Body-Vorschau
- CRM-Bereich: gefundener Kontakt mit "Im CRM öffnen" + "Als Aktivität"
speichern; kein Kontakt → "Kontakt anlegen" navigiert zu /crm/contacts
- "In Outlook öffnen" Link im Footer des Modals
Kalender Tab:
- WeekView: nur Arbeitstage Mo–Fr (5-Spalten-Grid statt 7)
- Neue Ansicht "Agenda": 14-Tage-Listenansicht (eigener Toggle-Button)
- Tages-Agenda nur bei Monat- und Wochenansicht sichtbar (nicht Agenda-View)
- Home-Tab: Tages-Agenda des heutigen Tages als Widget rechts
(nur sichtbar wenn M365 verbunden)
Co-Authored-By: Claude Sonnet 4.6
---
.../src/shell/DashboardCalendarTab.module.css | 128 ++++++++-
.../src/shell/DashboardCalendarTab.tsx | 163 +++++++++--
.../src/shell/DashboardEmailTab.module.css | 260 ++++++++++++++++--
.../frontend/src/shell/DashboardEmailTab.tsx | 214 ++++++++++++--
.../src/shell/DashboardPage.module.css | 21 ++
packages/frontend/src/shell/DashboardPage.tsx | 55 +++-
6 files changed, 759 insertions(+), 82 deletions(-)
diff --git a/packages/frontend/src/shell/DashboardCalendarTab.module.css b/packages/frontend/src/shell/DashboardCalendarTab.module.css
index 75e3f97..b73cfb9 100644
--- a/packages/frontend/src/shell/DashboardCalendarTab.module.css
+++ b/packages/frontend/src/shell/DashboardCalendarTab.module.css
@@ -277,11 +277,11 @@
padding-left: 4px;
}
-/* ── Wochenansicht ── */
+/* ── Wochenansicht (Mo–Fr) ── */
.weekGrid {
display: grid;
- grid-template-columns: repeat(7, 1fr);
+ grid-template-columns: repeat(5, 1fr); /* nur Arbeitstage */
}
.weekCol {
@@ -383,6 +383,130 @@
text-overflow: ellipsis;
}
+/* ── Agenda-Listenansicht (eigene View, 14 Tage) ── */
+
+.agendaFullView {
+ display: flex;
+ flex-direction: column;
+}
+
+.agendaFullDay {
+ display: flex;
+ align-items: flex-start;
+ gap: 0;
+ border-bottom: 1px solid var(--color-border);
+ cursor: pointer;
+ transition: background 0.1s;
+}
+
+.agendaFullDay:last-child {
+ border-bottom: none;
+}
+
+.agendaFullDay:hover {
+ background: rgba(59, 130, 246, 0.03);
+}
+
+.agendaFullDaySelected {
+ background: rgba(59, 130, 246, 0.06) !important;
+}
+
+.agendaFullDayHdr {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 72px;
+ flex-shrink: 0;
+ padding: 0.875rem 0.5rem;
+ gap: 0.125rem;
+ border-right: 1px solid var(--color-border);
+}
+
+.agendaFullDayHdrToday .agendaFullNum {
+ background: var(--color-primary);
+ color: #fff;
+ border-radius: 50%;
+}
+
+.agendaFullWeekday {
+ font-size: 0.6875rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--color-text-muted);
+}
+
+.agendaFullNum {
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--color-text);
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.agendaFullNumToday {
+ background: var(--color-primary);
+ color: #fff;
+ border-radius: 50%;
+}
+
+.agendaFullMonth {
+ font-size: 0.6875rem;
+ color: var(--color-text-muted);
+}
+
+.agendaFullEvents {
+ flex: 1;
+ padding: 0.625rem 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+}
+
+.agendaFullEmpty {
+ font-size: 0.8125rem;
+ color: var(--color-text-muted);
+ padding: 0.25rem 0;
+}
+
+.agendaFullEvent {
+ display: flex;
+ align-items: center;
+ gap: 0.625rem;
+ padding: 0.375rem 0.625rem;
+ border-left: 3px solid transparent;
+ border-radius: 2px;
+ text-decoration: none;
+ color: inherit;
+ background: var(--color-bg);
+ transition: background 0.12s;
+}
+
+.agendaFullEvent:hover {
+ background: rgba(59, 130, 246, 0.07);
+}
+
+.agendaFullEventTime {
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--color-text-muted);
+ flex-shrink: 0;
+ min-width: 42px;
+}
+
+.agendaFullEventSubj {
+ font-size: 0.875rem;
+ color: var(--color-text);
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
/* ── Tages-Agenda (rechte Spalte) ── */
.agenda {
diff --git a/packages/frontend/src/shell/DashboardCalendarTab.tsx b/packages/frontend/src/shell/DashboardCalendarTab.tsx
index 14c243b..749021c 100644
--- a/packages/frontend/src/shell/DashboardCalendarTab.tsx
+++ b/packages/frontend/src/shell/DashboardCalendarTab.tsx
@@ -3,7 +3,7 @@ import { useIntegrations, useOffice365CalendarRange } from '../crm/hooks';
import type { M365CalendarEvent } from '../crm/types';
import styles from './DashboardCalendarTab.module.css';
-type ViewMode = 'month' | 'week';
+export type CalendarViewMode = 'month' | 'week' | 'agenda';
// ── Date Helpers ───────────────────────────────────────────────────────────────
@@ -36,10 +36,10 @@ function getMonthCells(date: Date): Date[] {
return Array.from({ length: 42 }, (_, i) => addDays(gridStart, i));
}
-/** 7 Tage der Woche (Mo–So) */
-function getWeekDays(date: Date): Date[] {
+/** 5 Arbeitstage der Woche (Mo–Fr) */
+function getWorkWeekDays(date: Date): Date[] {
const monday = startOfWeekMonday(date);
- return Array.from({ length: 7 }, (_, i) => addDays(monday, i));
+ return Array.from({ length: 5 }, (_, i) => addDays(monday, i));
}
function toISODate(date: Date): string {
@@ -70,9 +70,9 @@ function eventColor(id: string): string {
return EVENT_COLORS[h];
}
-// ── Day Agenda (rechte Spalte) ─────────────────────────────────────────────────
+// ── Day Agenda (Tages-Detailansicht, auch für Home) ───────────────────────────
-function DayAgenda({
+export function DayAgenda({
day,
events,
}: {
@@ -137,6 +137,78 @@ function DayAgenda({
);
}
+// ── 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' })}
+
+
+
+
+ );
+ })}
+
+ );
+}
+
// ── Monatsansicht ─────────────────────────────────────────────────────────────
const WEEKDAY_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
@@ -211,7 +283,7 @@ function MonthView({
);
}
-// ── Wochenansicht ─────────────────────────────────────────────────────────────
+// ── Wochenansicht (Mo–Fr) ─────────────────────────────────────────────────────
function WeekView({
currentDate,
@@ -225,7 +297,7 @@ function WeekView({
onDayClick: (d: Date) => void;
}) {
const today = new Date();
- const weekDays = getWeekDays(currentDate);
+ const weekDays = getWorkWeekDays(currentDate); // nur Mo–Fr
return (
@@ -287,7 +359,7 @@ export function DashboardCalendarTab() {
(i) => i.provider === 'MICROSOFT_365' && i.connected,
) ?? false;
- const [viewMode, setViewMode] = useState
('month');
+ const [viewMode, setViewMode] = useState('month');
const [currentDate, setCurrentDate] = useState(() => new Date());
const [selectedDay, setSelectedDay] = useState(() => new Date());
@@ -297,12 +369,16 @@ export function DashboardCalendarTab() {
? startOfWeekMonday(
new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
)
- : startOfWeekMonday(currentDate);
+ : viewMode === 'week'
+ ? startOfWeekMonday(currentDate)
+ : currentDate; // agenda: ab currentDate
const rangeEnd =
viewMode === 'month'
- ? addDays(rangeStart, 42) // 6 Wochen
- : addDays(startOfWeekMonday(currentDate), 7);
+ ? 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),
@@ -314,8 +390,10 @@ export function DashboardCalendarTab() {
const d = new Date(currentDate);
if (viewMode === 'month') {
d.setMonth(d.getMonth() + delta);
- } else {
+ } else if (viewMode === 'week') {
d.setDate(d.getDate() + delta * 7);
+ } else {
+ d.setDate(d.getDate() + delta * 14);
}
setCurrentDate(d);
};
@@ -327,18 +405,26 @@ export function DashboardCalendarTab() {
};
// Anzeigebezeichnung
- const rangeLabel =
- viewMode === 'month'
- ? currentDate.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })
- : (() => {
- const days = getWeekDays(currentDate);
- const f = days[0];
- const l = days[6];
- 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' })}`;
- })();
+ 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…
;
@@ -403,6 +489,13 @@ export function DashboardCalendarTab() {
>
Woche
+ setViewMode('agenda')}
+ >
+ Agenda
+
@@ -416,18 +509,19 @@ export function DashboardCalendarTab() {
)}
- {/* Hauptbereich: Kalender + Agenda */}
+ {/* Hauptbereich */}
{!isLoading && (
- {viewMode === 'month' ? (
+ {viewMode === 'month' && (
- ) : (
+ )}
+ {viewMode === 'week' && (
)}
+ {viewMode === 'agenda' && (
+
+ )}
-
+ {/* Tages-Agenda nur bei Monat- und Wochenansicht */}
+ {(viewMode === 'month' || viewMode === 'week') && (
+
+ )}
)}
diff --git a/packages/frontend/src/shell/DashboardEmailTab.module.css b/packages/frontend/src/shell/DashboardEmailTab.module.css
index 81e94d4..06806b1 100644
--- a/packages/frontend/src/shell/DashboardEmailTab.module.css
+++ b/packages/frontend/src/shell/DashboardEmailTab.module.css
@@ -212,6 +212,7 @@
border-radius: var(--radius-sm);
transition: border-color 0.15s, box-shadow 0.15s;
overflow: hidden;
+ cursor: pointer;
}
.emailCard:hover {
@@ -223,12 +224,10 @@
border-left: 3px solid var(--color-primary);
}
-.emailLink {
+.emailCardInner {
display: block;
padding: 0.75rem 1rem;
- text-decoration: none;
color: inherit;
- padding-right: 6rem; /* Platz für den "Aktivität"-Button */
}
.emailHeader {
@@ -307,36 +306,261 @@
word-break: break-word;
}
-/* ── Aktivität-Button (absolut positioniert innerhalb der Karte) ── */
+/* ── Detail-Modal (E-Mail Lesefenster) ── */
-.saveActivityBtn {
- position: absolute;
- top: 0.75rem;
- right: 0.75rem;
- padding: 0.3rem 0.625rem;
+.detailModal {
+ background: var(--color-bg-card);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius);
+ width: 100%;
+ max-width: 680px;
+ max-height: 88vh;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);
+ overflow: hidden;
+}
+
+.detailHeader {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 1.25rem 1.5rem 1rem;
+ border-bottom: 1px solid var(--color-border);
+ flex-shrink: 0;
+}
+
+.detailSubject {
+ font-size: 1.0625rem;
+ font-weight: 600;
+ color: var(--color-text);
+ margin: 0;
+ line-height: 1.35;
+}
+
+.closeBtn {
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.75rem;
+ height: 1.75rem;
+ background: none;
+ border: none;
+ color: var(--color-text-muted);
+ font-size: 1rem;
+ cursor: pointer;
+ border-radius: 50%;
+ transition: background 0.12s, color 0.12s;
+ margin-top: -0.125rem;
+}
+
+.closeBtn:hover {
+ background: var(--color-bg);
+ color: var(--color-text);
+}
+
+.detailMeta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+ padding: 0.875rem 1.5rem;
+ background: var(--color-bg);
+ border-bottom: 1px solid var(--color-border);
+ flex-shrink: 0;
+}
+
+.detailMetaRow {
+ display: flex;
+ align-items: baseline;
+ gap: 0.75rem;
+}
+
+.detailMetaLabel {
font-size: 0.75rem;
font-weight: 600;
+ color: var(--color-text-muted);
+ min-width: 48px;
+ flex-shrink: 0;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.detailMetaValue {
+ font-size: 0.875rem;
+ color: var(--color-text);
+}
+
+.detailSenderEmail {
+ color: var(--color-text-muted);
+ font-size: 0.8125rem;
+}
+
+.detailBody {
+ flex: 1;
+ overflow-y: auto;
+ padding: 1.25rem 1.5rem;
+ font-size: 0.9rem;
+ color: var(--color-text);
+ line-height: 1.6;
+ white-space: pre-wrap;
+ word-break: break-word;
+ min-height: 100px;
+ max-height: 280px;
+ background: var(--color-bg-card);
+}
+
+.detailBodyEmpty {
+ color: var(--color-text-muted);
+ font-style: italic;
+}
+
+/* ── CRM-Bereich im Detail-Modal ── */
+
+.detailCrm {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.875rem 1.5rem;
background: var(--color-bg);
- color: var(--color-primary);
+ border-top: 1px solid var(--color-border);
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.detailCrmTitle {
+ font-size: 0.75rem;
+ font-weight: 700;
+ color: var(--color-text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ flex-shrink: 0;
+}
+
+.detailCrmLoading {
+ font-size: 0.875rem;
+ color: var(--color-text-muted);
+}
+
+.detailCrmFound {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.detailCrmInfo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.detailCrmAvatar {
+ font-size: 1.125rem;
+}
+
+.detailCrmName {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: var(--color-text);
+}
+
+.detailCrmCompany {
+ font-size: 0.875rem;
+ color: var(--color-text-muted);
+}
+
+.detailCrmActions {
+ display: flex;
+ gap: 0.5rem;
+ flex-shrink: 0;
+}
+
+.detailCrmOpenBtn {
+ padding: 0.375rem 0.875rem;
+ background: none;
border: 1px solid var(--color-primary);
border-radius: var(--radius-sm);
+ color: var(--color-primary);
+ font-size: 0.8125rem;
+ font-weight: 600;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
- opacity: 0;
- pointer-events: none;
}
-.emailCard:hover .saveActivityBtn {
- opacity: 1;
- pointer-events: auto;
-}
-
-.saveActivityBtn:hover {
+.detailCrmOpenBtn:hover {
background: var(--color-primary);
color: #fff;
}
+.detailCrmMissing {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 0.875rem;
+ flex-wrap: wrap;
+}
+
+.detailCrmMissingText {
+ font-size: 0.875rem;
+ color: var(--color-text-muted);
+}
+
+.detailCrmCreateBtn {
+ padding: 0.375rem 0.875rem;
+ background: none;
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ color: var(--color-text-muted);
+ font-size: 0.8125rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+
+.detailCrmCreateBtn:hover {
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+
+/* ── Detail-Footer ── */
+
+.detailFooter {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 0.75rem;
+ padding: 0.875rem 1.5rem;
+ border-top: 1px solid var(--color-border);
+ background: var(--color-bg-card);
+ flex-shrink: 0;
+}
+
+.outlookBtn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.25rem;
+ padding: 0.4375rem 1rem;
+ background: none;
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ color: var(--color-text-muted);
+ font-size: 0.875rem;
+ font-weight: 500;
+ text-decoration: none;
+ transition: all 0.15s;
+}
+
+.outlookBtn:hover {
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+
/* ── Aktivität-Modal ── */
.modalOverlay {
diff --git a/packages/frontend/src/shell/DashboardEmailTab.tsx b/packages/frontend/src/shell/DashboardEmailTab.tsx
index dc4a550..3805585 100644
--- a/packages/frontend/src/shell/DashboardEmailTab.tsx
+++ b/packages/frontend/src/shell/DashboardEmailTab.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import {
useIntegrations,
@@ -67,27 +68,28 @@ function formatEmailDate(iso: string): string {
});
}
-// ── EmailCard ─────────────────────────────────────────────────────────────────
+// ── EmailCard (Klick öffnet Detail-Modal) ────────────────────────────────────
interface EmailCardProps {
email: M365Email;
- onSaveActivity: (email: M365Email, contact: CrmContactLookup) => void;
+ onClick: () => void;
}
-function EmailCard({ email, onSaveActivity }: EmailCardProps) {
+function EmailCard({ email, onClick }: EmailCardProps) {
const senderEmail = email.from?.emailAddress?.address ?? null;
const { data: contactData } = useContactByEmail(senderEmail);
const contact = contactData?.data ?? null;
const displayName = email.from?.emailAddress?.name || senderEmail || '—';
return (
-
-
+ e.key === 'Enter' && onClick()}
+ >
+
{!email.isRead && }
@@ -114,17 +116,168 @@ function EmailCard({ email, onSaveActivity }: EmailCardProps) {
{email.bodyPreview && (
{email.bodyPreview}
)}
-
- {contact && (
- onSaveActivity(email, contact)}
- >
- + Aktivität
-
- )}
+
+
+ );
+}
+
+// ── EmailDetailModal ──────────────────────────────────────────────────────────
+
+interface EmailDetailModalProps {
+ email: M365Email;
+ onClose: () => void;
+ onSaveActivity: (email: M365Email, contact: CrmContactLookup) => void;
+}
+
+function EmailDetailModal({ email, onClose, onSaveActivity }: EmailDetailModalProps) {
+ const navigate = useNavigate();
+ const senderEmail = email.from?.emailAddress?.address ?? null;
+ const senderName = email.from?.emailAddress?.name ?? '';
+
+ const { data: contactData, isLoading: contactLoading } = useContactByEmail(senderEmail);
+ const contact = contactData?.data ?? null;
+
+ const contactName =
+ contact
+ ? [contact.firstName, contact.lastName].filter(Boolean).join(' ') ||
+ contact.email ||
+ '—'
+ : null;
+
+ const handleOpenContact = () => {
+ onClose();
+ navigate(`/crm/contacts/${contact!.id}`);
+ };
+
+ const handleCreateContact = () => {
+ onClose();
+ navigate('/crm/contacts');
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+ {/* ── Kopfzeile ── */}
+
+
+ {email.subject || '(Kein Betreff)'}
+
+
+ ✕
+
+
+
+ {/* ── Meta ── */}
+
+
+ Von
+
+ {senderName || senderEmail || '—'}
+ {senderName && senderEmail && (
+ <{senderEmail}>
+ )}
+
+
+
+ Datum
+
+ {new Date(email.receivedDateTime).toLocaleString('de-DE', {
+ weekday: 'short',
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+
+ {email.hasAttachments && (
+
+ Anhang
+ 📎 Vorhanden
+
+ )}
+
+
+ {/* ── Body-Vorschau ── */}
+
+ {email.bodyPreview || (
+ Kein Vorschautext verfügbar.
+ )}
+
+
+ {/* ── CRM-Bereich ── */}
+
+
CRM
+
+ {contactLoading ? (
+
Wird geprüft…
+ ) : contact ? (
+
+
+
👤
+
+ {contactName}
+ {contact.companyName && (
+ · {contact.companyName}
+ )}
+
+
+
+
+ Im CRM öffnen →
+
+ { onClose(); onSaveActivity(email, contact); }}
+ >
+ Als Aktivität speichern
+
+
+
+ ) : (
+
+
+ {senderEmail
+ ? `Kein CRM-Kontakt für ${senderEmail}`
+ : 'Kein Absender bekannt'}
+
+ {senderEmail && (
+
+ + Kontakt anlegen
+
+ )}
+
+ )}
+
+
+ {/* ── Footer ── */}
+
+
);
}
@@ -242,6 +395,7 @@ export function DashboardEmailTab() {
const [selectedFolderId, setSelectedFolderId] = useState
(null);
const [days, setDays] = useState(7);
+ const [detailEmail, setDetailEmail] = useState(null);
const [activityTarget, setActivityTarget] = useState(null);
const [lastSaved, setLastSaved] = useState(null);
@@ -252,7 +406,6 @@ export function DashboardEmailTab() {
// Standardmäßig Posteingang — Graph API akzeptiert Well-Known-Namen direkt als ID,
// sodass E-Mails sofort geladen werden, bevor die Ordnerliste verfügbar ist.
- // Sobald Ordner geladen sind, wird die echte ID aus der Liste verwendet.
const inboxFolder = sortedFolders.find(isInboxFolder);
const activeFolderId = selectedFolderId ?? inboxFolder?.id ?? 'inbox';
@@ -370,15 +523,28 @@ export function DashboardEmailTab() {
{
+ onClick={() => {
setLastSaved(null);
- setActivityTarget({ email: e, contact });
+ setDetailEmail(email);
}}
/>
))}
+ {/* Detail-Modal */}
+ {detailEmail && (
+ setDetailEmail(null)}
+ onSaveActivity={(e, contact) => {
+ setDetailEmail(null);
+ setLastSaved(null);
+ setActivityTarget({ email: e, contact });
+ }}
+ />
+ )}
+
{/* Aktivität-Modal */}
{activityTarget && (
i.provider === 'MICROSOFT_365' && i.connected,
+ ) ?? false;
+
+ const { data: eventsData, isLoading } = useOffice365CalendarRange(todayISO, tomorrowISO);
+ const events: M365CalendarEvent[] = eventsData?.data ?? [];
+
+ if (!isConnected) return null;
+ if (isLoading) return (
+
+
Kalender wird geladen…
+
+ );
+
+ return (
+
+
+
+ );
+}
+
// ── Tab-Inhalte ───────────────────────────────────────────────────────────────
function HomeTab({ firstName, lastName, city, role }: {
@@ -32,14 +64,19 @@ function HomeTab({ firstName, lastName, city, role }: {
-
-
-
- INSIGHT Platform - Sprint 1 Alpha
-
-
- Rolle: {role}
-
+
+
+
+
+
+ INSIGHT Platform - Sprint 1 Alpha
+
+
+ Rolle: {role}
+
+
+
+
>
);