import { useEffect, useMemo, useState } from 'react'; import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker'; import { KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { CalendarEvent, createEvent, Project } from '@/src/lib/api'; import { useAuth } from '@/src/providers/auth-provider'; const PRIMARY = '#69c350'; type EventCreateModalProps = { visible: boolean; projects?: Project[]; initialProjectId?: number | null; initialDate?: Date | null; onClose: () => void; onCreated: (event: CalendarEvent) => void | Promise; }; function pad(value: number): string { return String(value).padStart(2, '0'); } function formatDate(date: Date): string { return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()}`; } function formatTime(date: Date): string { return `${pad(date.getHours())}:${pad(date.getMinutes())}`; } function createDefaults(initialDate?: Date | null) { const start = initialDate ? new Date(initialDate) : new Date(); if (!initialDate) { start.setMinutes(Math.ceil(start.getMinutes() / 15) * 15, 0, 0); } else { start.setHours(9, 0, 0, 0); } const end = new Date(start.getTime() + 60 * 60 * 1000); return { start, end }; } function relationId(value: unknown): number | null { if (!value) return null; if (typeof value === 'object') { const id = (value as { id?: number | string }).id; return id ? Number(id) : null; } const id = Number(value); return Number.isFinite(id) ? id : null; } export function EventCreateModal({ visible, projects = [], initialProjectId = null, initialDate = null, onClose, onCreated, }: EventCreateModalProps) { const { token } = useAuth(); const [name, setName] = useState(''); const [notes, setNotes] = useState(''); const [link, setLink] = useState(''); const [start, setStart] = useState(() => createDefaults(initialDate).start); const [end, setEnd] = useState(() => createDefaults(initialDate).end); const [activePicker, setActivePicker] = useState<'startDate' | 'startTime' | 'endDate' | 'endTime' | null>(null); const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); const [projectSearch, setProjectSearch] = useState(''); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!visible) return; const defaults = createDefaults(initialDate); setName(''); setNotes(''); setLink(''); setStart(defaults.start); setEnd(defaults.end); setActivePicker(null); setSelectedProjectId(initialProjectId); setProjectSearch(''); setError(null); }, [initialDate, initialProjectId, visible]); const selectedProject = useMemo( () => projects.find((project) => Number(project.id) === selectedProjectId) || null, [projects, selectedProjectId] ); const projectOptions = useMemo(() => { const terms = projectSearch.trim().toLowerCase().split(/\s+/).filter(Boolean); return projects .filter((project) => { if (terms.length === 0) return true; const haystack = `${project.name || ''} ${project.projectNumber || ''}`.toLowerCase(); return terms.every((term) => haystack.includes(term)); }) .slice(0, 8); }, [projectSearch, projects]); function updatePicker(event: DateTimePickerEvent, selected?: Date) { if (Platform.OS === 'android') setActivePicker(null); if (event.type === 'dismissed' || !selected || !activePicker) return; const isStart = activePicker.startsWith('start'); const isDate = activePicker.endsWith('Date'); const current = new Date(isStart ? start : end); if (isDate) { current.setFullYear(selected.getFullYear(), selected.getMonth(), selected.getDate()); } else { current.setHours(selected.getHours(), selected.getMinutes(), 0, 0); } if (isStart) { setStart(current); if (end <= current) setEnd(new Date(current.getTime() + 60 * 60 * 1000)); } else { setEnd(current); } } async function save() { if (!token || saving) return; const title = name.trim(); if (!title) { setError('Bitte einen Namen für den Termin eingeben.'); return; } if (end <= start) { setError('Das Ende muss nach dem Beginn liegen.'); return; } setSaving(true); setError(null); try { const event = await createEvent(token, { name: title, startDate: start.toISOString(), endDate: end.toISOString(), project: selectedProjectId, customer: relationId(selectedProject?.customer), notes: notes.trim() || null, link: link.trim() || null, state: 'Final', repeatInterval: 'Keine Wiederholung', }); await onCreated(event); onClose(); } catch (err) { setError(err instanceof Error ? err.message : 'Der Termin konnte nicht gespeichert werden.'); } finally { setSaving(false); } } return ( Neuer Termin Beginn setActivePicker('startDate')}> {formatDate(start)} setActivePicker('startTime')}> {formatTime(start)} Ende setActivePicker('endDate')}> {formatDate(end)} setActivePicker('endTime')}> {formatTime(end)} {activePicker ? ( {Platform.OS === 'ios' ? ( setActivePicker(null)}> Übernehmen ) : null} ) : null} Projekt {selectedProject ? ( {selectedProject.name} {selectedProject.projectNumber ? ( Nr. {selectedProject.projectNumber} ) : null} setSelectedProjectId(null)}> Ändern ) : ( <> {projectOptions.map((project) => ( setSelectedProjectId(Number(project.id))}> {project.name} {project.projectNumber || `#${project.id}`} ))} )} {error ? {error} : null} Abbrechen {saving ? 'Speichere...' : 'Anlegen'} ); } const styles = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(17, 24, 39, 0.45)', justifyContent: 'center', padding: 20 }, keyboardWrap: { width: '100%' }, card: { maxHeight: '92%', backgroundColor: '#ffffff', borderRadius: 14, padding: 16, gap: 12 }, title: { color: '#111827', fontSize: 18, fontWeight: '700' }, scroll: { flexShrink: 1 }, form: { gap: 10, paddingBottom: 4 }, label: { color: '#374151', fontSize: 13, fontWeight: '700' }, input: { borderWidth: 1, borderColor: '#d1d5db', borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#111827', backgroundColor: '#ffffff', }, row: { flexDirection: 'row', gap: 8 }, pickerButton: { minHeight: 44, borderWidth: 1, borderColor: '#d1d5db', borderRadius: 10, paddingHorizontal: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: '#ffffff', }, pickerButtonText: { color: '#111827', fontSize: 15, fontWeight: '600' }, dateInput: { flex: 1 }, timeInput: { width: 92 }, pickerWrap: { borderWidth: 1, borderColor: '#e5e7eb', borderRadius: 10, padding: 8 }, pickerDoneButton: { alignSelf: 'flex-end', paddingHorizontal: 10, paddingVertical: 7 }, pickerDoneText: { color: '#3d7a30', fontSize: 14, fontWeight: '700' }, multiline: { minHeight: 90, textAlignVertical: 'top' }, selectedProject: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderWidth: 1, borderColor: '#b9e3ae', backgroundColor: '#eff9ea', borderRadius: 10, padding: 10, gap: 8, }, selectedProjectMain: { flex: 1 }, selectedProjectName: { color: '#111827', fontSize: 14, fontWeight: '700' }, selectedProjectNumber: { color: '#6b7280', fontSize: 12 }, removeProject: { color: '#3d7a30', fontSize: 13, fontWeight: '700' }, projectOption: { flexDirection: 'row', justifyContent: 'space-between', borderBottomWidth: 1, borderBottomColor: '#e5e7eb', paddingVertical: 8, gap: 8, }, projectOptionName: { flex: 1, color: '#111827', fontSize: 14, fontWeight: '600' }, projectOptionNumber: { color: '#6b7280', fontSize: 12 }, error: { color: '#dc2626', fontSize: 13 }, actions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 8 }, secondaryButton: { minHeight: 40, borderWidth: 1, borderColor: '#d1d5db', borderRadius: 10, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', }, secondaryButtonText: { color: '#374151', fontWeight: '600' }, primaryButton: { minHeight: 40, borderRadius: 10, paddingHorizontal: 14, alignItems: 'center', justifyContent: 'center', backgroundColor: PRIMARY, }, primaryButtonText: { color: '#ffffff', fontWeight: '700' }, disabled: { opacity: 0.6 }, });