From 3f0136e1c2e1ee9a51719d7c51ac1e5f9e5dc6ef Mon Sep 17 00:00:00 2001 From: Josef Rokos Date: Fri, 28 Mar 2025 14:30:50 +0100 Subject: [PATCH] Improved card with new reservations. --- Cargo.lock | 2 +- Cargo.toml | 2 +- assets/vendor/css/control.css | 4 ++ src/backend/data.rs | 89 +++++++++++++++++++++++++++++++++++ src/backend/reservation.rs | 37 +++++++++------ src/backend/user.rs | 2 +- src/components/header.rs | 2 + src/pages/new_reservations.rs | 28 +++++++++-- 8 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 assets/vendor/css/control.css diff --git a/Cargo.lock b/Cargo.lock index 72440ea..1105fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3211,7 +3211,7 @@ dependencies = [ [[package]] name = "rezervator" -version = "1.1.2" +version = "1.2.0" dependencies = [ "actix-files", "actix-multipart", diff --git a/Cargo.toml b/Cargo.toml index 977d599..fa0df67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rezervator" -version = "1.1.2" +version = "1.2.0" edition = "2021" [lib] diff --git a/assets/vendor/css/control.css b/assets/vendor/css/control.css new file mode 100644 index 0000000..cbcd7f5 --- /dev/null +++ b/assets/vendor/css/control.css @@ -0,0 +1,4 @@ +a.disabled { + pointer-events: none; + color: #ccc; +} diff --git a/src/backend/data.rs b/src/backend/data.rs index 08c42d8..54e1e50 100644 --- a/src/backend/data.rs +++ b/src/backend/data.rs @@ -17,6 +17,95 @@ pub enum ApiResponse { 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 { + page: Page, + record_count: i64, + data: Vec +} + +impl PagedResponse { + pub fn new(data: Vec, 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 { + self.data.clone() + } +} + #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Validate, Default)] #[cfg_attr(feature = "ssr", derive(sqlx::FromRow))] pub struct Company { diff --git a/src/backend/reservation.rs b/src/backend/reservation.rs index 1cf575b..2203485 100644 --- a/src/backend/reservation.rs +++ b/src/backend/reservation.rs @@ -1,6 +1,6 @@ use leptos::*; 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 cfg_if::cfg_if; use chrono::{NaiveDate, NaiveTime}; @@ -89,9 +89,9 @@ cfg_if! { if #[cfg(feature = "ssr")] { .await?) } - async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option) -> Result, ServerFnError> { + async fn reservations_in_range(from: &NaiveDate, to: &NaiveDate, state: Option, page: Page) -> Result<(Vec, i64), ServerFnError> { let pool = get_pool().await?; - let view = if let Some(s) = state { + let view = if let Some(s) = &state { query_as::<_, ResAllView>( "SELECT r.from, r.to, p.name, p.description, s.*, c.full_name, c.email, c.phone \ FROM reservation r \ @@ -131,7 +131,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { }; if view.is_empty() { - return Ok(vec![]) + return Ok((vec![], 0)) } let mut ret = view.into_iter().fold(HashMap::new(), |mut m, v| { @@ -148,7 +148,14 @@ cfg_if! { if #[cfg(feature = "ssr")] { }).collect::>(); 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::>(); + Ok((ret_paged, count)) } async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> { @@ -363,16 +370,18 @@ impl ForValidation for CreateReservation { } #[server] -pub async fn get_new_reservations() -> Result>, ServerFnError> { +pub async fn get_new_reservations(page: Page) -> Result>, 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?)) + let res = reservations_in_range(&Local::now().date_naive(), + &Local::now().checked_add_days(Days::new(60)).unwrap().date_naive(), + Some(ReservationState::New), page).await?; + + Ok(ApiResponse::Data(PagedResponse::new(res.0, page, res.1))) } #[server] @@ -385,7 +394,7 @@ pub async fn get_next_reservations() -> Result> 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?)) + Some(ReservationState::Approved), Page::default()).await?.0)) } #[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>, ServerFnError> { 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(), - None).await?; + None, Page::default()).await?; - Ok(ApiResponse::Data(data)) + Ok(ApiResponse::Data(data.0)) } #[server] @@ -522,8 +531,8 @@ pub async fn reservations_in_month(year: i32, month: u32) -> Result Result, ServerFnError> { pub async fn get_pow() -> Result { use leptos_captcha::spow::pow::Pow; - if !cfg!(debug_assertions) { + if cfg!(debug_assertions) { Ok(Pow::with_difficulty(10, 10)?.to_string()) } else { Ok(Pow::new(10)?.to_string()) diff --git a/src/components/header.rs b/src/components/header.rs index a576659..5640270 100644 --- a/src/components/header.rs +++ b/src/components/header.rs @@ -7,6 +7,7 @@ use crate::components::user_menu::MenuOpener; pub fn Header() -> impl IntoView { let drawer = use_context::().expect("No drawer opener"); let dlg_helper = use_context::().expect("No dialog helper"); + //let banner_css = create_signal(String::new()); view! { impl IntoView { + // diff --git a/src/pages/new_reservations.rs b/src/pages/new_reservations.rs index f14c3fa..e0e2244 100644 --- a/src/pages/new_reservations.rs +++ b/src/pages/new_reservations.rs @@ -1,5 +1,5 @@ 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::components::data_form::QuestionDialog; use crate::components::modal_box::{DialogOpener, ModalBody, ModalDialog}; @@ -56,8 +56,12 @@ fn create_dialog(opener: DialogOpener) -> impl IntoView { #[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 page = create_rw_signal(Page::new(5)); + 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()); view! { @@ -74,9 +78,12 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) - view! {
{trl("Something went wrong")}
{e.to_string()}
}} Ok(r) => { match r { ApiResponse::Data(r) => { + first.set(r.is_first()); + last.set(r.is_last()); + total_pages.set(r.total_pages()); view! {
- {move || { @@ -144,6 +151,19 @@ pub fn new_reservations(app_opener: DialogOpener, cancel_opener: DialogOpener) - + + + + + + + {move || page.get().page_num()} + + + + + +
}