Compare commits
3 Commits
ee181cf6cb
...
1fa3bf44b6
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fa3bf44b6 | |||
|
|
51d5552156 | ||
|
|
5a12c0e0d0 |
@@ -201,6 +201,7 @@ pub struct UpdateEventSeriesRequest {
|
|||||||
// Update scope control
|
// Update scope control
|
||||||
pub update_scope: String, // "this_only", "this_and_future", "all_in_series"
|
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 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)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use yew::prelude::*;
|
|||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
use gloo_storage::{LocalStorage, Storage};
|
use gloo_storage::{LocalStorage, Storage};
|
||||||
use web_sys::MouseEvent;
|
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::services::{CalendarService, calendar_service::UserInfo};
|
||||||
use crate::models::ical::VEvent;
|
use crate::models::ical::VEvent;
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
@@ -54,6 +54,7 @@ pub fn App() -> Html {
|
|||||||
let calendar_context_menu_date = use_state(|| -> Option<NaiveDate> { None });
|
let calendar_context_menu_date = use_state(|| -> Option<NaiveDate> { None });
|
||||||
let create_event_modal_open = use_state(|| false);
|
let create_event_modal_open = use_state(|| false);
|
||||||
let selected_date_for_event = use_state(|| -> Option<NaiveDate> { None });
|
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_modal_open = use_state(|| false);
|
||||||
let _recurring_edit_event = use_state(|| -> Option<VEvent> { None });
|
let _recurring_edit_event = use_state(|| -> Option<VEvent> { None });
|
||||||
let _recurring_edit_data = use_state(|| -> Option<EventCreationData> { 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_event = event_context_menu_event.clone();
|
||||||
let event_context_menu_open = event_context_menu_open.clone();
|
let event_context_menu_open = event_context_menu_open.clone();
|
||||||
let create_event_modal_open = create_event_modal_open.clone();
|
let create_event_modal_open = create_event_modal_open.clone();
|
||||||
move |_| {
|
let event_edit_scope = event_edit_scope.clone();
|
||||||
// Close the context menu and open the edit modal
|
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);
|
event_context_menu_open.set(false);
|
||||||
create_event_modal_open.set(true);
|
create_event_modal_open.set(true);
|
||||||
}
|
}
|
||||||
@@ -840,13 +843,16 @@ pub fn App() -> Html {
|
|||||||
is_open={*create_event_modal_open}
|
is_open={*create_event_modal_open}
|
||||||
selected_date={(*selected_date_for_event).clone()}
|
selected_date={(*selected_date_for_event).clone()}
|
||||||
event_to_edit={(*event_context_menu_event).clone()}
|
event_to_edit={(*event_context_menu_event).clone()}
|
||||||
|
edit_scope={(*event_edit_scope).clone()}
|
||||||
on_close={Callback::from({
|
on_close={Callback::from({
|
||||||
let create_event_modal_open = create_event_modal_open.clone();
|
let create_event_modal_open = create_event_modal_open.clone();
|
||||||
let event_context_menu_event = event_context_menu_event.clone();
|
let event_context_menu_event = event_context_menu_event.clone();
|
||||||
|
let event_edit_scope = event_edit_scope.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
create_event_modal_open.set(false);
|
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_context_menu_event.set(None);
|
||||||
|
event_edit_scope.set(None);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
on_create={on_event_create}
|
on_create={on_event_create}
|
||||||
@@ -854,10 +860,12 @@ pub fn App() -> Html {
|
|||||||
let auth_token = auth_token.clone();
|
let auth_token = auth_token.clone();
|
||||||
let create_event_modal_open = create_event_modal_open.clone();
|
let create_event_modal_open = create_event_modal_open.clone();
|
||||||
let event_context_menu_event = event_context_menu_event.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)| {
|
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);
|
create_event_modal_open.set(false);
|
||||||
event_context_menu_event.set(None);
|
event_context_menu_event.set(None);
|
||||||
|
event_edit_scope.set(None);
|
||||||
|
|
||||||
if let Some(token) = (*auth_token).clone() {
|
if let Some(token) = (*auth_token).clone() {
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
@@ -988,8 +996,61 @@ pub fn App() -> Html {
|
|||||||
web_sys::window().unwrap().alert_with_message("Cannot move event - original event missing calendar path").unwrap();
|
web_sys::window().unwrap().alert_with_message("Cannot move event - original event missing calendar path").unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Calendar hasn't changed - normal update
|
// Calendar hasn't changed - check if we should use series endpoint
|
||||||
match calendar_service.update_event(
|
let use_series_endpoint = updated_data.edit_scope.is_some() && original_event.rrule.is_some();
|
||||||
|
|
||||||
|
if use_series_endpoint {
|
||||||
|
// Use series endpoint for recurring event modal edits
|
||||||
|
let update_scope = match updated_data.edit_scope.as_ref().unwrap() {
|
||||||
|
EditAction::EditThis => "this_only",
|
||||||
|
EditAction::EditFuture => "this_and_future",
|
||||||
|
EditAction::EditAll => "all_in_series",
|
||||||
|
};
|
||||||
|
|
||||||
|
// For single occurrence edits, we need the occurrence date
|
||||||
|
let occurrence_date = if update_scope == "this_only" || update_scope == "this_and_future" {
|
||||||
|
// Use the original event's start date as the occurrence date
|
||||||
|
Some(original_event.dtstart.format("%Y-%m-%d").to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match calendar_service.update_series(
|
||||||
|
&token,
|
||||||
|
&password,
|
||||||
|
original_event.uid,
|
||||||
|
updated_data.title,
|
||||||
|
updated_data.description,
|
||||||
|
start_date,
|
||||||
|
start_time,
|
||||||
|
end_date,
|
||||||
|
end_time,
|
||||||
|
updated_data.location,
|
||||||
|
updated_data.all_day,
|
||||||
|
status_str,
|
||||||
|
class_str,
|
||||||
|
updated_data.priority,
|
||||||
|
updated_data.organizer,
|
||||||
|
updated_data.attendees,
|
||||||
|
updated_data.categories,
|
||||||
|
reminder_str,
|
||||||
|
recurrence_str,
|
||||||
|
updated_data.selected_calendar,
|
||||||
|
update_scope.to_string(),
|
||||||
|
occurrence_date,
|
||||||
|
).await {
|
||||||
|
Ok(_) => {
|
||||||
|
web_sys::console::log_1(&"Series updated successfully".into());
|
||||||
|
web_sys::window().unwrap().location().reload().unwrap();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
web_sys::console::error_1(&format!("Failed to update series: {}", err).into());
|
||||||
|
web_sys::window().unwrap().alert_with_message(&format!("Failed to update series: {}", err)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use regular event endpoint for non-recurring events or legacy updates
|
||||||
|
match calendar_service.update_event(
|
||||||
&token,
|
&token,
|
||||||
&password,
|
&password,
|
||||||
original_event.uid,
|
original_event.uid,
|
||||||
@@ -1025,6 +1086,7 @@ pub fn App() -> Html {
|
|||||||
web_sys::window().unwrap().alert_with_message(&format!("Failed to update event: {}", err)).unwrap();
|
web_sys::window().unwrap().alert_with_message(&format!("Failed to update event: {}", err)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use wasm_bindgen::JsCast;
|
|||||||
use chrono::{NaiveDate, NaiveTime, Local, TimeZone, Utc, Datelike};
|
use chrono::{NaiveDate, NaiveTime, Local, TimeZone, Utc, Datelike};
|
||||||
use crate::services::calendar_service::CalendarInfo;
|
use crate::services::calendar_service::CalendarInfo;
|
||||||
use crate::models::ical::VEvent;
|
use crate::models::ical::VEvent;
|
||||||
|
use crate::components::EditAction;
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct CreateEventModalProps {
|
pub struct CreateEventModalProps {
|
||||||
@@ -18,6 +19,8 @@ pub struct CreateEventModalProps {
|
|||||||
pub initial_start_time: Option<NaiveTime>,
|
pub initial_start_time: Option<NaiveTime>,
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub initial_end_time: Option<NaiveTime>,
|
pub initial_end_time: Option<NaiveTime>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub edit_scope: Option<EditAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[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_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 monthly_by_monthday: Option<u8>, // For monthly: day of month (1-31)
|
||||||
pub yearly_by_month: Vec<bool>, // For yearly: [Jan, Feb, Mar, ..., Dec]
|
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 {
|
impl Default for EventCreationData {
|
||||||
@@ -365,6 +372,10 @@ impl Default for EventCreationData {
|
|||||||
monthly_by_day: None,
|
monthly_by_day: None,
|
||||||
monthly_by_monthday: None,
|
monthly_by_monthday: None,
|
||||||
yearly_by_month: vec![false; 12], // [Jan, Feb, ..., Dec] - all false by default
|
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 {
|
} else {
|
||||||
vec![false; 12]
|
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());
|
let active_tab = use_state(|| ModalTab::default());
|
||||||
|
|
||||||
// Initialize with selected date or event data if provided
|
// 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();
|
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 {
|
if *is_open {
|
||||||
let mut data = if let Some(event) = event_to_edit {
|
let mut data = if let Some(event) = event_to_edit {
|
||||||
// Pre-populate with event data for editing
|
// 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());
|
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);
|
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 on_title_input = {
|
||||||
let event_data = event_data.clone();
|
let event_data = event_data.clone();
|
||||||
Callback::from(move |e: InputEvent| {
|
Callback::from(move |e: InputEvent| {
|
||||||
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
|
if let Some(input) = e.target_dyn_into::<HtmlInputElement>() {
|
||||||
let mut data = (*event_data).clone();
|
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);
|
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>() {
|
if let Some(select) = e.target_dyn_into::<HtmlSelectElement>() {
|
||||||
let mut data = (*event_data).clone();
|
let mut data = (*event_data).clone();
|
||||||
let value = select.value();
|
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);
|
event_data.set(data);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,13 +9,20 @@ pub enum DeleteAction {
|
|||||||
DeleteSeries,
|
DeleteSeries,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum EditAction {
|
||||||
|
EditThis,
|
||||||
|
EditFuture,
|
||||||
|
EditAll,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct EventContextMenuProps {
|
pub struct EventContextMenuProps {
|
||||||
pub is_open: bool,
|
pub is_open: bool,
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
pub event: Option<VEvent>,
|
pub event: Option<VEvent>,
|
||||||
pub on_edit: Callback<()>,
|
pub on_edit: Callback<EditAction>,
|
||||||
pub on_delete: Callback<DeleteAction>,
|
pub on_delete: Callback<DeleteAction>,
|
||||||
pub on_close: Callback<()>,
|
pub on_close: Callback<()>,
|
||||||
}
|
}
|
||||||
@@ -38,11 +45,11 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
|
|||||||
.map(|event| event.rrule.is_some())
|
.map(|event| event.rrule.is_some())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let on_edit_click = {
|
let create_edit_callback = |action: EditAction| {
|
||||||
let on_edit = props.on_edit.clone();
|
let on_edit = props.on_edit.clone();
|
||||||
let on_close = props.on_close.clone();
|
let on_close = props.on_close.clone();
|
||||||
Callback::from(move |_: MouseEvent| {
|
Callback::from(move |_: MouseEvent| {
|
||||||
on_edit.emit(());
|
on_edit.emit(action.clone());
|
||||||
on_close.emit(());
|
on_close.emit(());
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -62,9 +69,29 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
|
|||||||
class="context-menu"
|
class="context-menu"
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div class="context-menu-item" onclick={on_edit_click}>
|
{
|
||||||
{"Edit Event"}
|
if is_recurring {
|
||||||
</div>
|
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 {
|
if is_recurring {
|
||||||
html! {
|
html! {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub use week_view::WeekView;
|
|||||||
pub use event_modal::EventModal;
|
pub use event_modal::EventModal;
|
||||||
pub use create_calendar_modal::CreateCalendarModal;
|
pub use create_calendar_modal::CreateCalendarModal;
|
||||||
pub use context_menu::ContextMenu;
|
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 calendar_context_menu::CalendarContextMenu;
|
||||||
pub use create_event_modal::{CreateEventModal, EventCreationData, EventStatus, EventClass, ReminderType, RecurrenceType};
|
pub use create_event_modal::{CreateEventModal, EventCreationData, EventStatus, EventClass, ReminderType, RecurrenceType};
|
||||||
pub use sidebar::{Sidebar, ViewMode, Theme};
|
pub use sidebar::{Sidebar, ViewMode, Theme};
|
||||||
|
|||||||
Reference in New Issue
Block a user