...
This commit is contained in:
389
actix_mvc_app/src/controllers/calendar.rs
Normal file
389
actix_mvc_app/src/controllers/calendar.rs
Normal 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>,
|
||||
}
|
@@ -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;
|
Reference in New Issue
Block a user