Fix external calendar timezone conversion and styling

- Add comprehensive Windows timezone support for global external calendars
  - Map Windows timezone names (e.g. "Mountain Standard Time") to IANA zones (e.g. "America/Denver")
  - Support 60+ timezone mappings across North America, Europe, Asia, Asia Pacific, Africa, South America
  - Add chrono-tz dependency for proper timezone handling
- Fix external calendar event colors by setting calendar_path for color lookup
- Add visual distinction for external calendar events with dashed borders and calendar emoji
- Update timezone parsing to extract TZID parameters from iCalendar DTSTART/DTEND properties
- Pass external calendar data through component hierarchy for color matching

🤖 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 19:11:57 -04:00
parent 8caa1f45ae
commit f88c238b0a
8 changed files with 544 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
use crate::components::{EventCreationData, RecurringEditAction, RecurringEditModal};
use crate::models::ical::VEvent;
use crate::services::calendar_service::UserInfo;
use crate::services::calendar_service::{UserInfo, ExternalCalendar};
use chrono::{Datelike, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Weekday};
use std::collections::HashMap;
use web_sys::MouseEvent;
@@ -17,6 +17,8 @@ pub struct WeekViewProps {
#[prop_or_default]
pub user_info: Option<UserInfo>,
#[prop_or_default]
pub external_calendars: Vec<ExternalCalendar>,
#[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)>>,
@@ -81,8 +83,20 @@ pub fn week_view(props: &WeekViewProps) -> Html {
// Helper function to get calendar color for an event
let get_event_color = |event: &VEvent| -> String {
if let Some(user_info) = &props.user_info {
if let Some(calendar_path) = &event.calendar_path {
if let Some(calendar_path) = &event.calendar_path {
// Check external calendars first (path format: "external_{id}")
if calendar_path.starts_with("external_") {
if let Ok(id_str) = calendar_path.strip_prefix("external_").unwrap_or("").parse::<i32>() {
if let Some(external_calendar) = props.external_calendars
.iter()
.find(|cal| cal.id == id_str)
{
return external_calendar.color.clone();
}
}
}
// Check regular calendars
else if let Some(user_info) = &props.user_info {
if let Some(calendar) = user_info
.calendars
.iter()
@@ -371,6 +385,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
<div
class="all-day-event"
style={format!("background-color: {}", event_color)}
data-external={event.categories.contains(&"__EXTERNAL_CALENDAR__".to_string()).to_string()}
{onclick}
{oncontextmenu}
>
@@ -905,6 +920,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
column_width
)
}
data-external={event.categories.contains(&"__EXTERNAL_CALENDAR__".to_string()).to_string()}
{onclick}
{oncontextmenu}
onmousedown={onmousedown_event}
@@ -992,6 +1008,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
<div
class="temp-event-box moving-event"
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", preview_position, duration_pixels, event_color)}
data-external={event.categories.contains(&"__EXTERNAL_CALENDAR__".to_string()).to_string()}
>
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
{if duration_pixels > 30.0 {
@@ -1025,6 +1042,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
<div
class="temp-event-box resizing-event"
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", new_start_pixels, new_height, event_color)}
data-external={event.categories.contains(&"__EXTERNAL_CALENDAR__".to_string()).to_string()}
>
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
{if new_height > 30.0 {
@@ -1052,6 +1070,7 @@ pub fn week_view(props: &WeekViewProps) -> Html {
<div
class="temp-event-box resizing-event"
style={format!("top: {}px; height: {}px; background-color: {}; opacity: 0.7;", original_start_pixels, new_height, event_color)}
data-external={event.categories.contains(&"__EXTERNAL_CALENDAR__".to_string()).to_string()}
>
<div class="event-title">{event.summary.as_ref().unwrap_or(&"Untitled".to_string())}</div>
{if new_height > 30.0 {