import { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native'; import { router } from 'expo-router'; import { EventCreateModal } from '@/components/event-create-modal'; import { CalendarEvent, fetchEvents, fetchProjects, fetchStaffTimeSpans, fetchTasks, Project, Task } from '@/src/lib/api'; import { useAuth } from '@/src/providers/auth-provider'; const PRIMARY = '#69c350'; type DashboardData = { tasks: Task[]; openTasks: number; inProgressTasks: number; activeTimeStart: string | null; pendingSubmissions: number; todayMinutes: number; }; function normalizeTaskStatus(value: unknown): 'Offen' | 'In Bearbeitung' | 'Abgeschlossen' { if (value === 'In Bearbeitung') return 'In Bearbeitung'; if (value === 'Abgeschlossen') return 'Abgeschlossen'; return 'Offen'; } function formatMinutes(minutes: number): string { const h = Math.floor(minutes / 60); const m = minutes % 60; return `${h}h ${String(m).padStart(2, '0')}m`; } function formatDateTime(value: string | null): string { if (!value) return '-'; return new Date(value).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', }); } function dateKey(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } function formatEventTime(value: string): string { const date = new Date(value); return Number.isNaN(date.getTime()) ? '-' : date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); } export default function DashboardScreen() { const { token, user } = useAuth(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [events, setEvents] = useState([]); const [projects, setProjects] = useState([]); const [createEventOpen, setCreateEventOpen] = useState(false); const [selectedCalendarDate, setSelectedCalendarDate] = useState(() => new Date()); const [data, setData] = useState({ tasks: [], openTasks: 0, inProgressTasks: 0, activeTimeStart: null, pendingSubmissions: 0, todayMinutes: 0, }); const currentUserId = useMemo(() => (user?.id ? String(user.id) : null), [user]); const loadDashboard = useCallback( async (showSpinner = true) => { if (!token || !currentUserId) return; if (showSpinner) setLoading(true); setError(null); try { const [taskRows, spans, eventRows, projectRows] = await Promise.all([ fetchTasks(token), fetchStaffTimeSpans(token, currentUserId), fetchEvents(token), fetchProjects(token), ]); const tasks = taskRows || []; const openTasks = tasks.filter((task) => normalizeTaskStatus(task.categorie) === 'Offen').length; const inProgressTasks = tasks.filter( (task) => normalizeTaskStatus(task.categorie) === 'In Bearbeitung' ).length; const activeTime = spans.find((span) => !span.stopped_at) || null; const pendingSubmissions = spans.filter( (span) => (span.state === 'draft' || span.state === 'factual') && !!span.stopped_at ).length; const today = new Date(); const todayIso = today.toISOString().slice(0, 10); const todayMinutes = spans .filter((span) => span.started_at?.slice(0, 10) === todayIso) .reduce((sum, span) => sum + (span.duration_minutes || 0), 0); setData({ tasks, openTasks, inProgressTasks, activeTimeStart: activeTime?.started_at || null, pendingSubmissions, todayMinutes, }); setEvents(eventRows); setProjects(projectRows); } catch (err) { setError(err instanceof Error ? err.message : 'Dashboard konnte nicht geladen werden.'); } finally { setLoading(false); setRefreshing(false); } }, [currentUserId, token] ); useEffect(() => { if (!token || !currentUserId) return; void loadDashboard(true); }, [currentUserId, loadDashboard, token]); async function onRefresh() { setRefreshing(true); await loadDashboard(false); } const calendarDays = useMemo(() => { const start = new Date(); start.setHours(0, 0, 0, 0); return Array.from({ length: 7 }, (_, index) => { const date = new Date(start); date.setDate(start.getDate() + index); return date; }); }, []); const selectedEvents = useMemo(() => { const selectedKey = dateKey(selectedCalendarDate); return events .filter((event) => { const start = new Date(event.startDate); return !Number.isNaN(start.getTime()) && dateKey(start) === selectedKey; }) .sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()); }, [events, selectedCalendarDate]); return ( }> {error ? {error} : null} {loading ? ( Dashboard wird geladen... ) : null} {!loading ? ( <> Aktive Zeit {data.activeTimeStart ? `Seit ${formatDateTime(data.activeTimeStart)}` : 'Nicht aktiv'} Offene Aufgaben {data.openTasks} In Bearbeitung {data.inProgressTasks} Heute erfasst {formatMinutes(data.todayMinutes)} Zum Einreichen {data.pendingSubmissions} Kalender {selectedCalendarDate.toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: 'long' })} router.push('/more/calendar' as any)}> Monat öffnen {calendarDays.map((date) => { const key = dateKey(date); const selected = key === dateKey(selectedCalendarDate); const count = events.filter((event) => { const start = new Date(event.startDate); return !Number.isNaN(start.getTime()) && dateKey(start) === key; }).length; return ( setSelectedCalendarDate(date)}> {date.toLocaleDateString('de-DE', { weekday: 'short' }).replace('.', '')} {date.getDate()} 0 ? styles.calendarDotActive : null]} /> ); })} {selectedEvents.length === 0 ? Keine Termine an diesem Tag. : null} {selectedEvents.slice(0, 3).map((event) => ( router.push(`/more/event/${event.id}` as any)}> {formatEventTime(event.startDate)} {event.name} {event.state === 'Entwurf' ? Entwurf : null} › ))} setCreateEventOpen(true)}> + Termin eintragen Schnellzugriff router.push('/(tabs)/tasks')}> Aufgaben router.push('/(tabs)/projects')}> Projekten router.push('/(tabs)/time')}> Zeiten router.push('/more/inventory?action=scan')}> Inventar Scan setCreateEventOpen(false)} onCreated={() => loadDashboard(false)} /> ) : null} ); } const styles = StyleSheet.create({ container: { padding: 16, gap: 12, backgroundColor: '#f9fafb', }, row: { flexDirection: 'row', gap: 10, }, metricCard: { flex: 1, backgroundColor: '#ffffff', borderRadius: 12, borderWidth: 1, borderColor: '#e5e7eb', padding: 12, gap: 6, }, metricCardPrimary: { borderColor: PRIMARY, backgroundColor: '#eff9ea', }, metricLabel: { color: '#6b7280', fontSize: 12, textTransform: 'uppercase', }, metricValue: { color: '#111827', fontSize: 22, fontWeight: '700', }, metricLabelPrimary: { color: '#3d7a30', fontSize: 12, textTransform: 'uppercase', }, metricValuePrimary: { color: '#2f5f24', fontSize: 18, fontWeight: '700', }, quickActionsCard: { backgroundColor: '#ffffff', borderRadius: 12, borderWidth: 1, borderColor: '#e5e7eb', padding: 12, gap: 10, }, calendarCard: { backgroundColor: '#ffffff', borderRadius: 12, borderWidth: 1, borderColor: '#e5e7eb', padding: 12, gap: 10, }, calendarHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8 }, calendarTitle: { color: '#111827', fontSize: 16, fontWeight: '700' }, calendarSubtitle: { color: '#6b7280', fontSize: 12, marginTop: 2 }, calendarLink: { color: '#3d7a30', fontSize: 12, fontWeight: '700' }, calendarDays: { flexDirection: 'row', gap: 4 }, calendarDay: { flex: 1, minHeight: 62, borderRadius: 9, alignItems: 'center', justifyContent: 'center', gap: 2 }, calendarDaySelected: { backgroundColor: PRIMARY }, calendarWeekday: { color: '#6b7280', fontSize: 10, fontWeight: '600', textTransform: 'uppercase' }, calendarDayNumber: { color: '#111827', fontSize: 16, fontWeight: '700' }, calendarDayTextSelected: { color: '#ffffff' }, calendarDot: { width: 5, height: 5, borderRadius: 3, backgroundColor: 'transparent' }, calendarDotActive: { backgroundColor: '#f59e0b' }, calendarEmpty: { color: '#6b7280', fontSize: 13, paddingVertical: 4 }, calendarEvent: { minHeight: 44, flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 9, overflow: 'hidden' }, calendarEventMark: { width: 4, alignSelf: 'stretch' }, calendarEventTime: { color: '#3d7a30', fontSize: 12, fontWeight: '700', paddingHorizontal: 8 }, calendarEventName: { flex: 1, color: '#111827', fontSize: 13, fontWeight: '600' }, calendarEventDraft: { color: '#92400e', backgroundColor: '#fef3c7', borderRadius: 999, paddingHorizontal: 7, paddingVertical: 3, fontSize: 10, fontWeight: '700' }, calendarEventArrow: { color: '#9ca3af', fontSize: 21, paddingHorizontal: 7 }, calendarAddButton: { minHeight: 40, borderRadius: 9, backgroundColor: PRIMARY, alignItems: 'center', justifyContent: 'center' }, calendarAddButtonText: { color: '#ffffff', fontSize: 13, fontWeight: '700' }, quickActionsTitle: { color: '#111827', fontSize: 15, fontWeight: '600', }, quickActionsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, }, quickActionButton: { minWidth: 120, minHeight: 40, borderRadius: 10, backgroundColor: PRIMARY, alignItems: 'center', justifyContent: 'center', }, quickActionText: { color: '#ffffff', fontWeight: '600', }, error: { color: '#dc2626', fontSize: 13, }, loadingBox: { padding: 16, alignItems: 'center', gap: 8, }, loadingText: { color: '#6b7280', }, });