import { useEffect, useMemo, useState } from 'react'; 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 formatDateInput(date: Date): string { return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()}`; } function formatTimeInput(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 { startDate: formatDateInput(start), startTime: formatTimeInput(start), endDate: formatDateInput(end), endTime: formatTimeInput(end), }; } function parseLocalDate(dateValue: string, timeValue: string): Date | null { const match = dateValue.trim().match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/); const timeMatch = timeValue.trim().match(/^(\d{1,2}):(\d{2})$/); if (!match || !timeMatch) return null; const day = Number(match[1]); const month = Number(match[2]); const year = Number(match[3]); const hours = Number(timeMatch[1]); const minutes = Number(timeMatch[2]); if (hours > 23 || minutes > 59) return null; const date = new Date(year, month - 1, day, hours, minutes, 0, 0); if ( date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day ) { return null; } return date; } 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 [startDate, setStartDate] = useState(''); const [startTime, setStartTime] = useState(''); const [endDate, setEndDate] = useState(''); const [endTime, setEndTime] = useState(''); 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(''); setStartDate(defaults.startDate); setStartTime(defaults.startTime); setEndDate(defaults.endDate); setEndTime(defaults.endTime); 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]); async function save() { if (!token || saving) return; const title = name.trim(); if (!title) { setError('Bitte einen Namen für den Termin eingeben.'); return; } const start = parseLocalDate(startDate, startTime); const end = parseLocalDate(endDate, endTime); if (!start || !end) { setError('Bitte Datum und Uhrzeit im angegebenen Format 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 Ende 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 }, dateInput: { flex: 1 }, timeInput: { width: 92 }, 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 }, });