Implement complete CalDAV events integration
Added full CalDAV integration to display real calendar events from Baikal server: Backend changes: - Added CalDAV client with iCalendar parsing and XML handling - Created /api/calendar/events endpoint with authentication - Fixed multiline regex patterns for namespace-prefixed XML tags - Added proper calendar path discovery and event filtering Frontend changes: - Created CalendarService for API communication - Updated calendar UI to display events as blue boxes with titles - Added loading states and error handling - Integrated real-time event fetching on calendar load Now successfully displays 3 test events from the Baikal server with proper date formatting and responsive design. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
108
src/services/calendar_service.rs
Normal file
108
src/services/calendar_service.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use chrono::{DateTime, Utc, NaiveDate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CalendarEvent {
|
||||
pub uid: String,
|
||||
pub summary: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub start: DateTime<Utc>,
|
||||
pub end: Option<DateTime<Utc>>,
|
||||
pub location: Option<String>,
|
||||
pub status: String,
|
||||
pub all_day: bool,
|
||||
}
|
||||
|
||||
impl CalendarEvent {
|
||||
/// Get the date for this event (for calendar display)
|
||||
pub fn get_date(&self) -> NaiveDate {
|
||||
if self.all_day {
|
||||
self.start.date_naive()
|
||||
} else {
|
||||
self.start.date_naive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get display title for the event
|
||||
pub fn get_title(&self) -> String {
|
||||
self.summary.clone().unwrap_or_else(|| "Untitled Event".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CalendarService {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl CalendarService {
|
||||
pub fn new() -> Self {
|
||||
let base_url = option_env!("BACKEND_API_URL")
|
||||
.unwrap_or("http://localhost:3000/api")
|
||||
.to_string();
|
||||
|
||||
Self { base_url }
|
||||
}
|
||||
|
||||
/// Fetch calendar events for a specific month
|
||||
pub async fn fetch_events_for_month(
|
||||
&self,
|
||||
token: &str,
|
||||
year: i32,
|
||||
month: u32
|
||||
) -> Result<Vec<CalendarEvent>, String> {
|
||||
let window = web_sys::window().ok_or("No global window exists")?;
|
||||
|
||||
let opts = RequestInit::new();
|
||||
opts.set_method("GET");
|
||||
opts.set_mode(RequestMode::Cors);
|
||||
|
||||
let url = format!("{}/calendar/events?year={}&month={}", self.base_url, year, month);
|
||||
let request = Request::new_with_str_and_init(&url, &opts)
|
||||
.map_err(|e| format!("Request creation failed: {:?}", e))?;
|
||||
|
||||
request.headers().set("Authorization", &format!("Bearer {}", token))
|
||||
.map_err(|e| format!("Authorization header setting failed: {:?}", e))?;
|
||||
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request))
|
||||
.await
|
||||
.map_err(|e| format!("Network request failed: {:?}", e))?;
|
||||
|
||||
let resp: Response = resp_value.dyn_into()
|
||||
.map_err(|e| format!("Response cast failed: {:?}", e))?;
|
||||
|
||||
let text = JsFuture::from(resp.text()
|
||||
.map_err(|e| format!("Text extraction failed: {:?}", e))?)
|
||||
.await
|
||||
.map_err(|e| format!("Text promise failed: {:?}", e))?;
|
||||
|
||||
let text_string = text.as_string()
|
||||
.ok_or("Response text is not a string")?;
|
||||
|
||||
if resp.ok() {
|
||||
let events: Vec<CalendarEvent> = serde_json::from_str(&text_string)
|
||||
.map_err(|e| format!("JSON parsing failed: {}", e))?;
|
||||
Ok(events)
|
||||
} else {
|
||||
Err(format!("Request failed with status {}: {}", resp.status(), text_string))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert events to a HashMap grouped by date for calendar display
|
||||
pub fn group_events_by_date(events: Vec<CalendarEvent>) -> HashMap<NaiveDate, Vec<String>> {
|
||||
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);
|
||||
}
|
||||
|
||||
grouped
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user