This commit is contained in:
2025-04-21 10:52:19 +02:00
parent 1fa0b30169
commit 4b637b7e04
14 changed files with 1023 additions and 5 deletions

View File

@@ -0,0 +1,389 @@
use actix_web::{web, HttpResponse, Responder, Result};
use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use tera::Tera;
use crate::models::{CalendarEvent, CalendarViewMode};
use crate::utils::RedisCalendarService;
/// Controller for handling calendar-related routes
pub struct CalendarController;
impl CalendarController {
/// Handles the calendar page route
pub async fn calendar(
tmpl: web::Data<Tera>,
query: web::Query<CalendarQuery>,
) -> Result<impl Responder> {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "calendar");
// Parse the view mode from the query parameters
let view_mode = CalendarViewMode::from_str(&query.view.clone().unwrap_or_else(|| "month".to_string()));
ctx.insert("view_mode", &view_mode.to_str());
// Parse the date from the query parameters or use the current date
let date = if let Some(date_str) = &query.date {
match NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
Ok(naive_date) => Utc.from_utc_date(&naive_date).and_hms_opt(0, 0, 0).unwrap(),
Err(_) => Utc::now(),
}
} else {
Utc::now()
};
ctx.insert("current_date", &date.format("%Y-%m-%d").to_string());
ctx.insert("current_year", &date.year());
ctx.insert("current_month", &date.month());
ctx.insert("current_day", &date.day());
// Get events for the current view
let (start_date, end_date) = match view_mode {
CalendarViewMode::Year => {
let start = Utc.with_ymd_and_hms(date.year(), 1, 1, 0, 0, 0).unwrap();
let end = Utc.with_ymd_and_hms(date.year(), 12, 31, 23, 59, 59).unwrap();
(start, end)
},
CalendarViewMode::Month => {
let start = Utc.with_ymd_and_hms(date.year(), date.month(), 1, 0, 0, 0).unwrap();
let last_day = Self::last_day_of_month(date.year(), date.month());
let end = Utc.with_ymd_and_hms(date.year(), date.month(), last_day, 23, 59, 59).unwrap();
(start, end)
},
CalendarViewMode::Week => {
// Calculate the start of the week (Sunday)
let weekday = date.weekday().num_days_from_sunday();
let start_date = date.date_naive().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap().pred_opt().unwrap();
let start = Utc.from_utc_date(&start_date).and_hms_opt(0, 0, 0).unwrap();
let end = start + chrono::Duration::days(7);
(start, end)
},
CalendarViewMode::Day => {
let start = Utc.with_ymd_and_hms(date.year(), date.month(), date.day(), 0, 0, 0).unwrap();
let end = Utc.with_ymd_and_hms(date.year(), date.month(), date.day(), 23, 59, 59).unwrap();
(start, end)
},
};
// Get events from Redis
let events = match RedisCalendarService::get_events_in_range(start_date, end_date) {
Ok(events) => events,
Err(e) => {
log::error!("Failed to get events from Redis: {}", e);
vec![]
}
};
ctx.insert("events", &events);
// Generate calendar data based on the view mode
match view_mode {
CalendarViewMode::Year => {
let months = (1..=12).map(|month| {
let month_name = match month {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
_ => "",
};
let month_events = events.iter()
.filter(|event| {
event.start_time.month() == month || event.end_time.month() == month
})
.cloned()
.collect::<Vec<_>>();
CalendarMonth {
month,
name: month_name.to_string(),
events: month_events,
}
}).collect::<Vec<_>>();
ctx.insert("months", &months);
},
CalendarViewMode::Month => {
let days_in_month = Self::last_day_of_month(date.year(), date.month());
let first_day = Utc.with_ymd_and_hms(date.year(), date.month(), 1, 0, 0, 0).unwrap();
let first_weekday = first_day.weekday().num_days_from_sunday();
let mut calendar_days = Vec::new();
// Add empty days for the start of the month
for _ in 0..first_weekday {
calendar_days.push(CalendarDay {
day: 0,
events: vec![],
is_current_month: false,
});
}
// Add days for the current month
for day in 1..=days_in_month {
let day_events = events.iter()
.filter(|event| {
let day_start = Utc.with_ymd_and_hms(date.year(), date.month(), day, 0, 0, 0).unwrap();
let day_end = Utc.with_ymd_and_hms(date.year(), date.month(), day, 23, 59, 59).unwrap();
(event.start_time <= day_end && event.end_time >= day_start) ||
(event.all_day && event.start_time.day() <= day && event.end_time.day() >= day)
})
.cloned()
.collect::<Vec<_>>();
calendar_days.push(CalendarDay {
day,
events: day_events,
is_current_month: true,
});
}
// Fill out the rest of the calendar grid (6 rows of 7 days)
let remaining_days = 42 - calendar_days.len();
for day in 1..=remaining_days {
calendar_days.push(CalendarDay {
day: day as u32,
events: vec![],
is_current_month: false,
});
}
ctx.insert("calendar_days", &calendar_days);
ctx.insert("month_name", &Self::month_name(date.month()));
},
CalendarViewMode::Week => {
// Calculate the start of the week (Sunday)
let weekday = date.weekday().num_days_from_sunday();
let week_start = date - chrono::Duration::days(weekday as i64);
let mut week_days = Vec::new();
for i in 0..7 {
let day_date = week_start + chrono::Duration::days(i);
let day_events = events.iter()
.filter(|event| {
let day_start = Utc.with_ymd_and_hms(day_date.year(), day_date.month(), day_date.day(), 0, 0, 0).unwrap();
let day_end = Utc.with_ymd_and_hms(day_date.year(), day_date.month(), day_date.day(), 23, 59, 59).unwrap();
(event.start_time <= day_end && event.end_time >= day_start) ||
(event.all_day && event.start_time.day() <= day_date.day() && event.end_time.day() >= day_date.day())
})
.cloned()
.collect::<Vec<_>>();
week_days.push(CalendarDay {
day: day_date.day(),
events: day_events,
is_current_month: day_date.month() == date.month(),
});
}
ctx.insert("week_days", &week_days);
},
CalendarViewMode::Day => {
log::info!("Day view selected");
ctx.insert("day_name", &Self::day_name(date.weekday().num_days_from_sunday()));
// Add debug info
log::info!("Events count: {}", events.len());
log::info!("Current date: {}", date.format("%Y-%m-%d"));
log::info!("Day name: {}", Self::day_name(date.weekday().num_days_from_sunday()));
},
}
let rendered = tmpl.render("calendar/index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the new event page route
pub async fn new_event(tmpl: web::Data<Tera>) -> Result<impl Responder> {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "calendar");
let rendered = tmpl.render("calendar/new_event.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}
/// Handles the create event route
pub async fn create_event(
form: web::Form<EventForm>,
tmpl: web::Data<Tera>,
) -> Result<impl Responder> {
// Parse the start and end times
let start_time = match DateTime::parse_from_rfc3339(&form.start_time) {
Ok(dt) => dt.with_timezone(&Utc),
Err(e) => {
log::error!("Failed to parse start time: {}", e);
return Ok(HttpResponse::BadRequest().body("Invalid start time"));
}
};
let end_time = match DateTime::parse_from_rfc3339(&form.end_time) {
Ok(dt) => dt.with_timezone(&Utc),
Err(e) => {
log::error!("Failed to parse end time: {}", e);
return Ok(HttpResponse::BadRequest().body("Invalid end time"));
}
};
// Create the event
let event = CalendarEvent::new(
form.title.clone(),
form.description.clone(),
start_time,
end_time,
Some(form.color.clone()),
form.all_day,
None, // User ID would come from session in a real app
);
// Save the event to Redis
match RedisCalendarService::save_event(&event) {
Ok(_) => {
// Redirect to the calendar page
Ok(HttpResponse::SeeOther()
.append_header(("Location", "/calendar"))
.finish())
},
Err(e) => {
log::error!("Failed to save event to Redis: {}", e);
// Show an error message
let mut ctx = tera::Context::new();
ctx.insert("active_page", "calendar");
ctx.insert("error", "Failed to save event");
let rendered = tmpl.render("calendar/new_event.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::InternalServerError().content_type("text/html").body(rendered))
}
}
}
/// Handles the delete event route
pub async fn delete_event(
path: web::Path<String>,
) -> Result<impl Responder> {
let id = path.into_inner();
// Delete the event from Redis
match RedisCalendarService::delete_event(&id) {
Ok(_) => {
// Redirect to the calendar page
Ok(HttpResponse::SeeOther()
.append_header(("Location", "/calendar"))
.finish())
},
Err(e) => {
log::error!("Failed to delete event from Redis: {}", e);
Ok(HttpResponse::InternalServerError().body("Failed to delete event"))
}
}
}
/// Returns the last day of the month
fn last_day_of_month(year: i32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
29
} else {
28
}
},
_ => 30, // Default to 30 days
}
}
/// Returns the name of the month
fn month_name(month: u32) -> &'static str {
match month {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
_ => "",
}
}
/// Returns the name of the day
fn day_name(day: u32) -> &'static str {
match day {
0 => "Sunday",
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 => "Saturday",
_ => "",
}
}
}
/// Query parameters for the calendar page
#[derive(Debug, Deserialize)]
pub struct CalendarQuery {
pub view: Option<String>,
pub date: Option<String>,
}
/// Form data for creating an event
#[derive(Debug, Deserialize)]
pub struct EventForm {
pub title: String,
pub description: String,
pub start_time: String,
pub end_time: String,
pub color: String,
pub all_day: bool,
}
/// Represents a day in the calendar
#[derive(Debug, Serialize)]
struct CalendarDay {
day: u32,
events: Vec<CalendarEvent>,
is_current_month: bool,
}
/// Represents a month in the calendar
#[derive(Debug, Serialize)]
struct CalendarMonth {
month: u32,
name: String,
events: Vec<CalendarEvent>,
}

View File

@@ -2,8 +2,10 @@
pub mod home;
pub mod auth;
pub mod ticket;
pub mod calendar;
// Re-export controllers for easier imports
pub use home::HomeController;
pub use auth::AuthController;
pub use ticket::TicketController;
pub use ticket::TicketController;
pub use calendar::CalendarController;