KI-AGENT: Kalender, Terminbearbeitung und Dateivorschau verbessern
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user