Implemented admin overview.
parent
6c7fd2e46f
commit
e69db7f40b
@ -1,69 +1,24 @@
|
|||||||
use leptos::*;
|
use leptos::*;
|
||||||
use crate::components::modal_box::{DialogOpener, ModalDialog, ModalBody, ModalFooter};
|
use crate::components::modal_box::DialogOpener;
|
||||||
use crate::locales::trl;
|
use crate::locales::trl;
|
||||||
|
use crate::pages::new_reservations::NewReservations;
|
||||||
|
use crate::pages::today_reservations::NextReservations;
|
||||||
|
|
||||||
/// Renders the home page of your application.
|
/// Renders the home page of your application.
|
||||||
#[component]
|
#[component]
|
||||||
pub fn HomePage() -> impl IntoView {
|
pub fn HomePage() -> impl IntoView {
|
||||||
// Creates a reactive value to update the button
|
let app_opener = DialogOpener::new();
|
||||||
let (count, set_count) = create_signal(0);
|
let cancel_opener = DialogOpener::new();
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<ModalDialog opener={dialog} title="Titulek">
|
<h1>{trl("Overview")}</h1>
|
||||||
<ModalBody>
|
<div class="row mb-5">
|
||||||
<div class="row">
|
<div class="col-md">
|
||||||
<div class="col mb-3">
|
<NewReservations app_opener=app_opener cancel_opener=cancel_opener/>
|
||||||
<label for="nameWithTitle" class="form-label">"Name"</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="nameWithTitle"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="Enter Name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-2">
|
<div class="col-md">
|
||||||
<div class="col mb-0">
|
<NextReservations app_opener=app_opener cancel_opener=cancel_opener/>
|
||||||
<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>
|
</div>
|
||||||
</ModalBody>
|
</div>
|
||||||
<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>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue