Implement last used calendar tracking with localStorage and database sync
- 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 <noreply@anthropic.com>
This commit is contained in:
2
backend/migrations/005_add_last_used_calendar.sql
Normal file
2
backend/migrations/005_add_last_used_calendar.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- Add last used calendar preference to user preferences
|
||||||
|
ALTER TABLE user_preferences ADD COLUMN last_used_calendar TEXT;
|
||||||
@@ -93,6 +93,7 @@ impl AuthService {
|
|||||||
calendar_theme: preferences.calendar_theme,
|
calendar_theme: preferences.calendar_theme,
|
||||||
calendar_style: preferences.calendar_style,
|
calendar_style: preferences.calendar_style,
|
||||||
calendar_colors: preferences.calendar_colors,
|
calendar_colors: preferences.calendar_colors,
|
||||||
|
last_used_calendar: preferences.last_used_calendar,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ pub struct UserPreferences {
|
|||||||
pub calendar_theme: Option<String>,
|
pub calendar_theme: Option<String>,
|
||||||
pub calendar_style: Option<String>,
|
pub calendar_style: Option<String>,
|
||||||
pub calendar_colors: Option<String>, // JSON string
|
pub calendar_colors: Option<String>, // JSON string
|
||||||
|
pub last_used_calendar: Option<String>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ impl UserPreferences {
|
|||||||
calendar_theme: Some("light".to_string()),
|
calendar_theme: Some("light".to_string()),
|
||||||
calendar_style: Some("default".to_string()),
|
calendar_style: Some("default".to_string()),
|
||||||
calendar_colors: None,
|
calendar_colors: None,
|
||||||
|
last_used_calendar: None,
|
||||||
updated_at: Utc::now(),
|
updated_at: Utc::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,8 +268,8 @@ impl<'a> PreferencesRepository<'a> {
|
|||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO user_preferences
|
"INSERT INTO user_preferences
|
||||||
(user_id, calendar_selected_date, calendar_time_increment,
|
(user_id, calendar_selected_date, calendar_time_increment,
|
||||||
calendar_view_mode, calendar_theme, calendar_style, calendar_colors, updated_at)
|
calendar_view_mode, calendar_theme, calendar_style, calendar_colors, last_used_calendar, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
)
|
)
|
||||||
.bind(&prefs.user_id)
|
.bind(&prefs.user_id)
|
||||||
.bind(&prefs.calendar_selected_date)
|
.bind(&prefs.calendar_selected_date)
|
||||||
@@ -276,6 +278,7 @@ impl<'a> PreferencesRepository<'a> {
|
|||||||
.bind(&prefs.calendar_theme)
|
.bind(&prefs.calendar_theme)
|
||||||
.bind(&prefs.calendar_style)
|
.bind(&prefs.calendar_style)
|
||||||
.bind(&prefs.calendar_colors)
|
.bind(&prefs.calendar_colors)
|
||||||
|
.bind(&prefs.last_used_calendar)
|
||||||
.bind(&prefs.updated_at)
|
.bind(&prefs.updated_at)
|
||||||
.execute(self.db.pool())
|
.execute(self.db.pool())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -290,7 +293,7 @@ impl<'a> PreferencesRepository<'a> {
|
|||||||
"UPDATE user_preferences
|
"UPDATE user_preferences
|
||||||
SET calendar_selected_date = ?, calendar_time_increment = ?,
|
SET calendar_selected_date = ?, calendar_time_increment = ?,
|
||||||
calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?,
|
calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?,
|
||||||
calendar_colors = ?, updated_at = ?
|
calendar_colors = ?, last_used_calendar = ?, updated_at = ?
|
||||||
WHERE user_id = ?",
|
WHERE user_id = ?",
|
||||||
)
|
)
|
||||||
.bind(&prefs.calendar_selected_date)
|
.bind(&prefs.calendar_selected_date)
|
||||||
@@ -299,6 +302,7 @@ impl<'a> PreferencesRepository<'a> {
|
|||||||
.bind(&prefs.calendar_theme)
|
.bind(&prefs.calendar_theme)
|
||||||
.bind(&prefs.calendar_style)
|
.bind(&prefs.calendar_style)
|
||||||
.bind(&prefs.calendar_colors)
|
.bind(&prefs.calendar_colors)
|
||||||
|
.bind(&prefs.last_used_calendar)
|
||||||
.bind(Utc::now())
|
.bind(Utc::now())
|
||||||
.bind(&prefs.user_id)
|
.bind(&prefs.user_id)
|
||||||
.execute(self.db.pool())
|
.execute(self.db.pool())
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub async fn get_preferences(
|
|||||||
calendar_theme: preferences.calendar_theme,
|
calendar_theme: preferences.calendar_theme,
|
||||||
calendar_style: preferences.calendar_style,
|
calendar_style: preferences.calendar_style,
|
||||||
calendar_colors: preferences.calendar_colors,
|
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() {
|
if request.calendar_colors.is_some() {
|
||||||
preferences.calendar_colors = request.calendar_colors;
|
preferences.calendar_colors = request.calendar_colors;
|
||||||
}
|
}
|
||||||
|
if request.last_used_calendar.is_some() {
|
||||||
|
preferences.last_used_calendar = request.last_used_calendar;
|
||||||
|
}
|
||||||
|
|
||||||
prefs_repo
|
prefs_repo
|
||||||
.update(&preferences)
|
.update(&preferences)
|
||||||
@@ -100,6 +104,7 @@ pub async fn update_preferences(
|
|||||||
calendar_theme: preferences.calendar_theme,
|
calendar_theme: preferences.calendar_theme,
|
||||||
calendar_style: preferences.calendar_style,
|
calendar_style: preferences.calendar_style,
|
||||||
calendar_colors: preferences.calendar_colors,
|
calendar_colors: preferences.calendar_colors,
|
||||||
|
last_used_calendar: preferences.last_used_calendar,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub struct UserPreferencesResponse {
|
|||||||
pub calendar_theme: Option<String>,
|
pub calendar_theme: Option<String>,
|
||||||
pub calendar_style: Option<String>,
|
pub calendar_style: Option<String>,
|
||||||
pub calendar_colors: Option<String>,
|
pub calendar_colors: Option<String>,
|
||||||
|
pub last_used_calendar: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -40,6 +41,7 @@ pub struct UpdatePreferencesRequest {
|
|||||||
pub calendar_theme: Option<String>,
|
pub calendar_theme: Option<String>,
|
||||||
pub calendar_style: Option<String>,
|
pub calendar_style: Option<String>,
|
||||||
pub calendar_colors: Option<String>,
|
pub calendar_colors: Option<String>,
|
||||||
|
pub last_used_calendar: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|||||||
@@ -413,6 +413,21 @@ pub fn App() -> Html {
|
|||||||
let auth_token = auth_token.clone();
|
let auth_token = auth_token.clone();
|
||||||
Callback::from(move |event_data: EventCreationData| {
|
Callback::from(move |event_data: EventCreationData| {
|
||||||
web_sys::console::log_1(&format!("Creating event: {:?}", event_data).into());
|
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);
|
create_event_modal_open.set(false);
|
||||||
|
|
||||||
if let Some(_token) = (*auth_token).clone() {
|
if let Some(_token) = (*auth_token).clone() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::components::event_form::*;
|
|||||||
use crate::components::EditAction;
|
use crate::components::EditAction;
|
||||||
use crate::models::ical::VEvent;
|
use crate::models::ical::VEvent;
|
||||||
use crate::services::calendar_service::CalendarInfo;
|
use crate::services::calendar_service::CalendarInfo;
|
||||||
|
use gloo_storage::{LocalStorage, Storage};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
@@ -57,8 +58,26 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html {
|
|||||||
|
|
||||||
// Set default calendar
|
// Set default calendar
|
||||||
if data.selected_calendar.is_none() && !available_calendars.is_empty() {
|
if data.selected_calendar.is_none() && !available_calendars.is_empty() {
|
||||||
|
// 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::<String>("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());
|
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
|
// Set edit scope if provided
|
||||||
if let Some(scope) = &edit_scope {
|
if let Some(scope) = &edit_scope {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::types::*;
|
use super::types::*;
|
||||||
// Types are already imported from super::types::*
|
// Types are already imported from super::types::*
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{HtmlInputElement, HtmlSelectElement};
|
use web_sys::HtmlSelectElement;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[function_component(AdvancedTab)]
|
#[function_component(AdvancedTab)]
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use crate::models::ical::VEvent;
|
|
||||||
use crate::services::calendar_service::CalendarInfo;
|
use crate::services::calendar_service::CalendarInfo;
|
||||||
use chrono::{Datelike, Local, NaiveDate, NaiveTime, TimeZone, Utc};
|
use chrono::{Local, NaiveDate, NaiveTime};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ pub use create_calendar_modal::CreateCalendarModal;
|
|||||||
pub use create_event_modal::CreateEventModal;
|
pub use create_event_modal::CreateEventModal;
|
||||||
// Re-export event form types for backwards compatibility
|
// Re-export event form types for backwards compatibility
|
||||||
pub use event_form::EventCreationData;
|
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_context_menu::{DeleteAction, EditAction, EventContextMenu};
|
||||||
pub use event_modal::EventModal;
|
pub use event_modal::EventModal;
|
||||||
pub use login::Login;
|
pub use login::Login;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct UserPreferences {
|
|||||||
pub calendar_view_mode: Option<String>,
|
pub calendar_view_mode: Option<String>,
|
||||||
pub calendar_theme: Option<String>,
|
pub calendar_theme: Option<String>,
|
||||||
pub calendar_colors: Option<String>,
|
pub calendar_colors: Option<String>,
|
||||||
|
pub last_used_calendar: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@@ -22,6 +23,7 @@ pub struct UpdatePreferencesRequest {
|
|||||||
pub calendar_view_mode: Option<String>,
|
pub calendar_view_mode: Option<String>,
|
||||||
pub calendar_theme: Option<String>,
|
pub calendar_theme: Option<String>,
|
||||||
pub calendar_colors: Option<String>,
|
pub calendar_colors: Option<String>,
|
||||||
|
pub last_used_calendar: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -61,6 +63,7 @@ impl PreferencesService {
|
|||||||
calendar_view_mode: None,
|
calendar_view_mode: None,
|
||||||
calendar_theme: None,
|
calendar_theme: None,
|
||||||
calendar_colors: None,
|
calendar_colors: None,
|
||||||
|
last_used_calendar: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the specific field
|
// Update the specific field
|
||||||
@@ -95,6 +98,7 @@ impl PreferencesService {
|
|||||||
calendar_view_mode: preferences.calendar_view_mode.clone(),
|
calendar_view_mode: preferences.calendar_view_mode.clone(),
|
||||||
calendar_theme: preferences.calendar_theme.clone(),
|
calendar_theme: preferences.calendar_theme.clone(),
|
||||||
calendar_colors: preferences.calendar_colors.clone(),
|
calendar_colors: preferences.calendar_colors.clone(),
|
||||||
|
last_used_calendar: preferences.last_used_calendar.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sync_preferences(&session_token, &request).await
|
self.sync_preferences(&session_token, &request).await
|
||||||
@@ -156,6 +160,7 @@ impl PreferencesService {
|
|||||||
calendar_view_mode: LocalStorage::get::<String>("calendar_view_mode").ok(),
|
calendar_view_mode: LocalStorage::get::<String>("calendar_view_mode").ok(),
|
||||||
calendar_theme: LocalStorage::get::<String>("calendar_theme").ok(),
|
calendar_theme: LocalStorage::get::<String>("calendar_theme").ok(),
|
||||||
calendar_colors: LocalStorage::get::<String>("calendar_colors").ok(),
|
calendar_colors: LocalStorage::get::<String>("calendar_colors").ok(),
|
||||||
|
last_used_calendar: LocalStorage::get::<String>("last_used_calendar").ok(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only migrate if we have some preferences to migrate
|
// Only migrate if we have some preferences to migrate
|
||||||
@@ -164,6 +169,7 @@ impl PreferencesService {
|
|||||||
|| request.calendar_view_mode.is_some()
|
|| request.calendar_view_mode.is_some()
|
||||||
|| request.calendar_theme.is_some()
|
|| request.calendar_theme.is_some()
|
||||||
|| request.calendar_colors.is_some()
|
|| request.calendar_colors.is_some()
|
||||||
|
|| request.last_used_calendar.is_some()
|
||||||
{
|
{
|
||||||
self.sync_preferences(&session_token, &request).await?;
|
self.sync_preferences(&session_token, &request).await?;
|
||||||
|
|
||||||
@@ -177,4 +183,24 @@ impl PreferencesService {
|
|||||||
|
|
||||||
Ok(())
|
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::<String>("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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user