Files
calendar/frontend/src/components/event_form/reminders.rs
Connor Johnstone 037b733d48 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>
2025-09-21 14:08:31 -04:00

116 lines
3.5 KiB
Rust

use super::{types::*, AlarmList, AddAlarmModal};
use calendar_models::VAlarm;
use yew::prelude::*;
#[function_component(RemindersTab)]
pub fn reminders_tab(props: &TabProps) -> Html {
let data = &props.data;
// Modal state
let is_modal_open = use_state(|| false);
let editing_index = use_state(|| None::<usize>);
// Add alarm callback
let on_add_alarm = {
let is_modal_open = is_modal_open.clone();
let editing_index = editing_index.clone();
Callback::from(move |_| {
editing_index.set(None);
is_modal_open.set(true);
})
};
// Edit alarm callback
let on_alarm_edit = {
let is_modal_open = is_modal_open.clone();
let editing_index = editing_index.clone();
Callback::from(move |index: usize| {
editing_index.set(Some(index));
is_modal_open.set(true);
})
};
// Delete alarm callback
let on_alarm_delete = {
let data = data.clone();
Callback::from(move |index: usize| {
let mut current_data = (*data).clone();
if index < current_data.alarms.len() {
current_data.alarms.remove(index);
data.set(current_data);
}
})
};
// Close modal callback
let on_modal_close = {
let is_modal_open = is_modal_open.clone();
let editing_index = editing_index.clone();
Callback::from(move |_| {
is_modal_open.set(false);
editing_index.set(None);
})
};
// Save alarm callback
let on_alarm_save = {
let data = data.clone();
let is_modal_open = is_modal_open.clone();
let editing_index = editing_index.clone();
Callback::from(move |alarm: VAlarm| {
let mut current_data = (*data).clone();
if let Some(index) = *editing_index {
// Edit existing alarm
if index < current_data.alarms.len() {
current_data.alarms[index] = alarm;
}
} else {
// Add new alarm
current_data.alarms.push(alarm);
}
data.set(current_data);
is_modal_open.set(false);
editing_index.set(None);
})
};
// Get initial alarm for editing
let initial_alarm = (*editing_index).and_then(|index| {
data.alarms.get(index).cloned()
});
html! {
<div class="tab-panel">
<div class="form-group">
<div class="alarm-management-header">
<h5>{"Event Reminders"}</h5>
<button
class="add-alarm-button"
onclick={on_add_alarm}
type="button"
>
<i class="fas fa-plus"></i>
{" Add Reminder"}
</button>
</div>
<p class="form-help-text">{"Configure multiple reminders with custom timing and notification types"}</p>
</div>
<AlarmList
alarms={data.alarms.clone()}
on_alarm_delete={on_alarm_delete}
on_alarm_edit={on_alarm_edit}
/>
<AddAlarmModal
is_open={*is_modal_open}
editing_index={*editing_index}
initial_alarm={initial_alarm}
on_close={on_modal_close}
on_save={on_alarm_save}
/>
</div>
}
}