diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index e6f55b5..0540a9d 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -30,6 +30,7 @@ web-sys = { version = "0.3", features = [ "CssStyleDeclaration", ] } wasm-bindgen = "0.2" +js-sys = "0.3" # HTTP client for CalDAV requests reqwest = { version = "0.11", features = ["json"] } diff --git a/frontend/src/app.rs b/frontend/src/app.rs index 517df61..3a0f561 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -155,6 +155,108 @@ pub fn App() -> Html { let available_colors = use_state(|| get_theme_event_colors()); + // Function to refresh calendar data without full page reload + let refresh_calendar_data = { + let user_info = user_info.clone(); + let auth_token = auth_token.clone(); + let external_calendars = external_calendars.clone(); + let external_calendar_events = external_calendar_events.clone(); + + Callback::from(move |_| { + let user_info = user_info.clone(); + let auth_token = auth_token.clone(); + let external_calendars = external_calendars.clone(); + let external_calendar_events = external_calendar_events.clone(); + + wasm_bindgen_futures::spawn_local(async move { + // Refresh main calendar data if authenticated + if let Some(token) = (*auth_token).clone() { + let calendar_service = CalendarService::new(); + + let password = if let Ok(credentials_str) = + LocalStorage::get::("caldav_credentials") + { + if let Ok(credentials) = + serde_json::from_str::(&credentials_str) + { + credentials["password"].as_str().unwrap_or("").to_string() + } else { + String::new() + } + } else { + String::new() + }; + + if !password.is_empty() { + match calendar_service.fetch_user_info(&token, &password).await { + Ok(mut info) => { + // Apply saved colors + if let Ok(saved_colors_json) = + LocalStorage::get::("calendar_colors") + { + if let Ok(saved_info) = + serde_json::from_str::(&saved_colors_json) + { + for saved_cal in &saved_info.calendars { + for cal in &mut info.calendars { + if cal.path == saved_cal.path { + cal.color = saved_cal.color.clone(); + } + } + } + } + } + // Add timestamp to force re-render + info.last_updated = (js_sys::Date::now() / 1000.0) as u64; + user_info.set(Some(info)); + } + Err(err) => { + web_sys::console::log_1( + &format!("Failed to refresh main calendar data: {}", err).into(), + ); + } + } + } + } + + // Refresh external calendars data + match CalendarService::get_external_calendars().await { + Ok(calendars) => { + external_calendars.set(calendars.clone()); + + // Load events for visible external calendars + let mut all_external_events = Vec::new(); + for calendar in calendars { + if calendar.is_visible { + match CalendarService::fetch_external_calendar_events(calendar.id).await { + Ok(mut events) => { + // Set calendar_path for color matching + for event in &mut events { + event.calendar_path = Some(format!("external_{}", calendar.id)); + } + all_external_events.extend(events); + } + Err(e) => { + web_sys::console::log_1( + &format!("Failed to fetch events for external calendar {}: {}", calendar.id, e).into(), + ); + } + } + } + } + external_calendar_events.set(all_external_events); + } + Err(e) => { + web_sys::console::log_1( + &format!("Failed to refresh external calendars: {}", e).into(), + ); + } + } + + }); + }) + }; + let on_login = { let auth_token = auth_token.clone(); Callback::from(move |token: String| { @@ -531,6 +633,7 @@ pub fn App() -> Html { let on_event_create = { let create_event_modal_open = create_event_modal_open.clone(); let auth_token = auth_token.clone(); + let refresh_calendar_data = refresh_calendar_data.clone(); Callback::from(move |event_data: EventCreationData| { // Check if this is an update operation (has original_uid) or a create operation if let Some(original_uid) = event_data.original_uid.clone() { @@ -541,6 +644,7 @@ pub fn App() -> Html { // Handle the update operation using the existing backend update logic if let Some(token) = (*auth_token).clone() { let event_data_for_update = event_data.clone(); + let refresh_callback = refresh_calendar_data.clone(); wasm_bindgen_futures::spawn_local(async move { let calendar_service = CalendarService::new(); @@ -641,10 +745,8 @@ pub fn App() -> Html { match update_result { Ok(_) => { web_sys::console::log_1(&"Event updated successfully via modal".into()); - // Trigger a page reload to refresh events from all calendars - if let Some(window) = web_sys::window() { - let _ = window.location().reload(); - } + // Refresh calendar data without page reload + refresh_callback.emit(()); } Err(err) => { web_sys::console::error_1( @@ -680,6 +782,7 @@ pub fn App() -> Html { create_event_modal_open.set(false); if let Some(_token) = (*auth_token).clone() { + let refresh_callback = refresh_calendar_data.clone(); wasm_bindgen_futures::spawn_local(async move { let _calendar_service = CalendarService::new(); @@ -726,9 +829,8 @@ pub fn App() -> Html { match create_result { Ok(_) => { web_sys::console::log_1(&"Event created successfully".into()); - // Trigger a page reload to refresh events from all calendars - // TODO: This could be improved to do a more targeted refresh - web_sys::window().unwrap().location().reload().unwrap(); + // Refresh calendar data without page reload + refresh_callback.emit(()); } Err(err) => { web_sys::console::error_1( @@ -747,6 +849,7 @@ pub fn App() -> Html { let on_event_update = { let auth_token = auth_token.clone(); + let refresh_calendar_data = refresh_calendar_data.clone(); Callback::from( move |( original_event, @@ -781,6 +884,7 @@ pub fn App() -> Html { if let Some(token) = (*auth_token).clone() { let original_event = original_event.clone(); let backend_uid = backend_uid.clone(); + let refresh_callback = refresh_calendar_data.clone(); wasm_bindgen_futures::spawn_local(async move { let calendar_service = CalendarService::new(); @@ -965,14 +1069,8 @@ pub fn App() -> Html { match result { Ok(_) => { web_sys::console::log_1(&"Event updated successfully".into()); - // Add small delay before reload to let any pending requests complete - wasm_bindgen_futures::spawn_local(async { - gloo_timers::future::sleep(std::time::Duration::from_millis( - 100, - )) - .await; - web_sys::window().unwrap().location().reload().unwrap(); - }); + // Refresh calendar data without page reload + refresh_callback.emit(()); } Err(err) => { web_sys::console::error_1( @@ -1392,10 +1490,10 @@ pub fn App() -> Html { let auth_token = auth_token.clone(); let event_context_menu_event = event_context_menu_event.clone(); let event_context_menu_open = event_context_menu_open.clone(); - let refresh_calendars = refresh_calendars.clone(); + let refresh_calendar_data = refresh_calendar_data.clone(); move |delete_action: DeleteAction| { if let (Some(token), Some(event)) = ((*auth_token).clone(), (*event_context_menu_event).clone()) { - let _refresh_calendars = refresh_calendars.clone(); + let refresh_calendar_data = refresh_calendar_data.clone(); let event_context_menu_open = event_context_menu_open.clone(); // Log the delete action for now - we'll implement different behaviors later @@ -1405,6 +1503,7 @@ pub fn App() -> Html { DeleteAction::DeleteSeries => web_sys::console::log_1(&"Delete entire series".into()), } + let refresh_callback = refresh_calendar_data.clone(); wasm_bindgen_futures::spawn_local(async move { let calendar_service = CalendarService::new(); @@ -1452,8 +1551,8 @@ pub fn App() -> Html { // Close the context menu event_context_menu_open.set(false); - // Force a page reload to refresh the calendar events - web_sys::window().unwrap().location().reload().unwrap(); + // Refresh calendar data without page reload + refresh_callback.emit(()); } Err(err) => { web_sys::console::log_1(&format!("Failed to delete event: {}", err).into()); diff --git a/frontend/src/services/calendar_service.rs b/frontend/src/services/calendar_service.rs index 79462f4..a67e2a7 100644 --- a/frontend/src/services/calendar_service.rs +++ b/frontend/src/services/calendar_service.rs @@ -37,6 +37,12 @@ pub struct UserInfo { pub username: String, pub server_url: String, pub calendars: Vec, + #[serde(default = "default_timestamp")] + pub last_updated: u64, +} + +fn default_timestamp() -> u64 { + 0 } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]