Implement full-screen monthly calendar UI
Added a comprehensive monthly calendar component with modern styling: - Full monthly view with proper date calculations - Current day highlighting and navigation - Responsive design for all screen sizes - Event indicator support for future integration - Takes up most of screen space as requested 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										36
									
								
								src/app.rs
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								src/app.rs
									
									
									
									
									
								
							| @@ -1,7 +1,8 @@ | ||||
| use yew::prelude::*; | ||||
| use yew_router::prelude::*; | ||||
| use gloo_storage::{LocalStorage, Storage}; | ||||
| use crate::components::{Login, Register}; | ||||
| use crate::components::{Login, Register, Calendar}; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| #[derive(Clone, Routable, PartialEq)] | ||||
| enum Route { | ||||
| @@ -104,37 +105,12 @@ pub fn App() -> Html { | ||||
|  | ||||
| #[function_component] | ||||
| fn CalendarView() -> Html { | ||||
|     let counter = use_state(|| 0); | ||||
|     let onclick = { | ||||
|         let counter = counter.clone(); | ||||
|         move |_| { | ||||
|             let value = *counter + 1; | ||||
|             counter.set(value); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // Sample events for demonstration | ||||
|     let events = HashMap::new(); | ||||
|      | ||||
|     html! { | ||||
|         <div class="calendar-view"> | ||||
|             <h2>{"Welcome to your Calendar!"}</h2> | ||||
|             <p>{"You are now authenticated and can access your calendar."}</p> | ||||
|              | ||||
|             // Temporary counter demo - will be replaced with calendar functionality | ||||
|             <div class="demo-section"> | ||||
|                 <h3>{"Demo Counter"}</h3> | ||||
|                 <button {onclick}>{ "Click me!" }</button> | ||||
|                 <p>{ format!("Counter: {}", *counter) }</p> | ||||
|             </div> | ||||
|              | ||||
|             <div class="calendar-placeholder"> | ||||
|                 <p>{"Calendar functionality will be implemented here."}</p> | ||||
|                 <p>{"This will include:"}</p> | ||||
|                 <ul> | ||||
|                     <li>{"Calendar view with events"}</li> | ||||
|                     <li>{"Integration with CalDAV server"}</li> | ||||
|                     <li>{"Event creation and editing"}</li> | ||||
|                     <li>{"Synchronization with Baikal server"}</li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|             <Calendar events={events} /> | ||||
|         </div> | ||||
|     } | ||||
| } | ||||
							
								
								
									
										186
									
								
								src/components/calendar.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/components/calendar.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| use yew::prelude::*; | ||||
| use chrono::{Datelike, Local, NaiveDate, Duration, Weekday}; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| #[derive(Properties, PartialEq)] | ||||
| pub struct CalendarProps { | ||||
|     #[prop_or_default] | ||||
|     pub events: HashMap<NaiveDate, Vec<String>>, | ||||
| } | ||||
|  | ||||
| #[function_component] | ||||
| pub fn Calendar(props: &CalendarProps) -> Html { | ||||
|     let today = Local::now().date_naive(); | ||||
|     let current_month = use_state(|| today); | ||||
|      | ||||
|     let first_day_of_month = current_month.with_day(1).unwrap(); | ||||
|     let days_in_month = get_days_in_month(*current_month); | ||||
|     let first_weekday = first_day_of_month.weekday(); | ||||
|     let days_from_prev_month = get_days_from_previous_month(*current_month, first_weekday); | ||||
|      | ||||
|     let prev_month = { | ||||
|         let current_month = current_month.clone(); | ||||
|         Callback::from(move |_| { | ||||
|             let prev = *current_month - Duration::days(1); | ||||
|             let first_of_prev = prev.with_day(1).unwrap(); | ||||
|             current_month.set(first_of_prev); | ||||
|         }) | ||||
|     }; | ||||
|      | ||||
|     let next_month = { | ||||
|         let current_month = current_month.clone(); | ||||
|         Callback::from(move |_| { | ||||
|             let next = if current_month.month() == 12 { | ||||
|                 NaiveDate::from_ymd_opt(current_month.year() + 1, 1, 1).unwrap() | ||||
|             } else { | ||||
|                 NaiveDate::from_ymd_opt(current_month.year(), current_month.month() + 1, 1).unwrap() | ||||
|             }; | ||||
|             current_month.set(next); | ||||
|         }) | ||||
|     }; | ||||
|      | ||||
|     html! { | ||||
|         <div class="calendar"> | ||||
|             <div class="calendar-header"> | ||||
|                 <button class="nav-button" onclick={prev_month}>{"‹"}</button> | ||||
|                 <h2 class="month-year">{format!("{} {}", get_month_name(current_month.month()), current_month.year())}</h2> | ||||
|                 <button class="nav-button" onclick={next_month}>{"›"}</button> | ||||
|             </div> | ||||
|              | ||||
|             <div class="calendar-grid"> | ||||
|                 // Weekday headers | ||||
|                 <div class="weekday-header">{"Sun"}</div> | ||||
|                 <div class="weekday-header">{"Mon"}</div> | ||||
|                 <div class="weekday-header">{"Tue"}</div> | ||||
|                 <div class="weekday-header">{"Wed"}</div> | ||||
|                 <div class="weekday-header">{"Thu"}</div> | ||||
|                 <div class="weekday-header">{"Fri"}</div> | ||||
|                 <div class="weekday-header">{"Sat"}</div> | ||||
|                  | ||||
|                 // Days from previous month (grayed out) | ||||
|                 { | ||||
|                     days_from_prev_month.iter().map(|day| { | ||||
|                         html! { | ||||
|                             <div class="calendar-day prev-month">{*day}</div> | ||||
|                         } | ||||
|                     }).collect::<Html>() | ||||
|                 } | ||||
|                  | ||||
|                 // Days of current month | ||||
|                 { | ||||
|                     (1..=days_in_month).map(|day| { | ||||
|                         let date = current_month.with_day(day).unwrap(); | ||||
|                         let is_today = date == today; | ||||
|                         let events = props.events.get(&date).cloned().unwrap_or_default(); | ||||
|                          | ||||
|                         let mut classes = vec!["calendar-day", "current-month"]; | ||||
|                         if is_today { | ||||
|                             classes.push("today"); | ||||
|                         } | ||||
|                         if !events.is_empty() { | ||||
|                             classes.push("has-events"); | ||||
|                         } | ||||
|                          | ||||
|                         html! { | ||||
|                             <div class={classes!(classes)}> | ||||
|                                 <div class="day-number">{day}</div> | ||||
|                                 { | ||||
|                                     if !events.is_empty() { | ||||
|                                         html! { | ||||
|                                             <div class="event-indicators"> | ||||
|                                                 { | ||||
|                                                     events.iter().take(3).map(|event| { | ||||
|                                                         html! { <div class="event-dot" title={event.clone()}></div> } | ||||
|                                                     }).collect::<Html>() | ||||
|                                                 } | ||||
|                                                 { | ||||
|                                                     if events.len() > 3 { | ||||
|                                                         html! { <div class="more-events">{format!("+{}", events.len() - 3)}</div> } | ||||
|                                                     } else { | ||||
|                                                         html! {} | ||||
|                                                     } | ||||
|                                                 } | ||||
|                                             </div> | ||||
|                                         } | ||||
|                                     } else { | ||||
|                                         html! {} | ||||
|                                     } | ||||
|                                 } | ||||
|                             </div> | ||||
|                         } | ||||
|                     }).collect::<Html>() | ||||
|                 } | ||||
|                  | ||||
|                 { render_next_month_days(days_from_prev_month.len(), days_in_month) } | ||||
|             </div> | ||||
|         </div> | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn render_next_month_days(prev_days_count: usize, current_days_count: u32) -> Html { | ||||
|     let total_slots = 42; // 6 rows x 7 days | ||||
|     let used_slots = prev_days_count + current_days_count as usize; | ||||
|     let remaining_slots = if used_slots < total_slots { total_slots - used_slots } else { 0 }; | ||||
|      | ||||
|     (1..=remaining_slots).map(|day| { | ||||
|         html! { | ||||
|             <div class="calendar-day next-month">{day}</div> | ||||
|         } | ||||
|     }).collect::<Html>() | ||||
| } | ||||
|  | ||||
| fn get_days_in_month(date: NaiveDate) -> u32 { | ||||
|     NaiveDate::from_ymd_opt( | ||||
|         if date.month() == 12 { date.year() + 1 } else { date.year() }, | ||||
|         if date.month() == 12 { 1 } else { date.month() + 1 }, | ||||
|         1 | ||||
|     ) | ||||
|     .unwrap() | ||||
|     .pred_opt() | ||||
|     .unwrap() | ||||
|     .day() | ||||
| } | ||||
|  | ||||
| fn get_days_from_previous_month(current_month: NaiveDate, first_weekday: Weekday) -> Vec<u32> { | ||||
|     let days_before = match first_weekday { | ||||
|         Weekday::Sun => 0, | ||||
|         Weekday::Mon => 1, | ||||
|         Weekday::Tue => 2, | ||||
|         Weekday::Wed => 3, | ||||
|         Weekday::Thu => 4, | ||||
|         Weekday::Fri => 5, | ||||
|         Weekday::Sat => 6, | ||||
|     }; | ||||
|      | ||||
|     if days_before == 0 { | ||||
|         vec![] | ||||
|     } else { | ||||
|         // Calculate the previous month | ||||
|         let prev_month = if current_month.month() == 1 { | ||||
|             NaiveDate::from_ymd_opt(current_month.year() - 1, 12, 1).unwrap() | ||||
|         } else { | ||||
|             NaiveDate::from_ymd_opt(current_month.year(), current_month.month() - 1, 1).unwrap() | ||||
|         }; | ||||
|          | ||||
|         let prev_month_days = get_days_in_month(prev_month); | ||||
|         ((prev_month_days - days_before as u32 + 1)..=prev_month_days).collect() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn get_month_name(month: u32) -> &'static str { | ||||
|     match month { | ||||
|         1 => "January", | ||||
|         2 => "February",  | ||||
|         3 => "March", | ||||
|         4 => "April", | ||||
|         5 => "May", | ||||
|         6 => "June", | ||||
|         7 => "July", | ||||
|         8 => "August", | ||||
|         9 => "September", | ||||
|         10 => "October", | ||||
|         11 => "November", | ||||
|         12 => "December", | ||||
|         _ => "Invalid" | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,7 @@ | ||||
| pub mod login; | ||||
| pub mod register; | ||||
| pub mod calendar; | ||||
|  | ||||
| pub use login::Login; | ||||
| pub use register::Register; | ||||
| pub use register::Register; | ||||
| pub use calendar::Calendar; | ||||
		Reference in New Issue
	
	Block a user
	 Connor Johnstone
					Connor Johnstone