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

@@ -3,8 +3,9 @@ import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, T
import { useLocalSearchParams } from 'expo-router';
import * as WebBrowser from 'expo-web-browser';
import { EventCreateModal } from '@/components/event-create-modal';
import { HistorySection } from '@/components/history-section';
import { CalendarEvent, fetchEventById } from '@/src/lib/api';
import { CalendarEvent, EventState, fetchEventById, fetchProjects, Project, updateEvent } from '@/src/lib/api';
import { useAuth } from '@/src/providers/auth-provider';
const PRIMARY = '#69c350';
@@ -37,6 +38,9 @@ export default function EventDetailScreen() {
const eventId = Number(params.id);
const { token } = useAuth();
const [event, setEvent] = useState<CalendarEvent | null>(null);
const [projects, setProjects] = useState<Project[]>([]);
const [editOpen, setEditOpen] = useState(false);
const [updatingState, setUpdatingState] = useState(false);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -47,7 +51,12 @@ export default function EventDetailScreen() {
if (showSpinner) setLoading(true);
setError(null);
try {
setEvent(await fetchEventById(token, eventId));
const [eventRow, projectRows] = await Promise.all([
fetchEventById(token, eventId),
fetchProjects(token),
]);
setEvent(eventRow);
setProjects(projectRows);
} catch (err) {
setError(err instanceof Error ? err.message : 'Der Termin konnte nicht geladen werden.');
} finally {
@@ -72,13 +81,26 @@ export default function EventDetailScreen() {
await WebBrowser.openBrowserAsync(url, { controlsColor: PRIMARY, showTitle: true });
}
async function changeState(nextState: EventState) {
if (!token || !event || updatingState || event.state === nextState) return;
setUpdatingState(true);
setError(null);
try {
const updated = await updateEvent(token, eventId, { state: nextState });
setEvent((current) => current ? { ...current, state: updated.state } : updated);
} catch (err) {
setError(err instanceof Error ? err.message : 'Der Status konnte nicht geändert werden.');
} finally {
setUpdatingState(false);
}
}
const rows = event
? [
{ label: 'Beginn', value: formatDateTime(event.startDate) },
{ label: 'Ende', value: formatDateTime(event.endDate) },
{ label: 'Projekt', value: relationName(event.project) },
{ label: 'Kunde', value: relationName(event.customer) },
{ label: 'Status', value: String(event.state || '-') },
{ label: 'Wiederholung', value: String(event.repeatInterval || '-') },
]
: [];
@@ -97,6 +119,25 @@ export default function EventDetailScreen() {
<View style={styles.headingRow}>
<View style={[styles.colorMark, { backgroundColor: event.color || PRIMARY }]} />
<Text style={styles.title}>{event.name}</Text>
<Pressable style={styles.editButton} onPress={() => setEditOpen(true)}>
<Text style={styles.editButtonText}>Bearbeiten</Text>
</Pressable>
</View>
<View style={styles.statusSection}>
<Text style={styles.statusLabel}>Status</Text>
<View style={styles.statusRow}>
{(['Entwurf', 'Final'] as EventState[]).map((option) => (
<Pressable
key={option}
disabled={updatingState}
style={[styles.statusButton, event.state === option ? styles.statusButtonActive : null]}
onPress={() => changeState(option)}>
<Text style={[styles.statusButtonText, event.state === option ? styles.statusButtonTextActive : null]}>
{option}
</Text>
</Pressable>
))}
</View>
</View>
<View style={styles.table}>
{rows.map((row) => (
@@ -110,6 +151,13 @@ export default function EventDetailScreen() {
{event.link ? <Pressable style={styles.linkButton} onPress={openLink}><Text style={styles.linkButtonText}>Link öffnen</Text></Pressable> : null}
</View>
<HistorySection resource="events" resourceId={eventId} />
<EventCreateModal
visible={editOpen}
event={event}
projects={projects}
onClose={() => setEditOpen(false)}
onUpdated={() => load(false)}
/>
</>
) : null}
</ScrollView>
@@ -122,6 +170,15 @@ const styles = StyleSheet.create({
headingRow: { flexDirection: 'row', alignItems: 'center', gap: 9 },
colorMark: { width: 6, height: 28, borderRadius: 3 },
title: { flex: 1, color: '#111827', fontSize: 19, fontWeight: '700' },
editButton: { borderRadius: 8, backgroundColor: PRIMARY, paddingHorizontal: 10, paddingVertical: 7 },
editButtonText: { color: '#ffffff', fontSize: 12, fontWeight: '700' },
statusSection: { gap: 6 },
statusLabel: { color: '#6b7280', fontSize: 12, textTransform: 'uppercase', fontWeight: '600' },
statusRow: { flexDirection: 'row', gap: 8 },
statusButton: { flex: 1, minHeight: 40, borderRadius: 9, borderWidth: 1, borderColor: '#d1d5db', alignItems: 'center', justifyContent: 'center' },
statusButtonActive: { borderColor: PRIMARY, backgroundColor: '#eff9ea' },
statusButtonText: { color: '#6b7280', fontSize: 13, fontWeight: '700' },
statusButtonTextActive: { color: '#3d7a30' },
table: { borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 10, overflow: 'hidden' },
row: { borderBottomWidth: 1, borderBottomColor: '#e5e7eb', paddingHorizontal: 10, paddingVertical: 8, gap: 2 },
label: { color: '#6b7280', fontSize: 12, textTransform: 'uppercase', fontWeight: '600' },