From dce82d5f7d2d3418213c422009d5819797bd66a0 Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Wed, 3 Sep 2025 16:13:18 -0400 Subject: [PATCH] Implement last used calendar tracking with localStorage and database sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add database migration for last_used_calendar field in user preferences - Update backend models and handlers to support last_used_calendar persistence - Modify frontend preferences service with update_last_used_calendar() method - Implement automatic saving of selected calendar on event creation - Add localStorage fallback for offline usage and immediate UI response - Update create event modal to default to last used calendar for new events - Clean up unused imports from event form components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../migrations/005_add_last_used_calendar.sql | 2 ++ backend/src/auth.rs | 1 + backend/src/db.rs | 10 ++++--- backend/src/handlers/preferences.rs | 5 ++++ backend/src/models.rs | 2 ++ frontend/src/app.rs | 15 +++++++++++ frontend/src/components/create_event_modal.rs | 21 ++++++++++++++- .../src/components/event_form/advanced.rs | 2 +- frontend/src/components/event_form/types.rs | 4 +-- frontend/src/components/mod.rs | 4 --- frontend/src/services/preferences.rs | 26 +++++++++++++++++++ 11 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 backend/migrations/005_add_last_used_calendar.sql diff --git a/backend/migrations/005_add_last_used_calendar.sql b/backend/migrations/005_add_last_used_calendar.sql new file mode 100644 index 0000000..633ea61 --- /dev/null +++ b/backend/migrations/005_add_last_used_calendar.sql @@ -0,0 +1,2 @@ +-- Add last used calendar preference to user preferences +ALTER TABLE user_preferences ADD COLUMN last_used_calendar TEXT; \ No newline at end of file diff --git a/backend/src/auth.rs b/backend/src/auth.rs index cb47cba..ce2c6af 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -93,6 +93,7 @@ impl AuthService { calendar_theme: preferences.calendar_theme, calendar_style: preferences.calendar_style, calendar_colors: preferences.calendar_colors, + last_used_calendar: preferences.last_used_calendar, }, }) } diff --git a/backend/src/db.rs b/backend/src/db.rs index 0f6fdc7..86bc8b5 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -95,6 +95,7 @@ pub struct UserPreferences { pub calendar_theme: Option, pub calendar_style: Option, pub calendar_colors: Option, // JSON string + pub last_used_calendar: Option, pub updated_at: DateTime, } @@ -109,6 +110,7 @@ impl UserPreferences { calendar_theme: Some("light".to_string()), calendar_style: Some("default".to_string()), calendar_colors: None, + last_used_calendar: None, updated_at: Utc::now(), } } @@ -266,8 +268,8 @@ impl<'a> PreferencesRepository<'a> { sqlx::query( "INSERT INTO user_preferences (user_id, calendar_selected_date, calendar_time_increment, - calendar_view_mode, calendar_theme, calendar_style, calendar_colors, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + calendar_view_mode, calendar_theme, calendar_style, calendar_colors, last_used_calendar, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", ) .bind(&prefs.user_id) .bind(&prefs.calendar_selected_date) @@ -276,6 +278,7 @@ impl<'a> PreferencesRepository<'a> { .bind(&prefs.calendar_theme) .bind(&prefs.calendar_style) .bind(&prefs.calendar_colors) + .bind(&prefs.last_used_calendar) .bind(&prefs.updated_at) .execute(self.db.pool()) .await?; @@ -290,7 +293,7 @@ impl<'a> PreferencesRepository<'a> { "UPDATE user_preferences SET calendar_selected_date = ?, calendar_time_increment = ?, calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?, - calendar_colors = ?, updated_at = ? + calendar_colors = ?, last_used_calendar = ?, updated_at = ? WHERE user_id = ?", ) .bind(&prefs.calendar_selected_date) @@ -299,6 +302,7 @@ impl<'a> PreferencesRepository<'a> { .bind(&prefs.calendar_theme) .bind(&prefs.calendar_style) .bind(&prefs.calendar_colors) + .bind(&prefs.last_used_calendar) .bind(Utc::now()) .bind(&prefs.user_id) .execute(self.db.pool()) diff --git a/backend/src/handlers/preferences.rs b/backend/src/handlers/preferences.rs index aa405e2..04b99ce 100644 --- a/backend/src/handlers/preferences.rs +++ b/backend/src/handlers/preferences.rs @@ -40,6 +40,7 @@ pub async fn get_preferences( calendar_theme: preferences.calendar_theme, calendar_style: preferences.calendar_style, calendar_colors: preferences.calendar_colors, + last_used_calendar: preferences.last_used_calendar, })) } @@ -85,6 +86,9 @@ pub async fn update_preferences( if request.calendar_colors.is_some() { preferences.calendar_colors = request.calendar_colors; } + if request.last_used_calendar.is_some() { + preferences.last_used_calendar = request.last_used_calendar; + } prefs_repo .update(&preferences) @@ -100,6 +104,7 @@ pub async fn update_preferences( calendar_theme: preferences.calendar_theme, calendar_style: preferences.calendar_style, calendar_colors: preferences.calendar_colors, + last_used_calendar: preferences.last_used_calendar, }), )) } diff --git a/backend/src/models.rs b/backend/src/models.rs index d53b020..a001b4a 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -30,6 +30,7 @@ pub struct UserPreferencesResponse { pub calendar_theme: Option, pub calendar_style: Option, pub calendar_colors: Option, + pub last_used_calendar: Option, } #[derive(Debug, Deserialize)] @@ -40,6 +41,7 @@ pub struct UpdatePreferencesRequest { pub calendar_theme: Option, pub calendar_style: Option, pub calendar_colors: Option, + pub last_used_calendar: Option, } #[derive(Debug, Serialize)] diff --git a/frontend/src/app.rs b/frontend/src/app.rs index be11bba..24c113c 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -413,6 +413,21 @@ pub fn App() -> Html { let auth_token = auth_token.clone(); Callback::from(move |event_data: EventCreationData| { web_sys::console::log_1(&format!("Creating event: {:?}", event_data).into()); + + // Save the selected calendar as the last used calendar + if let Some(ref calendar_path) = event_data.selected_calendar { + let _ = LocalStorage::set("last_used_calendar", calendar_path); + + // Also sync to backend asynchronously + let calendar_path_for_sync = calendar_path.clone(); + wasm_bindgen_futures::spawn_local(async move { + let preferences_service = crate::services::preferences::PreferencesService::new(); + if let Err(e) = preferences_service.update_last_used_calendar(&calendar_path_for_sync).await { + web_sys::console::warn_1(&format!("Failed to sync last used calendar to backend: {}", e).into()); + } + }); + } + create_event_modal_open.set(false); if let Some(_token) = (*auth_token).clone() { diff --git a/frontend/src/components/create_event_modal.rs b/frontend/src/components/create_event_modal.rs index feae550..b7f4a90 100644 --- a/frontend/src/components/create_event_modal.rs +++ b/frontend/src/components/create_event_modal.rs @@ -2,6 +2,7 @@ use crate::components::event_form::*; use crate::components::EditAction; use crate::models::ical::VEvent; use crate::services::calendar_service::CalendarInfo; +use gloo_storage::{LocalStorage, Storage}; use yew::prelude::*; #[derive(Properties, PartialEq)] @@ -57,7 +58,25 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html { // Set default calendar if data.selected_calendar.is_none() && !available_calendars.is_empty() { - data.selected_calendar = Some(available_calendars[0].path.clone()); + // For new events, try to use the last used calendar + if event_to_edit.is_none() { + // Try to get last used calendar from localStorage + if let Ok(last_used_calendar) = LocalStorage::get::("last_used_calendar") { + // Check if the last used calendar is still available + if available_calendars.iter().any(|cal| cal.path == last_used_calendar) { + data.selected_calendar = Some(last_used_calendar); + } else { + // Fall back to first available calendar + data.selected_calendar = Some(available_calendars[0].path.clone()); + } + } else { + // No last used calendar, use first available + data.selected_calendar = Some(available_calendars[0].path.clone()); + } + } else { + // For editing existing events, keep the current calendar as default + data.selected_calendar = Some(available_calendars[0].path.clone()); + } } // Set edit scope if provided diff --git a/frontend/src/components/event_form/advanced.rs b/frontend/src/components/event_form/advanced.rs index 55d69e6..bb857c5 100644 --- a/frontend/src/components/event_form/advanced.rs +++ b/frontend/src/components/event_form/advanced.rs @@ -1,7 +1,7 @@ use super::types::*; // Types are already imported from super::types::* use wasm_bindgen::JsCast; -use web_sys::{HtmlInputElement, HtmlSelectElement}; +use web_sys::HtmlSelectElement; use yew::prelude::*; #[function_component(AdvancedTab)] diff --git a/frontend/src/components/event_form/types.rs b/frontend/src/components/event_form/types.rs index a8c1fe5..06def68 100644 --- a/frontend/src/components/event_form/types.rs +++ b/frontend/src/components/event_form/types.rs @@ -1,7 +1,5 @@ -use crate::models::ical::VEvent; use crate::services::calendar_service::CalendarInfo; -use chrono::{Datelike, Local, NaiveDate, NaiveTime, TimeZone, Utc}; -use serde::{Deserialize, Serialize}; +use chrono::{Local, NaiveDate, NaiveTime}; use yew::prelude::*; #[derive(Clone, PartialEq, Debug)] diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs index 328effb..889444a 100644 --- a/frontend/src/components/mod.rs +++ b/frontend/src/components/mod.rs @@ -24,10 +24,6 @@ pub use create_calendar_modal::CreateCalendarModal; pub use create_event_modal::CreateEventModal; // Re-export event form types for backwards compatibility pub use event_form::EventCreationData; -pub use event_form::{ - EventClass as EventFormClass, EventCreationData as EventFormData, EventStatus as EventFormStatus, - RecurrenceType as EventFormRecurrenceType, ReminderType as EventFormReminderType, -}; pub use event_context_menu::{DeleteAction, EditAction, EventContextMenu}; pub use event_modal::EventModal; pub use login::Login; diff --git a/frontend/src/services/preferences.rs b/frontend/src/services/preferences.rs index 7dc26a1..b153699 100644 --- a/frontend/src/services/preferences.rs +++ b/frontend/src/services/preferences.rs @@ -12,6 +12,7 @@ pub struct UserPreferences { pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_colors: Option, + pub last_used_calendar: Option, } #[derive(Debug, Serialize)] @@ -22,6 +23,7 @@ pub struct UpdatePreferencesRequest { pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_colors: Option, + pub last_used_calendar: Option, } #[allow(dead_code)] @@ -61,6 +63,7 @@ impl PreferencesService { calendar_view_mode: None, calendar_theme: None, calendar_colors: None, + last_used_calendar: None, }); // Update the specific field @@ -95,6 +98,7 @@ impl PreferencesService { calendar_view_mode: preferences.calendar_view_mode.clone(), calendar_theme: preferences.calendar_theme.clone(), calendar_colors: preferences.calendar_colors.clone(), + last_used_calendar: preferences.last_used_calendar.clone(), }; self.sync_preferences(&session_token, &request).await @@ -156,6 +160,7 @@ impl PreferencesService { calendar_view_mode: LocalStorage::get::("calendar_view_mode").ok(), calendar_theme: LocalStorage::get::("calendar_theme").ok(), calendar_colors: LocalStorage::get::("calendar_colors").ok(), + last_used_calendar: LocalStorage::get::("last_used_calendar").ok(), }; // Only migrate if we have some preferences to migrate @@ -164,6 +169,7 @@ impl PreferencesService { || request.calendar_view_mode.is_some() || request.calendar_theme.is_some() || request.calendar_colors.is_some() + || request.last_used_calendar.is_some() { self.sync_preferences(&session_token, &request).await?; @@ -177,4 +183,24 @@ impl PreferencesService { Ok(()) } + + /// Update the last used calendar and sync with backend + pub async fn update_last_used_calendar(&self, calendar_path: &str) -> Result<(), String> { + // Get session token + let session_token = LocalStorage::get::("session_token") + .map_err(|_| "No session token found".to_string())?; + + // Create minimal update request with only the last used calendar + let request = UpdatePreferencesRequest { + calendar_selected_date: None, + calendar_time_increment: None, + calendar_view_mode: None, + calendar_theme: None, + calendar_colors: None, + last_used_calendar: Some(calendar_path.to_string()), + }; + + // Sync to backend + self.sync_preferences(&session_token, &request).await + } } \ No newline at end of file