Added setting for closing days.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE closing_time (
|
||||
id SERIAL PRIMARY KEY,
|
||||
from_date DATE,
|
||||
to_date DATE
|
||||
)
|
||||
+26
-1
@@ -348,7 +348,8 @@ impl ResProperty {
|
||||
pub struct PublicFormData {
|
||||
pub property: ResProperty,
|
||||
pub hours: Vec<DayHour>,
|
||||
pub reservations: Vec<Reservation>
|
||||
pub reservations: Vec<Reservation>,
|
||||
pub closing_days: Option<ClosingTime>
|
||||
}
|
||||
|
||||
fn empty_slots() -> Vec<String> {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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::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<ReservationSum, Error> {
|
||||
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 props = get_props(Some("active = true")).await?;
|
||||
let reservations = reservations_for_day(&day).await?;
|
||||
let closing_days = get_closing_time().await?;
|
||||
|
||||
info!("Loading public form data");
|
||||
|
||||
Ok(ApiResponse::Data(props.into_iter().map(|p| PublicFormData {
|
||||
property: p,
|
||||
hours: hours.clone(),
|
||||
reservations: reservations.clone()
|
||||
reservations: reservations.clone(),
|
||||
closing_days: closing_days.clone()
|
||||
}).collect::<Vec<_>>()))
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+45
-2
@@ -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<WeekHours>) -> impl Int
|
||||
</div>
|
||||
</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 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! {
|
||||
<EditHours opener=editor hours=hrs.read_only() />
|
||||
<ClosingDays opener=closing_editor/>
|
||||
<DelClosingDays opener=closing_delete/>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bxs-watch"></i>" "{trl("Opening hours")}</h5>
|
||||
@@ -63,6 +68,28 @@ pub fn OpeningHours() -> impl IntoView {
|
||||
}}
|
||||
</Transition>
|
||||
</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>
|
||||
}
|
||||
|
||||
+55
-42
@@ -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<Reservation>,
|
||||
property: ResProperty,
|
||||
slots: RwSignal<Vec<String>>,
|
||||
price: RwSignal<Decimal>) -> impl IntoView {
|
||||
let checks = hours.into_iter().map(|h| {
|
||||
match property.slot {
|
||||
SlotType::Quarter => {
|
||||
let mut ret: Vec<TmCheck> = 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<Decimal>,
|
||||
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 {
|
||||
SlotType::Quarter => {
|
||||
let mut ret: Vec<TmCheck> = 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<TmCheck> = 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<TmCheck> = 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<TmCheck> = 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)
|
||||
});
|
||||
SlotType::Hour => {
|
||||
let mut ret: Vec<TmCheck> = 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<TmCheck> = vec![];
|
||||
ret.push(TmCheck {
|
||||
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<TmCheck> = vec![];
|
||||
ret.push(TmCheck {
|
||||
from: NaiveTime::from_hms_opt(0,0,0).unwrap(),
|
||||
to: NaiveTime::from_hms_opt(23, 59, 59).unwrap()
|
||||
});
|
||||
ret
|
||||
}
|
||||
}
|
||||
}).collect::<Vec<_>>().into_iter().flatten().collect::<Vec<_>>();
|
||||
}).collect::<Vec<_>>().into_iter().flatten().collect::<Vec<_>>()
|
||||
} 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}/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user