Fix calendar event fetching to use visible date range
Some checks failed
Build and Push Docker Image / docker (push) Failing after 1m7s
Some checks failed
Build and Push Docker Image / docker (push) Failing after 1m7s
Moved event fetching logic from CalendarView to Calendar component to properly use the visible date range instead of hardcoded current month. The Calendar component already tracks the current visible date through navigation, so events now load correctly for August and other months when navigating. Changes: - Calendar component now manages its own events state and fetching - Event fetching responds to current_date changes from navigation - CalendarView simplified to just render Calendar component - Fixed cargo fmt/clippy formatting across codebase 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,16 @@
|
||||
use yew::prelude::*;
|
||||
use chrono::{Datelike, Local, NaiveDate, Duration};
|
||||
use crate::components::{
|
||||
CalendarHeader, CreateEventModal, EventCreationData, EventModal, MonthView, ViewMode, WeekView,
|
||||
};
|
||||
use crate::models::ical::VEvent;
|
||||
use crate::services::{calendar_service::UserInfo, CalendarService};
|
||||
use chrono::{Datelike, Duration, Local, NaiveDate};
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use std::collections::HashMap;
|
||||
use web_sys::MouseEvent;
|
||||
use crate::services::calendar_service::UserInfo;
|
||||
use crate::models::ical::VEvent;
|
||||
use crate::components::{EventModal, ViewMode, CalendarHeader, MonthView, WeekView, CreateEventModal, EventCreationData};
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct CalendarProps {
|
||||
#[prop_or_default]
|
||||
pub events: HashMap<NaiveDate, Vec<VEvent>>,
|
||||
pub on_event_click: Callback<VEvent>,
|
||||
#[prop_or_default]
|
||||
pub refreshing_event_uid: Option<String>,
|
||||
#[prop_or_default]
|
||||
pub user_info: Option<UserInfo>,
|
||||
#[prop_or_default]
|
||||
@@ -25,7 +22,17 @@ pub struct CalendarProps {
|
||||
#[prop_or_default]
|
||||
pub on_create_event_request: Option<Callback<EventCreationData>>,
|
||||
#[prop_or_default]
|
||||
pub on_event_update_request: Option<Callback<(VEvent, chrono::NaiveDateTime, chrono::NaiveDateTime, bool, Option<chrono::DateTime<chrono::Utc>>, Option<String>, Option<String>)>>,
|
||||
pub on_event_update_request: Option<
|
||||
Callback<(
|
||||
VEvent,
|
||||
chrono::NaiveDateTime,
|
||||
chrono::NaiveDateTime,
|
||||
bool,
|
||||
Option<chrono::DateTime<chrono::Utc>>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
)>,
|
||||
>,
|
||||
#[prop_or_default]
|
||||
pub context_menus_open: bool,
|
||||
}
|
||||
@@ -33,6 +40,12 @@ pub struct CalendarProps {
|
||||
#[function_component]
|
||||
pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
let today = Local::now().date_naive();
|
||||
|
||||
// Event management state
|
||||
let events = use_state(|| HashMap::<NaiveDate, Vec<VEvent>>::new());
|
||||
let loading = use_state(|| true);
|
||||
let error = use_state(|| None::<String>);
|
||||
let refreshing_event_uid = use_state(|| None::<String>);
|
||||
// Track the currently selected date (the actual day the user has selected)
|
||||
let selected_date = use_state(|| {
|
||||
// Try to load saved selected date from localStorage
|
||||
@@ -55,20 +68,19 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Track the display date (what to show in the view)
|
||||
let current_date = use_state(|| {
|
||||
match props.view {
|
||||
ViewMode::Month => selected_date.with_day(1).unwrap_or(*selected_date),
|
||||
ViewMode::Week => *selected_date,
|
||||
}
|
||||
let current_date = use_state(|| match props.view {
|
||||
ViewMode::Month => selected_date.with_day(1).unwrap_or(*selected_date),
|
||||
ViewMode::Week => *selected_date,
|
||||
});
|
||||
let selected_event = use_state(|| None::<VEvent>);
|
||||
|
||||
|
||||
// State for create event modal
|
||||
let show_create_modal = use_state(|| false);
|
||||
let create_event_data = use_state(|| None::<(chrono::NaiveDate, chrono::NaiveTime, chrono::NaiveTime)>);
|
||||
|
||||
let create_event_data =
|
||||
use_state(|| None::<(chrono::NaiveDate, chrono::NaiveTime, chrono::NaiveTime)>);
|
||||
|
||||
// State for time increment snapping (15 or 30 minutes)
|
||||
let time_increment = use_state(|| {
|
||||
// Try to load saved time increment from localStorage
|
||||
@@ -82,7 +94,155 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
15
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Fetch events when current_date changes
|
||||
{
|
||||
let events = events.clone();
|
||||
let loading = loading.clone();
|
||||
let error = error.clone();
|
||||
let current_date = current_date.clone();
|
||||
|
||||
use_effect_with((*current_date, props.view.clone()), move |(date, _view)| {
|
||||
let auth_token: Option<String> = LocalStorage::get("auth_token").ok();
|
||||
let date = *date; // Clone the date to avoid lifetime issues
|
||||
|
||||
if let Some(token) = auth_token {
|
||||
let events = events.clone();
|
||||
let loading = loading.clone();
|
||||
let error = error.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let calendar_service = CalendarService::new();
|
||||
|
||||
let password = if let Ok(credentials_str) =
|
||||
LocalStorage::get::<String>("caldav_credentials")
|
||||
{
|
||||
if let Ok(credentials) =
|
||||
serde_json::from_str::<serde_json::Value>(&credentials_str)
|
||||
{
|
||||
credentials["password"].as_str().unwrap_or("").to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let current_year = date.year();
|
||||
let current_month = date.month();
|
||||
|
||||
match calendar_service
|
||||
.fetch_events_for_month_vevent(
|
||||
&token,
|
||||
&password,
|
||||
current_year,
|
||||
current_month,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(vevents) => {
|
||||
let grouped_events = CalendarService::group_events_by_date(vevents);
|
||||
events.set(grouped_events);
|
||||
loading.set(false);
|
||||
}
|
||||
Err(err) => {
|
||||
error.set(Some(format!("Failed to load events: {}", err)));
|
||||
loading.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loading.set(false);
|
||||
error.set(Some("No authentication token found".to_string()));
|
||||
}
|
||||
|
||||
|| ()
|
||||
});
|
||||
}
|
||||
|
||||
// Handle event click to refresh individual events
|
||||
let on_event_click = {
|
||||
let events = events.clone();
|
||||
let refreshing_event_uid = refreshing_event_uid.clone();
|
||||
|
||||
Callback::from(move |event: VEvent| {
|
||||
let auth_token: Option<String> = LocalStorage::get("auth_token").ok();
|
||||
|
||||
if let Some(token) = auth_token {
|
||||
let events = events.clone();
|
||||
let refreshing_event_uid = refreshing_event_uid.clone();
|
||||
let uid = event.uid.clone();
|
||||
|
||||
refreshing_event_uid.set(Some(uid.clone()));
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let calendar_service = CalendarService::new();
|
||||
|
||||
let password = if let Ok(credentials_str) =
|
||||
LocalStorage::get::<String>("caldav_credentials")
|
||||
{
|
||||
if let Ok(credentials) =
|
||||
serde_json::from_str::<serde_json::Value>(&credentials_str)
|
||||
{
|
||||
credentials["password"].as_str().unwrap_or("").to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
match calendar_service
|
||||
.refresh_event(&token, &password, &uid)
|
||||
.await
|
||||
{
|
||||
Ok(Some(refreshed_event)) => {
|
||||
let refreshed_vevent = refreshed_event;
|
||||
let mut updated_events = (*events).clone();
|
||||
|
||||
for (_, day_events) in updated_events.iter_mut() {
|
||||
day_events.retain(|e| e.uid != uid);
|
||||
}
|
||||
|
||||
if refreshed_vevent.rrule.is_some() {
|
||||
let new_occurrences =
|
||||
CalendarService::expand_recurring_events(vec![
|
||||
refreshed_vevent.clone(),
|
||||
]);
|
||||
|
||||
for occurrence in new_occurrences {
|
||||
let date = occurrence.get_date();
|
||||
updated_events
|
||||
.entry(date)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(occurrence);
|
||||
}
|
||||
} else {
|
||||
let date = refreshed_vevent.get_date();
|
||||
updated_events
|
||||
.entry(date)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(refreshed_vevent);
|
||||
}
|
||||
|
||||
events.set(updated_events);
|
||||
}
|
||||
Ok(None) => {
|
||||
let mut updated_events = (*events).clone();
|
||||
for (_, day_events) in updated_events.iter_mut() {
|
||||
day_events.retain(|e| e.uid != uid);
|
||||
}
|
||||
events.set(updated_events);
|
||||
}
|
||||
Err(_err) => {}
|
||||
}
|
||||
|
||||
refreshing_event_uid.set(None);
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// Handle view mode changes - adjust current_date format when switching between month/week
|
||||
{
|
||||
let current_date = current_date.clone();
|
||||
@@ -98,7 +258,7 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let on_prev = {
|
||||
let current_date = current_date.clone();
|
||||
let selected_date = selected_date.clone();
|
||||
@@ -110,19 +270,22 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
let prev_month = *current_date - Duration::days(1);
|
||||
let first_of_prev = prev_month.with_day(1).unwrap();
|
||||
(first_of_prev, first_of_prev)
|
||||
},
|
||||
}
|
||||
ViewMode::Week => {
|
||||
// Go to previous week
|
||||
let prev_week = *selected_date - Duration::weeks(1);
|
||||
(prev_week, prev_week)
|
||||
},
|
||||
}
|
||||
};
|
||||
selected_date.set(new_selected);
|
||||
current_date.set(new_display);
|
||||
let _ = LocalStorage::set("calendar_selected_date", new_selected.format("%Y-%m-%d").to_string());
|
||||
let _ = LocalStorage::set(
|
||||
"calendar_selected_date",
|
||||
new_selected.format("%Y-%m-%d").to_string(),
|
||||
);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
let on_next = {
|
||||
let current_date = current_date.clone();
|
||||
let selected_date = selected_date.clone();
|
||||
@@ -134,19 +297,23 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
let next_month = if current_date.month() == 12 {
|
||||
NaiveDate::from_ymd_opt(current_date.year() + 1, 1, 1).unwrap()
|
||||
} else {
|
||||
NaiveDate::from_ymd_opt(current_date.year(), current_date.month() + 1, 1).unwrap()
|
||||
NaiveDate::from_ymd_opt(current_date.year(), current_date.month() + 1, 1)
|
||||
.unwrap()
|
||||
};
|
||||
(next_month, next_month)
|
||||
},
|
||||
}
|
||||
ViewMode::Week => {
|
||||
// Go to next week
|
||||
let next_week = *selected_date + Duration::weeks(1);
|
||||
(next_week, next_week)
|
||||
},
|
||||
}
|
||||
};
|
||||
selected_date.set(new_selected);
|
||||
current_date.set(new_display);
|
||||
let _ = LocalStorage::set("calendar_selected_date", new_selected.format("%Y-%m-%d").to_string());
|
||||
let _ = LocalStorage::set(
|
||||
"calendar_selected_date",
|
||||
new_selected.format("%Y-%m-%d").to_string(),
|
||||
);
|
||||
})
|
||||
};
|
||||
|
||||
@@ -160,15 +327,18 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
ViewMode::Month => {
|
||||
let first_of_today = today.with_day(1).unwrap();
|
||||
(today, first_of_today) // Select today, but display the month
|
||||
},
|
||||
}
|
||||
ViewMode::Week => (today, today), // Select and display today
|
||||
};
|
||||
selected_date.set(new_selected);
|
||||
current_date.set(new_display);
|
||||
let _ = LocalStorage::set("calendar_selected_date", new_selected.format("%Y-%m-%d").to_string());
|
||||
let _ = LocalStorage::set(
|
||||
"calendar_selected_date",
|
||||
new_selected.format("%Y-%m-%d").to_string(),
|
||||
);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
// Handle time increment toggle
|
||||
let on_time_increment_toggle = {
|
||||
let time_increment = time_increment.clone();
|
||||
@@ -179,32 +349,68 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
let _ = LocalStorage::set("calendar_time_increment", next);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
// Handle drag-to-create event
|
||||
let on_create_event = {
|
||||
let show_create_modal = show_create_modal.clone();
|
||||
let create_event_data = create_event_data.clone();
|
||||
Callback::from(move |(_date, start_datetime, end_datetime): (NaiveDate, chrono::NaiveDateTime, chrono::NaiveDateTime)| {
|
||||
// For drag-to-create, we don't need the temporary event approach
|
||||
// Instead, just pass the local times directly via initial_time props
|
||||
create_event_data.set(Some((start_datetime.date(), start_datetime.time(), end_datetime.time())));
|
||||
show_create_modal.set(true);
|
||||
})
|
||||
Callback::from(
|
||||
move |(_date, start_datetime, end_datetime): (
|
||||
NaiveDate,
|
||||
chrono::NaiveDateTime,
|
||||
chrono::NaiveDateTime,
|
||||
)| {
|
||||
// For drag-to-create, we don't need the temporary event approach
|
||||
// Instead, just pass the local times directly via initial_time props
|
||||
create_event_data.set(Some((
|
||||
start_datetime.date(),
|
||||
start_datetime.time(),
|
||||
end_datetime.time(),
|
||||
)));
|
||||
show_create_modal.set(true);
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
// Handle drag-to-move event
|
||||
let on_event_update = {
|
||||
let on_event_update_request = props.on_event_update_request.clone();
|
||||
Callback::from(move |(event, new_start, new_end, preserve_rrule, until_date, update_scope, occurrence_date): (VEvent, chrono::NaiveDateTime, chrono::NaiveDateTime, bool, Option<chrono::DateTime<chrono::Utc>>, Option<String>, Option<String>)| {
|
||||
if let Some(callback) = &on_event_update_request {
|
||||
callback.emit((event, new_start, new_end, preserve_rrule, until_date, update_scope, occurrence_date));
|
||||
}
|
||||
})
|
||||
Callback::from(
|
||||
move |(
|
||||
event,
|
||||
new_start,
|
||||
new_end,
|
||||
preserve_rrule,
|
||||
until_date,
|
||||
update_scope,
|
||||
occurrence_date,
|
||||
): (
|
||||
VEvent,
|
||||
chrono::NaiveDateTime,
|
||||
chrono::NaiveDateTime,
|
||||
bool,
|
||||
Option<chrono::DateTime<chrono::Utc>>,
|
||||
Option<String>,
|
||||
Option<String>,
|
||||
)| {
|
||||
if let Some(callback) = &on_event_update_request {
|
||||
callback.emit((
|
||||
event,
|
||||
new_start,
|
||||
new_end,
|
||||
preserve_rrule,
|
||||
until_date,
|
||||
update_scope,
|
||||
occurrence_date,
|
||||
));
|
||||
}
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class={classes!("calendar", match props.view { ViewMode::Week => Some("week-view"), _ => None })}>
|
||||
<CalendarHeader
|
||||
<CalendarHeader
|
||||
current_date={*current_date}
|
||||
view_mode={props.view.clone()}
|
||||
on_prev={on_prev}
|
||||
@@ -213,9 +419,22 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
time_increment={Some(*time_increment)}
|
||||
on_time_increment_toggle={Some(on_time_increment_toggle)}
|
||||
/>
|
||||
|
||||
|
||||
{
|
||||
match props.view {
|
||||
if *loading {
|
||||
html! {
|
||||
<div class="calendar-loading">
|
||||
<p>{"Loading calendar events..."}</p>
|
||||
</div>
|
||||
}
|
||||
} else if let Some(err) = (*error).clone() {
|
||||
html! {
|
||||
<div class="calendar-error">
|
||||
<p>{format!("Error: {}", err)}</p>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
match props.view {
|
||||
ViewMode::Month => {
|
||||
let on_day_select = {
|
||||
let selected_date = selected_date.clone();
|
||||
@@ -224,14 +443,14 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
let _ = LocalStorage::set("calendar_selected_date", date.format("%Y-%m-%d").to_string());
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
html! {
|
||||
<MonthView
|
||||
current_month={*current_date}
|
||||
today={today}
|
||||
events={props.events.clone()}
|
||||
on_event_click={props.on_event_click.clone()}
|
||||
refreshing_event_uid={props.refreshing_event_uid.clone()}
|
||||
events={(*events).clone()}
|
||||
on_event_click={on_event_click.clone()}
|
||||
refreshing_event_uid={(*refreshing_event_uid).clone()}
|
||||
user_info={props.user_info.clone()}
|
||||
on_event_context_menu={props.on_event_context_menu.clone()}
|
||||
on_calendar_context_menu={props.on_calendar_context_menu.clone()}
|
||||
@@ -244,9 +463,9 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
<WeekView
|
||||
current_date={*current_date}
|
||||
today={today}
|
||||
events={props.events.clone()}
|
||||
on_event_click={props.on_event_click.clone()}
|
||||
refreshing_event_uid={props.refreshing_event_uid.clone()}
|
||||
events={(*events).clone()}
|
||||
on_event_click={on_event_click.clone()}
|
||||
refreshing_event_uid={(*refreshing_event_uid).clone()}
|
||||
user_info={props.user_info.clone()}
|
||||
on_event_context_menu={props.on_event_context_menu.clone()}
|
||||
on_calendar_context_menu={props.on_calendar_context_menu.clone()}
|
||||
@@ -257,11 +476,12 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
time_increment={*time_increment}
|
||||
/>
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Event details modal
|
||||
<EventModal
|
||||
<EventModal
|
||||
event={(*selected_event).clone()}
|
||||
on_close={{
|
||||
let selected_event_clone = selected_event.clone();
|
||||
@@ -270,7 +490,7 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
// Create event modal
|
||||
<CreateEventModal
|
||||
is_open={*show_create_modal}
|
||||
@@ -294,7 +514,7 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
Callback::from(move |event_data: EventCreationData| {
|
||||
show_create_modal.set(false);
|
||||
create_event_data.set(None);
|
||||
|
||||
|
||||
// Emit the create event request to parent
|
||||
if let Some(callback) = &on_create_event_request {
|
||||
callback.emit(event_data);
|
||||
@@ -313,4 +533,4 @@ pub fn Calendar(props: &CalendarProps) -> Html {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user