diff --git a/backend/src/calendar.rs b/backend/src/calendar.rs index f739037..0278f75 100644 --- a/backend/src/calendar.rs +++ b/backend/src/calendar.rs @@ -866,6 +866,11 @@ impl CalDAVClient { ical.push_str("END:VALARM\r\n"); } + // Recurrence rule + if let Some(rrule) = &event.recurrence_rule { + ical.push_str(&format!("RRULE:{}\r\n", rrule)); + } + ical.push_str("END:VEVENT\r\n"); ical.push_str("END:VCALENDAR\r\n"); diff --git a/backend/src/handlers.rs b/backend/src/handlers.rs index ff2fb85..7f0c57d 100644 --- a/backend/src/handlers.rs +++ b/backend/src/handlers.rs @@ -451,22 +451,83 @@ pub async fn create_event( .collect() }; - // Parse reminders (for now, just store as a simple reminder duration) - let reminders: Vec = match request.reminder.to_lowercase().as_str() { - "15min" => vec![chrono::Duration::minutes(15)], - "30min" => vec![chrono::Duration::minutes(30)], - "1hour" => vec![chrono::Duration::hours(1)], - "2hours" => vec![chrono::Duration::hours(2)], - "1day" => vec![chrono::Duration::days(1)], - "2days" => vec![chrono::Duration::days(2)], - "1week" => vec![chrono::Duration::weeks(1)], + // Parse reminders and convert to EventReminder structs + let reminders: Vec = match request.reminder.to_lowercase().as_str() { + "15min" => vec![crate::calendar::EventReminder { + minutes_before: 15, + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "30min" => vec![crate::calendar::EventReminder { + minutes_before: 30, + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "1hour" => vec![crate::calendar::EventReminder { + minutes_before: 60, + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "2hours" => vec![crate::calendar::EventReminder { + minutes_before: 120, + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "1day" => vec![crate::calendar::EventReminder { + minutes_before: 1440, // 24 * 60 + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "2days" => vec![crate::calendar::EventReminder { + minutes_before: 2880, // 48 * 60 + action: crate::calendar::ReminderAction::Display, + description: None, + }], + "1week" => vec![crate::calendar::EventReminder { + minutes_before: 10080, // 7 * 24 * 60 + action: crate::calendar::ReminderAction::Display, + description: None, + }], _ => Vec::new(), }; - // Parse recurrence (basic implementation) + // Parse recurrence with BYDAY support for weekly recurrence let recurrence_rule = match request.recurrence.to_lowercase().as_str() { "daily" => Some("FREQ=DAILY".to_string()), - "weekly" => Some("FREQ=WEEKLY".to_string()), + "weekly" => { + // Handle weekly recurrence with optional BYDAY parameter + let mut rrule = "FREQ=WEEKLY".to_string(); + + // Check if specific days are selected (recurrence_days has 7 elements: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]) + if request.recurrence_days.len() == 7 { + let selected_days: Vec<&str> = request.recurrence_days + .iter() + .enumerate() + .filter_map(|(i, &selected)| { + if selected { + Some(match i { + 0 => "SU", // Sunday + 1 => "MO", // Monday + 2 => "TU", // Tuesday + 3 => "WE", // Wednesday + 4 => "TH", // Thursday + 5 => "FR", // Friday + 6 => "SA", // Saturday + _ => return None, + }) + } else { + None + } + }) + .collect(); + + if !selected_days.is_empty() { + rrule.push_str(&format!(";BYDAY={}", selected_days.join(","))); + } + } + + Some(rrule) + }, "monthly" => Some("FREQ=MONTHLY".to_string()), "yearly" => Some("FREQ=YEARLY".to_string()), _ => None, diff --git a/backend/src/models.rs b/backend/src/models.rs index 5b8d53f..1ca248c 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -88,6 +88,7 @@ pub struct CreateEventRequest { pub categories: String, // comma-separated categories pub reminder: String, // reminder type pub recurrence: String, // recurrence type + pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence pub calendar_path: Option, // Optional - use first calendar if not specified } diff --git a/src/app.rs b/src/app.rs index bb10fb0..b83a65a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -272,6 +272,7 @@ pub fn App() -> Html { event_data.categories, reminder_str, recurrence_str, + event_data.recurrence_days, None // Let backend use first available calendar ).await { Ok(_) => { diff --git a/src/components/create_event_modal.rs b/src/components/create_event_modal.rs index c626f57..50a628a 100644 --- a/src/components/create_event_modal.rs +++ b/src/components/create_event_modal.rs @@ -87,6 +87,7 @@ pub struct EventCreationData { pub categories: String, // Comma-separated list pub reminder: ReminderType, pub recurrence: RecurrenceType, + pub recurrence_days: Vec, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence } impl Default for EventCreationData { @@ -112,6 +113,7 @@ impl Default for EventCreationData { categories: String::new(), reminder: ReminderType::default(), recurrence: RecurrenceType::default(), + recurrence_days: vec![false; 7], // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - all false by default } } } @@ -290,11 +292,29 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html { "yearly" => RecurrenceType::Yearly, _ => RecurrenceType::None, }; + // Reset recurrence days when changing recurrence type + data.recurrence_days = vec![false; 7]; event_data.set(data); } }) }; + let on_weekday_change = { + let event_data = event_data.clone(); + move |day_index: usize| { + let event_data = event_data.clone(); + Callback::from(move |e: Event| { + if let Some(input) = e.target_dyn_into::() { + let mut data = (*event_data).clone(); + if day_index < data.recurrence_days.len() { + data.recurrence_days[day_index] = input.checked(); + event_data.set(data); + } + } + }) + } + }; + let on_start_date_change = { let event_data = event_data.clone(); Callback::from(move |e: Event| { @@ -601,6 +621,35 @@ pub fn create_event_modal(props: &CreateEventModalProps) -> Html { + + // Show weekday selection only when weekly recurrence is selected + if matches!(data.recurrence, RecurrenceType::Weekly) { +
+ +
+ { + ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + .iter() + .enumerate() + .map(|(i, day)| { + let day_checked = data.recurrence_days.get(i).cloned().unwrap_or(false); + let on_change = on_weekday_change(i); + html! { + + } + }) + .collect::() + } +
+
+ }