Add external calendars feature: display read-only ICS calendars alongside CalDAV calendars

- Database: Add external_calendars table with user relationships and CRUD operations
- Backend: Implement REST API endpoints for external calendar management and ICS fetching
- Frontend: Add external calendar modal, sidebar section with visibility toggles
- Calendar integration: Merge external events with regular events in unified view
- ICS parsing: Support multiple datetime formats, recurring events, and timezone handling
- Authentication: Integrate with existing JWT token system for user-specific calendars
- UI: Visual distinction with 📅 indicator and separate management section

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-09-03 18:22:52 -04:00
parent 289284a532
commit 8caa1f45ae
16 changed files with 1207 additions and 18 deletions

View File

@@ -14,6 +14,8 @@ pub struct CalendarProps {
#[prop_or_default]
pub user_info: Option<UserInfo>,
#[prop_or_default]
pub external_calendar_events: Vec<VEvent>,
#[prop_or_default]
pub on_event_context_menu: Option<Callback<(web_sys::MouseEvent, VEvent)>>,
#[prop_or_default]
pub on_calendar_context_menu: Option<Callback<(web_sys::MouseEvent, NaiveDate)>>,
@@ -101,10 +103,13 @@ pub fn Calendar(props: &CalendarProps) -> Html {
let loading = loading.clone();
let error = error.clone();
let current_date = current_date.clone();
let external_events = props.external_calendar_events.clone(); // Clone before the effect
let view = props.view.clone(); // Clone before the effect
use_effect_with((*current_date, props.view.clone()), move |(date, _view)| {
use_effect_with((*current_date, view.clone(), external_events.len()), move |(date, _view, _external_len)| {
let auth_token: Option<String> = LocalStorage::get("auth_token").ok();
let date = *date; // Clone the date to avoid lifetime issues
let external_events = external_events.clone(); // Clone external events to avoid lifetime issues
if let Some(token) = auth_token {
let events = events.clone();
@@ -141,7 +146,11 @@ pub fn Calendar(props: &CalendarProps) -> Html {
.await
{
Ok(vevents) => {
let grouped_events = CalendarService::group_events_by_date(vevents);
// Combine regular events with external calendar events
let mut all_events = vevents;
all_events.extend(external_events);
let grouped_events = CalendarService::group_events_by_date(all_events);
events.set(grouped_events);
loading.set(false);
}