backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
161 lines
5.1 KiB
TypeScript
161 lines
5.1 KiB
TypeScript
'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'
|
|
import type { MailStats, SyncStatus, EmailAccount, TabId } from './types'
|
|
import { API_BASE, tabs } from './constants'
|
|
import OverviewTab from './_components/OverviewTab'
|
|
import AccountsTab from './_components/AccountsTab'
|
|
import AISettingsTab from './_components/AISettingsTab'
|
|
import TemplatesTab from './_components/TemplatesTab'
|
|
import AuditLogTab from './_components/AuditLogTab'
|
|
|
|
export default function MailAdminPage() {
|
|
const [activeTab, setActiveTab] = useState<TabId>('overview')
|
|
const [stats, setStats] = useState<MailStats | null>(null)
|
|
const [accounts, setAccounts] = useState<EmailAccount[]>([])
|
|
const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(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 (
|
|
<AdminLayout
|
|
title="Unified Inbox"
|
|
description="E-Mail-Konten & KI-gestützte Analyse verwalten"
|
|
>
|
|
{/* Error Banner */}
|
|
{error && (
|
|
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4 flex items-center gap-3">
|
|
<svg className="w-5 h-5 text-red-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span className="text-red-700">{error}</span>
|
|
<button onClick={fetchData} className="ml-auto text-red-600 hover:text-red-800 text-sm font-medium">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab Navigation */}
|
|
<div className="border-b border-slate-200 mb-6">
|
|
<nav className="-mb-px flex space-x-8">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`
|
|
flex items-center gap-2 py-4 px-1 border-b-2 font-medium text-sm transition-colors
|
|
${activeTab === tab.id
|
|
? 'border-primary-500 text-primary-600'
|
|
: 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'
|
|
}
|
|
`}
|
|
>
|
|
{tab.icon}
|
|
{tab.name}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'overview' && (
|
|
<OverviewTab
|
|
stats={stats}
|
|
syncStatus={syncStatus}
|
|
loading={loading}
|
|
onRefresh={fetchData}
|
|
/>
|
|
)}
|
|
{activeTab === 'accounts' && (
|
|
<AccountsTab
|
|
accounts={accounts}
|
|
loading={loading}
|
|
onRefresh={fetchData}
|
|
/>
|
|
)}
|
|
{activeTab === 'ai-settings' && (
|
|
<AISettingsTab />
|
|
)}
|
|
{activeTab === 'templates' && (
|
|
<TemplatesTab />
|
|
)}
|
|
{activeTab === 'logs' && (
|
|
<AuditLogTab />
|
|
)}
|
|
</AdminLayout>
|
|
)
|
|
}
|