Refactor calendar component into modular architecture with view switching

- Split monolithic Calendar component into focused sub-components:
  - CalendarHeader: Navigation buttons and title display
  - MonthView: Monthly calendar grid layout and event rendering
  - WeekView: Weekly calendar view with full-height day containers
- Add ViewMode enum for Month/Week view switching in sidebar dropdown
- Fix event styling by correcting CSS class from "event" to "event-box"
- Implement proper week view layout with full-height day containers
- Maintain all existing functionality: event handling, context menus, localStorage persistence

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-08-29 10:14:53 -04:00
parent 197157cecb
commit 9ab6377d16
9 changed files with 586 additions and 277 deletions

137
src/components/week_view.rs Normal file
View File

@@ -0,0 +1,137 @@
use yew::prelude::*;
use chrono::{Datelike, NaiveDate, Duration, Weekday};
use std::collections::HashMap;
use web_sys::MouseEvent;
use crate::services::calendar_service::{CalendarEvent, UserInfo};
#[derive(Properties, PartialEq)]
pub struct WeekViewProps {
pub current_date: NaiveDate,
pub today: NaiveDate,
pub events: HashMap<NaiveDate, Vec<CalendarEvent>>,
pub on_event_click: Callback<CalendarEvent>,
#[prop_or_default]
pub refreshing_event_uid: Option<String>,
#[prop_or_default]
pub user_info: Option<UserInfo>,
#[prop_or_default]
pub on_event_context_menu: Option<Callback<(web_sys::MouseEvent, CalendarEvent)>>,
#[prop_or_default]
pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, NaiveDate)>>,
}
#[function_component(WeekView)]
pub fn week_view(props: &WeekViewProps) -> Html {
let start_of_week = get_start_of_week(props.current_date);
let week_days: Vec<NaiveDate> = (0..7)
.map(|i| start_of_week + Duration::days(i))
.collect();
// Helper function to get calendar color for an event
let get_event_color = |event: &CalendarEvent| -> String {
if let Some(user_info) = &props.user_info {
if let Some(calendar_path) = &event.calendar_path {
if let Some(calendar) = user_info.calendars.iter()
.find(|cal| &cal.path == calendar_path) {
return calendar.color.clone();
}
}
}
"#3B82F6".to_string()
};
html! {
<div class="week-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>
// Week days
{
week_days.iter().map(|date| {
let is_today = *date == props.today;
let day_events = props.events.get(date).cloned().unwrap_or_default();
html! {
<div
class={classes!("calendar-day", if is_today { Some("today") } else { None })}
oncontextmenu={
if let Some(callback) = &props.on_calendar_context_menu {
let callback = callback.clone();
let date = *date;
Some(Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
callback.emit((e, date));
}))
} else {
None
}
}
>
<div class="day-number">{date.day()}</div>
<div class="day-events">
{
day_events.iter().map(|event| {
let event_color = get_event_color(event);
let is_refreshing = props.refreshing_event_uid.as_ref() == Some(&event.uid);
let onclick = {
let on_event_click = props.on_event_click.clone();
let event = event.clone();
Callback::from(move |_: MouseEvent| {
on_event_click.emit(event.clone());
})
};
let oncontextmenu = {
if let Some(callback) = &props.on_event_context_menu {
let callback = callback.clone();
let event = event.clone();
Some(Callback::from(move |e: web_sys::MouseEvent| {
e.prevent_default();
callback.emit((e, event.clone()));
}))
} else {
None
}
};
html! {
<div
class={classes!("event-box", if is_refreshing { Some("refreshing") } else { None })}
style={format!("background-color: {}", event_color)}
{onclick}
{oncontextmenu}
>
{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}
</div>
}
}).collect::<Html>()
}
</div>
</div>
}
}).collect::<Html>()
}
</div>
}
}
fn get_start_of_week(date: NaiveDate) -> NaiveDate {
let weekday = date.weekday();
let days_from_sunday = match weekday {
Weekday::Sun => 0,
Weekday::Mon => 1,
Weekday::Tue => 2,
Weekday::Wed => 3,
Weekday::Thu => 4,
Weekday::Fri => 5,
Weekday::Sat => 6,
};
date - Duration::days(days_from_sunday)
}