'use client' /** * Unified Inbox Mail Admin Page * * Admin interface for managing email accounts, viewing system status, * and configuring AI analysis settings. * * See: docs/klausur-modul/UNIFIED-INBOX-SPECIFICATION.md */ import { useState, useEffect, useCallback } from 'react' import AdminLayout from '@/components/admin/AdminLayout' // API Base URL for klausur-service const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086' // Types interface EmailAccount { id: string email: string displayName: string imapHost: string imapPort: number smtpHost: string smtpPort: number status: 'active' | 'inactive' | 'error' | 'syncing' lastSync: string | null emailCount: number unreadCount: number createdAt: string } interface MailStats { totalAccounts: number activeAccounts: number totalEmails: number unreadEmails: number totalTasks: number pendingTasks: number overdueTasks: number aiAnalyzedCount: number lastSyncTime: string | null } interface SyncStatus { running: boolean accountsInProgress: string[] lastCompleted: string | null errors: string[] } // Tab definitions type TabId = 'overview' | 'accounts' | 'ai-settings' | 'templates' | 'logs' const tabs: { id: TabId; name: string; icon: JSX.Element }[] = [ { id: 'overview', name: 'Übersicht', icon: ( ), }, { id: 'accounts', name: 'Konten', icon: ( ), }, { id: 'ai-settings', name: 'KI-Einstellungen', icon: ( ), }, { id: 'templates', name: 'Vorlagen', icon: ( ), }, { id: 'logs', name: 'Audit-Log', icon: ( ), }, ] // Main Component export default function MailAdminPage() { const [activeTab, setActiveTab] = useState('overview') const [stats, setStats] = useState(null) const [accounts, setAccounts] = useState([]) const [syncStatus, setSyncStatus] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const fetchData = useCallback(async () => { try { setLoading(true) // Fetch stats and accounts in parallel const [statsRes, accountsRes, statusRes] = await Promise.all([ fetch(`${API_BASE}/api/v1/mail/stats`), fetch(`${API_BASE}/api/v1/mail/accounts`), fetch(`${API_BASE}/api/v1/mail/sync/status`), ]) if (statsRes.ok) { const statsData = await statsRes.json() setStats({ totalAccounts: statsData.total_accounts || 0, activeAccounts: statsData.active_accounts || 0, totalEmails: statsData.total_emails || 0, unreadEmails: statsData.unread_emails || 0, totalTasks: statsData.total_tasks || 0, pendingTasks: statsData.pending_tasks || 0, overdueTasks: statsData.overdue_tasks || 0, aiAnalyzedCount: statsData.ai_analyzed_count || 0, lastSyncTime: statsData.last_sync_time, }) } if (accountsRes.ok) { const accountsData = await accountsRes.json() setAccounts(accountsData.accounts || []) } if (statusRes.ok) { const statusData = await statusRes.json() setSyncStatus({ running: statusData.running || false, accountsInProgress: statusData.accounts_in_progress || [], lastCompleted: statusData.last_completed, errors: statusData.errors || [], }) } setError(null) } catch (err) { console.error('Failed to fetch mail data:', err) setError('Verbindung zum Mail-Service fehlgeschlagen') } finally { setLoading(false) } }, []) useEffect(() => { fetchData() // Refresh every 10 seconds if syncing const interval = setInterval(() => { if (syncStatus?.running) { fetchData() } }, 10000) return () => clearInterval(interval) }, [fetchData, syncStatus?.running]) return ( {/* Error Banner */} {error && (
{error}
)} {/* Tab Navigation */}
{/* Tab Content */} {activeTab === 'overview' && ( )} {activeTab === 'accounts' && ( )} {activeTab === 'ai-settings' && ( )} {activeTab === 'templates' && ( )} {activeTab === 'logs' && ( )}
) } // ============================================================================ // Overview Tab // ============================================================================ function OverviewTab({ stats, syncStatus, loading, onRefresh }: { stats: MailStats | null syncStatus: SyncStatus | null loading: boolean onRefresh: () => void }) { const triggerSync = async () => { try { await fetch(`${API_BASE}/api/v1/mail/sync/all`, { method: 'POST', }) onRefresh() } catch (err) { console.error('Failed to trigger sync:', err) } } return (
{/* Header */}

System-Übersicht

Status aller E-Mail-Konten und Aufgaben

{/* Loading State */} {loading && (
)} {/* Stats Grid */} {!loading && stats && ( <>
0 ? 'red' : 'green'} />
{/* Sync Status */}

Synchronisierung

{syncStatus?.running ? ( <>
Synchronisiere {syncStatus.accountsInProgress.length} Konto(en)... ) : ( <>
Bereit )} {stats.lastSyncTime && ( Letzte Sync: {new Date(stats.lastSyncTime).toLocaleString('de-DE')} )}
{syncStatus?.errors && syncStatus.errors.length > 0 && (

Fehler

    {syncStatus.errors.slice(0, 3).map((error, i) => (
  • {error}
  • ))}
)}
{/* AI Stats */}

KI-Analyse

Analysiert

{stats.aiAnalyzedCount}

Analyse-Rate

{stats.totalEmails > 0 ? `${Math.round((stats.aiAnalyzedCount / stats.totalEmails) * 100)}%` : '0%'}

)}
) } function StatCard({ title, value, subtitle, color = 'blue' }: { title: string value: number subtitle?: string color?: 'blue' | 'green' | 'yellow' | 'red' }) { const colorClasses = { blue: 'text-blue-600', green: 'text-green-600', yellow: 'text-yellow-600', red: 'text-red-600', } return (

{title}

{value.toLocaleString()}

{subtitle &&

{subtitle}

}
) } // ============================================================================ // Accounts Tab // ============================================================================ function AccountsTab({ accounts, loading, onRefresh }: { accounts: EmailAccount[] loading: boolean onRefresh: () => void }) { const [showAddModal, setShowAddModal] = useState(false) const testConnection = async (accountId: string) => { try { const res = await fetch(`${API_BASE}/api/v1/mail/accounts/${accountId}/test`, { method: 'POST', }) if (res.ok) { alert('Verbindung erfolgreich!') } else { alert('Verbindungsfehler') } } catch (err) { alert('Verbindungsfehler') } } const statusColors = { active: 'bg-green-100 text-green-800', inactive: 'bg-gray-100 text-gray-800', error: 'bg-red-100 text-red-800', syncing: 'bg-yellow-100 text-yellow-800', } const statusLabels = { active: 'Aktiv', inactive: 'Inaktiv', error: 'Fehler', syncing: 'Synchronisiert...', } return (
{/* Header */}

E-Mail-Konten

Verwalten Sie die verbundenen E-Mail-Konten

{/* Loading State */} {loading && (
)} {/* Accounts Grid */} {!loading && (
{accounts.length === 0 ? (

Keine E-Mail-Konten

Fügen Sie Ihr erstes E-Mail-Konto hinzu.

) : ( accounts.map((account) => (

{account.displayName || account.email}

{account.email}

{statusLabels[account.status]}

E-Mails

{account.emailCount}

Ungelesen

{account.unreadCount}

IMAP

{account.imapHost}:{account.imapPort}

Letzte Sync

{account.lastSync ? new Date(account.lastSync).toLocaleString('de-DE') : 'Nie'}

)) )}
)} {/* Add Account Modal */} {showAddModal && ( setShowAddModal(false)} onSuccess={() => { setShowAddModal(false); onRefresh(); }} /> )}
) } function AddAccountModal({ onClose, onSuccess }: { onClose: () => void onSuccess: () => void }) { const [formData, setFormData] = useState({ email: '', displayName: '', imapHost: '', imapPort: 993, smtpHost: '', smtpPort: 587, username: '', password: '', }) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setSubmitting(true) setError(null) try { const res = await fetch(`${API_BASE}/api/v1/mail/accounts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: formData.email, display_name: formData.displayName, imap_host: formData.imapHost, imap_port: formData.imapPort, smtp_host: formData.smtpHost, smtp_port: formData.smtpPort, username: formData.username, password: formData.password, }), }) if (res.ok) { onSuccess() } else { const data = await res.json() setError(data.detail || 'Fehler beim Hinzufügen des Kontos') } } catch (err) { setError('Netzwerkfehler') } finally { setSubmitting(false) } } return (

E-Mail-Konto hinzufügen

{error && (
{error}
)}
setFormData({ ...formData, email: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" placeholder="schulleitung@grundschule-xy.de" />
setFormData({ ...formData, displayName: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" placeholder="Schulleitung" />
setFormData({ ...formData, imapHost: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" placeholder="imap.example.com" />
setFormData({ ...formData, imapPort: parseInt(e.target.value) })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" />
setFormData({ ...formData, smtpHost: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" placeholder="smtp.example.com" />
setFormData({ ...formData, smtpPort: parseInt(e.target.value) })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" />
setFormData({ ...formData, username: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" />
setFormData({ ...formData, password: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" />

Das Passwort wird verschlüsselt in Vault gespeichert.

) } // ============================================================================ // AI Settings Tab // ============================================================================ function AISettingsTab() { const [settings, setSettings] = useState({ autoAnalyze: true, autoCreateTasks: true, analysisModel: 'breakpilot-teacher-8b', confidenceThreshold: 0.7, }) return (

KI-Einstellungen

Konfigurieren Sie die automatische E-Mail-Analyse

{/* Auto-Analyze */}

Automatische Analyse

E-Mails automatisch beim Empfang analysieren

{/* Auto-Create Tasks */}

Aufgaben automatisch erstellen

Erkannte Fristen als Aufgaben anlegen

{/* Model Selection */}
{/* Confidence Threshold */}
setSettings({ ...settings, confidenceThreshold: parseFloat(e.target.value) })} className="w-full md:w-64" />

Mindest-Konfidenz für automatische Aufgabenerstellung

{/* Sender Classification */}

Bekannte Absender (Niedersachsen)

{[ { domain: '@mk.niedersachsen.de', type: 'Kultusministerium', priority: 'Hoch' }, { domain: '@rlsb.de', type: 'RLSB', priority: 'Hoch' }, { domain: '@landesschulbehoerde-nds.de', type: 'Landesschulbehörde', priority: 'Hoch' }, { domain: '@nibis.de', type: 'NiBiS', priority: 'Mittel' }, { domain: '@schultraeger.de', type: 'Schulträger', priority: 'Mittel' }, ].map((sender) => (

{sender.domain}

{sender.type}

{sender.priority}
))}
) } // ============================================================================ // Templates Tab // ============================================================================ function TemplatesTab() { const [templates] = useState([ { id: '1', name: 'Eingangsbestätigung', category: 'Standard', usageCount: 45 }, { id: '2', name: 'Terminbestätigung', category: 'Termine', usageCount: 23 }, { id: '3', name: 'Elternbrief-Vorlage', category: 'Eltern', usageCount: 67 }, ]) return (

E-Mail-Vorlagen

Verwalten Sie Antwort-Templates

{templates.map((template) => ( ))}
Name Kategorie Verwendet Aktionen
{template.name} {template.category} {template.usageCount}x
) } // ============================================================================ // Audit Log Tab // ============================================================================ function AuditLogTab() { const [logs] = useState([ { id: '1', action: 'account_created', user: 'admin@breakpilot.de', timestamp: new Date().toISOString(), details: 'Konto schulleitung@example.de hinzugefügt' }, { id: '2', action: 'email_analyzed', user: 'system', timestamp: new Date(Date.now() - 3600000).toISOString(), details: '5 E-Mails analysiert' }, { id: '3', action: 'task_created', user: 'system', timestamp: new Date(Date.now() - 7200000).toISOString(), details: 'Aufgabe aus Fristenerkennung erstellt' }, ]) const actionLabels: Record = { account_created: 'Konto erstellt', email_analyzed: 'E-Mail analysiert', task_created: 'Aufgabe erstellt', sync_completed: 'Sync abgeschlossen', } return (

Audit-Log

Alle Aktionen im Mail-System

{logs.map((log) => ( ))}
Zeit Aktion Benutzer Details
{new Date(log.timestamp).toLocaleString('de-DE')} {actionLabels[log.action] || log.action} {log.user} {log.details}
) }