Implemented admin overview.
This commit is contained in:
@@ -447,6 +447,22 @@ pub struct Reservation {
|
||||
pub summary: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct ResPropertyView {
|
||||
pub name: String,
|
||||
pub description: String
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct ResWithProperty {
|
||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||
pub reservation: Reservation,
|
||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||
pub property: ResPropertyView
|
||||
}
|
||||
|
||||
pub struct Reservations(Vec<Reservation>);
|
||||
|
||||
// Transform slots to reservations
|
||||
@@ -515,6 +531,13 @@ impl ReservationSum {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct ResSumWithItems {
|
||||
pub summary: ReservationSum,
|
||||
pub customer: Customer,
|
||||
pub reservations: Vec<ResWithProperty>
|
||||
}
|
||||
|
||||
/*
|
||||
pub enum MessageType {
|
||||
NewReservation,
|
||||
|
||||
+116
-3
@@ -1,18 +1,19 @@
|
||||
use leptos::*;
|
||||
use validator::Validate;
|
||||
use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData};
|
||||
use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData, ResSumWithItems};
|
||||
use crate::components::data_form::ForValidation;
|
||||
use cfg_if::cfg_if;
|
||||
use chrono::{NaiveDate, NaiveTime};
|
||||
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use sqlx::{Postgres, Transaction, query};
|
||||
use sqlx::query_as;
|
||||
use sqlx::Error;
|
||||
use uuid::Uuid;
|
||||
use std::ops::DerefMut;
|
||||
use std::str::FromStr;
|
||||
use crate::backend::data::ReservationSum;
|
||||
use futures_util::future::join_all;
|
||||
use crate::backend::data::{ReservationSum, ReservationState, ResWithProperty, Customer};
|
||||
use crate::backend::get_pool;
|
||||
|
||||
async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result<ReservationSum, Error> {
|
||||
@@ -41,6 +42,70 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
Ok(reservations?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option<ReservationState>) -> Result<Vec<ResSumWithItems>, ServerFnError> {
|
||||
let pool = get_pool().await?;
|
||||
let sums = if let Some(s) = state {
|
||||
query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 AND state = $3 ORDER BY date")
|
||||
.bind(from)
|
||||
.bind(to)
|
||||
.bind(s)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
} else {
|
||||
query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 ORDER BY date")
|
||||
.bind(from)
|
||||
.bind(to)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
};
|
||||
|
||||
let sums = if let Err(ref e) = sums {
|
||||
if matches!(e, Error::RowNotFound) {
|
||||
vec![]
|
||||
} else {
|
||||
sums?
|
||||
}
|
||||
} else {
|
||||
sums?
|
||||
};
|
||||
|
||||
if sums.is_empty() {
|
||||
return Ok(vec![])
|
||||
}
|
||||
|
||||
let res: Result<Vec<ResSumWithItems>, Error> = join_all(sums.into_iter().map(|s| async {
|
||||
let reservations = query_as::<_, ResWithProperty>(
|
||||
"SELECT r.id, r.from, r.to, r.property, r.summary, p.name, p,description \
|
||||
FROM reservation as r \
|
||||
JOIN property as p ON r.property = p.id WHERE r.summary = $1")
|
||||
.bind(s.id())
|
||||
.fetch_all(&pool)
|
||||
.await?;
|
||||
let customer = query_as::<_, Customer>("SELECT * FROM customer WHERE id = $1")
|
||||
.bind(s.customer)
|
||||
.fetch_one(&pool)
|
||||
.await?;
|
||||
Ok(ResSumWithItems {
|
||||
summary: s,
|
||||
customer,
|
||||
reservations
|
||||
})
|
||||
})).await.into_iter().collect();
|
||||
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> {
|
||||
let pool = get_pool().await?;
|
||||
query("UPDATE reservation_sum SET state = $1 WHERE uuid = $2")
|
||||
.bind(state)
|
||||
.bind(uuid)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}}
|
||||
|
||||
#[server]
|
||||
@@ -148,4 +213,52 @@ impl ForValidation for CreateReservation {
|
||||
fn entity(&self) -> &dyn Validate {
|
||||
&self.reservation
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_new_reservations() -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use chrono::{Days, Local};
|
||||
use crate::backend::data::ReservationState;
|
||||
|
||||
perm_check!(is_logged_in);
|
||||
|
||||
Ok(ApiResponse::Data(reservations_in_range(&Local::now().date_naive(),
|
||||
&Local::now().checked_add_days(Days::new(7)).unwrap().date_naive(),
|
||||
Some(ReservationState::New)).await?))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_next_reservations() -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use chrono::{Days, Local};
|
||||
use crate::backend::data::ReservationState;
|
||||
|
||||
perm_check!(is_logged_in);
|
||||
|
||||
Ok(ApiResponse::Data(reservations_in_range(&Local::now().date_naive(),
|
||||
&Local::now().checked_add_days(Days::new(7)).unwrap().date_naive(),
|
||||
Some(ReservationState::Approved)).await?))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn approve(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use crate::backend::data::ReservationState;
|
||||
|
||||
perm_check!(is_logged_in);
|
||||
set_state(Uuid::parse_str(&uuid)?, ReservationState::Approved).await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn cancel(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
use crate::perm_check;
|
||||
use crate::backend::data::ReservationState;
|
||||
|
||||
perm_check!(is_logged_in);
|
||||
set_state(Uuid::parse_str(&uuid)?, ReservationState::Canceled).await?;
|
||||
|
||||
Ok(ApiResponse::Data(()))
|
||||
}
|
||||
@@ -56,6 +56,28 @@ lazy_static! {
|
||||
("Date", "Datum"),
|
||||
("Note", "Poznámka"),
|
||||
("Enter note", "Zadejte poznámku"),
|
||||
("New bookings", "Nové rezervace"),
|
||||
("Next approved bookings", "Potvrzené rezervace"),
|
||||
("Approve", "Potvrdit"),
|
||||
("Cancel booking", "Zrušit rezervaci"),
|
||||
("Approve booking", "Potvrdit rezervaci"),
|
||||
("Reservation saved", "Rezervace byla uložena"),
|
||||
("Cancel", "Zrušit"),
|
||||
("Approve booking on ", "Potvrdit rezervaci na "),
|
||||
(" for ", " pro "),
|
||||
("Cancel booking on ", "Zrušit rezervaci na "),
|
||||
("Customer: ", "Zákazník: "),
|
||||
("Price: ", "Cena: "),
|
||||
("Note: ", "Poznámka: "),
|
||||
("Overview", "Přehled"),
|
||||
("Can't create reservation", "Rezervaci nelze vytvořit"),
|
||||
("Enter your full name", "Zadejte své jméno"),
|
||||
("Enter valid email address", "Zadejte platný e-mail"),
|
||||
("Select at last one time slot", "Zvolte čas rezervace"),
|
||||
("Enter your phone number", "Zadejte telefonní číslo"),
|
||||
("Your reservation has been successfully saved.", "Vaše rezervace byla úspěšně uložena."),
|
||||
("We look forward to seeing you on", "Těšíme se na vaši návštěvu"),
|
||||
("Create booking", "Vytvořit rezervaci"),
|
||||
])),
|
||||
("sk", HashMap::from( [
|
||||
("Dashboard", "Prehlad"),
|
||||
|
||||
+13
-1
@@ -1,4 +1,4 @@
|
||||
use chrono::NaiveDate;
|
||||
use chrono::{NaiveDate, Weekday};
|
||||
use leptos::use_context;
|
||||
use crate::locales::catalogues::get_dictionary;
|
||||
|
||||
@@ -51,4 +51,16 @@ pub fn loc_date(date: NaiveDate) -> impl Fn() -> String {
|
||||
}
|
||||
|
||||
move || { dt.clone() }
|
||||
}
|
||||
|
||||
pub fn show_day(day: &Weekday) -> impl Fn() -> String {
|
||||
match day {
|
||||
Weekday::Mon => { trl("Monday") }
|
||||
Weekday::Tue => { trl("Tuesday") }
|
||||
Weekday::Wed => { trl("Wednesday") }
|
||||
Weekday::Thu => { trl("Thursday") }
|
||||
Weekday::Fri => { trl("Friday") }
|
||||
Weekday::Sat => { trl("Saturday") }
|
||||
Weekday::Sun => { trl("Sunday") }
|
||||
}
|
||||
}
|
||||
+12
-57
@@ -1,69 +1,24 @@
|
||||
use leptos::*;
|
||||
use crate::components::modal_box::{DialogOpener, ModalDialog, ModalBody, ModalFooter};
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::pages::new_reservations::NewReservations;
|
||||
use crate::pages::today_reservations::NextReservations;
|
||||
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
pub fn HomePage() -> impl IntoView {
|
||||
// Creates a reactive value to update the button
|
||||
let (count, set_count) = create_signal(0);
|
||||
let on_click = move |_| set_count.update(|count| *count += 1);
|
||||
|
||||
let dialog = DialogOpener::new();
|
||||
|
||||
//let (dialog, set_dialog) = create_signal(false);
|
||||
//let on_dialog = move |_| dialog.set_visible.update(|dialog| {*dialog = true});
|
||||
|
||||
//let pok = use_context::<Request>();
|
||||
//log!("{:?}", pok);
|
||||
|
||||
let app_opener = DialogOpener::new();
|
||||
let cancel_opener = DialogOpener::new();
|
||||
|
||||
view! {
|
||||
<ModalDialog opener={dialog} title="Titulek">
|
||||
<ModalBody>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<label for="nameWithTitle" class="form-label">"Name"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="nameWithTitle"
|
||||
class="form-control"
|
||||
placeholder="Enter Name"
|
||||
/>
|
||||
</div>
|
||||
<h1>{trl("Overview")}</h1>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md">
|
||||
<NewReservations app_opener=app_opener cancel_opener=cancel_opener/>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col mb-0">
|
||||
<label for="emailWithTitle" class="form-label">"Email"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="emailWithTitle"
|
||||
class="form-control"
|
||||
placeholder="xxxx@xxx.xx"
|
||||
/>
|
||||
</div>
|
||||
<div class="col mb-0">
|
||||
<label for="dobWithTitle" class="form-label">"DOB"</label>
|
||||
<input
|
||||
type="text"
|
||||
id="dobWithTitle"
|
||||
class="form-control"
|
||||
placeholder="DD / MM / YY"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<NextReservations app_opener=app_opener cancel_opener=cancel_opener/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal" on:click=move |_| dialog.hide()>
|
||||
"Close"
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary">"Save changes"</button>
|
||||
</ModalFooter>
|
||||
</ModalDialog>
|
||||
|
||||
<h1>"Welcome to Leptos!"</h1>
|
||||
<button on:click=on_click>"Click Me: " {count}</button>
|
||||
<button on:click=move |_| dialog.show()>"Dialog"</button>
|
||||
<p>{trl("testik!")}</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,6 @@ mod properties;
|
||||
mod property_edit;
|
||||
mod property_delete;
|
||||
mod res_dialogs;
|
||||
mod today_reservations;
|
||||
mod new_reservations;
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::{ApiResponse, ResSumWithItems};
|
||||
use crate::backend::reservation::{Approve, Cancel, get_new_reservations};
|
||||
use crate::components::data_form::QuestionDialog;
|
||||
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog};
|
||||
use crate::components::user_menu::MenuOpener;
|
||||
use crate::locales::{loc_date, show_day, trl};
|
||||
use crate::pages::public::Public;
|
||||
use chrono::Datelike;
|
||||
|
||||
#[component]
|
||||
fn approve_dialog(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener) -> impl IntoView {
|
||||
let approve = create_server_action::<Approve>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=approve title="Approve booking">
|
||||
<input type="hidden" prop:value=move || reservation.get().summary.uuid.to_string() name="uuid"/>
|
||||
<div>
|
||||
{trl("Approve booking on ")}
|
||||
{move || loc_date(reservation.get().summary.date)}
|
||||
{trl(" for ")}
|
||||
{move || reservation.get().customer.full_name}"?"
|
||||
</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn cancel_dialog(reservation: ReadSignal<ResSumWithItems>, opener: DialogOpener) -> impl IntoView {
|
||||
let cancel = create_server_action::<Cancel>();
|
||||
|
||||
view! {
|
||||
<QuestionDialog opener=opener action=cancel title="Cancel booking">
|
||||
<input type="hidden" prop:value=move || reservation.get().summary.uuid.to_string() name="uuid"/>
|
||||
<div>
|
||||
{trl("Cancel booking on ")}
|
||||
{move || loc_date(reservation.get().summary.date)}
|
||||
{trl(" for ")}
|
||||
{move || reservation.get().customer.full_name}"?"
|
||||
</div>
|
||||
</QuestionDialog>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn create_dialog(opener: DialogOpener) -> impl IntoView {
|
||||
view! {
|
||||
<ModalDialog opener=opener title="Create booking">
|
||||
<ModalBody>
|
||||
<Public/>
|
||||
</ModalBody>
|
||||
</ModalDialog>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView {
|
||||
let create = DialogOpener::new();
|
||||
let res = create_blocking_resource(move || app_opener.visible() || cancel_opener.visible() || create.visible(),
|
||||
move |_| get_new_reservations());
|
||||
let reservation = create_rw_signal(ResSumWithItems::default());
|
||||
|
||||
view! {
|
||||
<ApproveDialog opener=app_opener reservation=reservation.read_only() />
|
||||
<CancelDialog opener=cancel_opener reservation=reservation.read_only() />
|
||||
<CreateDialog opener=create />
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-basket"></i>" "{trl("New bookings")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
{move || {
|
||||
res.get().map(|r| match r {
|
||||
Err(e) => {
|
||||
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
|
||||
Ok(r) => { match r {
|
||||
ApiResponse::Data(r) => {
|
||||
view! {
|
||||
<div>
|
||||
<For each=move || r.clone()
|
||||
key=|res| res.summary.id()
|
||||
let:data>
|
||||
{move || {
|
||||
let menu = MenuOpener::new();
|
||||
let data = data.clone();
|
||||
let app_data = data.clone();
|
||||
let cancel_data = data.clone();
|
||||
view! {
|
||||
<b>{show_day(&data.summary.date.weekday())}" - "{loc_date(data.summary.date)}</b><br/>
|
||||
<For each=move || data.reservations.clone()
|
||||
key=|item| item.reservation.id()
|
||||
let:item>
|
||||
{item.property.name}": "{item.reservation.from.to_string()}" - "{item.reservation.to.to_string()}<br/>
|
||||
</For>
|
||||
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email}</a>", "{data.customer.phone}<br/>
|
||||
{move || {
|
||||
let note = data.summary.note.clone();
|
||||
let show = note.is_some() && !note.clone().unwrap().is_empty();
|
||||
view! {
|
||||
<Show when=move || show>
|
||||
{trl("Note: ")}{note.clone()}<br/>
|
||||
</Show>
|
||||
}
|
||||
}}
|
||||
{trl("Price: ")}{data.summary.price.to_string()}<br/>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="100%"></td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
|
||||
on:click=move |_| menu.toggle()>
|
||||
<i class="bx bx-dots-vertical-rounded"></i>
|
||||
</button>
|
||||
<div class={move || if menu.visible() {"dropdown-menu show"} else {"dropdown-menu"} }
|
||||
style="position: absolute; insert: 0px 0px auto; margin: 0px; transform: translate3d(-160px, 0px, 0px);"
|
||||
on:mouseleave=move |_| menu.toggle()>
|
||||
<a class="dropdown-item" href="javascript:void(0);" on:click=move |_| {
|
||||
reservation.set(app_data.clone());
|
||||
app_opener.show();
|
||||
}>
|
||||
<i class="bx bx-edit-alt me-1"></i> {trl("Approve")}</a>
|
||||
<a class="dropdown-item text-danger" href="javascript:void(0);" on:click=move |_| {
|
||||
reservation.set(cancel_data.clone());
|
||||
cancel_opener.show();
|
||||
}>
|
||||
<i class="bx bx-trash me-1"></i> {trl("Cancel")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr/>
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}}}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</Transition>
|
||||
<a href="#" class="card-link" on:click=move |_| create.show()>
|
||||
<i class="bx bx-plus-circle fs-4 lh-0"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use leptos::*;
|
||||
use crate::backend::data::{DayHours, WeekHours};
|
||||
use crate::backend::opening_hours::get_hours;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::trl;
|
||||
use crate::locales::{show_day, trl};
|
||||
use crate::pages::hours_edit::EditHours;
|
||||
|
||||
fn show_time(tm: &str) -> impl Fn() -> String {
|
||||
@@ -14,18 +14,6 @@ fn show_time(tm: &str) -> impl Fn() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_day(day: &Weekday) -> impl Fn() -> String {
|
||||
match day {
|
||||
Weekday::Mon => { trl("Monday") }
|
||||
Weekday::Tue => { trl("Tuesday") }
|
||||
Weekday::Wed => { trl("Wednesday") }
|
||||
Weekday::Thu => { trl("Thursday") }
|
||||
Weekday::Fri => { trl("Friday") }
|
||||
Weekday::Sat => { trl("Saturday") }
|
||||
Weekday::Sun => { trl("Sunday") }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn OpeningHours() -> impl IntoView {
|
||||
let editor = DialogOpener::new();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use leptos::*;
|
||||
use crate::backend::data::ApiResponse;
|
||||
use crate::backend::reservation::get_next_reservations;
|
||||
use crate::components::modal_box::DialogOpener;
|
||||
use crate::locales::{loc_date, show_day, trl};
|
||||
use chrono::Datelike;
|
||||
|
||||
#[component]
|
||||
pub fn next_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView {
|
||||
let res = create_blocking_resource( move || app_opener.visible() || cancel_opener.visible(), move |_| get_next_reservations());
|
||||
|
||||
view! {
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bx bx-basket"></i>" "{trl("Next approved bookings")}</h5>
|
||||
<Transition fallback=move || view! {<p>{trl("Loading...")}</p> }>
|
||||
{move || {
|
||||
res.get().map(|r| match r {
|
||||
Err(e) => {
|
||||
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
|
||||
Ok(r) => { match r {
|
||||
ApiResponse::Data(r) => {
|
||||
view! {
|
||||
<div>
|
||||
<For each=move || r.clone()
|
||||
key=|res| res.summary.id()
|
||||
let:data>
|
||||
{move || {
|
||||
let data = data.clone();
|
||||
view! {
|
||||
<b>{show_day(&data.summary.date.weekday())}" - "{loc_date(data.summary.date)}</b><br/>
|
||||
<For each=move || data.reservations.clone()
|
||||
key=|item| item.reservation.id()
|
||||
let:item>
|
||||
{item.property.name}": "{item.reservation.from.to_string()}" - "{item.reservation.to.to_string()}<br/>
|
||||
</For>
|
||||
{trl("Customer: ")}{data.customer.full_name}", "<a href={format!("mailto:{}", data.customer.email)}>{data.customer.email}</a>", "{data.customer.phone}<br/>
|
||||
{move || {
|
||||
let note = data.summary.note.clone();
|
||||
let show = note.is_some() && !note.clone().unwrap().is_empty();
|
||||
view! {
|
||||
<Show when=move || show>
|
||||
{trl("Note: ")}{note.clone()}<br/>
|
||||
</Show>
|
||||
}
|
||||
}}
|
||||
{trl("Price: ")}{data.summary.price.to_string()}<br/>
|
||||
<hr/>
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
ApiResponse::Error(e) => {view! {<div>{trl(&e)}</div>}}}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user