Fix series RRULE updates: editing 'all events' now properly updates original series RRULE
- Backend now updates RRULE when recurrence_count or recurrence_end_date parameters are provided - Fixed update_entire_series() to modify COUNT/UNTIL instead of preserving original RRULE - Added comprehensive RRULE parsing functions to extract existing frequency, interval, count, until, and BYDAY components - Fixed frontend parameter mapping to pass recurrence parameters through update_series calls - Resolves issue where changing recurring event from 5 to 7 occurrences kept original COUNT=5 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -478,7 +478,10 @@ pub fn App() -> Html {
|
||||
params.13, // categories
|
||||
params.14, // reminder
|
||||
params.15, // recurrence
|
||||
params.17, // calendar_path (skipping recurrence_days)
|
||||
params.16, // recurrence_days
|
||||
params.18, // recurrence_count
|
||||
params.19, // recurrence_until
|
||||
params.17, // calendar_path
|
||||
scope,
|
||||
event_data_for_update.occurrence_date.map(|d| d.format("%Y-%m-%d").to_string()), // occurrence_date
|
||||
)
|
||||
@@ -759,6 +762,9 @@ pub fn App() -> Html {
|
||||
original_event.categories.join(","),
|
||||
reminder_str.clone(),
|
||||
recurrence_str.clone(),
|
||||
vec![false; 7],
|
||||
None,
|
||||
None,
|
||||
original_event.calendar_path.clone(),
|
||||
scope.clone(),
|
||||
occurrence_date,
|
||||
|
||||
@@ -290,16 +290,32 @@ fn vevent_to_creation_data(event: &crate::models::ical::VEvent, available_calend
|
||||
// Reminders - TODO: Parse alarm from VEvent if needed
|
||||
reminder: ReminderType::None,
|
||||
|
||||
// Recurrence - TODO: Parse RRULE if needed for advanced editing
|
||||
recurrence: if event.rrule.is_some() {
|
||||
RecurrenceType::Daily // Default, could be enhanced to parse actual RRULE
|
||||
// Recurrence - Parse RRULE if present
|
||||
recurrence: if let Some(ref rrule_str) = event.rrule {
|
||||
parse_rrule_frequency(rrule_str)
|
||||
} else {
|
||||
RecurrenceType::None
|
||||
},
|
||||
recurrence_interval: 1,
|
||||
recurrence_until: None,
|
||||
recurrence_count: None,
|
||||
recurrence_days: vec![false; 7],
|
||||
recurrence_interval: if let Some(ref rrule_str) = event.rrule {
|
||||
parse_rrule_interval(rrule_str)
|
||||
} else {
|
||||
1
|
||||
},
|
||||
recurrence_until: if let Some(ref rrule_str) = event.rrule {
|
||||
parse_rrule_until(rrule_str)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
recurrence_count: if let Some(ref rrule_str) = event.rrule {
|
||||
parse_rrule_count(rrule_str)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
recurrence_days: if let Some(ref rrule_str) = event.rrule {
|
||||
parse_rrule_days(rrule_str)
|
||||
} else {
|
||||
vec![false; 7]
|
||||
},
|
||||
|
||||
// Advanced recurrence
|
||||
monthly_by_day: None,
|
||||
@@ -327,4 +343,113 @@ fn vevent_to_creation_data(event: &crate::models::ical::VEvent, available_calend
|
||||
original_uid: Some(event.uid.clone()), // Preserve original UID for editing
|
||||
occurrence_date: Some(start_local.date()), // The occurrence date being edited
|
||||
}
|
||||
}
|
||||
|
||||
// Parse RRULE frequency component
|
||||
fn parse_rrule_frequency(rrule: &str) -> RecurrenceType {
|
||||
if rrule.contains("FREQ=DAILY") {
|
||||
RecurrenceType::Daily
|
||||
} else if rrule.contains("FREQ=WEEKLY") {
|
||||
RecurrenceType::Weekly
|
||||
} else if rrule.contains("FREQ=MONTHLY") {
|
||||
RecurrenceType::Monthly
|
||||
} else if rrule.contains("FREQ=YEARLY") {
|
||||
RecurrenceType::Yearly
|
||||
} else {
|
||||
RecurrenceType::None
|
||||
}
|
||||
}
|
||||
|
||||
// Parse RRULE interval component
|
||||
fn parse_rrule_interval(rrule: &str) -> u32 {
|
||||
if let Some(start) = rrule.find("INTERVAL=") {
|
||||
let interval_part = &rrule[start + 9..];
|
||||
if let Some(end) = interval_part.find(';') {
|
||||
interval_part[..end].parse().unwrap_or(1)
|
||||
} else {
|
||||
interval_part.parse().unwrap_or(1)
|
||||
}
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
// Parse RRULE count component
|
||||
fn parse_rrule_count(rrule: &str) -> Option<u32> {
|
||||
if let Some(start) = rrule.find("COUNT=") {
|
||||
let count_part = &rrule[start + 6..];
|
||||
if let Some(end) = count_part.find(';') {
|
||||
count_part[..end].parse().ok()
|
||||
} else {
|
||||
count_part.parse().ok()
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Parse RRULE until component
|
||||
fn parse_rrule_until(rrule: &str) -> Option<chrono::NaiveDate> {
|
||||
if let Some(start) = rrule.find("UNTIL=") {
|
||||
let until_part = &rrule[start + 6..];
|
||||
let until_str = if let Some(end) = until_part.find(';') {
|
||||
&until_part[..end]
|
||||
} else {
|
||||
until_part
|
||||
};
|
||||
|
||||
// UNTIL can be in format YYYYMMDD or YYYYMMDDTHHMMSSZ
|
||||
let date_part = if until_str.len() >= 8 {
|
||||
&until_str[..8]
|
||||
} else {
|
||||
until_str
|
||||
};
|
||||
|
||||
// Parse YYYYMMDD format
|
||||
if date_part.len() == 8 {
|
||||
if let (Ok(year), Ok(month), Ok(day)) = (
|
||||
date_part[0..4].parse::<i32>(),
|
||||
date_part[4..6].parse::<u32>(),
|
||||
date_part[6..8].parse::<u32>(),
|
||||
) {
|
||||
chrono::NaiveDate::from_ymd_opt(year, month, day)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Parse RRULE BYDAY component for weekly recurrence
|
||||
fn parse_rrule_days(rrule: &str) -> Vec<bool> {
|
||||
let mut days = vec![false; 7]; // [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
|
||||
|
||||
if let Some(start) = rrule.find("BYDAY=") {
|
||||
let byday_part = &rrule[start + 6..];
|
||||
let byday_str = if let Some(end) = byday_part.find(';') {
|
||||
&byday_part[..end]
|
||||
} else {
|
||||
byday_part
|
||||
};
|
||||
|
||||
// Parse comma-separated day codes: SU,MO,TU,WE,TH,FR,SA
|
||||
for day_code in byday_str.split(',') {
|
||||
match day_code.trim() {
|
||||
"SU" => days[0] = true, // Sunday
|
||||
"MO" => days[1] = true, // Monday
|
||||
"TU" => days[2] = true, // Tuesday
|
||||
"WE" => days[3] = true, // Wednesday
|
||||
"TH" => days[4] = true, // Thursday
|
||||
"FR" => days[5] = true, // Friday
|
||||
"SA" => days[6] = true, // Saturday
|
||||
_ => {} // Ignore unknown day codes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
days
|
||||
}
|
||||
@@ -1678,6 +1678,9 @@ impl CalendarService {
|
||||
categories: String,
|
||||
reminder: String,
|
||||
recurrence: String,
|
||||
recurrence_days: Vec<bool>,
|
||||
recurrence_count: Option<u32>,
|
||||
recurrence_until: Option<String>,
|
||||
calendar_path: Option<String>,
|
||||
update_scope: String,
|
||||
occurrence_date: Option<String>,
|
||||
@@ -1706,10 +1709,10 @@ impl CalendarService {
|
||||
"categories": categories,
|
||||
"reminder": reminder,
|
||||
"recurrence": recurrence,
|
||||
"recurrence_days": vec![false; 7], // Default - could be enhanced
|
||||
"recurrence_interval": 1_u32, // Default interval
|
||||
"recurrence_end_date": None as Option<String>, // No end date by default
|
||||
"recurrence_count": None as Option<u32>, // No count limit by default
|
||||
"recurrence_days": recurrence_days,
|
||||
"recurrence_interval": 1_u32, // Default interval - could be enhanced to be a parameter
|
||||
"recurrence_end_date": recurrence_until,
|
||||
"recurrence_count": recurrence_count,
|
||||
"calendar_path": calendar_path,
|
||||
"update_scope": update_scope,
|
||||
"occurrence_date": occurrence_date
|
||||
|
||||
Reference in New Issue
Block a user