import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useCurrentInstance } from '../../../hooks/useCurrentInstance'; import * as teamsbotApi from '../../../api/teamsbotApi'; import type { TeamsbotSession, MeetingModule } from '../../../api/teamsbotApi'; import styles from './Teamsbot.module.css'; import { useLanguage } from '../../../providers/language/LanguageContext'; /** * TeamsBot Dashboard — IA: KPIs, Modul-Aggregate, Quick-Actions. * Neues Meeting: Assistent (Wizard). Sessions sind via Module erreichbar. */ export const TeamsbotDashboardView: React.FC = () => { const { t } = useLanguage(); const { instance, mandateId, featureCode } = useCurrentInstance(); const instanceId = instance?.id || ''; const navigate = useNavigate(); const [sessions, setSessions] = useState([]); const [modules, setModules] = useState([]); const [error, setError] = useState(null); const dashboardEsRef = useRef(null); const dashboardReconnectRef = useRef(null); const applyDashboardPayload = useCallback((nextSessions: TeamsbotSession[], nextModules: MeetingModule[]) => { setSessions(nextSessions); setModules(nextModules); }, []); useEffect(() => { if (!instanceId) return; let cancelled = false; const clearReconnect = () => { if (dashboardReconnectRef.current) { window.clearTimeout(dashboardReconnectRef.current); dashboardReconnectRef.current = null; } }; const connect = () => { if (cancelled) return; dashboardEsRef.current?.close(); const es = teamsbotApi.createDashboardStream(instanceId); dashboardEsRef.current = es; es.onmessage = (event) => { try { const msg = JSON.parse(event.data) as { type?: string; sessions?: TeamsbotSession[]; modules?: MeetingModule[]; }; if (msg.type === 'dashboardState' && Array.isArray(msg.sessions) && Array.isArray(msg.modules)) { applyDashboardPayload(msg.sessions, msg.modules); setError(null); } } catch { /* ignore malformed SSE */ } }; es.onerror = () => { es.close(); dashboardEsRef.current = null; if (cancelled) return; clearReconnect(); dashboardReconnectRef.current = window.setTimeout(connect, 2500); }; }; connect(); return () => { cancelled = true; clearReconnect(); dashboardEsRef.current?.close(); dashboardEsRef.current = null; }; }, [instanceId, applyDashboardPayload]); const activeSessions = useMemo( () => sessions.filter((s) => ['pending', 'joining', 'active'].includes(s.status)), [sessions], ); const moduleTitleById = useMemo(() => { const m = new Map(); modules.forEach((mod) => m.set(mod.id, mod.title)); return m; }, [modules]); const topModules = useMemo(() => { const counts = new Map(); sessions.forEach((s) => { const mid = s.moduleId || '_adhoc'; counts.set(mid, (counts.get(mid) || 0) + 1); }); const rows = Array.from(counts.entries()) .map(([moduleId, sessionCount]) => ({ moduleId, sessionCount, title: moduleId === '_adhoc' ? t('Adhoc / ohne Modul') : (moduleTitleById.get(moduleId) || t('Unbekanntes Modul')), })) .sort((a, b) => b.sessionCount - a.sessionCount) .slice(0, 6); return rows; }, [sessions, moduleTitleById, t]); const totalSegments = useMemo(() => sessions.reduce((acc, s) => acc + (s.transcriptSegmentCount || 0), 0), [sessions]); const totalResponses = useMemo(() => sessions.reduce((acc, s) => acc + (s.botResponseCount || 0), 0), [sessions]); const _getStatusBadgeClass = (status: string) => { switch (status) { case 'active': return styles.statusActive; case 'joining': return styles.statusJoining; case 'pending': return styles.statusPending; case 'ended': return styles.statusEnded; case 'error': return styles.statusError; case 'leaving': return styles.statusLeaving; default: return ''; } }; const _getStatusLabel = (status: string) => { const labels: Record = { pending: t('Wartend'), joining: t('Beitritt…'), active: t('Aktiv'), leaving: t('Verlassen…'), ended: t('Beendet'), error: t('Fehler'), }; return labels[status] || status; }; const _sessionPath = (sessId: string) => `/mandates/${mandateId}/${featureCode}/${instanceId}/sessions?sessionId=${sessId}`; const _refreshLists = useCallback(async () => { if (!instanceId) return; try { const [r, m] = await Promise.all([ teamsbotApi.listSessions(instanceId, true), teamsbotApi.listModules(instanceId), ]); setSessions(r.sessions || []); setModules(m || []); } catch { /* ignore */ } }, [instanceId]); const _handleStopSession = async (sid: string) => { try { await teamsbotApi.stopSession(instanceId, sid); await _refreshLists(); } catch (err: any) { setError(err.message || t('Fehler beim Stoppen')); } }; return (

{t('Teams Bot')}

{t('Dashboard mit Übersicht, Modulen und Live-Sitzung — neues Meeting über den Assistenten starten.')}

{error &&
{error}
}
{modules.length}
{t('Meeting-Module')}
{activeSessions.length}
{t('Aktive Sitzungen')}
{sessions.length}
{t('Sitzungen gesamt')}
{totalSegments}
{t('Transkript-Segmente')}
{totalResponses} {t('Bot-Antworten')}

{t('Module nach Aktivität')}

{topModules.length === 0 ? (

{t('Noch keine Sitzungen — starte ein Meeting im Assistenten.')}

) : (
{topModules.map((row) => ( ))}
)}
{activeSessions.length > 0 && (

{t('Aktive Sitzungen')}

{activeSessions.map((session) => (
{session.botName} {_getStatusLabel(session.status)}
{session.moduleId && ( {moduleTitleById.get(session.moduleId) || session.moduleId} )} {session.transcriptSegmentCount} {t('Segmente')} {session.botResponseCount} {t('Antworten')}
{!['ended', 'error', 'leaving'].includes(session.status) && ( )}
))}
)}
); }; export default TeamsbotDashboardView;