Added setting for closing days.

main
Josef Rokos 11 months ago
parent 084911c992
commit 9f9b716781

@ -0,0 +1,5 @@
CREATE TABLE closing_time (
id SERIAL PRIMARY KEY,
from_date DATE,
to_date DATE
)

@ -348,7 +348,8 @@ impl ResProperty {
pub struct PublicFormData { pub struct PublicFormData {
pub property: ResProperty, pub property: ResProperty,
pub hours: Vec<DayHour>, pub hours: Vec<DayHour>,
pub reservations: Vec<Reservation> pub reservations: Vec<Reservation>,
pub closing_days: Option<ClosingTime>
} }
fn empty_slots() -> Vec<String> { fn empty_slots() -> Vec<String> {
@ -637,3 +638,27 @@ impl Appearance {
self.id 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
}
}

@ -3,7 +3,7 @@ use cfg_if::cfg_if;
use chrono::Weekday; use chrono::Weekday;
use leptos::*; use leptos::*;
use validator::Validate; use validator::Validate;
use crate::backend::data::{ApiResponse, DayHour, WeekHours}; use crate::backend::data::{ApiResponse, ClosingTime, DayHour, WeekHours};
use crate::components::data_form::ForValidation; use crate::components::data_form::ForValidation;
cfg_if! { if #[cfg(feature = "ssr")] { cfg_if! { if #[cfg(feature = "ssr")] {
@ -90,3 +90,69 @@ impl ForValidation for UpdateHours {
&self.hours &self.hours
} }
} }
#[server]
pub async fn get_closing_time() -> Result<Option<ClosingTime>, 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<ApiResponse<()>, 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<ApiResponse<()>, 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(()))
}

@ -26,6 +26,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
use crate::backend::user::emails_for_notify; use crate::backend::user::emails_for_notify;
use crate::locales::trl; use crate::locales::trl;
use rust_decimal::prelude::ToPrimitive; 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<ReservationSum, Error> { async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result<ReservationSum, Error> {
let reservation = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1") 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<ApiResponse<Vec<Publ
let hours = hours_for_day(day.weekday()).await?; let hours = hours_for_day(day.weekday()).await?;
let props = get_props(Some("active = true")).await?; let props = get_props(Some("active = true")).await?;
let reservations = reservations_for_day(&day).await?; let reservations = reservations_for_day(&day).await?;
let closing_days = get_closing_time().await?;
info!("Loading public form data"); info!("Loading public form data");
Ok(ApiResponse::Data(props.into_iter().map(|p| PublicFormData { Ok(ApiResponse::Data(props.into_iter().map(|p| PublicFormData {
property: p, property: p,
hours: hours.clone(), hours: hours.clone(),
reservations: reservations.clone() reservations: reservations.clone(),
closing_days: closing_days.clone()
}).collect::<Vec<_>>())) }).collect::<Vec<_>>()))
} }

@ -158,6 +158,10 @@ lazy_static! {
("October", "Říjen"), ("October", "Říjen"),
("November", "Listpopad"), ("November", "Listpopad"),
("December", "Prosinec"), ("December", "Prosinec"),
("Closing days: ", "Zavírací dny: "),
("Closing days", "Zavírací dny"),
("From", "Od"),
("To", "do")
])), ])),
("sk", HashMap::from( [ ("sk", HashMap::from( [
("Dashboard", "Prehlad"), ("Dashboard", "Prehlad"),

@ -1,7 +1,8 @@
use chrono::Local;
use leptos::*; use leptos::*;
use crate::backend::data::WeekHours; use crate::backend::data::WeekHours;
use crate::backend::opening_hours::UpdateHours; use crate::backend::opening_hours::{DeleteClosingTime, UpdateClosingTime, UpdateHours};
use crate::components::data_form::DataForm; use crate::components::data_form::{DataForm, QuestionDialog};
use crate::components::modal_box::DialogOpener; use crate::components::modal_box::DialogOpener;
use crate::locales::trl; use crate::locales::trl;
@ -28,3 +29,45 @@ pub fn EditHours(opener: DialogOpener, hours: ReadSignal<WeekHours>) -> impl Int
</DataForm> </DataForm>
} }
} }
#[component]
pub fn closing_days(opener: DialogOpener) -> impl IntoView {
let update_days = create_server_action::<UpdateClosingTime>();
view! {
<DataForm opener=opener action=update_days title="Closing days">
<input type="hidden" value=0 name="time[id]"/>
<div class="row">
<div class="col mb-3">
<label for="from_day" class="form-label">{trl("From")}</label>
<input
type="date"
id="from_day"
class="form-control"
prop:value={move || Local::now().date_naive().format("%Y-%m-%d").to_string()}
name="time[from_date]"
/>
<label for="to_day" class="form-label">{trl("To")}</label>
<input
type="date"
id="to_day"
class="form-control"
prop:value={move || Local::now().date_naive().format("%Y-%m-%d").to_string()}
name="time[to_date]"
/>
</div>
</div>
</DataForm>
}
}
#[component]
pub fn del_closing_days(opener: DialogOpener) -> impl IntoView {
let delete = create_server_action::<DeleteClosingTime>();
view! {
<QuestionDialog opener=opener action=delete title="Delete closing days">
<div>{trl("Are you sure you want to delete closing days?")}</div>
</QuestionDialog>
}
}

@ -1,10 +1,10 @@
use chrono::Weekday; use chrono::Weekday;
use leptos::*; use leptos::*;
use crate::backend::data::{DayHours, WeekHours}; 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::components::modal_box::DialogOpener;
use crate::locales::{show_day, trl}; use crate::locales::{loc_date, show_day, trl};
use crate::pages::hours_edit::EditHours; use crate::pages::hours_edit::{ClosingDays, DelClosingDays, EditHours};
fn show_time(tm: &str) -> impl Fn() -> String { fn show_time(tm: &str) -> impl Fn() -> String {
if tm.is_empty() { if tm.is_empty() {
@ -17,11 +17,16 @@ fn show_time(tm: &str) -> impl Fn() -> String {
#[component] #[component]
pub fn OpeningHours() -> impl IntoView { pub fn OpeningHours() -> impl IntoView {
let editor = DialogOpener::new(); 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 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()); let hrs = create_rw_signal(WeekHours::default());
view! { view! {
<EditHours opener=editor hours=hrs.read_only() /> <EditHours opener=editor hours=hrs.read_only() />
<ClosingDays opener=closing_editor/>
<DelClosingDays opener=closing_delete/>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<h5 class="card-title"><i class="bx bxs-watch"></i>" "{trl("Opening hours")}</h5> <h5 class="card-title"><i class="bx bxs-watch"></i>" "{trl("Opening hours")}</h5>
@ -63,6 +68,28 @@ pub fn OpeningHours() -> impl IntoView {
}} }}
</Transition> </Transition>
</p> </p>
<div align="center">
{trl("Closing days: ")}
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
{
closing_days.get().map(|cd| match cd {
Ok(cd) => {
if let Some(cd) = cd {
view! {<p>{loc_date(cd.from_date)}" - "{loc_date(cd.to_date)}</p>}
} else {
view! {<p></p>}
}
}
Err(_) => {view! {<p></p>}}})
}
</Transition>
<a href="javascript:void(0)" class="card-link" on:click = move |_| {
closing_editor.show();
}><i class="bx bx-edit-alt me-1"></i></a>
<a class="card-link text-danger" href="javascript:void(0);" on:click=move |_| {
closing_delete.show();
}><i class="bx bx-trash me-1"></i> </a>
</div>
</div> </div>
</div> </div>
} }

@ -5,7 +5,7 @@ use leptos_router::*;
use rust_decimal::Decimal; use rust_decimal::Decimal;
use crate::backend::appearance::get_appearance; use crate::backend::appearance::get_appearance;
use crate::backend::customer::get_remembered; 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::reservation::{CreateReservation, get_public_form_data, is_reserved};
use crate::backend::user::get_pow; use crate::backend::user::get_pow;
use crate::components::data_form::ForValidation; use crate::components::data_form::ForValidation;
@ -20,8 +20,16 @@ fn time_selector(
reservations: Vec<Reservation>, reservations: Vec<Reservation>,
property: ResProperty, property: ResProperty,
slots: RwSignal<Vec<String>>, slots: RwSignal<Vec<String>>,
price: RwSignal<Decimal>) -> impl IntoView { price: RwSignal<Decimal>,
let checks = hours.into_iter().map(|h| { day: ReadSignal<NaiveDate>,
closing_days: Option<ClosingTime>) -> 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 { match property.slot {
SlotType::Quarter => { SlotType::Quarter => {
let mut ret: Vec<TmCheck> = vec![]; let mut ret: Vec<TmCheck> = vec![];
@ -64,7 +72,10 @@ fn time_selector(
ret ret
} }
} }
}).collect::<Vec<_>>().into_iter().flatten().collect::<Vec<_>>(); }).collect::<Vec<_>>().into_iter().flatten().collect::<Vec<_>>()
} else {
vec![]
};
let prop_id = property.id(); let prop_id = property.id();
let closed = checks.is_empty(); let closed = checks.is_empty();
@ -222,6 +233,8 @@ pub fn Public() -> impl IntoView {
hours={data.hours.clone()} hours={data.hours.clone()}
reservations={data.reservations.clone()} reservations={data.reservations.clone()}
property={data.property.clone()} property={data.property.clone()}
day={day.read_only()}
closing_days={data.closing_days.clone()}
slots={slots} slots={slots}
price={price}/> price={price}/>
</div> </div>

Loading…
Cancel
Save