Fix recurring event series modification via drag and drop operations

This commit resolves the "Failed to fetch" errors when updating recurring
event series through drag operations by implementing proper request
sequencing and fixing time parameter handling.

Key fixes:
- Eliminate HTTP request cancellation by sequencing operations properly
- Add global mutex to prevent CalDAV HTTP race conditions
- Implement complete RFC 5545-compliant series splitting for "this_and_future"
- Fix frontend to pass dragged times instead of original times
- Add comprehensive error handling and request timing logs
- Backend now handles both UPDATE (add UNTIL) and CREATE (new series) in single request

Technical changes:
- Frontend: Remove concurrent CREATE request, pass dragged times to backend
- Backend: Implement full this_and_future logic with sequential operations
- CalDAV: Add mutex serialization and detailed error tracking
- Series: Create new series with occurrence date + dragged times

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-30 20:17:36 -04:00
parent 1794cf9a59
commit 783e13eb10
6 changed files with 315 additions and 204 deletions

View File

@@ -216,55 +216,17 @@ pub fn week_view(props: &WeekViewProps) -> Html {
until_utc.format("%Y-%m-%d %H:%M:%S UTC"),
edit.event.dtstart.format("%Y-%m-%d %H:%M:%S UTC")).into());
// Use the original series start time (not the dragged occurrence time)
let original_start = original_series.dtstart.with_timezone(&chrono::Local).naive_local();
let original_end = original_series.dtend.unwrap_or(original_series.dtstart).with_timezone(&chrono::Local).naive_local();
// Use the dragged times for the new series (not the original series times)
let new_start = edit.new_start; // The dragged start time
let new_end = edit.new_end; // The dragged end time
// Send until_date to backend instead of modifying RRULE on frontend
update_callback.emit((original_series, original_start, original_end, true, Some(until_utc), Some("this_and_future".to_string()), None)); // preserve_rrule = true, backend will add UNTIL
let occurrence_date = edit.event.dtstart.format("%Y-%m-%d").to_string();
update_callback.emit((original_series, new_start, new_end, true, Some(until_utc), Some("this_and_future".to_string()), Some(occurrence_date))); // preserve_rrule = true, backend will add UNTIL
}
// 2. Create new series starting from this occurrence with modified times
if let Some(create_callback) = &on_create_event_request {
// Convert the recurring event to EventCreationData for the create callback
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, // Default status
class: EventClass::Public, // Default class
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::<Vec<_>>().join(","),
categories: edit.event.categories.join(","),
reminder: ReminderType::None, // Default reminder
recurrence: if let Some(rrule) = &edit.event.rrule {
if rrule.contains("FREQ=DAILY") {
RecurrenceType::Daily
} else if rrule.contains("FREQ=WEEKLY") {
RecurrenceType::Weekly
} else if rrule.contains("FREQ=MONTHLY") {
RecurrenceType::Monthly
} else if rrule.contains("FREQ=YEARLY") {
RecurrenceType::Yearly
} else {
RecurrenceType::None
}
} else {
RecurrenceType::None
},
recurrence_days: vec![false; 7], // Default days
selected_calendar: edit.event.calendar_path.clone(),
};
// Create the new series
create_callback.emit(event_data);
}
// The backend will handle creating the new series as part of the this_and_future update
web_sys::console::log_1(&format!("✅ this_and_future update request sent - backend will handle both UPDATE (add UNTIL) and CREATE (new series) operations").into());
},
RecurringEditAction::AllEvents => {
// Modify the entire series