KI-AGENT: Terminpicker und Dokumentaufruf verbessert
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker';
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
@@ -29,11 +30,11 @@ function pad(value: number): string {
|
||||
return String(value).padStart(2, '0');
|
||||
}
|
||||
|
||||
function formatDateInput(date: Date): string {
|
||||
function formatDate(date: Date): string {
|
||||
return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
function formatTimeInput(date: Date): string {
|
||||
function formatTime(date: Date): string {
|
||||
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
@@ -45,35 +46,7 @@ function createDefaults(initialDate?: Date | null) {
|
||||
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;
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
function relationId(value: unknown): number | null {
|
||||
@@ -98,10 +71,9 @@ export function EventCreateModal({
|
||||
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 [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<number | null>(initialProjectId);
|
||||
const [projectSearch, setProjectSearch] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -113,10 +85,9 @@ export function EventCreateModal({
|
||||
setName('');
|
||||
setNotes('');
|
||||
setLink('');
|
||||
setStartDate(defaults.startDate);
|
||||
setStartTime(defaults.startTime);
|
||||
setEndDate(defaults.endDate);
|
||||
setEndTime(defaults.endTime);
|
||||
setStart(defaults.start);
|
||||
setEnd(defaults.end);
|
||||
setActivePicker(null);
|
||||
setSelectedProjectId(initialProjectId);
|
||||
setProjectSearch('');
|
||||
setError(null);
|
||||
@@ -138,6 +109,27 @@ export function EventCreateModal({
|
||||
.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();
|
||||
@@ -146,12 +138,6 @@ export function EventCreateModal({
|
||||
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;
|
||||
@@ -198,44 +184,42 @@ export function EventCreateModal({
|
||||
|
||||
<Text style={styles.label}>Beginn</Text>
|
||||
<View style={styles.row}>
|
||||
<TextInput
|
||||
placeholder="TT.MM.JJJJ"
|
||||
placeholderTextColor="#9ca3af"
|
||||
style={[styles.input, styles.dateInput]}
|
||||
value={startDate}
|
||||
onChangeText={setStartDate}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="HH:MM"
|
||||
placeholderTextColor="#9ca3af"
|
||||
style={[styles.input, styles.timeInput]}
|
||||
value={startTime}
|
||||
onChangeText={setStartTime}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
/>
|
||||
<Pressable style={[styles.pickerButton, styles.dateInput]} onPress={() => setActivePicker('startDate')}>
|
||||
<Text style={styles.pickerButtonText}>{formatDate(start)}</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.pickerButton, styles.timeInput]} onPress={() => setActivePicker('startTime')}>
|
||||
<Text style={styles.pickerButtonText}>{formatTime(start)}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={styles.label}>Ende</Text>
|
||||
<View style={styles.row}>
|
||||
<TextInput
|
||||
placeholder="TT.MM.JJJJ"
|
||||
placeholderTextColor="#9ca3af"
|
||||
style={[styles.input, styles.dateInput]}
|
||||
value={endDate}
|
||||
onChangeText={setEndDate}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="HH:MM"
|
||||
placeholderTextColor="#9ca3af"
|
||||
style={[styles.input, styles.timeInput]}
|
||||
value={endTime}
|
||||
onChangeText={setEndTime}
|
||||
keyboardType="numbers-and-punctuation"
|
||||
/>
|
||||
<Pressable style={[styles.pickerButton, styles.dateInput]} onPress={() => setActivePicker('endDate')}>
|
||||
<Text style={styles.pickerButtonText}>{formatDate(end)}</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.pickerButton, styles.timeInput]} onPress={() => setActivePicker('endTime')}>
|
||||
<Text style={styles.pickerButtonText}>{formatTime(end)}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{activePicker ? (
|
||||
<View style={styles.pickerWrap}>
|
||||
<DateTimePicker
|
||||
value={activePicker.startsWith('start') ? start : end}
|
||||
mode={activePicker.endsWith('Date') ? 'date' : 'time'}
|
||||
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
|
||||
locale="de-DE"
|
||||
minuteInterval={5}
|
||||
onChange={updatePicker}
|
||||
/>
|
||||
{Platform.OS === 'ios' ? (
|
||||
<Pressable style={styles.pickerDoneButton} onPress={() => setActivePicker(null)}>
|
||||
<Text style={styles.pickerDoneText}>Übernehmen</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<Text style={styles.label}>Projekt</Text>
|
||||
{selectedProject ? (
|
||||
<View style={styles.selectedProject}>
|
||||
@@ -324,8 +308,22 @@ const styles = StyleSheet.create({
|
||||
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',
|
||||
|
||||
Reference in New Issue
Block a user