Files
calendar/src/components/calendar.rs
Connor Johnstone 5b0e84121b Implement complete CalDAV events integration
Added full CalDAV integration to display real calendar events from Baikal server:

Backend changes:
- Added CalDAV client with iCalendar parsing and XML handling
- Created /api/calendar/events endpoint with authentication
- Fixed multiline regex patterns for namespace-prefixed XML tags
- Added proper calendar path discovery and event filtering

Frontend changes:
- Created CalendarService for API communication
- Updated calendar UI to display events as blue boxes with titles
- Added loading states and error handling
- Integrated real-time event fetching on calendar load

Now successfully displays 3 test events from the Baikal server with proper date formatting and responsive design.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 17:03:28 -04:00

196 lines
7.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(2).map(|event| {
html! {
<div class="event-box" title={event.clone()}>
{
if event.len() > 15 {
format!("{}...", &event[..12])
} else {
event.clone()
}
}
</div>
}
}).collect::<Html>()
}
{
if events.len() > 2 {
html! { <div class="more-events">{format!("+{} more", events.len() - 2)}</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"
}
}