import React from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { CheckCircle2, Circle, Loader2, Pencil, Plus, Trash2 } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { AdminLayout } from '../components/AdminLayout'; import { createTask, deleteTask, getTasks, PaginationMeta, TenantTask, TaskPayload, updateTask, } from '../api'; import { isAuthError } from '../auth/tokens'; import { ADMIN_EVENTS_PATH, ADMIN_TASK_COLLECTIONS_PATH } from '../constants'; type TaskFormState = { title: string; description: string; priority: TaskPayload['priority']; due_date: string; is_completed: boolean; }; const INITIAL_FORM: TaskFormState = { title: '', description: '', priority: 'medium', due_date: '', is_completed: false, }; export default function TasksPage() { const navigate = useNavigate(); const { t } = useTranslation('common'); const [tasks, setTasks] = React.useState([]); const [meta, setMeta] = React.useState(null); const [page, setPage] = React.useState(1); const [search, setSearch] = React.useState(''); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [dialogOpen, setDialogOpen] = React.useState(false); const [editingTask, setEditingTask] = React.useState(null); const [form, setForm] = React.useState(INITIAL_FORM); const [saving, setSaving] = React.useState(false); React.useEffect(() => { let cancelled = false; setLoading(true); setError(null); getTasks({ page, search: search.trim() || undefined }) .then((result) => { if (cancelled) return; setTasks(result.data); setMeta(result.meta); }) .catch((err) => { if (!isAuthError(err)) { setError('Tasks konnten nicht geladen werden.'); } }) .finally(() => { if (!cancelled) { setLoading(false); } }); return () => { cancelled = true; }; }, [page, search]); function openCreate() { setEditingTask(null); setForm(INITIAL_FORM); setDialogOpen(true); } function openEdit(task: TenantTask) { setEditingTask(task); setForm({ title: task.title, description: task.description ?? '', priority: task.priority ?? 'medium', due_date: task.due_date ? task.due_date.slice(0, 10) : '', is_completed: task.is_completed, }); setDialogOpen(true); } async function handleSubmit(event: React.FormEvent) { event.preventDefault(); if (!form.title.trim()) { setError('Bitte gib einen Titel ein.'); return; } setSaving(true); setError(null); const payload: TaskPayload = { title: form.title.trim(), description: form.description.trim() || null, priority: form.priority ?? undefined, due_date: form.due_date || undefined, is_completed: form.is_completed, }; try { if (editingTask) { const updated = await updateTask(editingTask.id, payload); setTasks((prev) => prev.map((task) => (task.id === updated.id ? updated : task))); } else { const created = await createTask(payload); setTasks((prev) => [created, ...prev]); } setDialogOpen(false); } catch (err) { if (!isAuthError(err)) { setError('Task konnte nicht gespeichert werden.'); } } finally { setSaving(false); } } async function handleDelete(taskId: number) { if (!window.confirm('Task wirklich loeschen?')) { return; } try { await deleteTask(taskId); setTasks((prev) => prev.filter((task) => task.id !== taskId)); } catch (err) { if (!isAuthError(err)) { setError('Task konnte nicht geloescht werden.'); } } } async function toggleCompletion(task: TenantTask) { if (task.tenant_id === null) { return; } try { const updated = await updateTask(task.id, { is_completed: !task.is_completed }); setTasks((prev) => prev.map((entry) => (entry.id === updated.id ? updated : entry))); } catch (err) { if (!isAuthError(err)) { setError('Status konnte nicht aktualisiert werden.'); } } } return ( } > {error && ( Fehler {error} )} Tasks verwalten Erstelle Aufgaben und ordne sie deinen Events zu.
setSearch(event.target.value)} className="sm:max-w-sm" />
{loading ? ( ) : tasks.length === 0 ? ( ) : (
{tasks.map((task) => ( void toggleCompletion(task)} onEdit={() => openEdit(task)} onDelete={() => void handleDelete(task.id)} /> ))}
)} {meta && meta.last_page > 1 && (
Seite {meta.current_page} von {meta.last_page}
)}
); } function TaskRow({ task, onToggle, onEdit, onDelete, }: { task: TenantTask; onToggle: () => void; onEdit: () => void; onDelete: () => void; }) { const assignedCount = task.assigned_events_count ?? task.assigned_events?.length ?? 0; const completed = task.is_completed; const isGlobal = task.tenant_id === null; return (

{task.title}

{mapPriority(task.priority)} {isGlobal && ( Global )}
{task.description &&

{task.description}

}
{task.due_date && Faellig: {formatDate(task.due_date)}} Zugeordnet: {assignedCount}
); } function TaskDialog({ open, onOpenChange, onSubmit, form, setForm, saving, isEditing, }: { open: boolean; onOpenChange: (open: boolean) => void; onSubmit: (event: React.FormEvent) => void; form: TaskFormState; setForm: React.Dispatch>; saving: boolean; isEditing: boolean; }) { return ( {isEditing ? 'Task bearbeiten' : 'Neuen Task anlegen'}
setForm((prev) => ({ ...prev, title: event.target.value }))} required />