import { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, KeyboardAvoidingView, Modal, Platform, Pressable, RefreshControl, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { createTask, fetchTasks, fetchTenantProfiles, Task, TaskStatus, updateTask } from '@/src/lib/api'; import { useAuth } from '@/src/providers/auth-provider'; const STATUSES: TaskStatus[] = ['Offen', 'In Bearbeitung', 'Abgeschlossen']; const PRIMARY = '#69c350'; function normalizeStatus(status: unknown): TaskStatus { if (status === 'In Bearbeitung' || status === 'Abgeschlossen') return status; return 'Offen'; } function getTaskAssigneeId(task: Task): string | null { return (task.userId || task.user_id || task.profile || null) as string | null; } export default function TasksScreen() { const { token, user, activeTenantId } = useAuth(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [saving, setSaving] = useState(false); const [updatingTaskId, setUpdatingTaskId] = useState(null); const [error, setError] = useState(null); const [tasks, setTasks] = useState([]); const [profiles, setProfiles] = useState<{ id: string; label: string }[]>([]); const [search, setSearch] = useState(''); const [statusFilter, setStatusFilter] = useState<'Alle' | TaskStatus>('Alle'); const [showCompleted, setShowCompleted] = useState(false); const [showSearchPanel, setShowSearchPanel] = useState(false); const [showFilterPanel, setShowFilterPanel] = useState(false); const [createModalOpen, setCreateModalOpen] = useState(false); const [createError, setCreateError] = useState(null); const [newTaskName, setNewTaskName] = useState(''); const [newTaskDescription, setNewTaskDescription] = useState(''); const currentUserId = useMemo(() => (user?.id ? String(user.id) : null), [user]); const filteredTasks = useMemo(() => { const needle = search.trim().toLowerCase(); return tasks .filter((task) => { const status = normalizeStatus(task.categorie); if (!showCompleted && status === 'Abgeschlossen') return false; const statusMatch = statusFilter === 'Alle' || status === statusFilter; const textMatch = !needle || [task.name, task.description, task.categorie].some((value) => String(value || '').toLowerCase().includes(needle) ); return statusMatch && textMatch; }) .sort((a, b) => Number(a.id) - Number(b.id)); }, [search, showCompleted, statusFilter, tasks]); function getAssigneeLabel(task: Task): string { const assigneeId = getTaskAssigneeId(task); if (!assigneeId) return '-'; return profiles.find((profile) => profile.id === assigneeId)?.label || assigneeId; } const loadTasks = useCallback( async (showSpinner = true) => { if (!token) return; if (showSpinner) setLoading(true); setError(null); try { const [taskRows, profileRows] = await Promise.all([fetchTasks(token), fetchTenantProfiles(token)]); setTasks(taskRows || []); setProfiles( (profileRows || []) .map((profile) => { const id = profile.user_id || (profile.id ? String(profile.id) : null); const label = profile.full_name || profile.fullName || profile.email || id; return id ? { id: String(id), label: String(label || id) } : null; }) .filter((value): value is { id: string; label: string } => Boolean(value)) ); } catch (err) { setError(err instanceof Error ? err.message : 'Aufgaben konnten nicht geladen werden.'); } finally { setLoading(false); setRefreshing(false); } }, [token] ); useEffect(() => { if (!token || !activeTenantId) return; void loadTasks(true); }, [token, activeTenantId, loadTasks]); async function onRefresh() { if (!token) return; setRefreshing(true); await loadTasks(false); } function closeCreateModal() { setCreateModalOpen(false); setCreateError(null); setNewTaskName(''); setNewTaskDescription(''); } async function onCreateTask() { if (!token) return; const name = newTaskName.trim(); if (!name) { setCreateError('Bitte einen Aufgabennamen eingeben.'); return; } setSaving(true); setCreateError(null); try { await createTask(token, { name, description: newTaskDescription.trim() || null, categorie: 'Offen', userId: currentUserId, }); closeCreateModal(); await loadTasks(false); } catch (err) { setCreateError(err instanceof Error ? err.message : 'Aufgabe konnte nicht erstellt werden.'); } finally { setSaving(false); } } async function setTaskStatus(task: Task, status: TaskStatus) { if (!token || !task?.id) return; if (normalizeStatus(task.categorie) === status) return; setUpdatingTaskId(Number(task.id)); setError(null); try { await updateTask(token, Number(task.id), { categorie: status }); setTasks((prev) => prev.map((item) => (item.id === task.id ? { ...item, categorie: status } : item))); } catch (err) { setError(err instanceof Error ? err.message : 'Status konnte nicht gesetzt werden.'); } finally { setUpdatingTaskId(null); } } return ( }> setShowSearchPanel((prev) => !prev)}> Suche setShowFilterPanel((prev) => !prev)}> Filter {showSearchPanel ? ( ) : null} {showFilterPanel ? ( {(['Alle', 'Offen', 'In Bearbeitung'] as const).map((status) => ( setStatusFilter(status)}> {status} ))} setShowCompleted((prev) => !prev)}> Abgeschlossene anzeigen ) : null} {error ? {error} : null} {loading ? ( Aufgaben werden geladen... ) : null} {!loading && filteredTasks.length === 0 ? ( Keine Aufgaben gefunden. ) : null} {!loading && filteredTasks.map((task) => { const status = normalizeStatus(task.categorie); const isUpdating = updatingTaskId === Number(task.id); return ( {task.name} {status} {task.description ? ( {task.description} ) : null} Zuweisung: {getAssigneeLabel(task)} {STATUSES.map((nextStatus) => ( setTaskStatus(task, nextStatus)} disabled={isUpdating || nextStatus === status}> {nextStatus} ))} ); })} setCreateModalOpen(true)}> + Neue Aufgabe {createError ? {createError} : null} Abbrechen {saving ? 'Speichere...' : 'Anlegen'} ); } const styles = StyleSheet.create({ screen: { flex: 1, backgroundColor: '#f9fafb', }, container: { padding: 16, gap: 12, paddingBottom: 96, }, topActions: { flexDirection: 'row', gap: 8, }, topActionButton: { borderWidth: 1, borderColor: '#d1d5db', borderRadius: 10, paddingHorizontal: 12, paddingVertical: 8, backgroundColor: '#ffffff', }, topActionButtonActive: { borderColor: PRIMARY, backgroundColor: '#eff9ea', }, topActionText: { color: '#374151', fontWeight: '600', }, topActionTextActive: { color: '#3d7a30', }, panel: { backgroundColor: '#ffffff', borderRadius: 12, padding: 12, borderWidth: 1, borderColor: '#e5e7eb', }, input: { borderWidth: 1, borderColor: '#d1d5db', borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#111827', backgroundColor: '#ffffff', }, inputMultiline: { minHeight: 72, textAlignVertical: 'top', }, filterRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, }, filterChip: { borderRadius: 999, borderWidth: 1, borderColor: '#d1d5db', paddingHorizontal: 10, paddingVertical: 6, backgroundColor: '#ffffff', }, filterChipActive: { borderColor: PRIMARY, backgroundColor: '#eff9ea', }, filterChipText: { color: '#374151', fontSize: 13, fontWeight: '500', }, filterChipTextActive: { color: '#3d7a30', }, error: { color: '#dc2626', fontSize: 13, }, loadingBox: { padding: 16, alignItems: 'center', gap: 8, }, loadingText: { color: '#6b7280', }, empty: { color: '#6b7280', textAlign: 'center', paddingVertical: 16, }, taskCard: { backgroundColor: '#ffffff', borderRadius: 12, padding: 12, gap: 8, borderWidth: 1, borderColor: '#e5e7eb', }, taskHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8, }, taskTitle: { flex: 1, color: '#111827', fontSize: 16, fontWeight: '600', }, statusBadge: { color: '#3d7a30', backgroundColor: '#eff9ea', borderRadius: 999, paddingHorizontal: 8, paddingVertical: 4, fontSize: 12, overflow: 'hidden', }, taskDescription: { color: '#374151', fontSize: 14, }, taskMeta: { color: '#6b7280', fontSize: 12, }, actionRow: { flexDirection: 'row', gap: 8, flexWrap: 'wrap', }, actionButton: { borderRadius: 8, borderWidth: 1, borderColor: '#d1d5db', backgroundColor: '#ffffff', paddingHorizontal: 10, paddingVertical: 7, }, actionButtonActive: { borderColor: PRIMARY, backgroundColor: '#eff9ea', }, actionButtonText: { color: '#1f2937', fontSize: 12, fontWeight: '500', }, fab: { position: 'absolute', right: 18, bottom: 24, width: 56, height: 56, borderRadius: 28, backgroundColor: PRIMARY, alignItems: 'center', justifyContent: 'center', elevation: 4, shadowColor: '#111827', shadowOpacity: 0.18, shadowRadius: 10, shadowOffset: { width: 0, height: 4 }, }, fabText: { color: '#ffffff', fontSize: 30, lineHeight: 30, fontWeight: '500', marginTop: -1, }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.35)', justifyContent: 'center', padding: 16, }, modalKeyboardWrap: { width: '100%', }, modalCard: { backgroundColor: '#ffffff', borderRadius: 14, padding: 14, gap: 10, }, modalTitle: { fontSize: 18, fontWeight: '700', color: '#111827', }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 8, marginTop: 2, }, secondaryButton: { minHeight: 42, borderRadius: 10, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', backgroundColor: '#e5e7eb', }, secondaryButtonText: { color: '#111827', fontWeight: '600', }, primaryButton: { minHeight: 42, borderRadius: 10, paddingHorizontal: 14, backgroundColor: PRIMARY, alignItems: 'center', justifyContent: 'center', }, primaryButtonText: { color: '#ffffff', fontWeight: '600', }, buttonDisabled: { opacity: 0.6, }, });