Editing Series Events via the Modal #5

Merged
connor merged 2 commits from feature/modal-series-editing into main 2025-08-31 19:10:27 -04:00
5 changed files with 91 additions and 16 deletions
Showing only changes of commit 5a12c0e0d0 - Show all commits

View File

@@ -201,6 +201,7 @@ pub struct UpdateEventSeriesRequest {
// Update scope control
pub update_scope: String, // "this_only", "this_and_future", "all_in_series"
pub occurrence_date: Option<String>, // ISO date string for specific occurrence being updated
pub changed_fields: Option<Vec<String>>, // List of field names that were changed (for optimization)
}
#[derive(Debug, Serialize)]

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 {

View File

@@ -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);
}
})

View File

@@ -9,13 +9,20 @@ pub enum DeleteAction {
DeleteSeries,
}
#[derive(Clone, PartialEq, Debug)]
pub enum EditAction {
EditThis,
EditFuture,
EditAll,
}
#[derive(Properties, PartialEq)]
pub struct EventContextMenuProps {
pub is_open: bool,
pub x: i32,
pub y: i32,
pub event: Option<VEvent>,
pub on_edit: Callback<()>,
pub on_edit: Callback<EditAction>,
pub on_delete: Callback<DeleteAction>,
pub on_close: Callback<()>,
}
@@ -38,11 +45,11 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
.map(|event| event.rrule.is_some())
.unwrap_or(false);
let on_edit_click = {
let create_edit_callback = |action: EditAction| {
let on_edit = props.on_edit.clone();
let on_close = props.on_close.clone();
Callback::from(move |_: MouseEvent| {
on_edit.emit(());
on_edit.emit(action.clone());
on_close.emit(());
})
};
@@ -62,9 +69,29 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
class="context-menu"
style={style}
>
<div class="context-menu-item" onclick={on_edit_click}>
{"Edit Event"}
</div>
{
if is_recurring {
html! {
<>
<div class="context-menu-item" onclick={create_edit_callback(EditAction::EditThis)}>
{"Edit This Event"}
</div>
<div class="context-menu-item" onclick={create_edit_callback(EditAction::EditFuture)}>
{"Edit This and Future Events"}
</div>
<div class="context-menu-item" onclick={create_edit_callback(EditAction::EditAll)}>
{"Edit All Events in Series"}
</div>
</>
}
} else {
html! {
<div class="context-menu-item" onclick={create_edit_callback(EditAction::EditThis)}>
{"Edit Event"}
</div>
}
}
}
{
if is_recurring {
html! {

View File

@@ -22,7 +22,7 @@ pub use week_view::WeekView;
pub use event_modal::EventModal;
pub use create_calendar_modal::CreateCalendarModal;
pub use context_menu::ContextMenu;
pub use event_context_menu::{EventContextMenu, DeleteAction};
pub use event_context_menu::{EventContextMenu, DeleteAction, EditAction};
pub use calendar_context_menu::CalendarContextMenu;
pub use create_event_modal::{CreateEventModal, EventCreationData, EventStatus, EventClass, ReminderType, RecurrenceType};
pub use sidebar::{Sidebar, ViewMode, Theme};