Compare commits
	
		
			5 Commits
		
	
	
		
			bbad327ea2
			...
			38b22287c7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 38b22287c7 | ||
|   | 0de2eee626 | ||
|   | aa7a15e6fa | ||
|   | b0a8ef09a8 | ||
|   | efbaea5ac1 | 
| @@ -22,6 +22,8 @@ web-sys = { version = "0.3", features = [ | |||||||
|     "Document", |     "Document", | ||||||
|     "Window", |     "Window", | ||||||
|     "Location", |     "Location", | ||||||
|  |     "Navigator", | ||||||
|  |     "DomTokenList", | ||||||
|     "Headers", |     "Headers", | ||||||
|     "Request", |     "Request", | ||||||
|     "RequestInit", |     "RequestInit", | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| use crate::components::{ | use crate::components::{ | ||||||
|     CalendarContextMenu, ContextMenu, CreateCalendarModal, CreateEventModal, DeleteAction, |     CalendarContextMenu, CalendarManagementModal, ContextMenu, CreateEventModal, DeleteAction, | ||||||
|     EditAction, EventContextMenu, EventModal, EventCreationData, ExternalCalendarModal, RouteHandler,  |     EditAction, EventContextMenu, EventModal, EventCreationData,   | ||||||
|     Sidebar, Theme, ViewMode, |     MobileWarningModal, RouteHandler, Sidebar, Theme, ViewMode, | ||||||
| }; | }; | ||||||
|  | use crate::components::mobile_warning_modal::is_mobile_device; | ||||||
| use crate::components::sidebar::{Style}; | use crate::components::sidebar::{Style}; | ||||||
| use crate::models::ical::VEvent; | use crate::models::ical::VEvent; | ||||||
| use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService}; | use crate::services::{calendar_service::{UserInfo, ExternalCalendar}, CalendarService}; | ||||||
| @@ -94,7 +95,7 @@ pub fn App() -> Html { | |||||||
|  |  | ||||||
|     let user_info = use_state(|| -> Option<UserInfo> { None }); |     let user_info = use_state(|| -> Option<UserInfo> { None }); | ||||||
|     let color_picker_open = use_state(|| -> Option<String> { None }); |     let color_picker_open = use_state(|| -> Option<String> { None }); | ||||||
|     let create_modal_open = use_state(|| false); |     let calendar_management_modal_open = use_state(|| false); | ||||||
|     let context_menu_open = use_state(|| false); |     let context_menu_open = use_state(|| false); | ||||||
|     let context_menu_pos = use_state(|| (0i32, 0i32)); |     let context_menu_pos = use_state(|| (0i32, 0i32)); | ||||||
|     let context_menu_calendar_path = use_state(|| -> Option<String> { None }); |     let context_menu_calendar_path = use_state(|| -> Option<String> { None }); | ||||||
| @@ -117,7 +118,9 @@ pub fn App() -> Html { | |||||||
|     // External calendar state |     // External calendar state | ||||||
|     let external_calendars = use_state(|| -> Vec<ExternalCalendar> { Vec::new() }); |     let external_calendars = use_state(|| -> Vec<ExternalCalendar> { Vec::new() }); | ||||||
|     let external_calendar_events = use_state(|| -> Vec<VEvent> { Vec::new() }); |     let external_calendar_events = use_state(|| -> Vec<VEvent> { Vec::new() }); | ||||||
|     let external_calendar_modal_open = use_state(|| false); |      | ||||||
|  |     // Mobile warning state | ||||||
|  |     let mobile_warning_open = use_state(|| is_mobile_device()); | ||||||
|     let refresh_interval = use_state(|| -> Option<Interval> { None }); |     let refresh_interval = use_state(|| -> Option<Interval> { None }); | ||||||
|  |  | ||||||
|     // Calendar view state - load from localStorage if available |     // Calendar view state - load from localStorage if available | ||||||
| @@ -274,6 +277,13 @@ pub fn App() -> Html { | |||||||
|         }) |         }) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     let on_mobile_warning_close = { | ||||||
|  |         let mobile_warning_open = mobile_warning_open.clone(); | ||||||
|  |         Callback::from(move |_| { | ||||||
|  |             mobile_warning_open.set(false); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     let on_view_change = { |     let on_view_change = { | ||||||
|         let current_view = current_view.clone(); |         let current_view = current_view.clone(); | ||||||
|         Callback::from(move |new_view: ViewMode| { |         Callback::from(move |new_view: ViewMode| { | ||||||
| @@ -1157,13 +1167,9 @@ pub fn App() -> Html { | |||||||
|                                 <Sidebar |                                 <Sidebar | ||||||
|                                     user_info={(*user_info).clone()} |                                     user_info={(*user_info).clone()} | ||||||
|                                     on_logout={on_logout} |                                     on_logout={on_logout} | ||||||
|                                     on_create_calendar={Callback::from({ |                                     on_add_calendar={Callback::from({ | ||||||
|                                         let create_modal_open = create_modal_open.clone(); |                                         let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|                                         move |_| create_modal_open.set(true) |                                         move |_| calendar_management_modal_open.set(true) | ||||||
|                                     })} |  | ||||||
|                                     on_create_external_calendar={Callback::from({ |  | ||||||
|                                         let external_calendar_modal_open = external_calendar_modal_open.clone(); |  | ||||||
|                                         move |_| external_calendar_modal_open.set(true) |  | ||||||
|                                     })} |                                     })} | ||||||
|                                     external_calendars={(*external_calendars).clone()} |                                     external_calendars={(*external_calendars).clone()} | ||||||
|                                     on_external_calendar_toggle={Callback::from({ |                                     on_external_calendar_toggle={Callback::from({ | ||||||
| @@ -1375,20 +1381,20 @@ pub fn App() -> Html { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 <CreateCalendarModal |                 <CalendarManagementModal | ||||||
|                     is_open={*create_modal_open} |                     is_open={*calendar_management_modal_open} | ||||||
|                     on_close={Callback::from({ |                     on_close={Callback::from({ | ||||||
|                         let create_modal_open = create_modal_open.clone(); |                         let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|                         move |_| create_modal_open.set(false) |                         move |_| calendar_management_modal_open.set(false) | ||||||
|                     })} |                     })} | ||||||
|                     on_create={Callback::from({ |                     on_create_calendar={Callback::from({ | ||||||
|                         let auth_token = auth_token.clone(); |                         let auth_token = auth_token.clone(); | ||||||
|                         let refresh_calendars = refresh_calendars.clone(); |                         let refresh_calendars = refresh_calendars.clone(); | ||||||
|                         let create_modal_open = create_modal_open.clone(); |                         let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|                         move |(name, description, color): (String, Option<String>, Option<String>)| { |                         move |(name, description, color): (String, Option<String>, Option<String>)| { | ||||||
|                             if let Some(token) = (*auth_token).clone() { |                             if let Some(token) = (*auth_token).clone() { | ||||||
|                                 let refresh_calendars = refresh_calendars.clone(); |                                 let refresh_calendars = refresh_calendars.clone(); | ||||||
|                                 let create_modal_open = create_modal_open.clone(); |                                 let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|  |  | ||||||
|                                 wasm_bindgen_futures::spawn_local(async move { |                                 wasm_bindgen_futures::spawn_local(async move { | ||||||
|                                     let calendar_service = CalendarService::new(); |                                     let calendar_service = CalendarService::new(); | ||||||
| @@ -1407,17 +1413,41 @@ pub fn App() -> Html { | |||||||
|                                         Ok(_) => { |                                         Ok(_) => { | ||||||
|                                             web_sys::console::log_1(&"Calendar created successfully!".into()); |                                             web_sys::console::log_1(&"Calendar created successfully!".into()); | ||||||
|                                             refresh_calendars.emit(()); |                                             refresh_calendars.emit(()); | ||||||
|                                             create_modal_open.set(false); |                                             calendar_management_modal_open.set(false); | ||||||
|                                         } |                                         } | ||||||
|                                         Err(err) => { |                                         Err(err) => { | ||||||
|                                             web_sys::console::log_1(&format!("Failed to create calendar: {}", err).into()); |                                             web_sys::console::log_1(&format!("Failed to create calendar: {}", err).into()); | ||||||
|                                             create_modal_open.set(false); |                                             calendar_management_modal_open.set(false); | ||||||
|                                         } |                                         } | ||||||
|                                     } |                                     } | ||||||
|                                 }); |                                 }); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     })} |                     })} | ||||||
|  |                     on_external_success={Callback::from({ | ||||||
|  |                         let external_calendars = external_calendars.clone(); | ||||||
|  |                         let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|  |                         move |new_id: i32| { | ||||||
|  |                             // Refresh external calendars list | ||||||
|  |                             let external_calendars = external_calendars.clone(); | ||||||
|  |                             let calendar_management_modal_open = calendar_management_modal_open.clone(); | ||||||
|  |  | ||||||
|  |                             wasm_bindgen_futures::spawn_local(async move { | ||||||
|  |                                 let calendar_service = CalendarService::new(); | ||||||
|  |                                 match CalendarService::get_external_calendars().await { | ||||||
|  |                                     Ok(calendars) => { | ||||||
|  |                                         external_calendars.set(calendars); | ||||||
|  |                                         calendar_management_modal_open.set(false); | ||||||
|  |                                         web_sys::console::log_1(&format!("External calendar {} added successfully!", new_id).into()); | ||||||
|  |                                     } | ||||||
|  |                                     Err(err) => { | ||||||
|  |                                         web_sys::console::error_1(&format!("Failed to refresh external calendars: {}", err).into()); | ||||||
|  |                                         calendar_management_modal_open.set(false); | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         } | ||||||
|  |                     })} | ||||||
|                     available_colors={available_colors.iter().map(|c| c.to_string()).collect::<Vec<_>>()} |                     available_colors={available_colors.iter().map(|c| c.to_string()).collect::<Vec<_>>()} | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
| @@ -1612,58 +1642,6 @@ pub fn App() -> Html { | |||||||
|                     available_calendars={user_info.as_ref().map(|ui| ui.calendars.clone()).unwrap_or_default()} |                     available_calendars={user_info.as_ref().map(|ui| ui.calendars.clone()).unwrap_or_default()} | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <ExternalCalendarModal |  | ||||||
|                     is_open={*external_calendar_modal_open} |  | ||||||
|                     on_close={Callback::from({ |  | ||||||
|                         let external_calendar_modal_open = external_calendar_modal_open.clone(); |  | ||||||
|                         move |_| external_calendar_modal_open.set(false) |  | ||||||
|                     })} |  | ||||||
|                     on_success={Callback::from({ |  | ||||||
|                         let external_calendars = external_calendars.clone(); |  | ||||||
|                         let external_calendar_events = external_calendar_events.clone(); |  | ||||||
|                         move |new_calendar_id: i32| { |  | ||||||
|                             let external_calendars = external_calendars.clone(); |  | ||||||
|                             let external_calendar_events = external_calendar_events.clone(); |  | ||||||
|                             wasm_bindgen_futures::spawn_local(async move { |  | ||||||
|                                 // First, refresh the calendar list to get the new calendar |  | ||||||
|                                 match CalendarService::get_external_calendars().await { |  | ||||||
|                                     Ok(calendars) => { |  | ||||||
|                                         external_calendars.set(calendars.clone()); |  | ||||||
|                                          |  | ||||||
|                                         // Then immediately fetch events for the new calendar if it's visible |  | ||||||
|                                         if let Some(new_calendar) = calendars.iter().find(|c| c.id == new_calendar_id) { |  | ||||||
|                                             if new_calendar.is_visible { |  | ||||||
|                                                 match CalendarService::fetch_external_calendar_events(new_calendar_id).await { |  | ||||||
|                                                     Ok(mut events) => { |  | ||||||
|                                                         // Set calendar_path for color matching |  | ||||||
|                                                         for event in &mut events { |  | ||||||
|                                                             event.calendar_path = Some(format!("external_{}", new_calendar_id)); |  | ||||||
|                                                         } |  | ||||||
|                                                          |  | ||||||
|                                                         // Add the new calendar's events to existing events |  | ||||||
|                                                         let mut all_events = (*external_calendar_events).clone(); |  | ||||||
|                                                         all_events.extend(events); |  | ||||||
|                                                         external_calendar_events.set(all_events); |  | ||||||
|                                                     } |  | ||||||
|                                                     Err(e) => { |  | ||||||
|                                                         web_sys::console::log_1( |  | ||||||
|                                                             &format!("Failed to fetch events for new calendar {}: {}", new_calendar_id, e).into(), |  | ||||||
|                                                         ); |  | ||||||
|                                                     } |  | ||||||
|                                                 } |  | ||||||
|                                             } |  | ||||||
|                                         } |  | ||||||
|                                     } |  | ||||||
|                                     Err(err) => { |  | ||||||
|                                         web_sys::console::log_1( |  | ||||||
|                                             &format!("Failed to refresh calendars after creation: {}", err).into(), |  | ||||||
|                                         ); |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                             }); |  | ||||||
|                         } |  | ||||||
|                     })} |  | ||||||
|                 /> |  | ||||||
|  |  | ||||||
|                 <EventModal |                 <EventModal | ||||||
|                     event={if *view_event_modal_open { (*view_event_modal_event).clone() } else { None }} |                     event={if *view_event_modal_open { (*view_event_modal_event).clone() } else { None }} | ||||||
| @@ -1676,6 +1654,12 @@ pub fn App() -> Html { | |||||||
|                         } |                         } | ||||||
|                     })} |                     })} | ||||||
|                 /> |                 /> | ||||||
|  |                  | ||||||
|  |                 // Mobile warning modal | ||||||
|  |                 <MobileWarningModal | ||||||
|  |                     is_open={*mobile_warning_open} | ||||||
|  |                     on_close={on_mobile_warning_close} | ||||||
|  |                 /> | ||||||
|             </div> |             </div> | ||||||
|         </BrowserRouter> |         </BrowserRouter> | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ pub fn calendar_list_item(props: &CalendarListItemProps) -> Html { | |||||||
|                     { |                     { | ||||||
|                         if props.color_picker_open { |                         if props.color_picker_open { | ||||||
|                             html! { |                             html! { | ||||||
|                                 <div class="color-picker"> |                                 <div class="color-picker-dropdown"> | ||||||
|                                     { |                                     { | ||||||
|                                         props.available_colors.iter().map(|color| { |                                         props.available_colors.iter().map(|color| { | ||||||
|                                             let color_str = color.clone(); |                                             let color_str = color.clone(); | ||||||
|   | |||||||
							
								
								
									
										420
									
								
								frontend/src/components/calendar_management_modal.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										420
									
								
								frontend/src/components/calendar_management_modal.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,420 @@ | |||||||
|  | use yew::prelude::*; | ||||||
|  | use web_sys::HtmlInputElement; | ||||||
|  | use wasm_bindgen::JsCast; | ||||||
|  | use crate::services::calendar_service::CalendarService; | ||||||
|  |  | ||||||
|  | #[derive(Clone, PartialEq)] | ||||||
|  | pub enum CalendarTab { | ||||||
|  |     Create, | ||||||
|  |     External, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Properties, PartialEq)] | ||||||
|  | pub struct CalendarManagementModalProps { | ||||||
|  |     pub is_open: bool, | ||||||
|  |     pub on_close: Callback<()>, | ||||||
|  |     pub on_create_calendar: Callback<(String, Option<String>, Option<String>)>, // name, description, color | ||||||
|  |     pub on_external_success: Callback<i32>, // Pass the newly created external calendar ID | ||||||
|  |     pub available_colors: Vec<String>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[function_component(CalendarManagementModal)] | ||||||
|  | pub fn calendar_management_modal(props: &CalendarManagementModalProps) -> Html { | ||||||
|  |     let active_tab = use_state(|| CalendarTab::Create); | ||||||
|  |      | ||||||
|  |     // Create Calendar state | ||||||
|  |     let calendar_name = use_state(|| String::new()); | ||||||
|  |     let description = use_state(|| String::new()); | ||||||
|  |     let selected_color = use_state(|| None::<String>); | ||||||
|  |     let create_error_message = use_state(|| None::<String>); | ||||||
|  |     let is_creating = use_state(|| false); | ||||||
|  |      | ||||||
|  |     // External Calendar state | ||||||
|  |     let external_name_ref = use_node_ref(); | ||||||
|  |     let external_url_ref = use_node_ref(); | ||||||
|  |     let external_selected_color = use_state(|| Some("#4285f4".to_string())); | ||||||
|  |     let external_is_loading = use_state(|| false); | ||||||
|  |     let external_error_message = use_state(|| None::<String>); | ||||||
|  |  | ||||||
|  |     // Reset state when modal opens | ||||||
|  |     use_effect_with(props.is_open, { | ||||||
|  |         let calendar_name = calendar_name.clone(); | ||||||
|  |         let description = description.clone(); | ||||||
|  |         let selected_color = selected_color.clone(); | ||||||
|  |         let create_error_message = create_error_message.clone(); | ||||||
|  |         let is_creating = is_creating.clone(); | ||||||
|  |         let external_is_loading = external_is_loading.clone(); | ||||||
|  |         let external_error_message = external_error_message.clone(); | ||||||
|  |         let external_selected_color = external_selected_color.clone(); | ||||||
|  |         let active_tab = active_tab.clone(); | ||||||
|  |          | ||||||
|  |         move |is_open| { | ||||||
|  |             if *is_open { | ||||||
|  |                 // Reset all state when modal opens | ||||||
|  |                 calendar_name.set(String::new()); | ||||||
|  |                 description.set(String::new()); | ||||||
|  |                 selected_color.set(None); | ||||||
|  |                 create_error_message.set(None); | ||||||
|  |                 is_creating.set(false); | ||||||
|  |                 external_is_loading.set(false); | ||||||
|  |                 external_error_message.set(None); | ||||||
|  |                 external_selected_color.set(Some("#4285f4".to_string())); | ||||||
|  |                 active_tab.set(CalendarTab::Create); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     let on_tab_click = { | ||||||
|  |         let active_tab = active_tab.clone(); | ||||||
|  |         Callback::from(move |tab: CalendarTab| { | ||||||
|  |             active_tab.set(tab); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_backdrop_click = { | ||||||
|  |         let on_close = props.on_close.clone(); | ||||||
|  |         Callback::from(move |e: MouseEvent| { | ||||||
|  |             if let Some(target) = e.target() { | ||||||
|  |                 let element = target.dyn_into::<web_sys::Element>().unwrap(); | ||||||
|  |                 if element.class_list().contains("modal-backdrop") { | ||||||
|  |                     on_close.emit(()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Create Calendar handlers | ||||||
|  |     let on_name_change = { | ||||||
|  |         let calendar_name = calendar_name.clone(); | ||||||
|  |         Callback::from(move |e: InputEvent| { | ||||||
|  |             let input: HtmlInputElement = e.target_unchecked_into(); | ||||||
|  |             calendar_name.set(input.value()); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_description_change = { | ||||||
|  |         let description = description.clone(); | ||||||
|  |         Callback::from(move |e: InputEvent| { | ||||||
|  |             let input: web_sys::HtmlTextAreaElement = e.target_unchecked_into(); | ||||||
|  |             description.set(input.value()); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_color_select = { | ||||||
|  |         let selected_color = selected_color.clone(); | ||||||
|  |         Callback::from(move |color: String| { | ||||||
|  |             selected_color.set(Some(color)); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_external_color_select = { | ||||||
|  |         let external_selected_color = external_selected_color.clone(); | ||||||
|  |         Callback::from(move |color: String| { | ||||||
|  |             external_selected_color.set(Some(color)); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let on_create_submit = { | ||||||
|  |         let calendar_name = calendar_name.clone(); | ||||||
|  |         let description = description.clone(); | ||||||
|  |         let selected_color = selected_color.clone(); | ||||||
|  |         let create_error_message = create_error_message.clone(); | ||||||
|  |         let is_creating = is_creating.clone(); | ||||||
|  |         let on_create_calendar = props.on_create_calendar.clone(); | ||||||
|  |  | ||||||
|  |         Callback::from(move |e: SubmitEvent| { | ||||||
|  |             e.prevent_default(); | ||||||
|  |  | ||||||
|  |             let name = (*calendar_name).trim(); | ||||||
|  |             if name.is_empty() { | ||||||
|  |                 create_error_message.set(Some("Calendar name is required".to_string())); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             is_creating.set(true); | ||||||
|  |             create_error_message.set(None); | ||||||
|  |  | ||||||
|  |             let desc = if description.is_empty() { | ||||||
|  |                 None | ||||||
|  |             } else { | ||||||
|  |                 Some((*description).clone()) | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             on_create_calendar.emit((name.to_string(), desc, (*selected_color).clone())); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // External Calendar handlers | ||||||
|  |     let on_external_submit = { | ||||||
|  |         let external_name_ref = external_name_ref.clone(); | ||||||
|  |         let external_url_ref = external_url_ref.clone(); | ||||||
|  |         let external_selected_color = external_selected_color.clone(); | ||||||
|  |         let external_is_loading = external_is_loading.clone(); | ||||||
|  |         let external_error_message = external_error_message.clone(); | ||||||
|  |         let on_close = props.on_close.clone(); | ||||||
|  |         let on_external_success = props.on_external_success.clone(); | ||||||
|  |  | ||||||
|  |         Callback::from(move |e: SubmitEvent| { | ||||||
|  |             e.prevent_default(); | ||||||
|  |              | ||||||
|  |             let name = external_name_ref | ||||||
|  |                 .cast::<HtmlInputElement>() | ||||||
|  |                 .map(|input| input.value()) | ||||||
|  |                 .unwrap_or_default() | ||||||
|  |                 .trim() | ||||||
|  |                 .to_string(); | ||||||
|  |              | ||||||
|  |             let url = external_url_ref | ||||||
|  |                 .cast::<HtmlInputElement>() | ||||||
|  |                 .map(|input| input.value()) | ||||||
|  |                 .unwrap_or_default() | ||||||
|  |                 .trim() | ||||||
|  |                 .to_string(); | ||||||
|  |                  | ||||||
|  |             let color = (*external_selected_color).clone().unwrap_or_else(|| "#4285f4".to_string()); | ||||||
|  |  | ||||||
|  |             if name.is_empty() || url.is_empty() { | ||||||
|  |                 external_error_message.set(Some("Name and URL are required".to_string())); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             external_is_loading.set(true); | ||||||
|  |             external_error_message.set(None); | ||||||
|  |  | ||||||
|  |             let external_is_loading = external_is_loading.clone(); | ||||||
|  |             let external_error_message = external_error_message.clone(); | ||||||
|  |             let on_close = on_close.clone(); | ||||||
|  |             let on_external_success = on_external_success.clone(); | ||||||
|  |  | ||||||
|  |             wasm_bindgen_futures::spawn_local(async move { | ||||||
|  |                 let calendar_service = CalendarService::new(); | ||||||
|  |                  | ||||||
|  |                 match CalendarService::create_external_calendar(&name, &url, &color).await { | ||||||
|  |                     Ok(calendar) => { | ||||||
|  |                         external_is_loading.set(false); | ||||||
|  |                         on_close.emit(()); | ||||||
|  |                         on_external_success.emit(calendar.id); | ||||||
|  |                     } | ||||||
|  |                     Err(e) => { | ||||||
|  |                         external_is_loading.set(false); | ||||||
|  |                         external_error_message.set(Some(format!("Failed to add calendar: {}", e))); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if !props.is_open { | ||||||
|  |         return html! {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     html! { | ||||||
|  |         <div class="modal-backdrop" onclick={on_backdrop_click}> | ||||||
|  |             <div class="modal-content calendar-management-modal"> | ||||||
|  |                 <div class="modal-header"> | ||||||
|  |                     <h2>{"Add Calendar"}</h2> | ||||||
|  |                     <button class="modal-close" onclick={ | ||||||
|  |                         let on_close = props.on_close.clone(); | ||||||
|  |                         Callback::from(move |_: MouseEvent| on_close.emit(())) | ||||||
|  |                     }>{"×"}</button> | ||||||
|  |                 </div> | ||||||
|  |                  | ||||||
|  |                 <div class="calendar-management-tabs"> | ||||||
|  |                     <button  | ||||||
|  |                         class={if *active_tab == CalendarTab::Create { "tab-button active" } else { "tab-button" }} | ||||||
|  |                         onclick={ | ||||||
|  |                             let on_tab_click = on_tab_click.clone(); | ||||||
|  |                             Callback::from(move |_: MouseEvent| on_tab_click.emit(CalendarTab::Create)) | ||||||
|  |                         } | ||||||
|  |                     > | ||||||
|  |                         {"Create Calendar"} | ||||||
|  |                     </button> | ||||||
|  |                     <button  | ||||||
|  |                         class={if *active_tab == CalendarTab::External { "tab-button active" } else { "tab-button" }} | ||||||
|  |                         onclick={ | ||||||
|  |                             let on_tab_click = on_tab_click.clone(); | ||||||
|  |                             Callback::from(move |_: MouseEvent| on_tab_click.emit(CalendarTab::External)) | ||||||
|  |                         } | ||||||
|  |                     > | ||||||
|  |                         {"Add External"} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     { | ||||||
|  |                         match *active_tab { | ||||||
|  |                             CalendarTab::Create => html! { | ||||||
|  |                                 <form onsubmit={on_create_submit}> | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label for="calendar-name">{"Calendar Name"}</label> | ||||||
|  |                                         <input | ||||||
|  |                                             type="text" | ||||||
|  |                                             id="calendar-name" | ||||||
|  |                                             value={(*calendar_name).clone()} | ||||||
|  |                                             oninput={on_name_change} | ||||||
|  |                                             placeholder="Enter calendar name" | ||||||
|  |                                             disabled={*is_creating} | ||||||
|  |                                         /> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label for="calendar-description">{"Description (optional)"}</label> | ||||||
|  |                                         <textarea | ||||||
|  |                                             id="calendar-description" | ||||||
|  |                                             value={(*description).clone()} | ||||||
|  |                                             oninput={on_description_change} | ||||||
|  |                                             placeholder="Enter calendar description" | ||||||
|  |                                             disabled={*is_creating} | ||||||
|  |                                         /> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label>{"Calendar Color"}</label> | ||||||
|  |                                         <div class="color-picker"> | ||||||
|  |                                             { | ||||||
|  |                                                 props.available_colors.iter().map(|color| { | ||||||
|  |                                                     let is_selected = selected_color.as_ref() == Some(color); | ||||||
|  |                                                     let color_clone = color.clone(); | ||||||
|  |                                                     let on_color_select = on_color_select.clone(); | ||||||
|  |                                                      | ||||||
|  |                                                     html! { | ||||||
|  |                                                         <div | ||||||
|  |                                                             key={color.clone()} | ||||||
|  |                                                             class={if is_selected { "color-option selected" } else { "color-option" }} | ||||||
|  |                                                             style={format!("background-color: {}", color)} | ||||||
|  |                                                             onclick={Callback::from(move |_: MouseEvent| { | ||||||
|  |                                                                 on_color_select.emit(color_clone.clone()); | ||||||
|  |                                                             })} | ||||||
|  |                                                         /> | ||||||
|  |                                                     } | ||||||
|  |                                                 }).collect::<Html>() | ||||||
|  |                                             } | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     { | ||||||
|  |                                         if let Some(ref error) = *create_error_message { | ||||||
|  |                                             html! { | ||||||
|  |                                                 <div class="error-message"> | ||||||
|  |                                                     {error} | ||||||
|  |                                                 </div> | ||||||
|  |                                             } | ||||||
|  |                                         } else { | ||||||
|  |                                             html! {} | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     <div class="modal-footer"> | ||||||
|  |                                         <button  | ||||||
|  |                                             type="button"  | ||||||
|  |                                             class="cancel-button"  | ||||||
|  |                                             onclick={ | ||||||
|  |                                                 let on_close = props.on_close.clone(); | ||||||
|  |                                                 Callback::from(move |_: MouseEvent| on_close.emit(())) | ||||||
|  |                                             } | ||||||
|  |                                             disabled={*is_creating} | ||||||
|  |                                         > | ||||||
|  |                                             {"Cancel"} | ||||||
|  |                                         </button> | ||||||
|  |                                         <button  | ||||||
|  |                                             type="submit"  | ||||||
|  |                                             class="create-button"  | ||||||
|  |                                             disabled={*is_creating} | ||||||
|  |                                         > | ||||||
|  |                                             {if *is_creating { "Creating..." } else { "Create Calendar" }} | ||||||
|  |                                         </button> | ||||||
|  |                                     </div> | ||||||
|  |                                 </form> | ||||||
|  |                             }, | ||||||
|  |                             CalendarTab::External => html! { | ||||||
|  |                                 <form onsubmit={on_external_submit}> | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label for="external-name">{"Calendar Name"}</label> | ||||||
|  |                                         <input | ||||||
|  |                                             type="text" | ||||||
|  |                                             id="external-name" | ||||||
|  |                                             ref={external_name_ref.clone()} | ||||||
|  |                                             placeholder="Enter calendar name" | ||||||
|  |                                             disabled={*external_is_loading} | ||||||
|  |                                         /> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label for="external-url">{"Calendar URL"}</label> | ||||||
|  |                                         <input | ||||||
|  |                                             type="url" | ||||||
|  |                                             id="external-url" | ||||||
|  |                                             ref={external_url_ref.clone()} | ||||||
|  |                                             placeholder="https://example.com/calendar.ics" | ||||||
|  |                                             disabled={*external_is_loading} | ||||||
|  |                                         /> | ||||||
|  |                                         <small class="help-text"> | ||||||
|  |                                             {"Enter the ICS/CalDAV URL for your external calendar"} | ||||||
|  |                                         </small> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     <div class="form-group"> | ||||||
|  |                                         <label>{"Calendar Color"}</label> | ||||||
|  |                                         <div class="color-picker"> | ||||||
|  |                                             { | ||||||
|  |                                                 props.available_colors.iter().map(|color| { | ||||||
|  |                                                     let is_selected = external_selected_color.as_ref() == Some(color); | ||||||
|  |                                                     let color_clone = color.clone(); | ||||||
|  |                                                     let on_external_color_select = on_external_color_select.clone(); | ||||||
|  |                                                      | ||||||
|  |                                                     html! { | ||||||
|  |                                                         <div | ||||||
|  |                                                             key={color.clone()} | ||||||
|  |                                                             class={if is_selected { "color-option selected" } else { "color-option" }} | ||||||
|  |                                                             style={format!("background-color: {}", color)} | ||||||
|  |                                                             onclick={Callback::from(move |_: MouseEvent| { | ||||||
|  |                                                                 on_external_color_select.emit(color_clone.clone()); | ||||||
|  |                                                             })} | ||||||
|  |                                                         /> | ||||||
|  |                                                     } | ||||||
|  |                                                 }).collect::<Html>() | ||||||
|  |                                             } | ||||||
|  |                                         </div> | ||||||
|  |                                     </div> | ||||||
|  |  | ||||||
|  |                                     { | ||||||
|  |                                         if let Some(ref error) = *external_error_message { | ||||||
|  |                                             html! { | ||||||
|  |                                                 <div class="error-message"> | ||||||
|  |                                                     {error} | ||||||
|  |                                                 </div> | ||||||
|  |                                             } | ||||||
|  |                                         } else { | ||||||
|  |                                             html! {} | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |  | ||||||
|  |                                     <div class="modal-footer"> | ||||||
|  |                                         <button  | ||||||
|  |                                             type="button"  | ||||||
|  |                                             class="cancel-button"  | ||||||
|  |                                             onclick={ | ||||||
|  |                                                 let on_close = props.on_close.clone(); | ||||||
|  |                                                 Callback::from(move |_: MouseEvent| on_close.emit(())) | ||||||
|  |                                             } | ||||||
|  |                                             disabled={*external_is_loading} | ||||||
|  |                                         > | ||||||
|  |                                             {"Cancel"} | ||||||
|  |                                         </button> | ||||||
|  |                                         <button  | ||||||
|  |                                             type="submit"  | ||||||
|  |                                             class="create-button"  | ||||||
|  |                                             disabled={*external_is_loading} | ||||||
|  |                                         > | ||||||
|  |                                             {if *external_is_loading { "Adding..." } else { "Add Calendar" }} | ||||||
|  |                                         </button> | ||||||
|  |                                     </div> | ||||||
|  |                                 </form> | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								frontend/src/components/mobile_warning_modal.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								frontend/src/components/mobile_warning_modal.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | use yew::prelude::*; | ||||||
|  | use web_sys::window; | ||||||
|  | use wasm_bindgen::JsCast; | ||||||
|  |  | ||||||
|  | #[derive(Properties, PartialEq)] | ||||||
|  | pub struct MobileWarningModalProps { | ||||||
|  |     pub is_open: bool, | ||||||
|  |     pub on_close: Callback<()>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[function_component(MobileWarningModal)] | ||||||
|  | pub fn mobile_warning_modal(props: &MobileWarningModalProps) -> Html { | ||||||
|  |     let on_backdrop_click = { | ||||||
|  |         let on_close = props.on_close.clone(); | ||||||
|  |         Callback::from(move |e: MouseEvent| { | ||||||
|  |             if let Some(target) = e.target() { | ||||||
|  |                 let element = target.dyn_into::<web_sys::Element>().unwrap(); | ||||||
|  |                 if element.class_list().contains("modal-overlay") { | ||||||
|  |                     on_close.emit(()); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     if !props.is_open { | ||||||
|  |         return html! {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     html! { | ||||||
|  |         <div class="modal-overlay mobile-warning-overlay" onclick={on_backdrop_click}> | ||||||
|  |             <div class="modal-content mobile-warning-modal"> | ||||||
|  |                 <div class="modal-header"> | ||||||
|  |                     <h2>{"Desktop Application"}</h2> | ||||||
|  |                     <button class="modal-close" onclick={ | ||||||
|  |                         let on_close = props.on_close.clone(); | ||||||
|  |                         Callback::from(move |_: MouseEvent| on_close.emit(())) | ||||||
|  |                     }>{"×"}</button> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-body"> | ||||||
|  |                     <div class="mobile-warning-icon"> | ||||||
|  |                         {"💻"} | ||||||
|  |                     </div> | ||||||
|  |                     <p class="mobile-warning-title"> | ||||||
|  |                         {"This calendar application is designed for desktop usage"} | ||||||
|  |                     </p> | ||||||
|  |                     <p class="mobile-warning-description"> | ||||||
|  |                         {"For the best mobile calendar experience, we recommend using dedicated CalDAV apps available on your device's app store:"} | ||||||
|  |                     </p> | ||||||
|  |                     <ul class="mobile-warning-apps"> | ||||||
|  |                         <li> | ||||||
|  |                             <strong>{"iOS:"}</strong> | ||||||
|  |                             {" Calendar (built-in), Calendars 5, Fantastical"} | ||||||
|  |                         </li> | ||||||
|  |                         <li> | ||||||
|  |                             <strong>{"Android:"}</strong> | ||||||
|  |                             {" Google Calendar, DAVx5, CalDAV Sync"} | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                     <p class="mobile-warning-note"> | ||||||
|  |                         {"These apps can sync with the same CalDAV server you're using here."} | ||||||
|  |                     </p> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="modal-footer"> | ||||||
|  |                     <button class="continue-anyway-button" onclick={ | ||||||
|  |                         let on_close = props.on_close.clone(); | ||||||
|  |                         Callback::from(move |_: MouseEvent| on_close.emit(())) | ||||||
|  |                     }> | ||||||
|  |                         {"Continue Anyway"} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Helper function to detect mobile devices | ||||||
|  | pub fn is_mobile_device() -> bool { | ||||||
|  |     if let Some(window) = window() { | ||||||
|  |         let navigator = window.navigator(); | ||||||
|  |         let user_agent = navigator.user_agent().unwrap_or_default(); | ||||||
|  |         let user_agent = user_agent.to_lowercase(); | ||||||
|  |          | ||||||
|  |         // Check for mobile device indicators | ||||||
|  |         user_agent.contains("mobile") | ||||||
|  |             || user_agent.contains("android") | ||||||
|  |             || user_agent.contains("iphone") | ||||||
|  |             || user_agent.contains("ipad") | ||||||
|  |             || user_agent.contains("ipod") | ||||||
|  |             || user_agent.contains("blackberry") | ||||||
|  |             || user_agent.contains("webos") | ||||||
|  |             || user_agent.contains("opera mini") | ||||||
|  |             || user_agent.contains("windows phone") | ||||||
|  |     } else { | ||||||
|  |         false | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| pub mod calendar; | pub mod calendar; | ||||||
| pub mod calendar_context_menu; | pub mod calendar_context_menu; | ||||||
|  | pub mod calendar_management_modal; | ||||||
| pub mod calendar_header; | pub mod calendar_header; | ||||||
| pub mod calendar_list_item; | pub mod calendar_list_item; | ||||||
| pub mod context_menu; | pub mod context_menu; | ||||||
| @@ -10,6 +11,7 @@ pub mod event_form; | |||||||
| pub mod event_modal; | pub mod event_modal; | ||||||
| pub mod external_calendar_modal; | pub mod external_calendar_modal; | ||||||
| pub mod login; | pub mod login; | ||||||
|  | pub mod mobile_warning_modal; | ||||||
| pub mod month_view; | pub mod month_view; | ||||||
| pub mod recurring_edit_modal; | pub mod recurring_edit_modal; | ||||||
| pub mod route_handler; | pub mod route_handler; | ||||||
| @@ -18,17 +20,17 @@ pub mod week_view; | |||||||
|  |  | ||||||
| pub use calendar::Calendar; | pub use calendar::Calendar; | ||||||
| pub use calendar_context_menu::CalendarContextMenu; | pub use calendar_context_menu::CalendarContextMenu; | ||||||
|  | pub use calendar_management_modal::CalendarManagementModal; | ||||||
| pub use calendar_header::CalendarHeader; | pub use calendar_header::CalendarHeader; | ||||||
| pub use calendar_list_item::CalendarListItem; | pub use calendar_list_item::CalendarListItem; | ||||||
| pub use context_menu::ContextMenu; | pub use context_menu::ContextMenu; | ||||||
| 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_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 external_calendar_modal::ExternalCalendarModal; |  | ||||||
| pub use login::Login; | pub use login::Login; | ||||||
|  | pub use mobile_warning_modal::MobileWarningModal; | ||||||
| pub use month_view::MonthView; | pub use month_view::MonthView; | ||||||
| pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal}; | pub use recurring_edit_modal::{RecurringEditAction, RecurringEditModal}; | ||||||
| pub use route_handler::RouteHandler; | pub use route_handler::RouteHandler; | ||||||
|   | |||||||
| @@ -100,8 +100,7 @@ impl Default for ViewMode { | |||||||
| pub struct SidebarProps { | pub struct SidebarProps { | ||||||
|     pub user_info: Option<UserInfo>, |     pub user_info: Option<UserInfo>, | ||||||
|     pub on_logout: Callback<()>, |     pub on_logout: Callback<()>, | ||||||
|     pub on_create_calendar: Callback<()>, |     pub on_add_calendar: Callback<()>, | ||||||
|     pub on_create_external_calendar: Callback<()>, |  | ||||||
|     pub external_calendars: Vec<ExternalCalendar>, |     pub external_calendars: Vec<ExternalCalendar>, | ||||||
|     pub on_external_calendar_toggle: Callback<i32>, |     pub on_external_calendar_toggle: Callback<i32>, | ||||||
|     pub on_external_calendar_delete: Callback<i32>, |     pub on_external_calendar_delete: Callback<i32>, | ||||||
| @@ -204,9 +203,6 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             </div> |             </div> | ||||||
|             <nav class="sidebar-nav"> |  | ||||||
|                 <Link<Route> to={Route::Calendar} classes="nav-link">{"Calendar"}</Link<Route>> |  | ||||||
|             </nav> |  | ||||||
|             { |             { | ||||||
|                 if let Some(ref info) = props.user_info { |                 if let Some(ref info) = props.user_info { | ||||||
|                     if !info.calendars.is_empty() { |                     if !info.calendars.is_empty() { | ||||||
| @@ -360,12 +356,8 @@ pub fn sidebar(props: &SidebarProps) -> Html { | |||||||
|             </div> |             </div> | ||||||
|              |              | ||||||
|             <div class="sidebar-footer"> |             <div class="sidebar-footer"> | ||||||
|                 <button onclick={props.on_create_calendar.reform(|_| ())} class="create-calendar-button"> |                 <button onclick={props.on_add_calendar.reform(|_| ())} class="add-calendar-button"> | ||||||
|                     {"+ Create Calendar"} |                     {"+ Add Calendar"} | ||||||
|                 </button> |  | ||||||
|                  |  | ||||||
|                 <button onclick={props.on_create_external_calendar.reform(|_| ())} class="create-external-calendar-button"> |  | ||||||
|                     {"+ Add External Calendar"} |  | ||||||
|                 </button> |                 </button> | ||||||
|  |  | ||||||
|                 <div class="view-selector"> |                 <div class="view-selector"> | ||||||
|   | |||||||
| @@ -81,6 +81,31 @@ pub fn week_view(props: &WeekViewProps) -> Html { | |||||||
|  |  | ||||||
|     let pending_recurring_edit = use_state(|| None::<PendingRecurringEdit>); |     let pending_recurring_edit = use_state(|| None::<PendingRecurringEdit>); | ||||||
|  |  | ||||||
|  |     // Current time state for time indicator | ||||||
|  |     let current_time = use_state(|| Local::now()); | ||||||
|  |  | ||||||
|  |     // Update current time every 5 seconds | ||||||
|  |     { | ||||||
|  |         let current_time = current_time.clone(); | ||||||
|  |         use_effect_with((), move |_| { | ||||||
|  |             let interval = gloo_timers::callback::Interval::new(5_000, move || { | ||||||
|  |                 current_time.set(Local::now()); | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             // Return the interval to keep it alive | ||||||
|  |             move || drop(interval) | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Helper function to calculate current time indicator position | ||||||
|  |     let calculate_current_time_position = |time_increment: u32| -> f64 { | ||||||
|  |         let now = current_time.time(); | ||||||
|  |         let hour = now.hour() as f64; | ||||||
|  |         let minute = now.minute() as f64; | ||||||
|  |         let pixels_per_hour = if time_increment == 15 { 120.0 } else { 60.0 }; | ||||||
|  |         (hour + minute / 60.0) * pixels_per_hour | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     // Helper function to get calendar color for an event |     // Helper function to get calendar color for an event | ||||||
|     let get_event_color = |event: &VEvent| -> String { |     let get_event_color = |event: &VEvent| -> String { | ||||||
|         if let Some(calendar_path) = &event.calendar_path { |         if let Some(calendar_path) = &event.calendar_path { | ||||||
| @@ -1089,6 +1114,29 @@ pub fn week_view(props: &WeekViewProps) -> Html { | |||||||
|                                                 html! {} |                                                 html! {} | ||||||
|                                             } |                                             } | ||||||
|                                         } |                                         } | ||||||
|  |  | ||||||
|  |                                         // Current time indicator - only show on today | ||||||
|  |                                         { | ||||||
|  |                                             if *date == props.today { | ||||||
|  |                                                 let current_time_position = calculate_current_time_position(props.time_increment); | ||||||
|  |                                                 let current_time_str = current_time.time().format("%I:%M %p").to_string(); | ||||||
|  |                                                  | ||||||
|  |                                                 html! { | ||||||
|  |                                                     <div class="current-time-indicator-container"> | ||||||
|  |                                                         <div  | ||||||
|  |                                                             class="current-time-indicator" | ||||||
|  |                                                             style={format!("top: {}px;", current_time_position)} | ||||||
|  |                                                         > | ||||||
|  |                                                             <div class="current-time-dot"></div> | ||||||
|  |                                                             <div class="current-time-line"></div> | ||||||
|  |                                                             <div class="current-time-label">{current_time_str}</div> | ||||||
|  |                                                         </div> | ||||||
|  |                                                     </div> | ||||||
|  |                                                 } | ||||||
|  |                                             } else { | ||||||
|  |                                                 html! {} | ||||||
|  |                                             } | ||||||
|  |                                         } | ||||||
|                                     </div> |                                     </div> | ||||||
|                                 } |                                 } | ||||||
|                             }).collect::<Html>() |                             }).collect::<Html>() | ||||||
|   | |||||||
							
								
								
									
										1080
									
								
								frontend/styles.css
									
									
									
									
									
								
							
							
						
						
									
										1080
									
								
								frontend/styles.css
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user