import React, {useMemo, useState} from 'react'; import { ActivityIndicator, Alert, KeyboardAvoidingView, Modal, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import {AgendaRow} from './AgendaRow'; import {EventForm} from './EventForm'; import {EventRow} from './EventRow'; import { generateTimeSlots, getAllDayEvents, getEventForSlot, getLocalItemForSlot, } from '../../agenda/utils'; import type {AgendaConfig, AgendaItem, TimeSlot} from '../../agenda/types'; import type {CalendarEvent} from '../../googleCalendar/types'; type EventSheetProps = { visible: boolean; dateLabel: string; date: string; events: CalendarEvent[]; localItems: AgendaItem[]; agendaConfig: Required; isLoading: boolean; error?: string | null; onClose: () => void; onCreate: (title: string, slot: TimeSlot) => Promise; onUpdate: (id: string, title: string) => Promise; onDelete: (id: string) => Promise; }; const EventSheet = ({ visible, dateLabel, date, events, localItems, agendaConfig, isLoading, error, onClose, onCreate, onUpdate, onDelete, }: EventSheetProps) => { const [title, setTitle] = useState(''); const [selectedSlot, setSelectedSlot] = useState(null); const [editingId, setEditingId] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [formError, setFormError] = useState(null); const slots = useMemo(() => generateTimeSlots(agendaConfig), [agendaConfig]); const allDayEvents = getAllDayEvents(events); const resetForm = () => { setTitle(''); setSelectedSlot(null); setEditingId(null); setFormError(null); }; const handleClose = () => { resetForm(); onClose(); }; const handleSelectSlot = (slot: TimeSlot) => { const booked = getEventForSlot(events, slot, date); const local = getLocalItemForSlot(localItems, slot, date); const reserved = booked ?? local; setSelectedSlot(slot); setEditingId(reserved?.id ?? null); setTitle(reserved?.title ?? ''); setFormError(null); }; const handleSubmit = async () => { const nextTitle = title.trim(); if (!nextTitle || !selectedSlot) { return; } setIsSubmitting(true); try { if (editingId) { await onUpdate(editingId, nextTitle); } else { await onCreate(nextTitle, selectedSlot); } resetForm(); } catch (caught) { const message = caught instanceof Error ? caught.message : 'Could not save reservation'; setFormError(message); } finally { setIsSubmitting(false); } }; const handleDelete = (eventId: string) => { Alert.alert('Remove reservation', 'This cannot be undone.', [ {text: 'Cancel', style: 'cancel'}, { text: 'Remove', style: 'destructive', onPress: () => { onDelete(eventId) .then(() => { if (editingId === eventId) { resetForm(); } }) .catch((caught) => { const message = caught instanceof Error ? caught.message : 'Could not remove reservation'; setFormError(message); }); }, }, ]); }; return ( Agenda ยท {dateLabel} Close {isLoading ? ( ) : null} {error || formError ? ( {formError ?? error} ) : null} {allDayEvents.map((event) => ( { setEditingId(item.id); setSelectedSlot(null); setTitle(item.title); }} onDelete={handleDelete} /> ))} {slots.map((slot) => { const booked = getEventForSlot(events, slot, date); const local = getLocalItemForSlot(localItems, slot, date); const reserved = booked ?? local; const isSelected = selectedSlot?.start === slot.start && selectedSlot?.end === slot.end; return ( handleSelectSlot(slot)} onDelete={ reserved ? () => handleDelete(reserved.id) : undefined } /> ); })} {selectedSlot ? ( ) : ( Select a time slot to reserve it. )} ); }; const MemoEventSheet = React.memo(EventSheet); export {MemoEventSheet as EventSheet}; const styles = StyleSheet.create({ backdrop: { flex: 1, justifyContent: 'flex-end', backgroundColor: 'rgba(0, 0, 0, 0.35)', }, overlay: { flex: 1, }, sheet: { backgroundColor: '#f3f6f8', borderTopLeftRadius: 18, borderTopRightRadius: 18, paddingHorizontal: 16, paddingTop: 16, paddingBottom: 24, maxHeight: '80%', }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, headerTitle: { fontSize: 17, fontWeight: '700', color: '#24353c', }, closeText: { fontSize: 14, fontWeight: '600', color: '#79afc1', }, loader: { marginVertical: 8, }, error: { color: '#a00', fontSize: 13, marginBottom: 8, }, list: { maxHeight: 320, marginBottom: 12, }, hint: { paddingVertical: 8, textAlign: 'center', color: '#5a727c', fontSize: 13, }, });