Implement comprehensive event series editing via modal

## Frontend Changes:
- Add EditAction enum (EditThis, EditFuture, EditAll) to event context menu
- Update context menu to show 3 edit options for recurring events
- Enhance EventCreationData with edit_scope and changed_fields tracking
- Update app component to handle EditAction types and pass to modal
- Add field change tracking infrastructure to CreateEventModal

## Backend Changes:
- Add changed_fields parameter to UpdateEventSeriesRequest for optimization
- Existing series endpoint already supports the three update types:
  - "this_only" - creates exception with EXDATE
  - "this_and_future" - creates new series with UNTIL on original
  - "all_in_series" - updates existing series in-place

## Implementation Details:
- Event context menu shows single edit option for non-recurring events
- Recurring events get three options: "Edit This Event", "Edit This and Future Events", "Edit All Events in Series"
- Modal tracks which fields user actually changed for efficient updates
- Backend series endpoint already has the logic for all three update scenarios
- Full RFC 5545 compliance with proper EXDATE and UNTIL handling

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-31 18:49:56 -04:00
parent ee181cf6cb
commit 5a12c0e0d0
5 changed files with 91 additions and 16 deletions

View File

@@ -2,7 +2,7 @@ use yew::prelude::*;
use yew_router::prelude::*;
use gloo_storage::{LocalStorage, Storage};
use web_sys::MouseEvent;
use crate::components::{Sidebar, ViewMode, Theme, CreateCalendarModal, ContextMenu, EventContextMenu, CalendarContextMenu, CreateEventModal, EventCreationData, RouteHandler, EventStatus, EventClass, ReminderType, RecurrenceType, DeleteAction};
use crate::components::{Sidebar, ViewMode, Theme, CreateCalendarModal, ContextMenu, EventContextMenu, CalendarContextMenu, CreateEventModal, EventCreationData, RouteHandler, EventStatus, EventClass, ReminderType, RecurrenceType, DeleteAction, EditAction};
use crate::services::{CalendarService, calendar_service::UserInfo};
use crate::models::ical::VEvent;
use chrono::NaiveDate;
@@ -54,6 +54,7 @@ pub fn App() -> Html {
let calendar_context_menu_date = use_state(|| -> Option<NaiveDate> { None });
let create_event_modal_open = use_state(|| false);
let selected_date_for_event = use_state(|| -> Option<NaiveDate> { None });
let event_edit_scope = use_state(|| -> Option<EditAction> { None });
let _recurring_edit_modal_open = use_state(|| false);
let _recurring_edit_event = use_state(|| -> Option<VEvent> { None });
let _recurring_edit_data = use_state(|| -> Option<EventCreationData> { None });
@@ -738,8 +739,10 @@ pub fn App() -> Html {
let _event_context_menu_event = event_context_menu_event.clone();
let event_context_menu_open = event_context_menu_open.clone();
let create_event_modal_open = create_event_modal_open.clone();
move |_| {
// Close the context menu and open the edit modal
let event_edit_scope = event_edit_scope.clone();
move |edit_action: EditAction| {
// Set the edit scope and close the context menu
event_edit_scope.set(Some(edit_action));
event_context_menu_open.set(false);
create_event_modal_open.set(true);
}
@@ -840,13 +843,16 @@ pub fn App() -> Html {
is_open={*create_event_modal_open}
selected_date={(*selected_date_for_event).clone()}
event_to_edit={(*event_context_menu_event).clone()}
edit_scope={(*event_edit_scope).clone()}
on_close={Callback::from({
let create_event_modal_open = create_event_modal_open.clone();
let event_context_menu_event = event_context_menu_event.clone();
let event_edit_scope = event_edit_scope.clone();
move |_| {
create_event_modal_open.set(false);
// Clear the event being edited
// Clear the event being edited and edit scope
event_context_menu_event.set(None);
event_edit_scope.set(None);
}
})}
on_create={on_event_create}
@@ -854,10 +860,12 @@ pub fn App() -> Html {
let auth_token = auth_token.clone();
let create_event_modal_open = create_event_modal_open.clone();
let event_context_menu_event = event_context_menu_event.clone();
let event_edit_scope = event_edit_scope.clone();
move |(original_event, updated_data): (VEvent, EventCreationData)| {
web_sys::console::log_1(&format!("Updating event: {:?}", updated_data).into());
web_sys::console::log_1(&format!("Updating event: {:?}, edit_scope: {:?}", updated_data, updated_data.edit_scope).into());
create_event_modal_open.set(false);
event_context_menu_event.set(None);
event_edit_scope.set(None);
if let Some(token) = (*auth_token).clone() {
wasm_bindgen_futures::spawn_local(async move {