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:
@@ -1,100 +1,116 @@
|
||||
use super::types::*;
|
||||
// Types are already imported from super::types::*
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlSelectElement;
|
||||
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;
|
||||
|
||||
let on_reminder_change = {
|
||||
// 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 |e: Event| {
|
||||
if let Some(target) = e.target() {
|
||||
if let Ok(select) = target.dyn_into::<HtmlSelectElement>() {
|
||||
let mut event_data = (*data).clone();
|
||||
event_data.reminder = match select.value().as_str() {
|
||||
"15min" => ReminderType::Minutes15,
|
||||
"30min" => ReminderType::Minutes30,
|
||||
"1hour" => ReminderType::Hour1,
|
||||
"1day" => ReminderType::Day1,
|
||||
"2days" => ReminderType::Days2,
|
||||
"1week" => ReminderType::Week1,
|
||||
_ => ReminderType::None,
|
||||
};
|
||||
data.set(event_data);
|
||||
}
|
||||
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">
|
||||
<label for="event-reminder-main">{"Primary Reminder"}</label>
|
||||
<select
|
||||
id="event-reminder-main"
|
||||
class="form-input"
|
||||
onchange={on_reminder_change}
|
||||
>
|
||||
<option value="none" selected={matches!(data.reminder, ReminderType::None)}>{"No reminder"}</option>
|
||||
<option value="15min" selected={matches!(data.reminder, ReminderType::Minutes15)}>{"15 minutes before"}</option>
|
||||
<option value="30min" selected={matches!(data.reminder, ReminderType::Minutes30)}>{"30 minutes before"}</option>
|
||||
<option value="1hour" selected={matches!(data.reminder, ReminderType::Hour1)}>{"1 hour before"}</option>
|
||||
<option value="1day" selected={matches!(data.reminder, ReminderType::Day1)}>{"1 day before"}</option>
|
||||
<option value="2days" selected={matches!(data.reminder, ReminderType::Days2)}>{"2 days before"}</option>
|
||||
<option value="1week" selected={matches!(data.reminder, ReminderType::Week1)}>{"1 week before"}</option>
|
||||
</select>
|
||||
<p class="form-help-text">{"Choose when you'd like to be reminded about this event"}</p>
|
||||
</div>
|
||||
|
||||
<div class="reminder-types">
|
||||
<h5>{"Reminder & Alarm Types"}</h5>
|
||||
<div class="alarm-examples">
|
||||
<div class="alarm-type">
|
||||
<strong>{"Display Alarm"}</strong>
|
||||
<p>{"Pop-up notification on your device"}</p>
|
||||
</div>
|
||||
<div class="alarm-type">
|
||||
<strong>{"Email Reminder"}</strong>
|
||||
<p>{"Email notification sent to your address"}</p>
|
||||
</div>
|
||||
<div class="alarm-type">
|
||||
<strong>{"Audio Alert"}</strong>
|
||||
<p>{"Sound notification with custom audio"}</p>
|
||||
</div>
|
||||
<div class="alarm-type">
|
||||
<strong>{"SMS/Text"}</strong>
|
||||
<p>{"Text message reminder (enterprise feature)"}</p>
|
||||
</div>
|
||||
<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">{"Multiple alarm types follow RFC 5545 VALARM standards"}</p>
|
||||
<p class="form-help-text">{"Configure multiple reminders with custom timing and notification types"}</p>
|
||||
</div>
|
||||
|
||||
<div class="reminder-info">
|
||||
<h5>{"Advanced Reminder Features"}</h5>
|
||||
<ul>
|
||||
<li>{"Multiple reminders per event with different timing"}</li>
|
||||
<li>{"Custom reminder messages and descriptions"}</li>
|
||||
<li>{"Recurring reminders for recurring events"}</li>
|
||||
<li>{"Snooze and dismiss functionality"}</li>
|
||||
<li>{"Integration with system notifications"}</li>
|
||||
</ul>
|
||||
<AlarmList
|
||||
alarms={data.alarms.clone()}
|
||||
on_alarm_delete={on_alarm_delete}
|
||||
on_alarm_edit={on_alarm_edit}
|
||||
/>
|
||||
|
||||
<div class="attachments-section">
|
||||
<h6>{"File Attachments & Documents"}</h6>
|
||||
<p>{"Future attachment features will include:"}</p>
|
||||
<ul>
|
||||
<li>{"Drag-and-drop file uploads"}</li>
|
||||
<li>{"Document preview and thumbnails"}</li>
|
||||
<li>{"Cloud storage integration (Google Drive, OneDrive)"}</li>
|
||||
<li>{"Version control for updated documents"}</li>
|
||||
<li>{"Shared access permissions for attendees"}</li>
|
||||
</ul>
|
||||
<p class="form-help-text">{"Attachment functionality will be implemented in a future release."}</p>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user