Improved card with new reservations.

main
Josef Rokos 3 weeks ago
parent 0f940a9f5e
commit 3f0136e1c2

2
Cargo.lock generated

@ -3211,7 +3211,7 @@ dependencies = [
[[package]] [[package]]
name = "rezervator" name = "rezervator"
version = "1.1.2" version = "1.2.0"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-multipart", "actix-multipart",

@ -1,6 +1,6 @@
[package] [package]
name = "rezervator" name = "rezervator"
version = "1.1.2" version = "1.2.0"
edition = "2021" edition = "2021"
[lib] [lib]

@ -0,0 +1,4 @@
a.disabled {
pointer-events: none;
color: #ccc;
}

@ -17,6 +17,95 @@ pub enum ApiResponse<T> {
Error(String) Error(String)
} }
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
pub struct Page {
page_size: i32,
page_num: i32
}
impl Page {
pub fn new(page_size: i32) -> Self {
Self {
page_size,
page_num: 1
}
}
pub fn with_page(self, page_num: i32) -> Self {
Self {
page_num,
..self
}
}
pub fn limit(&self) -> i32 {
if self.page_size == 0 {
i32::MAX
} else {
self.page_size
}
}
pub fn offset(&self) -> i32 {
(self.page_num - 1) * self.page_size
}
pub fn page_num(&self) -> i32 {
self.page_num
}
pub fn first(&mut self) {
self.page_num = 1;
}
pub fn prev(&mut self) {
if self.page_num > 1 {
self.page_num = self.page_num - 1;
}
}
pub fn next(&mut self) {
self.page_num = self.page_num + 1;
}
pub fn go_to(&mut self, page: i32) {
self.page_num = page;
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PagedResponse<T> {
page: Page,
record_count: i64,
data: Vec<T>
}
impl<T: Clone> PagedResponse<T> {
pub fn new(data: Vec<T>, page: Page, record_count: i64) -> Self {
Self {
page,
record_count,
data
}
}
pub fn is_first(&self) -> bool {
self.page.page_num() == 1
}
pub fn is_last(&self) -> bool {
self.page.offset() as i64 + self.page.limit() as i64 >= self.record_count
}
pub fn total_pages(&self) -> i32 {
(self.record_count as f64 / self.page.limit() as f64).ceil() as i32
}
pub fn data(&self) -> Vec<T> {
self.data.clone()
}
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)]
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
pub struct Company { pub struct Company {

@ -1,6 +1,6 @@
use leptos::*; use leptos::*;
use validator::Validate; use validator::Validate;
use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData, ResSumWithItems}; use crate::backend::data::{ApiResponse, CrReservation, Reservation, PublicFormData, ResSumWithItems, Page, PagedResponse};
use crate::components::data_form::ForValidation; use crate::components::data_form::ForValidation;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use chrono::{NaiveDate, NaiveTime}; use chrono::{NaiveDate, NaiveTime};
@ -89,9 +89,9 @@ cfg_if! { if #[cfg(feature = "ssr")] {
.await?) .await?)
} }
async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option<ReservationState>) -> Result<Vec<ResSumWithItems>, ServerFnError> { async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option<ReservationState>, page: Page) -> Result<(Vec<ResSumWithItems>, i64), ServerFnError> {
let pool = get_pool().await?; let pool = get_pool().await?;
let view = if let Some(s) = state { let view = if let Some(s) = &state {
query_as::<_, ResAllView>( query_as::<_, ResAllView>(
"SELECT r.from, r.to, p.name, p.description, s.*, c.full_name, c.email, c.phone \ "SELECT r.from, r.to, p.name, p.description, s.*, c.full_name, c.email, c.phone \
FROM reservation r \ FROM reservation r \
@ -131,7 +131,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
}; };
if view.is_empty() { if view.is_empty() {
return Ok(vec![]) return Ok((vec![], 0))
} }
let mut ret = view.into_iter().fold(HashMap::new(), |mut m, v| { let mut ret = view.into_iter().fold(HashMap::new(), |mut m, v| {
@ -148,7 +148,14 @@ cfg_if! { if #[cfg(feature = "ssr")] {
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
ret.sort_by(|a, b| a.summary.date.cmp(&b.summary.date) ); ret.sort_by(|a, b| a.summary.date.cmp(&b.summary.date) );
Ok(ret) let count = ret.len() as i64;
let last = if page.limit() == i32::MAX || ((page.offset() + page.limit()) as usize > ret.len()) { ret.len() }
else { (page.offset() + page.limit()) as usize };
let ret_paged = ret[page.offset() as usize..last]
.iter()
.map(|i| i.clone())
.collect::<Vec<_>>();
Ok((ret_paged, count))
} }
async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> { async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> {
@ -363,16 +370,18 @@ impl ForValidation for CreateReservation {
} }
#[server] #[server]
pub async fn get_new_reservations() -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> { pub async fn get_new_reservations(page: Page) -> Result<ApiResponse<PagedResponse<ResSumWithItems>>, ServerFnError> {
use crate::perm_check; use crate::perm_check;
use chrono::{Days, Local}; use chrono::{Days, Local};
use crate::backend::data::ReservationState; use crate::backend::data::ReservationState;
perm_check!(is_logged_in); perm_check!(is_logged_in);
Ok(ApiResponse::Data(reservations_in_range(&Local::now().date_naive(), let res = reservations_in_range(&Local::now().date_naive(),
&Local::now().checked_add_days(Days::new(7)).unwrap().date_naive(), &Local::now().checked_add_days(Days::new(60)).unwrap().date_naive(),
Some(ReservationState::New)).await?)) Some(ReservationState::New), page).await?;
Ok(ApiResponse::Data(PagedResponse::new(res.0, page, res.1)))
} }
#[server] #[server]
@ -385,7 +394,7 @@ pub async fn get_next_reservations() -> Result<ApiResponse<Vec<ResSumWithItems>>
Ok(ApiResponse::Data(reservations_in_range(&Local::now().date_naive(), Ok(ApiResponse::Data(reservations_in_range(&Local::now().date_naive(),
&Local::now().checked_add_days(Days::new(7)).unwrap().date_naive(), &Local::now().checked_add_days(Days::new(7)).unwrap().date_naive(),
Some(ReservationState::Approved)).await?)) Some(ReservationState::Approved), Page::default()).await?.0))
} }
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -402,9 +411,9 @@ fn num_days(month: u32, year: i32) -> i64 {
pub async fn get_reservations_for_month(month: u32, year: i32) -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> { pub async fn get_reservations_for_month(month: u32, year: i32) -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> {
let data = reservations_in_range(&NaiveDate::from_ymd_opt(year, month, 1).unwrap(), let data = reservations_in_range(&NaiveDate::from_ymd_opt(year, month, 1).unwrap(),
&NaiveDate::from_ymd_opt(year, month, num_days(month, year).to_u32().unwrap_or_default()).unwrap(), &NaiveDate::from_ymd_opt(year, month, num_days(month, year).to_u32().unwrap_or_default()).unwrap(),
None).await?; None, Page::default()).await?;
Ok(ApiResponse::Data(data)) Ok(ApiResponse::Data(data.0))
} }
#[server] #[server]
@ -522,8 +531,8 @@ pub async fn reservations_in_month(year: i32, month: u32) -> Result<ApiResponse<
let ret = reservations_in_range(&NaiveDate::from_ymd_opt(year, month, 1).ok_or(ServerFnError::new("Cannot parse date"))?, let ret = reservations_in_range(&NaiveDate::from_ymd_opt(year, month, 1).ok_or(ServerFnError::new("Cannot parse date"))?,
&NaiveDate::from_ymd_opt(year, month, num_days(month, year).to_u32().unwrap_or_default()) &NaiveDate::from_ymd_opt(year, month, num_days(month, year).to_u32().unwrap_or_default())
.ok_or(ServerFnError::new("Cannot parse date"))?, .ok_or(ServerFnError::new("Cannot parse date"))?,
None) None, Page::default())
.await?; .await?;
Ok(ApiResponse::Data(ret)) Ok(ApiResponse::Data(ret.0))
} }

@ -322,7 +322,7 @@ pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
pub async fn get_pow() -> Result<String, ServerFnError> { pub async fn get_pow() -> Result<String, ServerFnError> {
use leptos_captcha::spow::pow::Pow; use leptos_captcha::spow::pow::Pow;
if !cfg!(debug_assertions) { if cfg!(debug_assertions) {
Ok(Pow::with_difficulty(10, 10)?.to_string()) Ok(Pow::with_difficulty(10, 10)?.to_string())
} else { } else {
Ok(Pow::new(10)?.to_string()) Ok(Pow::new(10)?.to_string())

@ -7,6 +7,7 @@ use crate::components::user_menu::MenuOpener;
pub fn Header() -> impl IntoView { pub fn Header() -> impl IntoView {
let drawer = use_context::<MenuOpener>().expect("No drawer opener"); let drawer = use_context::<MenuOpener>().expect("No drawer opener");
let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper"); let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper");
//let banner_css = create_signal(String::new());
view! { view! {
<Html <Html
@ -39,6 +40,7 @@ pub fn Header() -> impl IntoView {
<Link rel="stylesheet" href="/vendor/css/theme-default.css" /> <Link rel="stylesheet" href="/vendor/css/theme-default.css" />
<Link rel="stylesheet" href="/css/demo.css" /> <Link rel="stylesheet" href="/css/demo.css" />
<Link rel="stylesheet" href="/banner.css" /> <Link rel="stylesheet" href="/banner.css" />
<Link rel="stylesheet" href="/vendor/css/control.css" />
//<!-- Vendors CSS --> //<!-- Vendors CSS -->
<Link rel="stylesheet" href="/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" /> <Link rel="stylesheet" href="/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />

@ -1,5 +1,5 @@
use leptos::*; use leptos::*;
use crate::backend::data::{ApiResponse, ResSumWithItems}; use crate::backend::data::{ApiResponse, Page, ResSumWithItems};
use crate::backend::reservation::{Approve, Cancel, get_new_reservations}; use crate::backend::reservation::{Approve, Cancel, get_new_reservations};
use crate::components::data_form::QuestionDialog; use crate::components::data_form::QuestionDialog;
use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog}; use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog};
@ -56,8 +56,12 @@ fn create_dialog(opener: DialogOpener) -> impl IntoView {
#[component] #[component]
pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView { pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -> impl IntoView {
let create = DialogOpener::new(); let create = DialogOpener::new();
let res = create_blocking_resource(move || app_opener.visible() || cancel_opener.visible() || create.visible(), let page = create_rw_signal(Page::new(5));
move |_| get_new_reservations()); let first = create_rw_signal(false);
let last = create_rw_signal(false);
let total_pages = create_rw_signal(0i32);
let res = create_blocking_resource(move || (app_opener.visible() || cancel_opener.visible() || create.visible(), page),
move |d| get_new_reservations(d.1.get()));
let reservation = create_rw_signal(ResSumWithItems::default()); let reservation = create_rw_signal(ResSumWithItems::default());
view! { view! {
@ -74,9 +78,12 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}} view! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
Ok(r) => { match r { Ok(r) => { match r {
ApiResponse::Data(r) => { ApiResponse::Data(r) => {
first.set(r.is_first());
last.set(r.is_last());
total_pages.set(r.total_pages());
view! { view! {
<div> <div>
<For each=move || r.clone() <For each=move || r.data()
key=|res| res.summary.id() key=|res| res.summary.id()
let:data> let:data>
{move || { {move || {
@ -144,6 +151,19 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) -
<a href="#" class="card-link" on:click=move |_| create.show()> <a href="#" class="card-link" on:click=move |_| create.show()>
<i class="bx bx-plus-circle fs-4 lh-0"></i> <i class="bx bx-plus-circle fs-4 lh-0"></i>
</a> </a>
<a href="#" class={move || {if first.get() {"card-link disabled"} else {"card-link"}}} on:click=move |_| page.update(move |p| p.first())>
<i class="bx bx-first-page fs-4 lh-0"></i>
</a>
<a href="#" class={move || {if first.get() {"card-link disabled"} else {"card-link"}}} on:click=move |_| page.update(move |p| p.prev())>
<i class="bx bx-chevron-left fs-4 lh-0"></i>
</a>
<span class="card-link">{move || page.get().page_num()}</span>
<a href="#" class={move || {if last.get() {"card-link disabled"} else {"card-link"}}} on:click=move |_| page.update(move |p| p.next())>
<i class="bx bx-chevron-right fs-4 lh-0"></i>
</a>
<a href="#" class={move || {if last.get() {"card-link disabled"} else {"card-link"}}} on:click=move |_| page.update(move |p| p.go_to(total_pages.get()))>
<i class="bx bx-last-page fs-4 lh-0"></i>
</a>
</div> </div>
</div> </div>
} }

Loading…
Cancel
Save