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:
183
src/components/month_view.rs
Normal file
183
src/components/month_view.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use yew::prelude::*;
|
||||
use chrono::{Datelike, NaiveDate, Weekday};
|
||||
use std::collections::HashMap;
|
||||
use web_sys::MouseEvent;
|
||||
use crate::services::calendar_service::{CalendarEvent, UserInfo};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct MonthViewProps {
|
||||
pub current_month: 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(MonthView)]
|
||||
pub fn month_view(props: &MonthViewProps) -> Html {
|
||||
let first_day_of_month = props.current_month.with_day(1).unwrap();
|
||||
let days_in_month = get_days_in_month(props.current_month);
|
||||
let first_weekday = first_day_of_month.weekday();
|
||||
let days_from_prev_month = get_days_from_previous_month(props.current_month, first_weekday);
|
||||
|
||||
// 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="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 the current month
|
||||
{
|
||||
(1..=days_in_month).map(|day| {
|
||||
let date = props.current_month.with_day(day).unwrap();
|
||||
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();
|
||||
Some(Callback::from(move |e: web_sys::MouseEvent| {
|
||||
e.prevent_default();
|
||||
callback.emit((e, date));
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
>
|
||||
<div class="day-number">{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>()
|
||||
}
|
||||
|
||||
{ render_next_month_days(days_from_prev_month.len(), days_in_month) }
|
||||
</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 {
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user