tenis booking site done by claude
This commit is contained in:
225
ht_booking/src/controllers/calendar.rs
Normal file
225
ht_booking/src/controllers/calendar.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::unused_async)]
|
||||
//! Public, read-only week-view calendar.
|
||||
//!
|
||||
//! Shows a 7-day grid (one column per day, one row per hour) for a single
|
||||
//! court. Booked slots are coloured; free slots are blank. The same grid is
|
||||
//! reused by the admin dashboard with `is_admin = true`.
|
||||
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
use chrono::{Datelike, Duration, NaiveDate, Utc};
|
||||
use loco_rs::prelude::*;
|
||||
use sea_orm::QueryOrder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::_entities::{bookings, courts};
|
||||
|
||||
/// First bookable hour (06:00).
|
||||
pub const FIRST_HOUR: i32 = 6;
|
||||
/// Last bookable hour slot start (21:00 — i.e. the 21:00-22:00 slot).
|
||||
pub const LAST_HOUR: i32 = 21;
|
||||
|
||||
const DAY_KEYS: [&str; 7] = [
|
||||
"day-mon", "day-tue", "day-wed", "day-thu", "day-fri", "day-sat", "day-sun",
|
||||
];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CalQuery {
|
||||
pub court: Option<i32>,
|
||||
pub week: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CourtOpt {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub selected: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DayHead {
|
||||
pub date: String,
|
||||
pub key: String,
|
||||
pub num: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Cell {
|
||||
pub date: String,
|
||||
pub hour: i32,
|
||||
pub booked: bool,
|
||||
pub color: String,
|
||||
pub booking_id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Row {
|
||||
pub hour_label: String,
|
||||
pub cells: Vec<Cell>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CalendarPage {
|
||||
pub lang: String,
|
||||
pub is_admin: bool,
|
||||
pub base_path: String,
|
||||
pub has_courts: bool,
|
||||
pub courts: Vec<CourtOpt>,
|
||||
pub court_id: i32,
|
||||
pub court_name: String,
|
||||
pub week: String,
|
||||
pub week_label: String,
|
||||
pub prev_week: String,
|
||||
pub this_week: String,
|
||||
pub next_week: String,
|
||||
pub days: Vec<DayHead>,
|
||||
pub rows: Vec<Row>,
|
||||
}
|
||||
|
||||
/// Resolves the UI language from the `lang` cookie (`sk` or `en`, default `en`).
|
||||
#[must_use]
|
||||
pub fn current_lang(jar: &CookieJar) -> String {
|
||||
match jar.get("lang").map(|c| c.value().to_string()) {
|
||||
Some(ref l) if l == "sk" => "sk".to_string(),
|
||||
_ => "en".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn monday_of(date: NaiveDate) -> NaiveDate {
|
||||
date - Duration::days(i64::from(date.weekday().num_days_from_monday()))
|
||||
}
|
||||
|
||||
fn week_monday(week: Option<&str>) -> NaiveDate {
|
||||
let base = week
|
||||
.and_then(|w| NaiveDate::parse_from_str(w, "%Y-%m-%d").ok())
|
||||
.unwrap_or_else(|| Utc::now().date_naive());
|
||||
monday_of(base)
|
||||
}
|
||||
|
||||
/// Builds the calendar grid for the selected court and week.
|
||||
pub async fn build_calendar(
|
||||
ctx: &AppContext,
|
||||
lang: &str,
|
||||
is_admin: bool,
|
||||
q_court: Option<i32>,
|
||||
q_week: Option<String>,
|
||||
) -> Result<CalendarPage> {
|
||||
let court_list = courts::Entity::find()
|
||||
.order_by_asc(courts::Column::Id)
|
||||
.all(&ctx.db)
|
||||
.await?;
|
||||
|
||||
let selected = q_court
|
||||
.filter(|id| court_list.iter().any(|c| c.id == *id))
|
||||
.or_else(|| court_list.first().map(|c| c.id))
|
||||
.unwrap_or(0);
|
||||
|
||||
let courts_opts: Vec<CourtOpt> = court_list
|
||||
.iter()
|
||||
.map(|c| CourtOpt {
|
||||
id: c.id,
|
||||
name: c.name.clone().unwrap_or_else(|| format!("Court {}", c.id)),
|
||||
selected: c.id == selected,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let court_name = courts_opts
|
||||
.iter()
|
||||
.find(|c| c.selected)
|
||||
.map(|c| c.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let monday = week_monday(q_week.as_deref());
|
||||
let sunday = monday + Duration::days(6);
|
||||
|
||||
let days: Vec<DayHead> = (0..7i64)
|
||||
.map(|i| {
|
||||
let d = monday + Duration::days(i);
|
||||
DayHead {
|
||||
date: d.format("%Y-%m-%d").to_string(),
|
||||
key: DAY_KEYS[i as usize].to_string(),
|
||||
num: d.day(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let week_bookings = if selected == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
bookings::Entity::find()
|
||||
.filter(bookings::Column::CourtId.eq(selected))
|
||||
.filter(bookings::Column::Date.gte(monday))
|
||||
.filter(bookings::Column::Date.lte(sunday))
|
||||
.all(&ctx.db)
|
||||
.await?
|
||||
};
|
||||
|
||||
let rows: Vec<Row> = (FIRST_HOUR..=LAST_HOUR)
|
||||
.map(|hour| {
|
||||
let cells = (0..7i64)
|
||||
.map(|i| {
|
||||
let d = monday + Duration::days(i);
|
||||
let iso = d.format("%Y-%m-%d").to_string();
|
||||
match week_bookings.iter().find(|b| b.date == d && b.hour == hour) {
|
||||
Some(b) => Cell {
|
||||
date: iso,
|
||||
hour,
|
||||
booked: true,
|
||||
color: b.color.clone(),
|
||||
booking_id: b.id,
|
||||
name: b.name.clone(),
|
||||
},
|
||||
None => Cell {
|
||||
date: iso,
|
||||
hour,
|
||||
booked: false,
|
||||
color: String::new(),
|
||||
booking_id: 0,
|
||||
name: String::new(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Row {
|
||||
hour_label: format!("{hour:02}:00"),
|
||||
cells,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(CalendarPage {
|
||||
lang: lang.to_string(),
|
||||
is_admin,
|
||||
base_path: if is_admin { "/admin" } else { "/" }.to_string(),
|
||||
has_courts: !courts_opts.is_empty(),
|
||||
courts: courts_opts,
|
||||
court_id: selected,
|
||||
court_name,
|
||||
week: monday.format("%Y-%m-%d").to_string(),
|
||||
week_label: format!("{} – {}", monday.format("%d %b"), sunday.format("%d %b %Y")),
|
||||
prev_week: (monday - Duration::days(7)).format("%Y-%m-%d").to_string(),
|
||||
this_week: monday_of(Utc::now().date_naive())
|
||||
.format("%Y-%m-%d")
|
||||
.to_string(),
|
||||
next_week: (monday + Duration::days(7)).format("%Y-%m-%d").to_string(),
|
||||
days,
|
||||
rows,
|
||||
})
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn index(
|
||||
ViewEngine(v): ViewEngine<TeraView>,
|
||||
State(ctx): State<AppContext>,
|
||||
jar: CookieJar,
|
||||
Query(q): Query<CalQuery>,
|
||||
) -> Result<Response> {
|
||||
let lang = current_lang(&jar);
|
||||
let page = build_calendar(&ctx, &lang, false, q.court, q.week).await?;
|
||||
format::render().view(&v, "calendar/week.html", &page)
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new().add("/", get(index))
|
||||
}
|
||||
Reference in New Issue
Block a user