'use client' /** * Arbeitsvorrat (Task Manager) - User Frontend * * Task management view with: * - All tasks extracted from emails * - Deadline tracking and reminders * - Priority-based organization * * See: docs/klausur-modul/UNIFIED-INBOX-SPECIFICATION.md */ import { useState, useEffect, useCallback } from 'react' // API Base URL const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086' // Types interface Task { id: string title: string description: string status: 'pending' | 'in_progress' | 'completed' priority: 'urgent' | 'high' | 'medium' | 'low' deadline: string | null emailId: string | null sourceSubject: string | null sourceSender: string | null senderType: string | null aiExtracted: boolean confidenceScore: number | null createdAt: string updatedAt: string } interface DashboardStats { totalTasks: number pendingTasks: number inProgressTasks: number completedTasks: number overdueTasks: number dueToday: number dueThisWeek: number byPriority: Record bySenderType: Record } type FilterStatus = 'all' | 'pending' | 'in_progress' | 'completed' type FilterPriority = 'all' | 'urgent' | 'high' | 'medium' | 'low' export default function TasksPage() { const [tasks, setTasks] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [filterStatus, setFilterStatus] = useState('all') const [filterPriority, setFilterPriority] = useState('all') const [showCreateModal, setShowCreateModal] = useState(false) const fetchTasks = useCallback(async () => { try { setLoading(true) const params = new URLSearchParams() if (filterStatus !== 'all') { params.append('status', filterStatus) } if (filterPriority !== 'all') { params.append('priority', filterPriority) } params.append('include_completed', filterStatus === 'completed' ? 'true' : 'false') const [tasksRes, statsRes] = await Promise.all([ fetch(`${API_BASE}/api/v1/mail/tasks?${params}`), fetch(`${API_BASE}/api/v1/mail/tasks/dashboard`), ]) if (tasksRes.ok) { const data = await tasksRes.json() setTasks(data.tasks || []) } if (statsRes.ok) { const data = await statsRes.json() setStats(data) } } catch (err) { console.error('Failed to fetch tasks:', err) } finally { setLoading(false) } }, [filterStatus, filterPriority]) useEffect(() => { fetchTasks() }, [fetchTasks]) const updateTaskStatus = async (taskId: string, status: string) => { try { await fetch(`${API_BASE}/api/v1/mail/tasks/${taskId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }), }) fetchTasks() } catch (err) { console.error('Failed to update task:', err) } } const priorityColors: Record = { urgent: { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-200' }, high: { bg: 'bg-orange-50', text: 'text-orange-700', border: 'border-orange-200' }, medium: { bg: 'bg-yellow-50', text: 'text-yellow-700', border: 'border-yellow-200' }, low: { bg: 'bg-green-50', text: 'text-green-700', border: 'border-green-200' }, } const priorityLabels: Record = { urgent: 'Dringend', high: 'Hoch', medium: 'Mittel', low: 'Niedrig', } const statusLabels: Record = { pending: 'Offen', in_progress: 'In Bearbeitung', completed: 'Erledigt', } const getOverdueIndicator = (deadline: string | null) => { if (!deadline) return null const deadlineDate = new Date(deadline) const now = new Date() const diffDays = Math.ceil((deadlineDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) if (diffDays < 0) { return { label: 'Überfällig', color: 'bg-red-100 text-red-800', urgent: true } } else if (diffDays === 0) { return { label: 'Heute', color: 'bg-orange-100 text-orange-800', urgent: true } } else if (diffDays <= 3) { return { label: `In ${diffDays} Tag${diffDays > 1 ? 'en' : ''}`, color: 'bg-yellow-100 text-yellow-800', urgent: false } } else if (diffDays <= 7) { return { label: `In ${diffDays} Tagen`, color: 'bg-blue-100 text-blue-800', urgent: false } } return null } return (
{/* Header */}

Arbeitsvorrat

Aufgaben aus E-Mails und manuelle Einträge

Zurück zur Inbox
{/* Dashboard Stats */} {stats && (
0} /> 0} />
)} {/* Filters */}
{/* Status Filter */}
Status:
{(['all', 'pending', 'in_progress', 'completed'] as FilterStatus[]).map((status) => ( ))}
{/* Priority Filter */}
Priorität:
{(['all', 'urgent', 'high', 'medium', 'low'] as FilterPriority[]).map((priority) => ( ))}
{/* Task List */} {loading ? (
) : tasks.length === 0 ? (

Keine Aufgaben

{filterStatus !== 'all' || filterPriority !== 'all' ? 'Keine Aufgaben mit den gewählten Filtern gefunden.' : 'Lassen Sie E-Mails analysieren oder erstellen Sie Aufgaben manuell.'}

) : (
{tasks.map((task) => { const colors = priorityColors[task.priority] const overdueInfo = getOverdueIndicator(task.deadline) return (
{/* Header */}
{priorityLabels[task.priority]} {overdueInfo && ( {overdueInfo.label} )} {task.aiExtracted && ( KI )}
{/* Title */}

{task.title}

{/* Description */} {task.description && (

{task.description}

)} {/* Source Email */} {task.sourceSubject && (
Von: {task.sourceSender} - {task.sourceSubject}
)} {/* Meta */}
{task.deadline && (
{new Date(task.deadline).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', })}
)}
Erstellt: {new Date(task.createdAt).toLocaleDateString('de-DE')}
{/* Actions */}
{task.status === 'pending' && ( )} {task.status === 'in_progress' && ( )} {task.status === 'completed' && ( Erledigt )}
) })}
)}
{/* Create Task Modal */} {showCreateModal && ( setShowCreateModal(false)} onSuccess={() => { setShowCreateModal(false) fetchTasks() }} /> )}
) } function StatCard({ label, value, color = 'slate', highlight = false }: { label: string value: number color?: 'slate' | 'blue' | 'green' | 'yellow' | 'red' | 'orange' highlight?: boolean }) { const colorClasses: Record = { slate: 'text-slate-900', blue: 'text-blue-600', green: 'text-green-600', yellow: 'text-yellow-600', red: 'text-red-600', orange: 'text-orange-600', } return (

{label}

{value}

) } function CreateTaskModal({ onClose, onSuccess }: { onClose: () => void onSuccess: () => void }) { const [formData, setFormData] = useState({ title: '', description: '', priority: 'medium', deadline: '', }) const [submitting, setSubmitting] = useState(false) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setSubmitting(true) try { const res = await fetch(`${API_BASE}/api/v1/mail/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: formData.title, description: formData.description, priority: formData.priority, deadline: formData.deadline || null, }), }) if (res.ok) { onSuccess() } } catch (err) { console.error('Failed to create task:', err) } finally { setSubmitting(false) } } return (

Neue Aufgabe erstellen

setFormData({ ...formData, title: e.target.value })} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500" placeholder="Was muss erledigt werden?" />