diff --git a/frontend/src/components/week_view.rs b/frontend/src/components/week_view.rs index caa24b2..7aaeca2 100644 --- a/frontend/src/components/week_view.rs +++ b/frontend/src/components/week_view.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use web_sys::MouseEvent; use crate::services::calendar_service::UserInfo; use crate::models::ical::VEvent; -use crate::components::{RecurringEditModal, RecurringEditAction, EventCreationData, EventStatus, EventClass, ReminderType, RecurrenceType}; +use crate::components::{RecurringEditModal, RecurringEditAction, EventCreationData}; #[derive(Properties, PartialEq)] pub struct WeekViewProps { @@ -106,57 +106,49 @@ pub fn week_view(props: &WeekViewProps) -> Html { let pending_recurring_edit = pending_recurring_edit.clone(); let on_event_update = props.on_event_update.clone(); let _on_create_event = props.on_create_event.clone(); - let on_create_event_request = props.on_create_event_request.clone(); let events = props.events.clone(); Callback::from(move |action: RecurringEditAction| { if let Some(edit) = (*pending_recurring_edit).clone() { match action { RecurringEditAction::ThisEvent => { - // Use the series endpoint with "this_only" scope for RFC 5545 compliant single occurrence modification - - web_sys::console::log_1(&format!("🎯 Single occurrence modification: calling series update with this_only scope for event '{}'", - edit.event.summary.as_deref().unwrap_or("Untitled") - ).into()); - - // TODO: Need to call calendar service directly with update_scope "this_only" and occurrence_date - // For now, fall back to the old method but with better logging + // RFC 5545 Compliant Single Occurrence Modification: "This Event Only" + // + // When a user chooses to modify "this event only" for a recurring series, + // we implement an exception-based modification that: + // + // 1. **Add EXDATE to Original Series**: The original series is updated with + // an EXDATE entry to exclude this specific occurrence from generation + // 2. **Create Exception Event**: A new standalone event is created with + // RECURRENCE-ID pointing to the original occurrence, containing the modifications + // + // Example: User drags Aug 22 occurrence of "Daily 9AM meeting" to 2PM: + // - Original series: "Daily 9AM meeting" + EXDATE for Aug 22 (continues as normal except Aug 22) + // - Exception event: "Daily 2PM meeting" with RECURRENCE-ID=Aug22 (only affects Aug 22) + // + // This approach ensures: + // - All other occurrences remain unchanged (past and future) + // - Modified occurrence displays user's changes + // - RFC 5545 compliance through EXDATE and RECURRENCE-ID + // - CalDAV compatibility with standard calendar applications + // + // The backend handles both operations atomically within a single API call. if let Some(update_callback) = &on_event_update { - // This currently goes to regular update endpoint, but we need it to go to series endpoint - // with update_scope: "this_only" and occurrence_date: edit.event.dtstart.format("%Y-%m-%d") - let updated_event = edit.event.clone(); + // Extract occurrence date for backend processing + let occurrence_date = edit.event.dtstart.format("%Y-%m-%d").to_string(); - web_sys::console::log_1(&format!("⚠️ Using regular update callback - this should be changed to use series endpoint with this_only scope").into()); - - update_callback.emit((updated_event, edit.new_start, edit.new_end, true, None, Some("this_only".to_string()), Some(edit.event.dtstart.format("%Y-%m-%d").to_string()))); // preserve_rrule = true, update_scope = this_only - } - - // Note: The proper fix requires calling calendar_service.update_event_with_scope() directly - // with update_scope: "this_only" and occurrence_date - if let Some(create_callback) = &on_create_event_request { - // Convert to EventCreationData for single event - let event_data = EventCreationData { - title: edit.event.summary.clone().unwrap_or_default(), - description: edit.event.description.clone().unwrap_or_default(), - start_date: edit.new_start.date(), - start_time: edit.new_start.time(), - end_date: edit.new_end.date(), - end_time: edit.new_end.time(), - location: edit.event.location.clone().unwrap_or_default(), - all_day: edit.event.all_day, - status: EventStatus::Confirmed, - class: EventClass::Public, - priority: edit.event.priority, - organizer: edit.event.organizer.as_ref().map(|o| o.cal_address.clone()).unwrap_or_default(), - attendees: edit.event.attendees.iter().map(|a| a.cal_address.clone()).collect::>().join(","), - categories: edit.event.categories.join(","), - reminder: ReminderType::None, - recurrence: RecurrenceType::None, // Single event, no recurrence - recurrence_days: vec![false; 7], - selected_calendar: edit.event.calendar_path.clone(), - }; - - // Create the single event - create_callback.emit(event_data); + // Send single request to backend with "this_only" scope + // Backend will atomically: + // 1. Add EXDATE to original series (excludes this occurrence) + // 2. Create exception event with RECURRENCE-ID and user's modifications + update_callback.emit(( + edit.event.clone(), // Original event (series to modify) + edit.new_start, // Dragged start time for exception + edit.new_end, // Dragged end time for exception + true, // preserve_rrule = true + None, // No until_date for this_only + Some("this_only".to_string()), // Update scope + Some(occurrence_date) // Date of occurrence being modified + )); } }, RecurringEditAction::FutureEvents => {