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>
110 lines
3.1 KiB
TypeScript
110 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import type { Email, Account } from './types'
|
|
import { API_BASE } from './types'
|
|
|
|
export function useMail() {
|
|
const [emails, setEmails] = useState<Email[]>([])
|
|
const [accounts, setAccounts] = useState<Account[]>([])
|
|
const [selectedEmail, setSelectedEmail] = useState<Email | null>(null)
|
|
const [selectedAccount, setSelectedAccount] = useState<string>('all')
|
|
const [selectedFolder, setSelectedFolder] = useState('inbox')
|
|
const [loading, setLoading] = useState(true)
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
|
|
const fetchEmails = useCallback(async () => {
|
|
try {
|
|
setLoading(true)
|
|
const params = new URLSearchParams()
|
|
if (selectedAccount !== 'all') {
|
|
params.append('account_id', selectedAccount)
|
|
}
|
|
if (selectedFolder === 'unread') {
|
|
params.append('unread_only', 'true')
|
|
}
|
|
if (searchQuery) {
|
|
params.append('search', searchQuery)
|
|
}
|
|
|
|
const res = await fetch(`${API_BASE}/api/v1/mail/inbox?${params}`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setEmails(data.emails || [])
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch emails:', err)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [selectedAccount, selectedFolder, searchQuery])
|
|
|
|
const fetchAccounts = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/v1/mail/accounts`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setAccounts(data.accounts || [])
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch accounts:', err)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
fetchAccounts()
|
|
}, [fetchAccounts])
|
|
|
|
useEffect(() => {
|
|
fetchEmails()
|
|
}, [fetchEmails])
|
|
|
|
const analyzeEmail = async (emailId: string) => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/v1/mail/analyze/${emailId}`, {
|
|
method: 'POST',
|
|
})
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setSelectedEmail((prev) => prev ? { ...prev, ...data, aiAnalyzed: true } : null)
|
|
setEmails((prev) =>
|
|
prev.map((e) => (e.id === emailId ? { ...e, ...data, aiAnalyzed: true } : e))
|
|
)
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to analyze email:', err)
|
|
}
|
|
}
|
|
|
|
const markAsRead = async (emailId: string) => {
|
|
try {
|
|
await fetch(`${API_BASE}/api/v1/mail/inbox/${emailId}/read`, {
|
|
method: 'POST',
|
|
})
|
|
setEmails((prev) =>
|
|
prev.map((e) => (e.id === emailId ? { ...e, isRead: true } : e))
|
|
)
|
|
} catch (err) {
|
|
console.error('Failed to mark as read:', err)
|
|
}
|
|
}
|
|
|
|
const handleEmailClick = (email: Email) => {
|
|
setSelectedEmail(email)
|
|
if (!email.isRead) {
|
|
markAsRead(email.id)
|
|
}
|
|
}
|
|
|
|
const totalUnread = accounts.reduce((sum, acc) => sum + acc.unreadCount, 0)
|
|
|
|
return {
|
|
emails, accounts,
|
|
selectedEmail, setSelectedEmail,
|
|
selectedAccount, setSelectedAccount,
|
|
selectedFolder, setSelectedFolder,
|
|
loading, searchQuery, setSearchQuery,
|
|
analyzeEmail, handleEmailClick, totalUnread,
|
|
}
|
|
}
|