import { useState } from 'react'; import { Link } from 'react-router-dom'; import { useIntegrations, useOffice365TasksFlat, useCrmOpenTasks, usePushTaskToO365, useCompleteO365Task, useCompleteCrmTask, } from '../crm/hooks'; import type { M365TaskFlat, CrmOpenTask } from '../crm/types'; import styles from './DashboardTasksTab.module.css'; // ── CRM-Sync-Marker Regex ──────────────────────────────────────────────────── 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; } // ── Typen für die vereinheitlichte Liste ───────────────────────────────────── type TaskSource = 'o365' | 'crm' | 'synced'; interface UnifiedTask { key: string; source: TaskSource; title: string; dueDate: string | null; contactLabel: string | null; contactId: string | null; companyLabel: string | null; companyId: string | null; importance?: 'low' | 'normal' | 'high'; // O365-spezifisch o365ListId?: string; o365TaskId?: string; // CRM-spezifisch crmActivityId?: string; crmType?: 'TASK' | 'FOLLOWUP'; } // ── Datum formatieren ──────────────────────────────────────────────────────── 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', year: 'numeric' }); } catch { return null; } } function isDue(isoOrDateTime: string | null): boolean { if (!isoOrDateTime) return false; try { const d = new Date(isoOrDateTime); return d < new Date(); } catch { return false; } } // ── Source-Badge ───────────────────────────────────────────────────────────── function SourceBadge({ source }: { source: TaskSource }) { return ( {(source === 'o365' || source === 'synced') && ( O365 )} {(source === 'crm' || source === 'synced') && ( CRM )} ); } // ── Einzelne Aufgaben-Zeile ─────────────────────────────────────────────────── function TaskRow({ task, onComplete, onPushToO365, isPushPending, isCompletePending, isO365Connected, }: { task: UnifiedTask; onComplete: (t: UnifiedTask) => void; onPushToO365: (t: UnifiedTask) => void; isPushPending: boolean; isCompletePending: boolean; isO365Connected: boolean; }) { const overdue = isDue(task.dueDate); const dueFmt = formatDue(task.dueDate); return (
{task.title}
{task.contactLabel && task.contactId && ( {task.contactLabel} )} {task.companyLabel && task.companyId && ( {task.companyLabel} )} {dueFmt && ( {dueFmt} )} {task.importance === 'high' && ( ! )}
{/* CRM-Aufgabe in O365 übernehmen */} {task.source === 'crm' && isO365Connected && ( )} {/* Erledigen */}
); } // ── Hauptkomponente ─────────────────────────────────────────────────────────── export function DashboardTasksTab() { 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 pushMutation = usePushTaskToO365(); const completeO365 = useCompleteO365Task(); const completeCrm = useCompleteCrmTask(); // IDs, auf denen gerade eine Aktion läuft const [pendingComplete, setPendingComplete] = useState(null); const [pendingPush, setPendingPush] = useState(null); // ── Unified list aufbauen ──────────────────────────────────────────────── const o365Tasks: M365TaskFlat[] = o365Data?.data ?? []; const crmTasks: CrmOpenTask[] = crmData?.data ?? []; // CRM-IDs, die bereits in einer O365-Aufgabe verknüpft sind const syncedCrmIds = new Set(); for (const t of o365Tasks) { const crmId = extractCrmId(t.bodyContent); if (crmId) syncedCrmIds.add(crmId); } const unified: UnifiedTask[] = []; // O365-Aufgaben (inkl. synced) for (const t of o365Tasks) { const crmId = extractCrmId(t.bodyContent); const crmActivity = crmId ? crmTasks.find((c) => c.id === crmId) ?? null : null; const source: TaskSource = crmId ? 'synced' : 'o365'; unified.push({ key: `o365-${t.id}`, source, title: t.title, dueDate: t.dueDateTime?.dateTime ?? null, importance: t.importance as 'low' | 'normal' | 'high', contactLabel: crmActivity?.contact ? (`${crmActivity.contact.firstName ?? ''} ${crmActivity.contact.lastName ?? ''}`.trim() || crmActivity.contact.companyName) ?? null : null, contactId: crmActivity?.contactId ?? null, companyLabel: crmActivity?.company?.name ?? null, companyId: crmActivity?.companyId ?? null, o365ListId: t.listId, o365TaskId: t.id, crmActivityId: crmId ?? undefined, }); } // CRM-Aufgaben, die NICHT in O365 synchronisiert sind for (const c of crmTasks) { if (syncedCrmIds.has(c.id)) continue; const contactLabel = c.contact ? (`${c.contact.firstName ?? ''} ${c.contact.lastName ?? ''}`.trim() || c.contact.companyName) ?? null : null; unified.push({ key: `crm-${c.id}`, source: 'crm', title: c.subject, dueDate: c.scheduledAt, contactLabel, contactId: c.contactId, companyLabel: c.company?.name ?? null, companyId: c.companyId, crmActivityId: c.id, crmType: c.type, }); } // Sortierung: Überfällige zuerst, dann nach Datum, dann Rest unified.sort((a, b) => { const aOver = a.dueDate && isDue(a.dueDate); const bOver = b.dueDate && isDue(b.dueDate); if (aOver && !bOver) return -1; if (!aOver && bOver) 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; }); // ── Event-Handler ──────────────────────────────────────────────────────── function handleComplete(task: UnifiedTask) { setPendingComplete(task.key); const promises: Promise[] = []; if (task.o365TaskId && task.o365ListId) { promises.push( completeO365.mutateAsync({ listId: task.o365ListId, taskId: task.o365TaskId }), ); } if (task.crmActivityId) { promises.push(completeCrm.mutateAsync(task.crmActivityId)); } Promise.allSettled(promises).finally(() => setPendingComplete(null)); } function handlePushToO365(task: UnifiedTask) { if (!task.crmActivityId) return; setPendingPush(task.key); const crmActivity = crmTasks.find((c) => c.id === task.crmActivityId); const dueDateISO = crmActivity?.scheduledAt ?? undefined; pushMutation .mutateAsync({ title: task.title, bodyContent: `[INSIGHT_CRM:${task.crmActivityId}] ${crmActivity?.description ?? ''}`.trim(), dueDateISO: dueDateISO ?? undefined, }) .finally(() => setPendingPush(null)); } // ── Render ─────────────────────────────────────────────────────────────── const isLoading = (isO365Connected && o365Loading) || crmLoading; return (
{/* Header */}

Aufgaben

O365 Microsoft 365 CRM CRM-Aktivität
{isLoading && (

Aufgaben werden geladen…

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

Keine offenen Aufgaben

{isO365Connected ? 'Alle Aufgaben aus CRM und Microsoft 365 sind erledigt.' : 'Alle CRM-Aufgaben sind erledigt. Microsoft 365 nicht verbunden.'}

)} {!isLoading && unified.length > 0 && (
{unified.map((task) => ( ))}
)} {!isO365Connected && (

💡 Verbinden Sie{' '} Microsoft 365 {' '} um auch To-Do-Aufgaben anzuzeigen.

)}
); }