Implemented public part and creating reservations.
This commit is contained in:
+254
-1
@@ -1,8 +1,261 @@
|
||||
use chrono::{Duration, Local, NaiveDate, NaiveTime, Timelike};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use rust_decimal::Decimal;
|
||||
use crate::backend::data::{ApiResponse, DayHour, Reservation, ResProperty, SlotType, TmCheck};
|
||||
use crate::backend::reservation::{CreateReservation, get_public_form_data, is_reserved};
|
||||
use crate::components::data_form::ForValidation;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::res_dialogs::{ResError, ResSaved};
|
||||
use crate::validator::Validator;
|
||||
|
||||
#[component]
|
||||
fn time_selector(
|
||||
hours: Vec<DayHour>,
|
||||
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)
|
||||
});
|
||||
}
|
||||
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)
|
||||
});
|
||||
}
|
||||
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)
|
||||
});
|
||||
}
|
||||
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<_>>();
|
||||
|
||||
let prop_id = property.id();
|
||||
view! {
|
||||
<For each=move || checks.clone() key=|c| c.from.minute() let:data>
|
||||
<input type="checkbox"
|
||||
class="btn-check"
|
||||
id={data.from.to_string() + &property.name.clone()}
|
||||
autocomplete="off"
|
||||
disabled={is_reserved(&reservations, &data.from, prop_id)}
|
||||
on:change= move |ev| {
|
||||
let mut sl = slots.get();
|
||||
if event_target_checked(&ev) {
|
||||
sl.push(data.from.to_string() + "-" + &data.to.to_string() + "|" + &prop_id.to_string());
|
||||
price.set(price.get() + property.price);
|
||||
slots.set(sl);
|
||||
} else {
|
||||
slots.set(sl.into_iter().filter(|s| { s.clone() != (data.from.to_string() + "-" + &data.to.to_string() + "|" + &prop_id.to_string())}).collect());
|
||||
price.set(price.get() - property.price);
|
||||
}
|
||||
}/>
|
||||
<label class="btn btn-outline-primary" for={data.from.to_string() + &property.name.clone()}>{data.from.format("%H:%M").to_string()}</label>
|
||||
</For>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Public() -> impl IntoView {
|
||||
let day = create_rw_signal(NaiveDate::default());
|
||||
let form_data = create_blocking_resource(move || day.get(), move |d| get_public_form_data(d));
|
||||
let slots = create_rw_signal::<Vec<String>>(vec![]);
|
||||
let price = create_rw_signal(Decimal::from(0));
|
||||
let cr_reservation = create_server_action::<CreateReservation>();
|
||||
let validator = Validator::new();
|
||||
let invalid_dlg = DialogOpener::new();
|
||||
let result_dlg = DialogOpener::new();
|
||||
let result = cr_reservation.value();
|
||||
|
||||
create_effect(move |_| {
|
||||
day.set(Local::now().date_naive());
|
||||
});
|
||||
|
||||
view! {
|
||||
<div>"public"</div>
|
||||
<ResError opener=invalid_dlg validator=validator/>
|
||||
<ResSaved opener=result_dlg save_result=result day=day price=price.write_only() slots=slots.write_only()/>
|
||||
<div class="card-body">
|
||||
<ActionForm
|
||||
on:submit=move |ev| {
|
||||
let act = CreateReservation::from_event(&ev);
|
||||
if !act.is_err() {
|
||||
validator.check(act.unwrap().entity(), &ev);
|
||||
}
|
||||
if !validator.is_valid() {
|
||||
invalid_dlg.show();
|
||||
} else {
|
||||
result_dlg.show();
|
||||
}
|
||||
}
|
||||
action=cr_reservation>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-basket"></i>" "{trl("Booking")}</h5>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="date" class="form-label">{trl("Date")}</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
class="form-control"
|
||||
prop:value={move || day.get().format("%Y-%m-%d").to_string()}
|
||||
on:input=move |ev| {
|
||||
price.set(Decimal::from(0));
|
||||
slots.set(vec![]);
|
||||
day.set(NaiveDate::parse_from_str(&event_target_value(&ev), "%Y-%m-%d").unwrap());
|
||||
}
|
||||
name="reservation[date]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Transition fallback=|| view! {<p>{trl("Loading...")}</p> }>
|
||||
{move || {
|
||||
form_data.get().map(|u| match u {
|
||||
Err(e) => {
|
||||
view! {<div>{e.to_string()}</div>}}
|
||||
Ok(u) => {
|
||||
match u {
|
||||
ApiResponse::Data(p) => {
|
||||
view! {
|
||||
<div>
|
||||
<For each=move || p.clone()
|
||||
key=|prop| prop.property.id()
|
||||
let:data>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<div class="form-label">{data.property.name.clone()}</div>
|
||||
<div>
|
||||
<TimeSelector
|
||||
hours={data.hours.clone()}
|
||||
reservations={data.reservations.clone()}
|
||||
property={data.property.clone()}
|
||||
slots={slots}
|
||||
price={price}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</For>
|
||||
<For each=move || slots.get()
|
||||
key=|s| s.clone()
|
||||
let:data>
|
||||
<input type="hidden"
|
||||
name="reservation[slots][]"
|
||||
value={data}/>
|
||||
</For>
|
||||
</div>
|
||||
}
|
||||
},
|
||||
ApiResponse::Error(s) => {
|
||||
view! {<div>{trl(&s)}</div>}
|
||||
}
|
||||
}}
|
||||
})
|
||||
}}
|
||||
</Transition>
|
||||
<div class="form-label">{trl("Total price of booking")}</div>
|
||||
<div>{move || format!("{} Kč", price.get())}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bxs-contact"></i>" "{trl("Who is booking")}</h5>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="full_name" class="form-label">{trl("Full name")}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="full_name"
|
||||
class="form-control"
|
||||
placeholder={trl("Enter full name")}
|
||||
//prop:value={move || opener.empty()}
|
||||
name="reservation[full_name]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="email" class="form-label">"Email"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
class="form-control"
|
||||
placeholder={trl("Enter e-mail address")}
|
||||
//prop:value={move || opener.empty()}
|
||||
name="reservation[email]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="phone" class="form-label">{trl("Phone number")}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
class="form-control"
|
||||
placeholder={trl("Enter phone number")}
|
||||
//prop:value={move || opener.empty()}
|
||||
name="reservation[phone]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="note" class="form-label">{trl("Note")}</label>
|
||||
<input
|
||||
type="text"
|
||||
id="note"
|
||||
class="form-control"
|
||||
placeholder={trl("Enter note")}
|
||||
//prop:value={move || opener.empty()}
|
||||
name="reservation[note]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{trl("Book")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ActionForm>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user