Implement comprehensive theme system with calendar view support

- Add 8 attractive themes (Default, Ocean, Forest, Sunset, Purple, Dark, Rose, Mint)
- Extend theme system to calendar header, month view, and week view components
- Add dynamic theme-aware event color palettes that change with selected theme
- Implement CSS custom properties for consistent theming across all components
- Add localStorage persistence for user theme preferences
- Create theme-aware calendar styling including day states, headers, and grid lines
- Optimize dark theme with proper contrast and readability improvements
- Add reactive event color system that updates when themes change

🤖 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 16:42:25 -04:00
parent 81805289e4
commit 4af4aafd98
11 changed files with 804 additions and 107 deletions

View File

@@ -809,8 +809,10 @@ pub async fn update_event(
let is_series_update = request.update_action.as_deref() == Some("update_series");
// Search for the event by UID across the specified calendars
// For recurring events, we might need to find by base UID pattern if exact match fails
let mut found_event: Option<(CalendarEvent, String, String)> = None; // (event, calendar_path, href)
for calendar_path in &calendar_paths {
// First try exact match
match client.fetch_event_by_uid(calendar_path, &search_uid).await {
Ok(Some(event)) => {
if let Some(href) = event.href.clone() {
@@ -818,7 +820,32 @@ pub async fn update_event(
break;
}
},
Ok(None) => continue, // Event not found in this calendar
Ok(None) => {
// If exact match fails, try to find by base UID pattern for recurring events
println!("🔍 Exact match failed for '{}', searching by base UID pattern", search_uid);
match client.fetch_events(calendar_path).await {
Ok(events) => {
// Look for any event whose UID starts with the search_uid
for event in events {
if let Some(href) = &event.href {
// Check if this event's UID starts with our search UID (base pattern)
if event.uid.starts_with(&search_uid) && event.uid != search_uid {
println!("🎯 Found recurring event by pattern: '{}' matches '{}'", event.uid, search_uid);
found_event = Some((event.clone(), calendar_path.clone(), href.clone()));
break;
}
}
}
if found_event.is_some() {
break;
}
},
Err(e) => {
eprintln!("Error fetching events from {}: {:?}", calendar_path, e);
continue;
}
}
},
Err(e) => {
eprintln!("Failed to fetch event from calendar {}: {}", calendar_path, e);
continue;
@@ -1021,6 +1048,56 @@ pub async fn update_event(
// Keep existing recurrence rule (don't overwrite with recurrence_rule variable)
// event.recurrence_rule stays as-is from the original event
// However, allow exception_dates to be updated - this is needed for "This and Future" events
if let Some(exception_dates_str) = &request.exception_dates {
// Parse the ISO datetime strings into DateTime<Utc>
let mut new_exception_dates = Vec::new();
for date_str in exception_dates_str {
if let Ok(parsed_date) = chrono::DateTime::parse_from_rfc3339(date_str) {
new_exception_dates.push(parsed_date.with_timezone(&chrono::Utc));
} else if let Ok(parsed_date) = chrono::DateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S UTC") {
new_exception_dates.push(parsed_date.with_timezone(&chrono::Utc));
} else {
eprintln!("Failed to parse exception date: {}", date_str);
}
}
// Merge with existing exception dates (avoid duplicates)
for new_date in new_exception_dates {
if !event.exception_dates.contains(&new_date) {
event.exception_dates.push(new_date);
}
}
println!("🔄 Updated exception dates: {} total", event.exception_dates.len());
}
// Handle UNTIL date modification for "This and Future Events"
if let Some(until_date_str) = &request.until_date {
println!("🔄 Adding UNTIL clause to RRULE: {}", until_date_str);
if let Some(ref rrule) = event.recurrence_rule {
// Remove existing UNTIL if present and add new one
let rrule_without_until = rrule.split(';')
.filter(|part| !part.starts_with("UNTIL="))
.collect::<Vec<&str>>()
.join(";");
// Parse the until_date and format for RRULE
if let Ok(until_datetime) = chrono::DateTime::parse_from_rfc3339(until_date_str) {
let until_utc = until_datetime.with_timezone(&chrono::Utc);
let until_formatted = until_utc.format("%Y%m%dT%H%M%SZ").to_string();
event.recurrence_rule = Some(format!("{};UNTIL={}", rrule_without_until, until_formatted));
println!("🔄 Modified RRULE: {}", event.recurrence_rule.as_ref().unwrap());
// Clear exception dates since we're using UNTIL instead
event.exception_dates.clear();
println!("🔄 Cleared exception dates for UNTIL approach");
}
}
}
} else {
// For regular updates, use the new recurrence rule
event.recurrence_rule = recurrence_rule;

View File

@@ -124,6 +124,9 @@ pub struct UpdateEventRequest {
pub calendar_path: Option<String>, // Optional - search all calendars if not specified
pub update_action: Option<String>, // "update_series" for recurring events
pub occurrence_date: Option<String>, // ISO date string for specific occurrence
pub exception_dates: Option<Vec<String>>, // ISO datetime strings for EXDATE
#[serde(skip_serializing_if = "Option::is_none")]
pub until_date: Option<String>, // ISO datetime string for RRULE UNTIL clause
}
#[derive(Debug, Serialize)]