Improved closing days. Add more than one closing days interval is now possible.
This commit is contained in:
+1
-1
@@ -349,7 +349,7 @@ pub struct PublicFormData {
|
||||
pub property: ResProperty,
|
||||
pub hours: Vec<DayHour>,
|
||||
pub reservations: Vec<Reservation>,
|
||||
pub closing_days: Option<ClosingTime>
|
||||
pub closing_days: Vec<ClosingTime>
|
||||
}
|
||||
|
||||
fn empty_slots() -> Vec<String> {
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::components::data_form::ForValidation;
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use crate::error::AppError;
|
||||
use chrono::Local;
|
||||
|
||||
pub async fn hours_for_day(day: Weekday) -> Result<Vec<DayHour>, AppError> {
|
||||
use crate::backend::get_pool;
|
||||
@@ -21,6 +22,18 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
|
||||
Ok(hours.into_iter().map(|h| { DayHour::new(h.from, h.to, h.discount)}).collect())
|
||||
}
|
||||
|
||||
async fn purge_closing_days() -> Result<(), AppError> {
|
||||
use crate::backend::get_pool;
|
||||
|
||||
let pool = get_pool().await?;
|
||||
sqlx::query("DELETE FROM closing_time WHERE to_date < $1")
|
||||
.bind(Local::now())
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}}
|
||||
|
||||
#[server]
|
||||
@@ -97,7 +110,7 @@ pub async fn get_closing_time() -> Result<Option<ClosingTime>, ServerFnError> {
|
||||
use sqlx::{Error, query_as};
|
||||
|
||||
let pool = get_pool().await?;
|
||||
let ct = query_as::<_, ClosingTime>("SELECT * FROM closing_time")
|
||||
let ct = query_as::<_, ClosingTime>("SELECT * FROM closing_time ORDER BY from_date")
|
||||
.fetch_one(&pool)
|
||||
.await;
|
||||
|
||||
@@ -113,44 +126,49 @@ pub async fn get_closing_time() -> Result<Option<ClosingTime>, ServerFnError> {
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn update_closing_time(time: ClosingTime) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
pub async fn get_closing_times() -> Result<Vec<ClosingTime>, ServerFnError> {
|
||||
use crate::backend::get_pool;
|
||||
use sqlx::query_as;
|
||||
|
||||
let pool = get_pool().await?;
|
||||
purge_closing_days().await?;
|
||||
Ok(query_as::<_, ClosingTime>("SELECT * FROM closing_time ORDER BY from_date").fetch_all(&pool).await?)
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn insert_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)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
impl ForValidation for UpdateClosingTime {
|
||||
impl ForValidation for InsertClosingTime {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.time
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_closing_time() -> Result<ApiResponse<()>, ServerFnError> {
|
||||
pub async fn delete_closing_time(id: i32) -> 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")
|
||||
sqlx::query("DELETE FROM closing_time WHERE id = $1")
|
||||
.bind(id)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ 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")
|
||||
@@ -236,12 +235,13 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
pub async fn get_public_form_data(day: NaiveDate) -> Result<ApiResponse<Vec<PublicFormData>>, ServerFnError> {
|
||||
use crate::backend::opening_hours::hours_for_day;
|
||||
use crate::backend::property::get_props;
|
||||
use crate::backend::opening_hours::get_closing_times;
|
||||
use chrono::Datelike;
|
||||
|
||||
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?;
|
||||
let closing_days = get_closing_times().await?;
|
||||
|
||||
info!("Loading public form data");
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ fn settings_menu(opener: MenuOpener) -> impl IntoView {
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/admin/appearance">
|
||||
<i class="bx bx-envelope me-2"></i>
|
||||
<i class="bx bx-show me-2"></i>
|
||||
<span class="align-middle">{trl("Appearance")}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -167,7 +167,9 @@ lazy_static! {
|
||||
("Bad username or password", "Špatné uživatelské jméno nebo heslo"),
|
||||
("You can't escalate your privileges", "Nemůžete povýšit práva sami sobě"),
|
||||
("Username already exists", "Uživatel již existuje"),
|
||||
("You can't delete yourself", "Nemůžete smazat sami sebe")
|
||||
("You can't delete yourself", "Nemůžete smazat sami sebe"),
|
||||
("Are you sure you want to delete closing days from ", "Opravdu chcete smazat zavírací dny od "),
|
||||
(" to ", " do ")
|
||||
])),
|
||||
("sk", HashMap::from( [
|
||||
("Dashboard", "Prehlad"),
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
use chrono::Local;
|
||||
use leptos::*;
|
||||
use crate::backend::data::ClosingTime;
|
||||
use crate::backend::opening_hours::{get_closing_times, DeleteClosingTime, InsertClosingTime};
|
||||
use crate::components::data_form::{DataForm, QuestionDialog};
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::{loc_date, trl};
|
||||
|
||||
#[component]
|
||||
pub fn del_closing_day(closing_time: ReadSignal<ClosingTime>, opener: DialogOpener) -> impl IntoView {
|
||||
let delete = create_server_action::<DeleteClosingTime>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=delete title="Delete closing days">
|
||||
<input type="hidden" prop:value={move || closing_time.get().id()} name="id"/>
|
||||
<div>{trl("Are you sure you want to delete closing days from ")}{move || loc_date(closing_time.get().from_date)}{trl(" to ")}{move || loc_date(closing_time.get().to_date)}"?"</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn insert_closing_days(opener: DialogOpener) -> impl IntoView {
|
||||
let insert_day = create_server_action::<InsertClosingTime>();
|
||||
|
||||
view! {
|
||||
<DataForm opener=opener action=insert_day 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 closing_days() -> impl IntoView {
|
||||
let delete_dialog = DialogOpener::new();
|
||||
let editor = DialogOpener::new();
|
||||
let times = create_blocking_resource(move || editor.visible() || delete_dialog.visible(), move |_| {get_closing_times()});
|
||||
let time_to_del = create_rw_signal(ClosingTime::default());
|
||||
view! {
|
||||
<DelClosingDay closing_time=time_to_del.read_only() opener=delete_dialog/>
|
||||
<InsertClosingDays opener=editor/>
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-calendar-x"></i>" "{trl("Closing days")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
<table class="table card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{trl("From")}</th>
|
||||
<th>{trl("To")}</th>
|
||||
<th>{trl("Actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{move || {
|
||||
times.get().map(|c| match c {
|
||||
Err(e) => {
|
||||
let err = if e.to_string().contains("403") {
|
||||
"Only admin can edit closing times".to_string()
|
||||
} else {
|
||||
e.to_string()
|
||||
};
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<tr><td colspan=3>{trl(&err)}</td></tr></tbody>}}
|
||||
Ok(c) => {
|
||||
view! {<tbody class="table-border-bottom-0">
|
||||
<For each=move || c.clone()
|
||||
key=|ct| ct.id()
|
||||
children=move |ct: ClosingTime| {
|
||||
let ct_delete = ct.clone();
|
||||
view! {
|
||||
<tr>
|
||||
<td>{loc_date(ct.from_date)}</td>
|
||||
<td>{loc_date(ct.to_date)}</td>
|
||||
<td>
|
||||
<a class="dropdown-item text-danger" href="javascript:void(0);" on:click=move |_| {
|
||||
time_to_del.set(ct_delete.clone());
|
||||
delete_dialog.show();
|
||||
}>
|
||||
<i class="bx bx-trash me-1"></i> {trl("Delete")}</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}/></tbody>
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</table>
|
||||
</Transition>
|
||||
<a href="#" class="card-link" on:click=move |_| editor.show()>
|
||||
<i class="bx bx-plus-circle fs-4 lh-0"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
+1
-33
@@ -1,7 +1,6 @@
|
||||
use chrono::Local;
|
||||
use leptos::*;
|
||||
use crate::backend::data::WeekHours;
|
||||
use crate::backend::opening_hours::{DeleteClosingTime, UpdateClosingTime, UpdateHours};
|
||||
use crate::backend::opening_hours::{DeleteClosingTime, UpdateHours};
|
||||
use crate::components::data_form::{DataForm, QuestionDialog};
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::trl;
|
||||
@@ -30,37 +29,6 @@ pub fn EditHours(opener: DialogOpener, hours: ReadSignal<WeekHours>) -> impl Int
|
||||
}
|
||||
}
|
||||
|
||||
#[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>();
|
||||
|
||||
@@ -22,4 +22,5 @@ mod mail_view;
|
||||
pub mod all_reservations;
|
||||
pub mod customers;
|
||||
pub mod appearance_settings;
|
||||
mod closing_days;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use chrono::Weekday;
|
||||
use leptos::*;
|
||||
use crate::backend::data::{DayHours, WeekHours};
|
||||
use crate::backend::opening_hours::{get_closing_time, get_hours};
|
||||
use crate::backend::opening_hours::get_hours;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::{loc_date, show_day, trl};
|
||||
use crate::pages::hours_edit::{ClosingDays, DelClosingDays, EditHours};
|
||||
use crate::locales::{show_day, trl};
|
||||
use crate::pages::hours_edit::{DelClosingDays, EditHours};
|
||||
|
||||
fn show_time(tm: &str) -> impl Fn() -> String {
|
||||
if tm.is_empty() {
|
||||
@@ -17,15 +17,12 @@ 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">
|
||||
@@ -68,28 +65,6 @@ 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>
|
||||
}
|
||||
|
||||
+9
-6
@@ -22,12 +22,15 @@ fn time_selector(
|
||||
slots: RwSignal<Vec<String>>,
|
||||
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
|
||||
};
|
||||
closing_days: Vec<ClosingTime>) -> impl IntoView {
|
||||
let mut closed = false;
|
||||
for cd in closing_days {
|
||||
if day.get() >= cd.from_date && day.get() <= cd.to_date {
|
||||
closed = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let checks = if !closed {
|
||||
hours.into_iter().map(|h| {
|
||||
match property.slot {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use leptos::*;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::closing_days::ClosingDays;
|
||||
use crate::pages::company_info::CompanyInfo;
|
||||
use crate::pages::opening_hours::OpeningHours;
|
||||
use crate::pages::users::Users;
|
||||
@@ -25,5 +26,11 @@ pub fn Settings() -> impl IntoView {
|
||||
<Properties/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md">
|
||||
<ClosingDays/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user