KI-AGENT: Kalender, Terminbearbeitung und Dateivorschau verbessern

This commit is contained in:
2026-09-08 23:08:36 +02:00
parent 718ecefd46
commit 2c1047e624
11 changed files with 390 additions and 74 deletions

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
import { router } from 'expo-router';
import { fetchStaffTimeSpans, fetchTasks, Task } from '@/src/lib/api';
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';
@@ -38,12 +39,25 @@ function formatDateTime(value: string | null): string {
});
}
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<string | null>(null);
const [events, setEvents] = useState<CalendarEvent[]>([]);
const [projects, setProjects] = useState<Project[]>([]);
const [createEventOpen, setCreateEventOpen] = useState(false);
const [selectedCalendarDate, setSelectedCalendarDate] = useState(() => new Date());
const [data, setData] = useState<DashboardData>({
tasks: [],
openTasks: 0,
@@ -62,9 +76,11 @@ export default function DashboardScreen() {
setError(null);
try {
const [taskRows, spans] = await Promise.all([
const [taskRows, spans, eventRows, projectRows] = await Promise.all([
fetchTasks(token),
fetchStaffTimeSpans(token, currentUserId),
fetchEvents(token),
fetchProjects(token),
]);
const tasks = taskRows || [];
@@ -92,6 +108,8 @@ export default function DashboardScreen() {
pendingSubmissions,
todayMinutes,
});
setEvents(eventRows);
setProjects(projectRows);
} catch (err) {
setError(err instanceof Error ? err.message : 'Dashboard konnte nicht geladen werden.');
} finally {
@@ -112,6 +130,26 @@ export default function DashboardScreen() {
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 (
<ScrollView
contentContainerStyle={styles.container}
@@ -158,6 +196,60 @@ export default function DashboardScreen() {
</View>
</View>
<View style={styles.calendarCard}>
<View style={styles.calendarHeader}>
<View>
<Text style={styles.calendarTitle}>Kalender</Text>
<Text style={styles.calendarSubtitle}>
{selectedCalendarDate.toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: 'long' })}
</Text>
</View>
<Pressable onPress={() => router.push('/more/calendar' as any)}>
<Text style={styles.calendarLink}>Monat öffnen</Text>
</Pressable>
</View>
<View style={styles.calendarDays}>
{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 (
<Pressable
key={key}
style={[styles.calendarDay, selected ? styles.calendarDaySelected : null]}
onPress={() => setSelectedCalendarDate(date)}>
<Text style={[styles.calendarWeekday, selected ? styles.calendarDayTextSelected : null]}>
{date.toLocaleDateString('de-DE', { weekday: 'short' }).replace('.', '')}
</Text>
<Text style={[styles.calendarDayNumber, selected ? styles.calendarDayTextSelected : null]}>{date.getDate()}</Text>
<View style={[styles.calendarDot, count > 0 ? styles.calendarDotActive : null]} />
</Pressable>
);
})}
</View>
{selectedEvents.length === 0 ? <Text style={styles.calendarEmpty}>Keine Termine an diesem Tag.</Text> : null}
{selectedEvents.slice(0, 3).map((event) => (
<Pressable
key={String(event.id)}
style={styles.calendarEvent}
onPress={() => router.push(`/more/event/${event.id}` as any)}>
<View style={[styles.calendarEventMark, { backgroundColor: event.color || PRIMARY }]} />
<Text style={styles.calendarEventTime}>{formatEventTime(event.startDate)}</Text>
<Text style={styles.calendarEventName} numberOfLines={1}>{event.name}</Text>
{event.state === 'Entwurf' ? <Text style={styles.calendarEventDraft}>Entwurf</Text> : null}
<Text style={styles.calendarEventArrow}>›</Text>
</Pressable>
))}
<Pressable style={styles.calendarAddButton} onPress={() => setCreateEventOpen(true)}>
<Text style={styles.calendarAddButtonText}>+ Termin eintragen</Text>
</Pressable>
</View>
<View style={styles.quickActionsCard}>
<Text style={styles.quickActionsTitle}>Schnellzugriff</Text>
<View style={styles.quickActionsRow}>
@@ -175,6 +267,14 @@ export default function DashboardScreen() {
</Pressable>
</View>
</View>
<EventCreateModal
visible={createEventOpen}
projects={projects}
initialDate={selectedCalendarDate}
onClose={() => setCreateEventOpen(false)}
onCreated={() => loadDashboard(false)}
/>
</>
) : null}
</ScrollView>
@@ -232,6 +332,35 @@ const styles = StyleSheet.create({
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,