From 4d41aa91367412678beab3aa08628c6ba63b546d Mon Sep 17 00:00:00 2001 From: c-d-p Date: Wed, 23 Apr 2025 19:25:50 +0200 Subject: [PATCH] add allday to calendar model --- .../__pycache__/schemas.cpython-312.pyc | Bin 2880 -> 2906 bytes backend/modules/calendar/schemas.py | 3 +- .../src/components/calendar/EventItem.tsx | 7 +- .../nativeapp/src/screens/EventFormScreen.tsx | 132 ++++++++++++------ interfaces/nativeapp/src/types/calendar.ts | 3 + 5 files changed, 103 insertions(+), 42 deletions(-) diff --git a/backend/modules/calendar/__pycache__/schemas.cpython-312.pyc b/backend/modules/calendar/__pycache__/schemas.cpython-312.pyc index 7f3ff14986bc78e5129eaa04877d42178d8f8cee..7612fb2833fc29df70c0bce3ddb08d1b74d44d93 100644 GIT binary patch delta 325 zcmX>gc1w)!G%qg~0}yyCa%MPjY~(9q5#| zQ@B#N*YK=n0_g$D3#9P2Fsz1f;B=H=C6gxK8aB9PoGI*BEaPHgaA!#2 zXkkdo}9{|I$4lSVe(RrCPvH28l0IqH1kwYP7f9i0VOR~}!09N#N+wO-$+sC@H!CvzV4OUG-EDI`%S2|zg2^A* z)%@9@CNo3LWKUsEVOhfpHJsF~#gaqJ`mxlw2o&kvx!HWC|qwG`T1L=aiZ($7L = ({ event, showTime = true }) => { navigation.navigate('EventForm', { eventId: event.id }); }; - const timeString = showTime ? format(startDate, 'p') : ''; // Format time like 1:00 PM + // Determine if time should be shown: only if showTime is true AND the event is NOT all_day + const shouldDisplayTime = showTime && !event.all_day; + const timeString = shouldDisplayTime ? format(startDate, 'p') : ''; // Format time like 1:00 PM return ( - {showTime && {timeString} } + {/* Conditionally render time based on shouldDisplayTime */} + {shouldDisplayTime && {timeString} } {event.title} {/* Optional: Display tags if they exist */} diff --git a/interfaces/nativeapp/src/screens/EventFormScreen.tsx b/interfaces/nativeapp/src/screens/EventFormScreen.tsx index 2485d2c..9bafc01 100644 --- a/interfaces/nativeapp/src/screens/EventFormScreen.tsx +++ b/interfaces/nativeapp/src/screens/EventFormScreen.tsx @@ -2,8 +2,8 @@ import React, { useState, useEffect, useCallback } from 'react'; // Add Platform import import { View, StyleSheet, ScrollView, Alert, TouchableOpacity, Platform } from 'react-native'; -// Add Chip -import { TextInput, Button, useTheme, Text, ActivityIndicator, HelperText, Chip } from 'react-native-paper'; +// Add Chip and Switch +import { TextInput, Button, useTheme, Text, ActivityIndicator, HelperText, Chip, Switch } from 'react-native-paper'; import { useNavigation, useRoute, RouteProp } from '@react-navigation/native'; // Conditionally import DateTimePickerModal only if not on web // Note: This dynamic import might not work as expected depending on the bundler setup. @@ -35,6 +35,7 @@ const EventFormScreen = () => { const [endDate, setEndDate] = useState(null); const [color, setColor] = useState(''); // Basic color input for now const [location, setLocation] = useState(''); // Add location state + const [isAllDay, setIsAllDay] = useState(false); // Add all_day state const [tags, setTags] = useState([]); // Add tags state const [currentTagInput, setCurrentTagInput] = useState(''); // State for tag input field @@ -63,6 +64,7 @@ const EventFormScreen = () => { setDescription(event.description || ''); setColor(event.color || ''); // Use optional color setLocation(event.location || ''); // Set location state + setIsAllDay(event.all_day ?? false); // Set all_day state, default to false if null/undefined setTags(event.tags || []); // Load tags or default to empty array // Ensure dates are Date objects if (event.start && isValid(parseISO(event.start))) { @@ -116,6 +118,7 @@ const EventFormScreen = () => { setEndDate(null); setWebEndDateInput(''); } + setIsAllDay(false); // Default all_day to false for new events setTags([]); // Ensure tags start empty for new event } else { // Default start date to now if creating without a selected date @@ -124,6 +127,7 @@ const EventFormScreen = () => { setWebStartDateInput(formatForWebInput(now)); // Init web input setEndDate(null); setWebEndDateInput(''); + setIsAllDay(false); // Default all_day to false for new events setTags([]); // Ensure tags start empty for new event } }, [eventId, selectedDate]); @@ -281,6 +285,7 @@ const EventFormScreen = () => { end: endDate ? endDate.toISOString() : null, location: location.trim() || null, // Include location color: color.trim() || null, // Include color + all_day: isAllDay, // Include all_day state tags: tags.length > 0 ? tags : null, // Include tags, send null if empty }; @@ -334,8 +339,9 @@ const EventFormScreen = () => { const formatDateTime = (date: Date | null): string => { if (!date) return ''; try { - // Native uses 'MMM d, yyyy, p', web input is handled separately - return format(date, "MMM d, yyyy, p"); + // If all_day, only show the date part + const formatString = isAllDay ? "MMM d, yyyy" : "MMM d, yyyy, p"; + return format(date, formatString); } catch { return "Invalid Date"; } @@ -345,7 +351,9 @@ const EventFormScreen = () => { const formatForWebInput = (date: Date | null): string => { if (!date || !isValid(date)) return ''; try { - return format(date, "yyyy-MM-dd HH:mm"); + // If all_day, only format the date part for the input + const formatString = isAllDay ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm"; + return format(date, formatString); } catch { return ''; // Return empty if formatting fails } @@ -364,6 +372,32 @@ const EventFormScreen = () => { setTags(tags.filter(tag => tag !== tagToRemove)); }; + // --- All Day Toggle Logic --- + const onToggleAllDay = () => { + const newAllDay = !isAllDay; + setIsAllDay(newAllDay); + + // Adjust dates when toggling all_day + if (startDate) { + const newStartDate = new Date(startDate); + if (newAllDay) { + // Set time to beginning of the day + newStartDate.setHours(0, 0, 0, 0); + // Optionally clear end date or set it to end of the same day + setEndDate(null); + setWebEndDateInput(''); + } else { + // Restore a default time (e.g., 9 AM) or keep the previous time if stored + newStartDate.setHours(9, 0, 0, 0); + } + setStartDate(newStartDate); + setWebStartDateInput(formatForWebInput(newStartDate)); // Update web input + + // Re-validate if end date was cleared or start date changed + validateForm({ start: newStartDate, end: newAllDay ? null : endDate }); + } + }; + if (isLoading && !title) { // Show loading indicator only during initial fetch return ; } @@ -387,44 +421,54 @@ const EventFormScreen = () => { {formErrors.title} + {/* All Day Switch */} + + All-day event + + {/* Start Date Input - Conditional Logic */} - + handleWebDateInputChange(text, 'start') : undefined} - placeholder={Platform.OS === 'web' ? 'YYYY-MM-DD HH:mm' : ''} + editable={Platform.OS === 'web' && !isAllDay} + onChangeText={Platform.OS === 'web' && !isAllDay ? (text) => handleWebDateInputChange(text, 'start') : undefined} + placeholder={Platform.OS === 'web' ? (isAllDay ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm') : ''} mode="outlined" style={styles.input} - right={Platform.OS !== 'web' ? : null} + right={Platform.OS !== 'web' && !isAllDay ? : } error={!!formErrors.start} + disabled={isAllDay && Platform.OS !== 'web'} // Disable native text input editing for all-day /> {formErrors.start} - {/* End Date Input - Conditional Logic */} - - handleWebDateInputChange(text, 'end') : undefined} - placeholder={Platform.OS === 'web' ? 'YYYY-MM-DD HH:mm' : ''} - mode="outlined" - style={styles.input} - right={Platform.OS !== 'web' ? : null} - error={!!formErrors.end} - /> - - - {formErrors.end} - + {/* End Date Input - Conditionally render based on all_day */} + {!isAllDay && ( + <> + + handleWebDateInputChange(text, 'end') : undefined} + placeholder={Platform.OS === 'web' ? 'YYYY-MM-DD HH:mm' : ''} + mode="outlined" + style={styles.input} + right={Platform.OS !== 'web' ? : null} + error={!!formErrors.end} + /> + + + {formErrors.end} + + + )} {/* Add Location Input */} { <> - + {/* Only show end date picker if not all_day */} + {!isAllDay && ( + + )} )} @@ -549,6 +596,13 @@ const styles = StyleSheet.create({ input: { marginBottom: 5, // Reduced margin as HelperText adds space }, + switchContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 15, // Add some space below the switch + paddingHorizontal: 4, // Align text with TextInput label + }, button: { marginTop: 15, }, diff --git a/interfaces/nativeapp/src/types/calendar.ts b/interfaces/nativeapp/src/types/calendar.ts index 581a39e..eedf78d 100644 --- a/interfaces/nativeapp/src/types/calendar.ts +++ b/interfaces/nativeapp/src/types/calendar.ts @@ -8,6 +8,7 @@ export interface CalendarEvent { end?: string | null; // ISO string format location?: string | null; // Make optional color?: string | null; // Keep optional + all_day?: boolean | null; // Add all_day field tags?: string[]; // Add optional tags array user_id: number; // Add user_id as it's in the response } @@ -20,6 +21,7 @@ export type CalendarEventCreate = { end?: string | null; // ISO string format location?: string | null; color?: string | null; + all_day?: boolean | null; // Add all_day field tags?: string[]; // Add optional tags array }; @@ -31,5 +33,6 @@ export type CalendarEventUpdate = { end?: string | null; // ISO string format location?: string | null; color?: string | null; + all_day?: boolean | null; // Add all_day field tags?: string[]; // Add optional tags array }; \ No newline at end of file