Improved card with new reservations.
This commit is contained in:
Generated
+1
-1
@@ -3211,7 +3211,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rezervator"
|
||||
version = "1.1.2"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-multipart",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rezervator"
|
||||
version = "1.1.2"
|
||||
version = "1.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
a.disabled {
|
||||
pointer-events: none;
|
||||
color: #ccc;
|
||||
}
|
||||
@@ -17,6 +17,95 @@ pub enum ApiResponse<T> {
|
||||
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)]
|
||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||
pub struct Company {
|
||||
|
||||
+23
-14
@@ -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<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 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::<Vec<_>>();
|
||||
|
||||
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> {
|
||||
@@ -363,16 +370,18 @@ impl ForValidation for CreateReservation {
|
||||
}
|
||||
|
||||
#[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 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<ApiResponse<Vec<ResSumWithItems>>
|
||||
|
||||
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<ApiResponse<Vec<ResSumWithItems>>, 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<ApiResponse<
|
||||
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())
|
||||
.ok_or(ServerFnError::new("Cannot parse date"))?,
|
||||
None)
|
||||
None, Page::default())
|
||||
.await?;
|
||||
|
||||
Ok(ApiResponse::Data(ret))
|
||||
Ok(ApiResponse::Data(ret.0))
|
||||
}
|
||||
+1
-1
@@ -322,7 +322,7 @@ pub async fn delete_user(id: i32) -> Result<ApiResponse<()>, ServerFnError> {
|
||||
pub async fn get_pow() -> Result<String, ServerFnError> {
|
||||
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())
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::components::user_menu::MenuOpener;
|
||||
pub fn Header() -> impl IntoView {
|
||||
let drawer = use_context::<MenuOpener>().expect("No drawer opener");
|
||||
let dlg_helper = use_context::<DialogHelper>().expect("No dialog helper");
|
||||
//let banner_css = create_signal(String::new());
|
||||
|
||||
view! {
|
||||
<Html
|
||||
@@ -39,6 +40,7 @@ pub fn Header() -> impl IntoView {
|
||||
<Link rel="stylesheet" href="/vendor/css/theme-default.css" />
|
||||
<Link rel="stylesheet" href="/css/demo.css" />
|
||||
<Link rel="stylesheet" href="/banner.css" />
|
||||
<Link rel="stylesheet" href="/vendor/css/control.css" />
|
||||
|
||||
//<!-- Vendors CSS -->
|
||||
<Link rel="stylesheet" href="/vendor/libs/perfect-scrollbar/perfect-scrollbar.css" />
|
||||
|
||||
@@ -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! {<div>{trl("Something went wrong")}<br/>{e.to_string()}</div>}}
|
||||
Ok(r) => { match r {
|
||||
ApiResponse::Data(r) => {
|
||||
first.set(r.is_first());
|
||||
last.set(r.is_last());
|
||||
total_pages.set(r.total_pages());
|
||||
view! {
|
||||
<div>
|
||||
<For each=move || r.clone()
|
||||
<For each=move || r.data()
|
||||
key=|res| res.summary.id()
|
||||
let:data>
|
||||
{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()>
|
||||
<i class="bx bx-plus-circle 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.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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user