Implement custom reminders with multiple VAlarms per event

Major Features:
- Replace single ReminderType enum with Vec<VAlarm> throughout stack
- Add comprehensive alarm management UI with AlarmList and AddAlarmModal components
- Support relative (15min before, 2hrs after) and absolute (specific date/time) triggers
- Display reminder icons in both month and week calendar views
- RFC 5545 compliant VALARM implementation using calendar-models library

Frontend Changes:
- Create AlarmList component for displaying configured reminders
- Create AddAlarmModal with full alarm configuration (trigger, timing, description)
- Update RemindersTab to use new alarm management interface
- Replace old ReminderType dropdown with modern multi-alarm system
- Add reminder icons to event displays in month/week views
- Fix event title ellipsis behavior in week view with proper CSS constraints

Backend Changes:
- Update all request/response models to use Vec<VAlarm> instead of String
- Remove EventReminder conversion logic, pass VAlarms directly through
- Maintain RFC 5545 compliance for CalDAV server compatibility

UI/UX Improvements:
- Improved basic details tab layout (calendar/repeat side-by-side, All Day checkbox repositioned)
- Simplified reminder system to single notification type for user clarity
- Font Awesome icons throughout instead of emojis for consistency
- Clean modal styling with proper button padding and hover states
- Removed non-standard custom message fields for maximum CalDAV compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Connor Johnstone
2025-09-21 14:08:31 -04:00
parent cb1bb23132
commit 037b733d48
14 changed files with 879 additions and 215 deletions

View File

@@ -522,19 +522,8 @@ pub async fn create_event(
.collect()
};
// Parse alarms - convert from minutes string to EventReminder structs
let alarms: Vec<crate::calendar::EventReminder> = if request.reminder.trim().is_empty() {
Vec::new()
} else {
match request.reminder.parse::<i32>() {
Ok(minutes) => vec![crate::calendar::EventReminder {
minutes_before: minutes,
action: crate::calendar::ReminderAction::Display,
description: None,
}],
Err(_) => Vec::new(),
}
};
// Use VAlarms directly from request (no conversion needed)
let alarms = request.alarms;
// Check if recurrence is already a full RRULE or just a simple type
let rrule = if request.recurrence.starts_with("FREQ=") {
@@ -645,21 +634,7 @@ pub async fn create_event(
event.categories = categories;
event.rrule = rrule;
event.all_day = request.all_day;
event.alarms = alarms
.into_iter()
.map(|reminder| VAlarm {
action: AlarmAction::Display,
trigger: AlarmTrigger::Duration(chrono::Duration::minutes(
-reminder.minutes_before as i64,
)),
duration: None,
repeat: None,
description: reminder.description,
summary: None,
attendees: Vec::new(),
attach: Vec::new(),
})
.collect();
event.alarms = alarms;
event.calendar_path = Some(calendar_path.clone());
// Create the event on the CalDAV server

View File

@@ -3,6 +3,7 @@ use axum::{
response::{IntoResponse, Response},
Json,
};
use calendar_models::VAlarm;
use serde::{Deserialize, Serialize};
// API request/response types
@@ -113,7 +114,7 @@ pub struct CreateEventRequest {
pub organizer: String, // organizer email
pub attendees: String, // comma-separated attendee emails
pub categories: String, // comma-separated categories
pub reminder: String, // reminder type
pub alarms: Vec<VAlarm>, // event alarms
pub recurrence: String, // recurrence type
pub recurrence_days: Vec<bool>, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence
pub calendar_path: Option<String>, // Optional - use first calendar if not specified
@@ -144,7 +145,7 @@ pub struct UpdateEventRequest {
pub organizer: String, // organizer email
pub attendees: String, // comma-separated attendee emails
pub categories: String, // comma-separated categories
pub reminder: String, // reminder type
pub alarms: Vec<VAlarm>, // event alarms
pub recurrence: String, // recurrence type
pub recurrence_days: Vec<bool>, // [Sun, Mon, Tue, Wed, Thu, Fri, Sat] for weekly recurrence
pub recurrence_interval: Option<u32>, // Every N days/weeks/months/years
@@ -181,7 +182,7 @@ pub struct CreateEventSeriesRequest {
pub organizer: String, // organizer email
pub attendees: String, // comma-separated attendee emails
pub categories: String, // comma-separated categories
pub reminder: String, // reminder type
pub alarms: Vec<VAlarm>, // event alarms
// Series-specific fields
pub recurrence: String, // recurrence type (daily, weekly, monthly, yearly)
@@ -219,7 +220,7 @@ pub struct UpdateEventSeriesRequest {
pub organizer: String, // organizer email
pub attendees: String, // comma-separated attendee emails
pub categories: String, // comma-separated categories
pub reminder: String, // reminder type
pub alarms: Vec<VAlarm>, // event alarms
// Series-specific fields
pub recurrence: String, // recurrence type (daily, weekly, monthly, yearly)