From 9f9b7167810b4164ef690795fdb68c3a20ba50bf Mon Sep 17 00:00:00 2001 From: Josef Rokos Date: Sun, 26 May 2024 21:07:09 +0200 Subject: [PATCH] Added setting for closing days. --- migrations/04_closing_time.sql | 5 ++ src/backend/data.rs | 27 +++++++++- src/backend/opening_hours.rs | 68 ++++++++++++++++++++++++- src/backend/reservation.rs | 5 +- src/locales/catalogues.rs | 4 ++ src/pages/hours_edit.rs | 47 ++++++++++++++++- src/pages/opening_hours.rs | 33 ++++++++++-- src/pages/public.rs | 93 +++++++++++++++++++--------------- 8 files changed, 234 insertions(+), 48 deletions(-) create mode 100644 migrations/04_closing_time.sql diff --git a/migrations/04_closing_time.sql b/migrations/04_closing_time.sql new file mode 100644 index 0000000..eaceb74 --- /dev/null +++ b/migrations/04_closing_time.sql @@ -0,0 +1,5 @@ +CREATE TABLE closing_time ( + id SERIAL PRIMARY KEY, + from_date DATE, + to_date DATE +) \ No newline at end of file diff --git a/src/backend/data.rs b/src/backend/data.rs index fef715c..b23ae2c 100644 --- a/src/backend/data.rs +++ b/src/backend/data.rs @@ -348,7 +348,8 @@ impl ResProperty { pub struct PublicFormData { pub property: ResProperty, pub hours: Vec, - pub reservations: Vec + pub reservations: Vec, + pub closing_days: Option } fn empty_slots() -> Vec { @@ -637,3 +638,27 @@ impl Appearance { self.id } } + +pub fn validate_closing_time(time: &ClosingTime) -> Result<(), ValidationError> { + if time.from_date > time.to_date { + let mut err = ValidationError::new("to date in past"); + err.message = Some("To date must be after from date".into()); + Err(err) + } else { + Ok(()) + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, Eq, PartialEq)] +#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] +#[validate(schema(function = "validate_closing_time"))] +pub struct ClosingTime { + id: i32, + pub from_date: NaiveDate, + pub to_date: NaiveDate +} +impl ClosingTime { + pub fn id(&self) -> i32 { + self.id + } +} diff --git a/src/backend/opening_hours.rs b/src/backend/opening_hours.rs index 1c6d070..0a499b7 100644 --- a/src/backend/opening_hours.rs +++ b/src/backend/opening_hours.rs @@ -3,7 +3,7 @@ use cfg_if::cfg_if; use chrono::Weekday; use leptos::*; use validator::Validate; -use crate::backend::data::{ApiResponse, DayHour, WeekHours}; +use crate::backend::data::{ApiResponse, ClosingTime, DayHour, WeekHours}; use crate::components::data_form::ForValidation; cfg_if! { if #[cfg(feature = "ssr")] { @@ -89,4 +89,70 @@ impl ForValidation for UpdateHours { fn entity(&self) -> &dyn Validate { &self.hours } +} + +#[server] +pub async fn get_closing_time() -> Result, ServerFnError> { + use crate::backend::get_pool; + use sqlx::{Error, query_as}; + + let pool = get_pool().await?; + let ct = query_as::<_, ClosingTime>("SELECT * FROM closing_time") + .fetch_one(&pool) + .await; + + Ok(if let Err(ref e) = ct { + if matches!(e, Error::RowNotFound) { + None + } else { + Some(ct?) + } + } else { + Some(ct?) + }) +} + +#[server] +pub async fn update_closing_time(time: ClosingTime) -> Result, ServerFnError> { + use crate::perm_check; + use crate::backend::get_pool; + + perm_check!(is_admin); + + let pool = get_pool().await?; + let mut tx = pool.begin().await?; + + sqlx::query("DELETE FROM closing_time") + .execute(&mut *tx) + .await?; + sqlx::query("INSERT INTO closing_time(from_date, to_date) VALUES($1, $2)") + .bind(time.from_date) + .bind(time.to_date) + .execute(&mut *tx) + .await?; + + tx.commit().await?; + + Ok(ApiResponse::Data(())) +} + +impl ForValidation for UpdateClosingTime { + fn entity(&self) -> &dyn Validate { + &self.time + } +} + +#[server] +pub async fn delete_closing_time() -> Result, ServerFnError> { + use crate::perm_check; + use crate::backend::get_pool; + + perm_check!(is_admin); + + let pool = get_pool().await?; + sqlx::query("DELETE FROM closing_time") + .execute(&pool) + .await?; + + Ok(ApiResponse::Data(())) } \ No newline at end of file diff --git a/src/backend/reservation.rs b/src/backend/reservation.rs index fe47407..035c9e7 100644 --- a/src/backend/reservation.rs +++ b/src/backend/reservation.rs @@ -26,6 +26,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { use crate::backend::user::emails_for_notify; use crate::locales::trl; use rust_decimal::prelude::ToPrimitive; + use crate::backend::opening_hours::get_closing_time; async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result { let reservation = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1") @@ -240,13 +241,15 @@ pub async fn get_public_form_data(day: NaiveDate) -> Result>())) } diff --git a/src/locales/catalogues.rs b/src/locales/catalogues.rs index c6b4472..37f4eca 100644 --- a/src/locales/catalogues.rs +++ b/src/locales/catalogues.rs @@ -158,6 +158,10 @@ lazy_static! { ("October", "Říjen"), ("November", "Listpopad"), ("December", "Prosinec"), + ("Closing days: ", "Zavírací dny: "), + ("Closing days", "Zavírací dny"), + ("From", "Od"), + ("To", "do") ])), ("sk", HashMap::from( [ ("Dashboard", "Prehlad"), diff --git a/src/pages/hours_edit.rs b/src/pages/hours_edit.rs index c4d4af6..e05e6d7 100644 --- a/src/pages/hours_edit.rs +++ b/src/pages/hours_edit.rs @@ -1,7 +1,8 @@ +use chrono::Local; use leptos::*; use crate::backend::data::WeekHours; -use crate::backend::opening_hours::UpdateHours; -use crate::components::data_form::DataForm; +use crate::backend::opening_hours::{DeleteClosingTime, UpdateClosingTime, UpdateHours}; +use crate::components::data_form::{DataForm, QuestionDialog}; use crate::components::modal_box::DialogOpener; use crate::locales::trl; @@ -27,4 +28,46 @@ pub fn EditHours(opener: DialogOpener, hours: ReadSignal) -> impl Int } +} + +#[component] +pub fn closing_days(opener: DialogOpener) -> impl IntoView { + let update_days = create_server_action::(); + + view! { + + +
+
+ + + + +
+
+
+ } +} + +#[component] +pub fn del_closing_days(opener: DialogOpener) -> impl IntoView { + let delete = create_server_action::(); + + view! { + +
{trl("Are you sure you want to delete closing days?")}
+
+ } } \ No newline at end of file diff --git a/src/pages/opening_hours.rs b/src/pages/opening_hours.rs index 247bf23..8dd7064 100644 --- a/src/pages/opening_hours.rs +++ b/src/pages/opening_hours.rs @@ -1,10 +1,10 @@ use chrono::Weekday; use leptos::*; use crate::backend::data::{DayHours, WeekHours}; -use crate::backend::opening_hours::get_hours; +use crate::backend::opening_hours::{get_closing_time, get_hours}; use crate::components::modal_box::DialogOpener; -use crate::locales::{show_day, trl}; -use crate::pages::hours_edit::EditHours; +use crate::locales::{loc_date, show_day, trl}; +use crate::pages::hours_edit::{ClosingDays, DelClosingDays, EditHours}; fn show_time(tm: &str) -> impl Fn() -> String { if tm.is_empty() { @@ -17,11 +17,16 @@ fn show_time(tm: &str) -> impl Fn() -> String { #[component] pub fn OpeningHours() -> impl IntoView { let editor = DialogOpener::new(); + let closing_editor = DialogOpener::new(); + let closing_delete = DialogOpener::new(); let hours = create_blocking_resource(move || editor.visible(), move |_| {get_hours()}); + let closing_days = create_blocking_resource(move || closing_editor.visible() | closing_delete.visible(), move |_| get_closing_time()); let hrs = create_rw_signal(WeekHours::default()); view! { + +
" "{trl("Opening hours")}
@@ -63,6 +68,28 @@ pub fn OpeningHours() -> impl IntoView { }}

+
+ {trl("Closing days: ")} + {trl("Loading...")}

}> + { + closing_days.get().map(|cd| match cd { + Ok(cd) => { + if let Some(cd) = cd { + view! {

{loc_date(cd.from_date)}" - "{loc_date(cd.to_date)}

} + } else { + view! {

} + } + } + Err(_) => {view! {

}}}) + } +
+ + +
} diff --git a/src/pages/public.rs b/src/pages/public.rs index 41cd715..a4c1ef1 100644 --- a/src/pages/public.rs +++ b/src/pages/public.rs @@ -5,7 +5,7 @@ use leptos_router::*; use rust_decimal::Decimal; use crate::backend::appearance::get_appearance; use crate::backend::customer::get_remembered; -use crate::backend::data::{ApiResponse, Customer, DayHour, Reservation, ResProperty, SlotType, TmCheck}; +use crate::backend::data::{ApiResponse, ClosingTime, Customer, DayHour, Reservation, ResProperty, SlotType, TmCheck}; use crate::backend::reservation::{CreateReservation, get_public_form_data, is_reserved}; use crate::backend::user::get_pow; use crate::components::data_form::ForValidation; @@ -20,51 +20,62 @@ fn time_selector( reservations: Vec, property: ResProperty, slots: RwSignal>, - price: RwSignal) -> impl IntoView { - let checks = hours.into_iter().map(|h| { - match property.slot { - SlotType::Quarter => { - let mut ret: Vec = vec![]; - logging::log!("quarter"); - for n in 0..(h.to() - h.from()).num_minutes() * 4 / 60 { - ret.push(TmCheck { - from: h.from() + Duration::minutes(n * 15), - to: h.from() + Duration::minutes((n + 1) * 15) - }); + price: RwSignal, + day: ReadSignal, + closing_days: Option) -> impl IntoView { + let closed = if let Some(c) = closing_days { + day.get() >= c.from_date && day.get() <= c.to_date + } else { + false + }; + let checks = if !closed { + hours.into_iter().map(|h| { + match property.slot { + SlotType::Quarter => { + let mut ret: Vec = vec![]; + logging::log!("quarter"); + for n in 0..(h.to() - h.from()).num_minutes() * 4 / 60 { + ret.push(TmCheck { + from: h.from() + Duration::minutes(n * 15), + to: h.from() + Duration::minutes((n + 1) * 15) + }); + } + ret } - ret - } - SlotType::Half => { - let mut ret: Vec = vec![]; - logging::log!("half"); - for n in 0..(h.to() - h.from()).num_minutes() * 2 / 60 { - ret.push(TmCheck { - from: h.from() + Duration::minutes(n * 30), - to: h.from() + Duration::minutes((n + 1) * 30) - }); + SlotType::Half => { + let mut ret: Vec = vec![]; + logging::log!("half"); + for n in 0..(h.to() - h.from()).num_minutes() * 2 / 60 { + ret.push(TmCheck { + from: h.from() + Duration::minutes(n * 30), + to: h.from() + Duration::minutes((n + 1) * 30) + }); + } + ret } - ret - } - SlotType::Hour => { - let mut ret: Vec = vec![]; - for n in 0..(h.to() - h.from()).num_minutes() / 60 { + SlotType::Hour => { + let mut ret: Vec = vec![]; + for n in 0..(h.to() - h.from()).num_minutes() / 60 { + ret.push(TmCheck { + from: h.from() + Duration::minutes(n * 60), + to: h.from() + Duration::minutes((n + 1) * 60) + }); + } + ret + } + SlotType::Day => { + let mut ret: Vec = vec![]; ret.push(TmCheck { - from: h.from() + Duration::minutes(n * 60), - to: h.from() + Duration::minutes((n + 1) * 60) + from: NaiveTime::from_hms_opt(0, 0, 0).unwrap(), + to: NaiveTime::from_hms_opt(23, 59, 59).unwrap() }); + ret } - ret - } - SlotType::Day => { - let mut ret: Vec = vec![]; - ret.push(TmCheck { - from: NaiveTime::from_hms_opt(0,0,0).unwrap(), - to: NaiveTime::from_hms_opt(23, 59, 59).unwrap() - }); - ret } - } - }).collect::>().into_iter().flatten().collect::>(); + }).collect::>().into_iter().flatten().collect::>() + } else { + vec![] + }; let prop_id = property.id(); let closed = checks.is_empty(); @@ -222,6 +233,8 @@ pub fn Public() -> impl IntoView { hours={data.hours.clone()} reservations={data.reservations.clone()} property={data.property.clone()} + day={day.read_only()} + closing_days={data.closing_days.clone()} slots={slots} price={price}/>