From b1b8e1e580a8f691b4309f81eb02cc848955f6d3 Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Thu, 28 Aug 2025 17:18:39 -0400 Subject: [PATCH] Refactor event modal into standalone component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created dedicated EventModal component in src/components/event_modal.rs - Extracted modal logic and styling from calendar component for better separation - Updated data flow to pass full CalendarEvent objects instead of strings - Added PartialEq derive to CalendarEvent for component props - Updated service layer to group events by CalendarEvent objects - Enhanced event click handling to show detailed event information - Modal displays title, description, location, start/end times, and status - Maintained existing modal styling and user interaction patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- index.html | 129 +++++++++++++++++++++++++++++++ src/app.rs | 4 +- src/components/calendar.rs | 37 +++++++-- src/components/event_modal.rs | 101 ++++++++++++++++++++++++ src/components/mod.rs | 4 +- src/services/calendar_service.rs | 7 +- src/services/mod.rs | 2 +- 7 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 src/components/event_modal.rs diff --git a/index.html b/index.html index c8b3af7..088747a 100644 --- a/index.html +++ b/index.html @@ -434,6 +434,135 @@ } } + /* Event Modal Styles */ + .modal-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(2px); + } + + .modal-content { + background: white; + border-radius: 12px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + position: relative; + animation: modalAppear 0.2s ease-out; + } + + @keyframes modalAppear { + from { + opacity: 0; + transform: scale(0.9) translateY(-20px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } + } + + .modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem 2rem 1rem; + border-bottom: 1px solid #e9ecef; + } + + .modal-header h3 { + margin: 0; + color: #333; + font-size: 1.4rem; + font-weight: 600; + } + + .modal-close { + background: none; + border: none; + font-size: 1.8rem; + color: #999; + cursor: pointer; + padding: 0; + width: 30px; + height: 30px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + line-height: 1; + } + + .modal-close:hover { + background: #f8f9fa; + color: #666; + transform: scale(1.1); + } + + .modal-body { + padding: 1.5rem 2rem 2rem; + } + + .event-detail { + display: grid; + grid-template-columns: 100px 1fr; + gap: 1rem; + margin-bottom: 1rem; + align-items: start; + } + + .event-detail strong { + color: #555; + font-weight: 600; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .event-detail span { + color: #333; + font-size: 1rem; + line-height: 1.5; + word-break: break-word; + } + + /* Mobile adjustments for modal */ + @media (max-width: 768px) { + .modal-content { + margin: 1rem; + width: calc(100% - 2rem); + } + + .modal-header, .modal-body { + padding: 1rem 1.5rem; + } + + .event-detail { + grid-template-columns: 80px 1fr; + gap: 0.75rem; + margin-bottom: 0.75rem; + } + + .event-detail strong { + font-size: 0.8rem; + } + + .event-detail span { + font-size: 0.9rem; + } + } + @media (max-width: 768px) { .app-header { flex-direction: column; diff --git a/src/app.rs b/src/app.rs index 0016abd..87902f1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use yew::prelude::*; use yew_router::prelude::*; use gloo_storage::{LocalStorage, Storage}; use crate::components::{Login, Register, Calendar}; -use crate::services::CalendarService; +use crate::services::{CalendarService, CalendarEvent}; use std::collections::HashMap; use chrono::{Local, NaiveDate, Datelike}; @@ -107,7 +107,7 @@ pub fn App() -> Html { #[function_component] fn CalendarView() -> Html { - let events = use_state(|| HashMap::>::new()); + let events = use_state(|| HashMap::>::new()); let loading = use_state(|| true); let error = use_state(|| None::); diff --git a/src/components/calendar.rs b/src/components/calendar.rs index 2f63da9..3084996 100644 --- a/src/components/calendar.rs +++ b/src/components/calendar.rs @@ -1,11 +1,13 @@ use yew::prelude::*; use chrono::{Datelike, Local, NaiveDate, Duration, Weekday}; use std::collections::HashMap; +use crate::services::calendar_service::CalendarEvent; +use crate::components::EventModal; #[derive(Properties, PartialEq)] pub struct CalendarProps { #[prop_or_default] - pub events: HashMap>, + pub events: HashMap>, } #[function_component] @@ -13,6 +15,7 @@ pub fn Calendar(props: &CalendarProps) -> Html { let today = Local::now().date_naive(); let current_month = use_state(|| today); let selected_day = use_state(|| today); + let selected_event = use_state(|| None::); let first_day_of_month = current_month.with_day(1).unwrap(); let days_in_month = get_days_in_month(*current_month); @@ -100,13 +103,23 @@ pub fn Calendar(props: &CalendarProps) -> Html {
{ events.iter().take(2).map(|event| { + let event_clone = event.clone(); + let selected_event_clone = selected_event.clone(); + let event_click = Callback::from(move |e: MouseEvent| { + e.stop_propagation(); // Prevent day selection + selected_event_clone.set(Some(event_clone.clone())); + }); + + let title = event.get_title(); html! { -
+
{ - if event.len() > 15 { - format!("{}...", &event[..12]) + if title.len() > 15 { + format!("{}...", &title[..12]) } else { - event.clone() + title } }
@@ -133,6 +146,17 @@ pub fn Calendar(props: &CalendarProps) -> Html { { render_next_month_days(days_from_prev_month.len(), days_in_month) }
+ + // Event details modal +
} } @@ -203,4 +227,5 @@ fn get_month_name(month: u32) -> &'static str { 12 => "December", _ => "Invalid" } -} \ No newline at end of file +} + diff --git a/src/components/event_modal.rs b/src/components/event_modal.rs new file mode 100644 index 0000000..3ad351a --- /dev/null +++ b/src/components/event_modal.rs @@ -0,0 +1,101 @@ +use yew::prelude::*; +use chrono::{DateTime, Utc}; +use crate::services::CalendarEvent; + +#[derive(Properties, PartialEq)] +pub struct EventModalProps { + pub event: Option, + pub on_close: Callback<()>, +} + +#[function_component] +pub fn EventModal(props: &EventModalProps) -> Html { + let close_modal = { + let on_close = props.on_close.clone(); + Callback::from(move |_| { + on_close.emit(()); + }) + }; + + let backdrop_click = { + let on_close = props.on_close.clone(); + Callback::from(move |e: MouseEvent| { + if e.target() == e.current_target() { + on_close.emit(()); + } + }) + }; + + if let Some(ref event) = props.event { + html! { + + } + } else { + html! {} + } +} + +fn format_datetime(dt: &DateTime, all_day: bool) -> String { + if all_day { + dt.format("%B %d, %Y").to_string() + } else { + dt.format("%B %d, %Y at %I:%M %p").to_string() + } +} \ No newline at end of file diff --git a/src/components/mod.rs b/src/components/mod.rs index ba99ef4..2da518a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,7 +1,9 @@ pub mod login; pub mod register; pub mod calendar; +pub mod event_modal; pub use login::Login; pub use register::Register; -pub use calendar::Calendar; \ No newline at end of file +pub use calendar::Calendar; +pub use event_modal::EventModal; \ No newline at end of file diff --git a/src/services/calendar_service.rs b/src/services/calendar_service.rs index e46163d..22ad37d 100644 --- a/src/services/calendar_service.rs +++ b/src/services/calendar_service.rs @@ -5,7 +5,7 @@ use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; use std::collections::HashMap; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct CalendarEvent { pub uid: String, pub summary: Option, @@ -91,16 +91,15 @@ impl CalendarService { } /// Convert events to a HashMap grouped by date for calendar display - pub fn group_events_by_date(events: Vec) -> HashMap> { + pub fn group_events_by_date(events: Vec) -> HashMap> { let mut grouped = HashMap::new(); for event in events { let date = event.get_date(); - let title = event.get_title(); grouped.entry(date) .or_insert_with(Vec::new) - .push(title); + .push(event); } grouped diff --git a/src/services/mod.rs b/src/services/mod.rs index 0f69ad1..cfbc455 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,3 +1,3 @@ pub mod calendar_service; -pub use calendar_service::CalendarService; \ No newline at end of file +pub use calendar_service::{CalendarService, CalendarEvent}; \ No newline at end of file