Replace page reloads with dynamic calendar refresh functionality
All checks were successful
Build and Push Docker Image / docker (push) Successful in 29s

- Add refresh_calendar_data function to replace window.location.reload()
- Implement dynamic event re-fetching without full page refresh
- Add last_updated timestamp to UserInfo to force component re-renders
- Fix WASM compatibility by using js_sys::Date::now() instead of SystemTime
- Remove debug logging from refresh operations
- Maintain same user experience with improved performance

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-09-04 16:53:58 -04:00
parent 72273a3f1c
commit bbad327ea2
3 changed files with 125 additions and 19 deletions

View File

@@ -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"] }

View File

@@ -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::<String>("caldav_credentials")
{
if let Ok(credentials) =
serde_json::from_str::<serde_json::Value>(&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::<String>("calendar_colors")
{
if let Ok(saved_info) =
serde_json::from_str::<UserInfo>(&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());

View File

@@ -37,6 +37,12 @@ pub struct UserInfo {
pub username: String,
pub server_url: String,
pub calendars: Vec<CalendarInfo>,
#[serde(default = "default_timestamp")]
pub last_updated: u64,
}
fn default_timestamp() -> u64 {
0
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]