diff --git a/calendar-models/src/vevent.rs b/calendar-models/src/vevent.rs index d1f2764..6f50df2 100644 --- a/calendar-models/src/vevent.rs +++ b/calendar-models/src/vevent.rs @@ -146,4 +146,38 @@ impl VEvent { pub fn is_exception(&self) -> bool { self.recurrence_id.is_some() } + + /// Get display string for status + pub fn get_status_display(&self) -> &'static str { + match &self.status { + Some(EventStatus::Tentative) => "Tentative", + Some(EventStatus::Confirmed) => "Confirmed", + Some(EventStatus::Cancelled) => "Cancelled", + None => "Confirmed", // Default + } + } + + /// Get display string for class + pub fn get_class_display(&self) -> &'static str { + match &self.class { + Some(EventClass::Public) => "Public", + Some(EventClass::Private) => "Private", + Some(EventClass::Confidential) => "Confidential", + None => "Public", // Default + } + } + + /// Get display string for priority + pub fn get_priority_display(&self) -> String { + match self.priority { + None => "Not set".to_string(), + Some(0) => "Undefined".to_string(), + Some(1) => "High".to_string(), + Some(p) if p <= 4 => "High".to_string(), + Some(5) => "Medium".to_string(), + Some(p) if p <= 8 => "Low".to_string(), + Some(9) => "Low".to_string(), + Some(p) => format!("Priority {}", p), + } + } } \ No newline at end of file diff --git a/frontend/src/components/route_handler.rs b/frontend/src/components/route_handler.rs index 514a477..278cee9 100644 --- a/frontend/src/components/route_handler.rs +++ b/frontend/src/components/route_handler.rs @@ -159,7 +159,7 @@ pub fn calendar_view(props: &CalendarViewProps) -> Html { match calendar_service.refresh_event(&token, &password, &uid).await { Ok(Some(refreshed_event)) => { - let refreshed_vevent = VEvent::from_calendar_event(&refreshed_event); + let refreshed_vevent = refreshed_event; // CalendarEvent is now VEvent let mut updated_events = (*events).clone(); for (_, day_events) in updated_events.iter_mut() { diff --git a/frontend/src/models/ical.rs b/frontend/src/models/ical.rs index 9b6e416..bd81c69 100644 --- a/frontend/src/models/ical.rs +++ b/frontend/src/models/ical.rs @@ -1,841 +1,2 @@ -// RFC 5545 Compliant iCalendar Data Structures -// This file contains updated structures that fully comply with RFC 5545 iCalendar specification - -use chrono::{DateTime, Utc, Duration}; -use serde::{Deserialize, Serialize}; - -// ==================== CALENDAR OBJECT (VCALENDAR) ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ICalendarObject { - // Required calendar properties - pub prodid: String, // Product identifier (PRODID) - pub version: String, // Version (typically "2.0") - - // Optional calendar properties - pub calscale: Option, // Calendar scale (CALSCALE) - default "GREGORIAN" - pub method: Option, // Method (METHOD) - - // Components - pub events: Vec, // VEVENT components - pub todos: Vec, // VTODO components - pub journals: Vec, // VJOURNAL components - pub freebusys: Vec, // VFREEBUSY components - pub timezones: Vec, // VTIMEZONE components -} - -// ==================== VEVENT COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VEvent { - // Required properties - pub dtstamp: DateTime, // Date-time stamp (DTSTAMP) - REQUIRED - pub uid: String, // Unique identifier (UID) - REQUIRED - pub dtstart: DateTime, // Start date-time (DTSTART) - REQUIRED - - // Optional properties (commonly used) - pub dtend: Option>, // End date-time (DTEND) - pub duration: Option, // Duration (DURATION) - alternative to DTEND - pub summary: Option, // Summary/title (SUMMARY) - pub description: Option, // Description (DESCRIPTION) - pub location: Option, // Location (LOCATION) - - // Classification and status - pub class: Option, // Classification (CLASS) - pub status: Option, // Status (STATUS) - pub transp: Option, // Time transparency (TRANSP) - pub priority: Option, // Priority 0-9 (PRIORITY) - - // People and organization - pub organizer: Option, // Organizer (ORGANIZER) - pub attendees: Vec, // Attendees (ATTENDEE) - pub contact: Option, // Contact information (CONTACT) - - // Categorization and relationships - pub categories: Vec, // Categories (CATEGORIES) - pub comment: Option, // Comment (COMMENT) - pub resources: Vec, // Resources (RESOURCES) - pub related_to: Option, // Related component (RELATED-TO) - pub url: Option, // URL (URL) - - // Geographical - pub geo: Option, // Geographic position (GEO) - - // Versioning and modification - pub sequence: Option, // Sequence number (SEQUENCE) - pub created: Option>, // Creation time (CREATED) - pub last_modified: Option>, // Last modified (LAST-MODIFIED) - - // Recurrence - pub rrule: Option, // Recurrence rule (RRULE) - pub rdate: Vec>, // Recurrence dates (RDATE) - pub exdate: Vec>, // Exception dates (EXDATE) - pub recurrence_id: Option>, // Recurrence ID (RECURRENCE-ID) - - // Alarms and attachments - pub alarms: Vec, // VALARM components - pub attachments: Vec, // Attachments (ATTACH) - - // CalDAV specific (for implementation) - pub etag: Option, // ETag for CalDAV - pub href: Option, // Href for CalDAV - pub calendar_path: Option, // Calendar path - pub all_day: bool, // All-day event flag -} - -// ==================== VTODO COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VTodo { - // Required properties - pub dtstamp: DateTime, // Date-time stamp (DTSTAMP) - REQUIRED - pub uid: String, // Unique identifier (UID) - REQUIRED - - // Optional date-time properties - pub dtstart: Option>, // Start date-time (DTSTART) - pub due: Option>, // Due date-time (DUE) - pub duration: Option, // Duration (DURATION) - pub completed: Option>, // Completion date-time (COMPLETED) - - // Descriptive properties - pub summary: Option, // Summary/title (SUMMARY) - pub description: Option, // Description (DESCRIPTION) - pub location: Option, // Location (LOCATION) - - // Status and completion - pub status: Option, // Status (STATUS) - pub percent_complete: Option, // Percent complete 0-100 (PERCENT-COMPLETE) - pub priority: Option, // Priority 0-9 (PRIORITY) - - // People and organization - pub organizer: Option, // Organizer (ORGANIZER) - pub attendees: Vec, // Attendees (ATTENDEE) - pub contact: Option, // Contact information (CONTACT) - - // Categorization and relationships - pub categories: Vec, // Categories (CATEGORIES) - pub comment: Option, // Comment (COMMENT) - pub resources: Vec, // Resources (RESOURCES) - pub related_to: Option, // Related component (RELATED-TO) - pub url: Option, // URL (URL) - - // Geographical - pub geo: Option, // Geographic position (GEO) - - // Versioning and modification - pub sequence: Option, // Sequence number (SEQUENCE) - pub created: Option>, // Creation time (CREATED) - pub last_modified: Option>, // Last modified (LAST-MODIFIED) - - // Recurrence - pub rrule: Option, // Recurrence rule (RRULE) - pub rdate: Vec>, // Recurrence dates (RDATE) - pub exdate: Vec>, // Exception dates (EXDATE) - pub recurrence_id: Option>, // Recurrence ID (RECURRENCE-ID) - - // Alarms and attachments - pub alarms: Vec, // VALARM components - pub attachments: Vec, // Attachments (ATTACH) -} - -// ==================== VJOURNAL COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VJournal { - // Required properties - pub dtstamp: DateTime, // Date-time stamp (DTSTAMP) - REQUIRED - pub uid: String, // Unique identifier (UID) - REQUIRED - - // Optional properties - pub dtstart: Option>, // Start date-time (DTSTART) - pub summary: Option, // Summary/title (SUMMARY) - pub description: Option, // Description (DESCRIPTION) - - // Classification and status - pub class: Option, // Classification (CLASS) - pub status: Option, // Status (STATUS) - - // People and organization - pub organizer: Option, // Organizer (ORGANIZER) - pub contact: Option, // Contact information (CONTACT) - - // Categorization and relationships - pub categories: Vec, // Categories (CATEGORIES) - pub comment: Option, // Comment (COMMENT) - pub related_to: Option, // Related component (RELATED-TO) - pub url: Option, // URL (URL) - - // Versioning and modification - pub sequence: Option, // Sequence number (SEQUENCE) - pub created: Option>, // Creation time (CREATED) - pub last_modified: Option>, // Last modified (LAST-MODIFIED) - - // Recurrence - pub rrule: Option, // Recurrence rule (RRULE) - pub rdate: Vec>, // Recurrence dates (RDATE) - pub exdate: Vec>, // Exception dates (EXDATE) - pub recurrence_id: Option>, // Recurrence ID (RECURRENCE-ID) - - // Attachments - pub attachments: Vec, // Attachments (ATTACH) -} - -// ==================== VFREEBUSY COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VFreeBusy { - // Required properties - pub dtstamp: DateTime, // Date-time stamp (DTSTAMP) - REQUIRED - pub uid: String, // Unique identifier (UID) - REQUIRED - - // Optional properties - pub dtstart: Option>, // Start date-time (DTSTART) - pub dtend: Option>, // End date-time (DTEND) - pub duration: Option, // Duration (DURATION) - - // Free/busy information - pub freebusy: Vec, // Free/busy periods (FREEBUSY) - - // People and organization - pub organizer: Option, // Organizer (ORGANIZER) - pub attendees: Vec, // Attendees (ATTENDEE) - pub contact: Option, // Contact information (CONTACT) - - // Additional properties - pub comment: Option, // Comment (COMMENT) - pub url: Option, // URL (URL) -} - -// ==================== VTIMEZONE COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VTimeZone { - // Required properties - pub tzid: String, // Time zone identifier (TZID) - REQUIRED - - // Optional properties - pub tzname: Option, // Time zone name (TZNAME) - pub tzurl: Option, // Time zone URL (TZURL) - - // Standard and daylight components - pub standard: Vec, // Standard time components - pub daylight: Vec, // Daylight time components - - // Last modified - pub last_modified: Option>, // Last modified (LAST-MODIFIED) -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TimeZoneComponent { - pub dtstart: DateTime, // Start date-time (DTSTART) - REQUIRED - pub tzoffsetfrom: String, // UTC offset from (TZOFFSETFROM) - REQUIRED - pub tzoffsetto: String, // UTC offset to (TZOFFSETTO) - REQUIRED - - pub tzname: Option, // Time zone name (TZNAME) - pub comment: Option, // Comment (COMMENT) - - // Recurrence - pub rrule: Option, // Recurrence rule (RRULE) - pub rdate: Vec>, // Recurrence dates (RDATE) -} - -// ==================== VALARM COMPONENT ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VAlarm { - pub action: AlarmAction, // Action (ACTION) - REQUIRED - pub trigger: AlarmTrigger, // Trigger (TRIGGER) - REQUIRED - - // Optional properties (some required based on action) - pub description: Option, // Description (DESCRIPTION) - pub summary: Option, // Summary (SUMMARY) - pub duration: Option, // Duration (DURATION) - pub repeat: Option, // Repeat count (REPEAT) - pub attendees: Vec, // Attendees (ATTENDEE) - for EMAIL action - pub attachments: Vec, // Attachments (ATTACH) -} - -// ==================== SUPPORTING TYPES ==================== - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EventClass { - Public, - Private, - Confidential, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EventStatus { - Tentative, - Confirmed, - Cancelled, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum TodoStatus { - NeedsAction, - Completed, - InProcess, - Cancelled, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum JournalStatus { - Draft, - Final, - Cancelled, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum TimeTransparency { - Opaque, // Time is not available (default) - Transparent, // Time is available despite event -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum AlarmAction { - Audio, - Display, - Email, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum AlarmTrigger { - Duration(Duration), // Relative to start/end - DateTime(DateTime), // Absolute time -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct CalendarUser { - pub cal_address: String, // Calendar address (email) - pub cn: Option, // Common name (CN parameter) - pub dir: Option, // Directory entry (DIR parameter) - pub sent_by: Option, // Sent by (SENT-BY parameter) - pub language: Option, // Language (LANGUAGE parameter) -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Attendee { - pub cal_address: String, // Calendar address (email) - pub cn: Option, // Common name (CN parameter) - pub role: Option, // Role (ROLE parameter) - pub partstat: Option, // Participation status (PARTSTAT parameter) - pub rsvp: Option, // RSVP expectation (RSVP parameter) - pub cutype: Option, // Calendar user type (CUTYPE parameter) - pub member: Vec, // Group/list membership (MEMBER parameter) - pub delegated_to: Vec, // Delegated to (DELEGATED-TO parameter) - pub delegated_from: Vec, // Delegated from (DELEGATED-FROM parameter) - pub sent_by: Option, // Sent by (SENT-BY parameter) - pub dir: Option, // Directory entry (DIR parameter) - pub language: Option, // Language (LANGUAGE parameter) -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum AttendeeRole { - Chair, - ReqParticipant, - OptParticipant, - NonParticipant, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum ParticipationStatus { - NeedsAction, - Accepted, - Declined, - Tentative, - Delegated, - Completed, - InProcess, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum CalendarUserType { - Individual, - Group, - Resource, - Room, - Unknown, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct GeographicPosition { - pub latitude: f64, // Latitude in decimal degrees - pub longitude: f64, // Longitude in decimal degrees -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Attachment { - pub data: AttachmentData, // Attachment data - pub fmttype: Option, // Format type (FMTTYPE parameter) - pub encoding: Option, // Encoding (ENCODING parameter) - pub filename: Option, // Filename (X-FILENAME parameter) -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum AttachmentData { - Uri(String), // URI reference - Binary(Vec), // Binary data -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct FreeBusyTime { - pub period: (DateTime, DateTime), // Start and end time - pub fbtype: Option, // Free/busy type -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum FreeBusyType { - Free, - Busy, - BusyUnavailable, - BusyTentative, -} - -// ==================== COMPATIBILITY LAYER ==================== - -use crate::services::calendar_service::{CalendarEvent, EventReminder, ReminderAction}; - -// Conversion from new VEvent to existing CalendarEvent -impl From for CalendarEvent { - fn from(vevent: VEvent) -> Self { - Self { - uid: vevent.uid, - summary: vevent.summary, - description: vevent.description, - start: vevent.dtstart, - end: vevent.dtend, - location: vevent.location, - status: vevent.status.unwrap_or(EventStatus::Confirmed).into(), - class: vevent.class.unwrap_or(EventClass::Public).into(), - priority: vevent.priority, - organizer: vevent.organizer.map(|o| o.cal_address), - attendees: vevent.attendees.into_iter().map(|a| a.cal_address).collect(), - categories: vevent.categories, - created: vevent.created, - last_modified: vevent.last_modified, - recurrence_rule: vevent.rrule, - exception_dates: vevent.exdate, - all_day: vevent.all_day, - reminders: vevent.alarms.into_iter().map(|a| a.into()).collect(), - etag: vevent.etag, - href: vevent.href, - calendar_path: vevent.calendar_path, - } - } -} - -// Conversion from existing CalendarEvent to new VEvent -impl From for VEvent { - fn from(event: CalendarEvent) -> Self { - use chrono::Utc; - - Self { - // Required properties - dtstamp: Utc::now(), // Add required DTSTAMP - uid: event.uid, - dtstart: event.start, - - // Optional properties - dtend: event.end, - duration: None, // Will be calculated from dtend if needed - summary: event.summary, - description: event.description, - location: event.location, - - // Classification and status - class: Some(event.class.into()), - status: Some(event.status.into()), - transp: None, // Default to None, can be enhanced later - priority: event.priority, - - // People and organization - organizer: event.organizer.map(|email| CalendarUser { - cal_address: email, - cn: None, - dir: None, - sent_by: None, - language: None, - }), - attendees: event.attendees.into_iter().map(|email| Attendee { - cal_address: email, - cn: None, - role: None, - partstat: None, - rsvp: None, - cutype: None, - member: Vec::new(), - delegated_to: Vec::new(), - delegated_from: Vec::new(), - sent_by: None, - dir: None, - language: None, - }).collect(), - contact: None, - - // Categorization and relationships - categories: event.categories, - comment: None, - resources: Vec::new(), - related_to: None, - url: None, - - // Geographical - geo: None, - - // Versioning and modification - sequence: Some(0), // Start with sequence 0 - created: event.created, - last_modified: event.last_modified, - - // Recurrence - rrule: event.recurrence_rule, - rdate: Vec::new(), - exdate: event.exception_dates, - recurrence_id: None, - - // Alarms and attachments - alarms: event.reminders.into_iter().map(|r| r.into()).collect(), - attachments: Vec::new(), - - // CalDAV specific - etag: event.etag, - href: event.href, - calendar_path: event.calendar_path, - all_day: event.all_day, - } - } -} - -// Convert between status enums -impl From for crate::services::calendar_service::EventStatus { - fn from(status: EventStatus) -> Self { - match status { - EventStatus::Tentative => crate::services::calendar_service::EventStatus::Tentative, - EventStatus::Confirmed => crate::services::calendar_service::EventStatus::Confirmed, - EventStatus::Cancelled => crate::services::calendar_service::EventStatus::Cancelled, - } - } -} - -impl From for EventStatus { - fn from(status: crate::services::calendar_service::EventStatus) -> Self { - match status { - crate::services::calendar_service::EventStatus::Tentative => EventStatus::Tentative, - crate::services::calendar_service::EventStatus::Confirmed => EventStatus::Confirmed, - crate::services::calendar_service::EventStatus::Cancelled => EventStatus::Cancelled, - } - } -} - -// Convert between class enums -impl From for crate::services::calendar_service::EventClass { - fn from(class: EventClass) -> Self { - match class { - EventClass::Public => crate::services::calendar_service::EventClass::Public, - EventClass::Private => crate::services::calendar_service::EventClass::Private, - EventClass::Confidential => crate::services::calendar_service::EventClass::Confidential, - } - } -} - -impl From for EventClass { - fn from(class: crate::services::calendar_service::EventClass) -> Self { - match class { - crate::services::calendar_service::EventClass::Public => EventClass::Public, - crate::services::calendar_service::EventClass::Private => EventClass::Private, - crate::services::calendar_service::EventClass::Confidential => EventClass::Confidential, - } - } -} - -// Convert between reminder types -impl From for EventReminder { - fn from(alarm: VAlarm) -> Self { - let minutes_before = match alarm.trigger { - AlarmTrigger::Duration(duration) => { - // Convert duration to minutes (assuming it's negative for "before") - (-duration.num_minutes()) as i32 - }, - AlarmTrigger::DateTime(_) => 0, // Absolute time alarms default to 0 minutes - }; - - let action = match alarm.action { - AlarmAction::Display => ReminderAction::Display, - AlarmAction::Audio => ReminderAction::Audio, - AlarmAction::Email => ReminderAction::Email, - }; - - Self { - minutes_before, - action, - description: alarm.description, - } - } -} - -impl From for VAlarm { - fn from(reminder: EventReminder) -> Self { - use chrono::Duration; - - let action = match reminder.action { - ReminderAction::Display => AlarmAction::Display, - ReminderAction::Audio => AlarmAction::Audio, - ReminderAction::Email => AlarmAction::Email, - }; - - let trigger = AlarmTrigger::Duration(Duration::minutes(-reminder.minutes_before as i64)); - - Self { - action, - trigger, - description: reminder.description, - summary: None, - duration: None, - repeat: None, - attendees: Vec::new(), - attachments: Vec::new(), - } - } -} - -// ==================== CONVERSION FUNCTIONS ==================== - -impl VEvent { - /// Convert from legacy CalendarEvent to RFC 5545 compliant VEvent - pub fn from_calendar_event(event: &crate::services::calendar_service::CalendarEvent) -> Self { - let now = chrono::Utc::now(); - - // Convert attendees from strings to structured Attendee objects - let attendees = event.attendees.iter() - .map(|email| Attendee { - cal_address: email.clone(), - cn: None, - role: Some(AttendeeRole::ReqParticipant), - partstat: Some(ParticipationStatus::NeedsAction), - rsvp: Some(true), - cutype: Some(CalendarUserType::Individual), - member: Vec::new(), - delegated_to: Vec::new(), - delegated_from: Vec::new(), - sent_by: None, - dir: None, - language: None, - }) - .collect(); - - // Convert reminders to alarms - let alarms = event.reminders.iter() - .map(|reminder| { - let action = match reminder.action { - crate::services::calendar_service::ReminderAction::Display => AlarmAction::Display, - crate::services::calendar_service::ReminderAction::Audio => AlarmAction::Audio, - crate::services::calendar_service::ReminderAction::Email => AlarmAction::Email, - }; - - VAlarm { - action, - trigger: AlarmTrigger::Duration(chrono::Duration::minutes(-reminder.minutes_before as i64)), - description: reminder.description.clone(), - summary: None, - duration: None, - repeat: None, - attendees: Vec::new(), - attachments: Vec::new(), - } - }) - .collect(); - - // Convert status - let status = match event.status { - crate::services::calendar_service::EventStatus::Tentative => Some(EventStatus::Tentative), - crate::services::calendar_service::EventStatus::Confirmed => Some(EventStatus::Confirmed), - crate::services::calendar_service::EventStatus::Cancelled => Some(EventStatus::Cancelled), - }; - - // Convert class - let class = match event.class { - crate::services::calendar_service::EventClass::Public => Some(EventClass::Public), - crate::services::calendar_service::EventClass::Private => Some(EventClass::Private), - crate::services::calendar_service::EventClass::Confidential => Some(EventClass::Confidential), - }; - - Self { - // Required properties - dtstamp: event.last_modified.unwrap_or(now), - uid: event.uid.clone(), - dtstart: event.start, - - // Date/time properties - dtend: event.end, - duration: None, // Will use dtend instead - - // Content properties - summary: event.summary.clone(), - description: event.description.clone(), - location: event.location.clone(), - - // Classification and status - class, - status, - transp: None, // Default transparency - priority: event.priority, - - // People and organization - organizer: event.organizer.as_ref().map(|org| CalendarUser { - cal_address: org.clone(), - cn: None, - dir: None, - sent_by: None, - language: None, - }), - attendees, - contact: None, - - // Categorization - categories: event.categories.clone(), - comment: None, - resources: Vec::new(), - related_to: None, - url: None, - - // Geographic - geo: None, - - // Versioning - sequence: Some(0), // Start with sequence 0 - created: event.created, - last_modified: event.last_modified, - - // Recurrence - rrule: event.recurrence_rule.clone(), - rdate: Vec::new(), - exdate: event.exception_dates.clone(), - recurrence_id: None, - - // Alarms and attachments - alarms, - attachments: Vec::new(), - - // CalDAV specific - etag: event.etag.clone(), - href: event.href.clone(), - calendar_path: event.calendar_path.clone(), - all_day: event.all_day, - } - } - - /// Convert to legacy CalendarEvent for backward compatibility - #[allow(dead_code)] - pub fn to_calendar_event(&self) -> crate::services::calendar_service::CalendarEvent { - // Convert attendees back to simple email strings - let attendees = self.attendees.iter() - .map(|attendee| attendee.cal_address.clone()) - .collect(); - - // Convert alarms back to simple reminders - let reminders = self.alarms.iter() - .filter_map(|alarm| { - if let AlarmTrigger::Duration(duration) = &alarm.trigger { - Some(crate::services::calendar_service::EventReminder { - minutes_before: (-duration.num_minutes()) as i32, - action: match alarm.action { - AlarmAction::Display => crate::services::calendar_service::ReminderAction::Display, - AlarmAction::Audio => crate::services::calendar_service::ReminderAction::Audio, - AlarmAction::Email => crate::services::calendar_service::ReminderAction::Email, - }, - description: alarm.description.clone(), - }) - } else { - None - } - }) - .collect(); - - // Convert status - let status = match self.status { - Some(EventStatus::Tentative) => crate::services::calendar_service::EventStatus::Tentative, - Some(EventStatus::Cancelled) => crate::services::calendar_service::EventStatus::Cancelled, - _ => crate::services::calendar_service::EventStatus::Confirmed, - }; - - // Convert class - let class = match self.class { - Some(EventClass::Private) => crate::services::calendar_service::EventClass::Private, - Some(EventClass::Confidential) => crate::services::calendar_service::EventClass::Confidential, - _ => crate::services::calendar_service::EventClass::Public, - }; - - crate::services::calendar_service::CalendarEvent { - uid: self.uid.clone(), - summary: self.summary.clone(), - description: self.description.clone(), - start: self.dtstart, - end: self.dtend, - location: self.location.clone(), - status, - class, - priority: self.priority, - organizer: self.organizer.as_ref().map(|org| org.cal_address.clone()), - attendees, - categories: self.categories.clone(), - created: self.created, - last_modified: self.last_modified, - recurrence_rule: self.rrule.clone(), - exception_dates: self.exdate.clone(), - all_day: self.all_day, - reminders, - etag: self.etag.clone(), - href: self.href.clone(), - calendar_path: self.calendar_path.clone(), - } - } - - /// Get the date for this event (for calendar display) - pub fn get_date(&self) -> chrono::NaiveDate { - if self.all_day { - self.dtstart.date_naive() - } else { - self.dtstart.date_naive() - } - } - - /// Get display title for the event - pub fn get_title(&self) -> String { - self.summary.clone().unwrap_or_else(|| "Untitled Event".to_string()) - } - - /// Get display string for status - pub fn get_status_display(&self) -> &'static str { - match self.status { - Some(EventStatus::Tentative) => "Tentative", - Some(EventStatus::Confirmed) => "Confirmed", - Some(EventStatus::Cancelled) => "Cancelled", - None => "Confirmed", // Default - } - } - - /// Get display string for class - pub fn get_class_display(&self) -> &'static str { - match self.class { - Some(EventClass::Public) => "Public", - Some(EventClass::Private) => "Private", - Some(EventClass::Confidential) => "Confidential", - None => "Public", // Default - } - } - - /// Get display string for priority - pub fn get_priority_display(&self) -> String { - match self.priority { - None => "Not set".to_string(), - Some(0) => "Undefined".to_string(), - Some(1) => "High".to_string(), - Some(p) if p <= 4 => "High".to_string(), - Some(5) => "Medium".to_string(), - Some(p) if p <= 8 => "Low".to_string(), - Some(9) => "Low".to_string(), - Some(p) => format!("Priority {}", p), - } - } -} \ No newline at end of file +// Re-export from shared calendar-models library for backward compatibility +pub use calendar_models::*; \ No newline at end of file diff --git a/frontend/src/services/calendar_service.rs b/frontend/src/services/calendar_service.rs index e7510c2..23744ea 100644 --- a/frontend/src/services/calendar_service.rs +++ b/frontend/src/services/calendar_service.rs @@ -5,10 +5,11 @@ use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; use std::collections::HashMap; -// Import RFC 5545 compliant VEvent -use crate::models::ical::VEvent; +// Import RFC 5545 compliant VEvent from shared library +use calendar_models::VEvent; -// Create type alias for smooth transition +// Create type alias for backward compatibility +pub type CalendarEvent = VEvent; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct EventReminder { @@ -44,56 +45,7 @@ pub struct CalendarInfo { pub color: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct CalendarEvent { - pub uid: String, - pub summary: Option, - pub description: Option, - pub start: DateTime, - pub end: Option>, - pub location: Option, - pub status: EventStatus, - pub class: EventClass, - pub priority: Option, - pub organizer: Option, - pub attendees: Vec, - pub categories: Vec, - pub created: Option>, - pub last_modified: Option>, - pub recurrence_rule: Option, - pub exception_dates: Vec>, - pub all_day: bool, - pub reminders: Vec, - pub etag: Option, - pub href: Option, - pub calendar_path: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EventStatus { - Tentative, - Confirmed, - Cancelled, -} - -impl Default for EventStatus { - fn default() -> Self { - EventStatus::Confirmed - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EventClass { - Public, - Private, - Confidential, -} - -impl Default for EventClass { - fn default() -> Self { - EventClass::Public - } -} +// CalendarEvent, EventStatus, and EventClass are now imported from shared library // ==================== V2 API MODELS ==================== @@ -228,15 +180,8 @@ impl CalendarService { year: i32, month: u32 ) -> Result, String> { - // Fetch CalendarEvents first - let calendar_events = self.fetch_events_for_month(token, password, year, month).await?; - - // Convert to VEvent - let vevents = calendar_events.into_iter() - .map(|event| VEvent::from_calendar_event(&event)) - .collect(); - - Ok(vevents) + // Since CalendarEvent is now a type alias for VEvent, just call the main method + self.fetch_events_for_month(token, password, year, month).await } pub async fn fetch_events_for_month(