diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs index 5c6107e..89ab1af 100644 --- a/backend/src/handlers/auth.rs +++ b/backend/src/handlers/auth.rs @@ -93,6 +93,7 @@ pub async fn get_user_info( path: path.clone(), display_name: extract_calendar_name(path), color: generate_calendar_color(path), + is_visible: true, // Default to visible }) .collect(); diff --git a/backend/src/models.rs b/backend/src/models.rs index a001b4a..2b77fde 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -56,6 +56,7 @@ pub struct CalendarInfo { pub path: String, pub display_name: String, pub color: String, + pub is_visible: bool, } #[derive(Debug, Deserialize)] diff --git a/frontend/src/app.rs b/frontend/src/app.rs index b667966..923da7b 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -1067,6 +1067,21 @@ pub fn App() -> Html { on_color_picker_toggle={on_color_picker_toggle} available_colors={(*available_colors).clone()} on_calendar_context_menu={on_calendar_context_menu} + on_calendar_visibility_toggle={Callback::from({ + let user_info = user_info.clone(); + move |calendar_path: String| { + let user_info = user_info.clone(); + wasm_bindgen_futures::spawn_local(async move { + if let Some(mut info) = (*user_info).clone() { + // Toggle the visibility + if let Some(calendar) = info.calendars.iter_mut().find(|c| c.path == calendar_path) { + calendar.is_visible = !calendar.is_visible; + user_info.set(Some(info)); + } + } + }); + } + })} current_view={(*current_view).clone()} on_view_change={on_view_change} current_theme={(*current_theme).clone()} diff --git a/frontend/src/components/calendar.rs b/frontend/src/components/calendar.rs index c2d8856..8608e00 100644 --- a/frontend/src/components/calendar.rs +++ b/frontend/src/components/calendar.rs @@ -108,10 +108,11 @@ pub fn Calendar(props: &CalendarProps) -> Html { let external_events = props.external_calendar_events.clone(); // Clone before the effect let view = props.view.clone(); // Clone before the effect - use_effect_with((*current_date, view.clone(), external_events.len()), move |(date, _view, _external_len)| { + use_effect_with((*current_date, view.clone(), external_events.len(), props.user_info.clone()), move |(date, _view, _external_len, user_info)| { let auth_token: Option = LocalStorage::get("auth_token").ok(); let date = *date; // Clone the date to avoid lifetime issues let external_events = external_events.clone(); // Clone external events to avoid lifetime issues + let user_info = user_info.clone(); // Clone user_info to avoid lifetime issues if let Some(token) = auth_token { let events = events.clone(); @@ -148,8 +149,24 @@ pub fn Calendar(props: &CalendarProps) -> Html { .await { Ok(vevents) => { - // Combine regular events with external calendar events - let mut all_events = vevents; + // Filter CalDAV events based on calendar visibility + let mut filtered_events = if let Some(user_info) = user_info.as_ref() { + vevents.into_iter() + .filter(|event| { + if let Some(calendar_path) = event.calendar_path.as_ref() { + // Find the calendar info for this event + user_info.calendars.iter() + .find(|cal| &cal.path == calendar_path) + .map(|cal| cal.is_visible) + .unwrap_or(true) // Default to visible if not found + } else { + true // Show events without calendar path + } + }) + .collect() + } else { + vevents // Show all events if no user info + }; // Mark external events as external by adding a special category let marked_external_events: Vec = external_events @@ -161,9 +178,9 @@ pub fn Calendar(props: &CalendarProps) -> Html { }) .collect(); - all_events.extend(marked_external_events); + filtered_events.extend(marked_external_events); - let grouped_events = CalendarService::group_events_by_date(all_events); + let grouped_events = CalendarService::group_events_by_date(filtered_events); events.set(grouped_events); loading.set(false); } diff --git a/frontend/src/components/calendar_list_item.rs b/frontend/src/components/calendar_list_item.rs index 6119bcb..555bd31 100644 --- a/frontend/src/components/calendar_list_item.rs +++ b/frontend/src/components/calendar_list_item.rs @@ -10,6 +10,7 @@ pub struct CalendarListItemProps { pub on_color_picker_toggle: Callback, // calendar_path pub available_colors: Vec, pub on_context_menu: Callback<(MouseEvent, String)>, // (event, calendar_path) + pub on_visibility_toggle: Callback, // calendar_path } #[function_component(CalendarListItem)] @@ -32,44 +33,59 @@ pub fn calendar_list_item(props: &CalendarListItemProps) -> Html { }) }; + let on_visibility_toggle = { + let cal_path = props.calendar.path.clone(); + let on_visibility_toggle = props.on_visibility_toggle.clone(); + Callback::from(move |_| { + on_visibility_toggle.emit(cal_path.clone()); + }) + }; + html! {
  • - - { - if props.color_picker_open { - html! { -
    - { - props.available_colors.iter().map(|color| { - let color_str = color.clone(); - let cal_path = props.calendar.path.clone(); - let on_color_change = props.on_color_change.clone(); +
    + + + { + if props.color_picker_open { + html! { +
    + { + props.available_colors.iter().map(|color| { + let color_str = color.clone(); + let cal_path = props.calendar.path.clone(); + let on_color_change = props.on_color_change.clone(); - let on_color_select = Callback::from(move |_: MouseEvent| { - on_color_change.emit((cal_path.clone(), color_str.clone())); - }); + let on_color_select = Callback::from(move |_: MouseEvent| { + on_color_change.emit((cal_path.clone(), color_str.clone())); + }); - let is_selected = props.calendar.color == *color; - let class_name = if is_selected { "color-option selected" } else { "color-option" }; + let is_selected = props.calendar.color == *color; + let class_name = if is_selected { "color-option selected" } else { "color-option" }; - html! { -
    -
    - } - }).collect::() - } -
    + html! { +
    +
    + } + }).collect::() + } +
    + } + } else { + html! {} } - } else { - html! {} } - } - - {&props.calendar.display_name} + + {&props.calendar.display_name} +
  • } } diff --git a/frontend/src/components/sidebar.rs b/frontend/src/components/sidebar.rs index a7d265d..3708e2c 100644 --- a/frontend/src/components/sidebar.rs +++ b/frontend/src/components/sidebar.rs @@ -110,6 +110,7 @@ pub struct SidebarProps { pub on_color_picker_toggle: Callback, pub available_colors: Vec, pub on_calendar_context_menu: Callback<(MouseEvent, String)>, + pub on_calendar_visibility_toggle: Callback, pub current_view: ViewMode, pub on_view_change: Callback, pub current_theme: Theme, @@ -221,6 +222,7 @@ pub fn sidebar(props: &SidebarProps) -> Html { on_color_picker_toggle={props.on_color_picker_toggle.clone()} available_colors={props.available_colors.clone()} on_context_menu={props.on_calendar_context_menu.clone()} + on_visibility_toggle={props.on_calendar_visibility_toggle.clone()} /> } }).collect::() diff --git a/frontend/src/services/calendar_service.rs b/frontend/src/services/calendar_service.rs index e153f7e..60b87ad 100644 --- a/frontend/src/services/calendar_service.rs +++ b/frontend/src/services/calendar_service.rs @@ -44,6 +44,7 @@ pub struct CalendarInfo { pub path: String, pub display_name: String, pub color: String, + pub is_visible: bool, } // CalendarEvent, EventStatus, and EventClass are now imported from shared library diff --git a/frontend/styles.css b/frontend/styles.css index d053dcb..249bd0c 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -3822,6 +3822,29 @@ body { flex-shrink: 0; } +/* CalDAV Calendar Styles */ +.calendar-info { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border-radius: 8px; + transition: all 0.2s ease; + cursor: pointer; +} + +.calendar-info:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(2px); +} + +.calendar-info input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: rgba(255, 255, 255, 0.8); + cursor: pointer; +} + /* Create External Calendar Button */ .create-external-calendar-button { background: rgba(255, 255, 255, 0.15);