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:
@@ -4,6 +4,7 @@ use wasm_bindgen::JsCast;
|
||||
use chrono::{NaiveDate, NaiveTime, Local, TimeZone, Utc, Datelike};
|
||||
use crate::services::calendar_service::CalendarInfo;
|
||||
use crate::models::ical::VEvent;
|
||||
use crate::components::EditAction;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct CreateEventModalProps {
|
||||
@@ -18,6 +19,8 @@ pub struct CreateEventModalProps {
|
||||
pub initial_start_time: Option<NaiveTime>,
|
||||
#[prop_or_default]
|
||||
pub initial_end_time: Option<NaiveTime>,
|
||||
#[prop_or_default]
|
||||
pub edit_scope: Option<EditAction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
@@ -330,6 +333,10 @@ pub struct EventCreationData {
|
||||
pub monthly_by_day: Option<String>, // For monthly: "1MO" = first Monday, "2TU" = second Tuesday, etc.
|
||||
pub monthly_by_monthday: Option<u8>, // For monthly: day of month (1-31)
|
||||
pub yearly_by_month: Vec<bool>, // For yearly: [Jan, Feb, Mar, ..., Dec]
|
||||
|
||||
// Edit scope and tracking fields
|
||||
pub edit_scope: Option<EditAction>,
|
||||
pub changed_fields: Vec<String>, // List of field names that were changed
|
||||
}
|
||||
|
||||
impl Default for EventCreationData {
|
||||
@@ -365,6 +372,10 @@ impl Default for EventCreationData {
|
||||
monthly_by_day: None,
|
||||
monthly_by_monthday: None,
|
||||
yearly_by_month: vec![false; 12], // [Jan, Feb, ..., Dec] - all false by default
|
||||
|
||||
// Edit scope and tracking defaults
|
||||
edit_scope: None,
|
||||
changed_fields: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,6 +577,10 @@ impl EventCreationData {
|
||||
} else {
|
||||
vec![false; 12]
|
||||
},
|
||||
|
||||
// Edit scope and tracking defaults (will be set later if needed)
|
||||
edit_scope: None,
|
||||
changed_fields: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,9 +608,9 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html {
|
||||
let active_tab = use_state(|| ModalTab::default());
|
||||
|
||||
// Initialize with selected date or event data if provided
|
||||
use_effect_with((props.selected_date, props.event_to_edit.clone(), props.is_open, props.available_calendars.clone(), props.initial_start_time, props.initial_end_time), {
|
||||
use_effect_with((props.selected_date, props.event_to_edit.clone(), props.is_open, props.available_calendars.clone(), props.initial_start_time, props.initial_end_time, props.edit_scope.clone()), {
|
||||
let event_data = event_data.clone();
|
||||
move |(selected_date, event_to_edit, is_open, available_calendars, initial_start_time, initial_end_time)| {
|
||||
move |(selected_date, event_to_edit, is_open, available_calendars, initial_start_time, initial_end_time, edit_scope)| {
|
||||
if *is_open {
|
||||
let mut data = if let Some(event) = event_to_edit {
|
||||
// Pre-populate with event data for editing
|
||||
@@ -625,6 +640,11 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html {
|
||||
data.selected_calendar = Some(available_calendars[0].path.clone());
|
||||
}
|
||||
|
||||
// Set edit scope if provided
|
||||
if let Some(scope) = edit_scope {
|
||||
data.edit_scope = Some(scope.clone());
|
||||
}
|
||||
|
||||
event_data.set(data);
|
||||
}
|
||||
|| ()
|
||||
@@ -644,12 +664,25 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html {
|
||||
})
|
||||
};
|
||||
|
||||
// Helper function to track field changes
|
||||
let _track_field_change = |data: &mut EventCreationData, field_name: &str| {
|
||||
if !data.changed_fields.contains(&field_name.to_string()) {
|
||||
data.changed_fields.push(field_name.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let on_title_input = {
|
||||
let event_data = event_data.clone();
|
||||
Callback::from(move |e: InputEvent| {
|
||||
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
|
||||
let mut data = (*event_data).clone();
|
||||
data.title = input.value();
|
||||
let new_value = input.value();
|
||||
if data.title != new_value {
|
||||
data.title = new_value;
|
||||
if !data.changed_fields.contains(&"title".to_string()) {
|
||||
data.changed_fields.push("title".to_string());
|
||||
}
|
||||
}
|
||||
event_data.set(data);
|
||||
}
|
||||
})
|
||||
@@ -661,7 +694,13 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html {
|
||||
if let Some(select) = e.target_dyn_into::<HtmlSelectElement>() {
|
||||
let mut data = (*event_data).clone();
|
||||
let value = select.value();
|
||||
data.selected_calendar = if value.is_empty() { None } else { Some(value) };
|
||||
let new_calendar = if value.is_empty() { None } else { Some(value) };
|
||||
if data.selected_calendar != new_calendar {
|
||||
data.selected_calendar = new_calendar;
|
||||
if !data.changed_fields.contains(&"selected_calendar".to_string()) {
|
||||
data.changed_fields.push("selected_calendar".to_string());
|
||||
}
|
||||
}
|
||||
event_data.set(data);
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user