backend-lehrer (10 files): - game/database.py (785 → 5), correction_api.py (683 → 4) - classroom_engine/antizipation.py (676 → 5) - llm_gateway schools/edu_search already done in prior batch klausur-service (12 files): - orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4) - zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5) - eh_templates.py (658 → 5), mail/api.py (651 → 5) - qdrant_service.py (638 → 5), training_api.py (625 → 4) website (6 pages): - middleware (696 → 8), mail (733 → 6), consent (628 → 8) - compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7) studio-v2 (3 components): - B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2) - dashboard-experimental (739 → 2) admin-lehrer (4 files): - uebersetzungen (769 → 4), manager (670 → 2) - ChunkBrowserQA (675 → 6), dsfa/page (674 → 5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
6.5 KiB
TypeScript
159 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Unified Inbox - User Frontend
|
|
*
|
|
* Main email interface for users with:
|
|
* - Unified inbox view (all accounts)
|
|
* - Email detail view with AI analysis
|
|
* - Quick actions and response suggestions
|
|
*
|
|
* See: docs/klausur-modul/UNIFIED-INBOX-SPECIFICATION.md
|
|
*/
|
|
|
|
import type { Folder } from './_components/types'
|
|
import { useMail } from './_components/useMail'
|
|
import MailSidebar from './_components/MailSidebar'
|
|
import EmailListItem from './_components/EmailListItem'
|
|
import EmailDetail from './_components/EmailDetail'
|
|
|
|
export default function MailPage() {
|
|
const mail = useMail()
|
|
|
|
const folders: Folder[] = [
|
|
{
|
|
name: 'inbox',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
name: 'unread',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
name: 'tasks',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
name: 'sent',
|
|
icon: (
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
|
</svg>
|
|
),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div className="h-screen flex flex-col bg-slate-100">
|
|
{/* Header */}
|
|
<header className="bg-white border-b border-slate-200 px-6 py-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<h1 className="text-xl font-semibold text-slate-900">Unified Inbox</h1>
|
|
{mail.totalUnread > 0 && (
|
|
<span className="px-2 py-1 bg-primary-100 text-primary-700 text-sm font-medium rounded-full">
|
|
{mail.totalUnread} ungelesen
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
{/* Search */}
|
|
<div className="relative">
|
|
<input
|
|
type="text"
|
|
placeholder="E-Mails durchsuchen..."
|
|
value={mail.searchQuery}
|
|
onChange={(e) => mail.setSearchQuery(e.target.value)}
|
|
className="w-64 pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
/>
|
|
<svg className="absolute left-3 top-2.5 w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
</svg>
|
|
</div>
|
|
{/* Compose Button */}
|
|
<a
|
|
href="/mail/compose"
|
|
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 flex items-center gap-2"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
</svg>
|
|
Verfassen
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main Content */}
|
|
<div className="flex-1 flex overflow-hidden">
|
|
<MailSidebar
|
|
folders={folders}
|
|
accounts={mail.accounts}
|
|
selectedFolder={mail.selectedFolder}
|
|
setSelectedFolder={mail.setSelectedFolder}
|
|
selectedAccount={mail.selectedAccount}
|
|
setSelectedAccount={mail.setSelectedAccount}
|
|
totalUnread={mail.totalUnread}
|
|
/>
|
|
|
|
{/* Email List */}
|
|
<div className="w-96 bg-white border-r border-slate-200 overflow-y-auto">
|
|
{mail.loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
|
</div>
|
|
) : mail.emails.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center py-12 text-center px-4">
|
|
<svg className="w-12 h-12 text-slate-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
<p className="text-slate-500">Keine E-Mails gefunden</p>
|
|
</div>
|
|
) : (
|
|
<div className="divide-y divide-slate-100">
|
|
{mail.emails.map((email) => (
|
|
<EmailListItem
|
|
key={email.id}
|
|
email={email}
|
|
isSelected={mail.selectedEmail?.id === email.id}
|
|
onClick={() => mail.handleEmailClick(email)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Email Detail / AI Panel */}
|
|
<div className="flex-1 bg-white overflow-y-auto">
|
|
{mail.selectedEmail ? (
|
|
<EmailDetail
|
|
email={mail.selectedEmail}
|
|
onAnalyze={() => mail.analyzeEmail(mail.selectedEmail!.id)}
|
|
/>
|
|
) : (
|
|
<div className="flex flex-col items-center justify-center h-full text-center px-4">
|
|
<svg className="w-16 h-16 text-slate-300 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
<p className="text-slate-500 text-lg">Wählen Sie eine E-Mail aus</p>
|
|
<p className="text-slate-400 text-sm mt-1">
|
|
Klicken Sie auf eine E-Mail, um sie zu lesen
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|