Implement complete recurring event deletion with EXDATE and RRULE UNTIL support

Frontend Changes:
- Add DeleteAction enum with DeleteThis, DeleteFollowing, DeleteSeries options
- Update EventContextMenu to show different delete options for recurring events
- Add exception_dates field to CalendarEvent struct
- Fix occurrence generation to respect EXDATE exclusions
- Add comprehensive RRULE parsing with UNTIL date support
- Fix UNTIL date parsing to handle backend format (YYYYMMDDTHHMMSSZ)
- Enhanced debugging for RRULE processing and occurrence generation

Backend Changes:
- Add exception_dates field to CalendarEvent struct with EXDATE parsing/generation
- Implement update_event method for CalDAV client
- Add fetch_event_by_href helper function
- Update DeleteEventRequest model with delete_action and occurrence_date fields
- Implement proper delete_this logic with EXDATE addition
- Implement delete_following logic with RRULE UNTIL modification
- Add comprehensive logging for delete operations

CalDAV Integration:
- Proper EXDATE generation in iCal format for excluded occurrences
- RRULE modification with UNTIL clause for partial series deletion
- Event updating via CalDAV PUT operations
- Full iCal RFC 5545 compliance for recurring event modifications

🤖 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 09:18:35 -04:00
parent e1578ed11c
commit 1b57adab98
7 changed files with 440 additions and 40 deletions

View File

@@ -1,12 +1,21 @@
use yew::prelude::*;
use web_sys::MouseEvent;
use crate::services::calendar_service::CalendarEvent;
#[derive(Clone, PartialEq, Debug)]
pub enum DeleteAction {
DeleteThis,
DeleteFollowing,
DeleteSeries,
}
#[derive(Properties, PartialEq)]
pub struct EventContextMenuProps {
pub is_open: bool,
pub x: i32,
pub y: i32,
pub on_delete: Callback<MouseEvent>,
pub event: Option<CalendarEvent>,
pub on_delete: Callback<DeleteAction>,
pub on_close: Callback<()>,
}
@@ -23,11 +32,16 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
props.x, props.y
);
let on_delete_click = {
// Check if the event is recurring
let is_recurring = props.event.as_ref()
.map(|event| event.recurrence_rule.is_some())
.unwrap_or(false);
let create_delete_callback = |action: DeleteAction| {
let on_delete = props.on_delete.clone();
let on_close = props.on_close.clone();
Callback::from(move |e: MouseEvent| {
on_delete.emit(e);
Callback::from(move |_: MouseEvent| {
on_delete.emit(action.clone());
on_close.emit(());
})
};
@@ -38,10 +52,33 @@ pub fn event_context_menu(props: &EventContextMenuProps) -> Html {
class="context-menu"
style={style}
>
<div class="context-menu-item context-menu-delete" onclick={on_delete_click}>
<span class="context-menu-icon">{"🗑️"}</span>
{"Delete Event"}
</div>
{
if is_recurring {
html! {
<>
<div class="context-menu-item context-menu-delete" onclick={create_delete_callback(DeleteAction::DeleteThis)}>
<span class="context-menu-icon">{"🗑️"}</span>
{"Delete This Event"}
</div>
<div class="context-menu-item context-menu-delete" onclick={create_delete_callback(DeleteAction::DeleteFollowing)}>
<span class="context-menu-icon">{"🗑️"}</span>
{"Delete Following Events"}
</div>
<div class="context-menu-item context-menu-delete" onclick={create_delete_callback(DeleteAction::DeleteSeries)}>
<span class="context-menu-icon">{"🗑️"}</span>
{"Delete Entire Series"}
</div>
</>
}
} else {
html! {
<div class="context-menu-item context-menu-delete" onclick={create_delete_callback(DeleteAction::DeleteThis)}>
<span class="context-menu-icon">{"🗑️"}</span>
{"Delete Event"}
</div>
}
}
}
</div>
}
}