Implemented booking overview.
This commit is contained in:
Generated
+91
-20
@@ -54,7 +54,7 @@ dependencies = [
|
|||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash 0.8.3",
|
"ahash 0.8.3",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.2",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"bytestring",
|
"bytestring",
|
||||||
@@ -488,9 +488,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.0"
|
version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -704,6 +704,32 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charts-rs"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "078698cfc3b1c699a0285167d63362de2e57af09a30d6329a465c921eb75be95"
|
||||||
|
dependencies = [
|
||||||
|
"charts-rs-derive",
|
||||||
|
"fontdue",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"snafu",
|
||||||
|
"substring",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charts-rs-derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "273f0cf07255ec493cfb6559d83620a856bac2ca8aa4f09df400eb09f8d5d7b2"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.31"
|
version = "0.4.31"
|
||||||
@@ -942,12 +968,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.16"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
@@ -1277,6 +1300,16 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fontdue"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9099a2f86b8e674b75d03ff154b3fe4c5208ed249ced8d69cc313a9fa40bb488"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.0",
|
||||||
|
"ttf-parser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -2449,9 +2482,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
@@ -2465,7 +2498,7 @@ version = "0.10.63"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
|
checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.2",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2830,9 +2863,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.2"
|
version = "1.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -2842,9 +2875,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.3"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -2873,7 +2906,9 @@ dependencies = [
|
|||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-session",
|
"actix-session",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"base64 0.21.7",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"charts-rs",
|
||||||
"chrono",
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -3017,7 +3052,7 @@ version = "0.38.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.2",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -3187,9 +3222,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.99"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -3400,6 +3435,27 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "snafu"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d342c51730e54029130d7dc9fd735d28c4cd360f1368c01981d4f03ff207f096"
|
||||||
|
dependencies = [
|
||||||
|
"snafu-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "snafu-derive"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "080c44971436b1af15d6f61ddd8b543995cf63ab8e677d46b00cc06f4ef267a0"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -3577,7 +3633,7 @@ checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.2",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -3622,7 +3678,7 @@ checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.2",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
@@ -3710,6 +3766,15 @@ version = "0.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substring"
|
||||||
|
version = "1.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@@ -3959,6 +4024,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ttf-parser"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-builder"
|
name = "typed-builder"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ getopts = "0.2.21"
|
|||||||
leptos-use = "0.10.1"
|
leptos-use = "0.10.1"
|
||||||
lettre = {version = "0.11", features = ["tokio1-native-tls", "smtp-transport", "file-transport"], optional = true}
|
lettre = {version = "0.11", features = ["tokio1-native-tls", "smtp-transport", "file-transport"], optional = true}
|
||||||
leptos-captcha = "0.2.0"
|
leptos-captcha = "0.2.0"
|
||||||
|
charts-rs = { version = "0.3.3", optional = true}
|
||||||
|
#image = { version = "0.24.8", optional = true }
|
||||||
|
base64 = "0.21.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||||
@@ -46,6 +49,8 @@ ssr = [
|
|||||||
"dep:actix-session",
|
"dep:actix-session",
|
||||||
"dep:sqlx",
|
"dep:sqlx",
|
||||||
"dep:lettre",
|
"dep:lettre",
|
||||||
|
"dep:charts-rs",
|
||||||
|
#"dep:image",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use leptos_router::*;
|
|||||||
use crate::components::admin_portal::AdminPortal;
|
use crate::components::admin_portal::AdminPortal;
|
||||||
use crate::components::header::Header;
|
use crate::components::header::Header;
|
||||||
use crate::components::user_menu::MenuOpener;
|
use crate::components::user_menu::MenuOpener;
|
||||||
|
use crate::pages::all_reservations::Bookings;
|
||||||
use crate::pages::login::Login;
|
use crate::pages::login::Login;
|
||||||
use crate::pages::mail_settings::MailSettings;
|
use crate::pages::mail_settings::MailSettings;
|
||||||
use crate::pages::public::Public;
|
use crate::pages::public::Public;
|
||||||
@@ -91,6 +92,11 @@ pub fn App() -> impl IntoView {
|
|||||||
<MailSettings/>
|
<MailSettings/>
|
||||||
</AdminPortal>
|
</AdminPortal>
|
||||||
}/>
|
}/>
|
||||||
|
<Route path="admin/bookings" view=|| view! {
|
||||||
|
<AdminPortal>
|
||||||
|
<Bookings/>
|
||||||
|
</AdminPortal>
|
||||||
|
}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
+40
-9
@@ -1,6 +1,6 @@
|
|||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use chrono::{Local, NaiveDate, NaiveTime, Weekday};
|
use chrono::{Local, NaiveDate, NaiveTime, Weekday};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@@ -310,15 +310,20 @@ fn def_true() -> bool {
|
|||||||
#[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 ResProperty {
|
pub struct ResProperty {
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
id: i32,
|
id: i32,
|
||||||
#[validate(length(min = 1,message = "Name cannot be empty"))]
|
#[validate(length(min = 1,message = "Name cannot be empty"))]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub price: Decimal,
|
pub price: Decimal,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub slot: SlotType,
|
pub slot: SlotType,
|
||||||
#[serde(default = "def_true")]
|
#[serde(default = "def_true")]
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub allow_multi: bool,
|
pub allow_multi: bool,
|
||||||
#[serde(default = "def_true")]
|
#[serde(default = "def_true")]
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub active: bool
|
pub active: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,10 +414,12 @@ impl CrReservation {
|
|||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
|
||||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||||
pub struct Customer {
|
pub struct Customer {
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
id: i32,
|
id: i32,
|
||||||
pub full_name: String,
|
pub full_name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub phone: String,
|
pub phone: String,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub discount: i32
|
pub discount: i32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,30 +444,36 @@ pub enum ReservationState {
|
|||||||
Canceled,
|
Canceled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ReservationState {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", match self {
|
||||||
|
ReservationState::New => {"New"}
|
||||||
|
ReservationState::Approved => {"Approved"}
|
||||||
|
ReservationState::Canceled => {"Canceled"}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default)]
|
||||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||||
pub struct Reservation {
|
pub struct Reservation {
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
id: i32,
|
id: i32,
|
||||||
pub from: NaiveTime,
|
pub from: NaiveTime,
|
||||||
pub to: NaiveTime,
|
pub to: NaiveTime,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub property: i32,
|
pub property: i32,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(default))]
|
||||||
pub summary: i32,
|
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)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||||
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||||
pub struct ResWithProperty {
|
pub struct ResWithProperty {
|
||||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
pub reservation: Reservation,
|
pub reservation: Reservation,
|
||||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
pub property: ResPropertyView
|
pub property: ResProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Reservations(Vec<Reservation>);
|
pub struct Reservations(Vec<Reservation>);
|
||||||
@@ -538,6 +551,17 @@ pub struct ResSumWithItems {
|
|||||||
pub reservations: Vec<ResWithProperty>
|
pub reservations: Vec<ResWithProperty>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||||
|
pub struct ResAllView {
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
|
pub reservation: ResWithProperty,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
|
pub summary: ReservationSum,
|
||||||
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
|
pub customer: Customer,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||||
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
|
#[cfg_attr(feature = "ssr", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "ssr", sqlx(type_name = "message_type"))]
|
#[cfg_attr(feature = "ssr", sqlx(type_name = "message_type"))]
|
||||||
@@ -576,3 +600,10 @@ impl Message {
|
|||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "ssr", derive(sqlx::FromRow))]
|
||||||
|
pub struct ChartData {
|
||||||
|
pub count: i64,
|
||||||
|
pub period: f64
|
||||||
|
}
|
||||||
|
|||||||
+169
-19
@@ -12,9 +12,9 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use futures_util::future::join_all;
|
use std::collections::HashMap;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use crate::backend::data::{ReservationSum, ReservationState, ResWithProperty, Customer, Message, MessageType};
|
use crate::backend::data::{ReservationSum, ReservationState, ResWithProperty, Customer, Message, MessageType, ChartData, ResAllView};
|
||||||
use crate::backend::get_pool;
|
use crate::backend::get_pool;
|
||||||
use crate::backend::get_mailing;
|
use crate::backend::get_mailing;
|
||||||
use crate::backend::mail::MailMessage;
|
use crate::backend::mail::MailMessage;
|
||||||
@@ -23,6 +23,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use crate::backend::user::admin_email;
|
use crate::backend::user::admin_email;
|
||||||
use crate::backend::user::emails_for_notify;
|
use crate::backend::user::emails_for_notify;
|
||||||
|
use rust_decimal::prelude::ToPrimitive;
|
||||||
|
|
||||||
async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result<ReservationSum, Error> {
|
async fn find_sum_by_uuid(uuid: &Uuid, tx: &mut Transaction<'_, Postgres>) -> Result<ReservationSum, Error> {
|
||||||
let reservation = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1")
|
let reservation = query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE uuid = $1")
|
||||||
@@ -88,46 +89,64 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
|||||||
|
|
||||||
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>) -> Result<Vec<ResSumWithItems>, ServerFnError> {
|
||||||
let pool = get_pool().await?;
|
let pool = get_pool().await?;
|
||||||
let sums = if let Some(s) = state {
|
let view = if let Some(s) = state {
|
||||||
query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 AND state = $3 ORDER BY date")
|
query_as::<_, ResAllView>(
|
||||||
|
"SELECT r.from, r.to, p.name, p.description, s.*, c.full_name, c.email, c.phone \
|
||||||
|
FROM reservation r \
|
||||||
|
JOIN property p on p.id = r.property \
|
||||||
|
JOIN reservation_sum s on s.id = r.summary \
|
||||||
|
JOIN customer c on c.id = s.customer \
|
||||||
|
WHERE s.date >= $1 AND s.date <= $2 AND s.state = $3 \
|
||||||
|
ORDER BY s.id, s.date")
|
||||||
.bind(from)
|
.bind(from)
|
||||||
.bind(to)
|
.bind(to)
|
||||||
.bind(s)
|
.bind(s)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
query_as::<_, ReservationSum>("SELECT * FROM reservation_sum WHERE date >= $1 AND date <= $2 ORDER BY date")
|
query_as::<_, ResAllView>(
|
||||||
|
"SELECT r.from, r.to, p.name, p.description, s.*, c.full_name, c.email, c.phone \
|
||||||
|
FROM reservation r \
|
||||||
|
JOIN property p on p.id = r.property \
|
||||||
|
JOIN reservation_sum s on s.id = r.summary \
|
||||||
|
JOIN customer c on c.id = s.customer \
|
||||||
|
WHERE s.date >= $1 AND s.date <= $2 \
|
||||||
|
ORDER BY s.id, s.date")
|
||||||
.bind(from)
|
.bind(from)
|
||||||
.bind(to)
|
.bind(to)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
let sums = if let Err(ref e) = sums {
|
let view = if let Err(ref e) = view {
|
||||||
if matches!(e, Error::RowNotFound) {
|
if matches!(e, Error::RowNotFound) {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
sums?
|
view?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sums?
|
view?
|
||||||
};
|
};
|
||||||
|
|
||||||
if sums.is_empty() {
|
if view.is_empty() {
|
||||||
return Ok(vec![])
|
return Ok(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
let res: Result<Vec<ResSumWithItems>, ServerFnError> = join_all(sums.into_iter().map(|s| async {
|
let mut ret = view.into_iter().fold(HashMap::new(), |mut m, v| {
|
||||||
let reservations = items_for_reservation(s.id(), &pool).await?;
|
m.entry(v.summary.id())
|
||||||
let customer = customer_for_reservation(s.customer, &pool).await?;
|
.and_modify(|i: &mut (ReservationSum, Customer, Vec<ResWithProperty>)| i.2.push(v.reservation.clone()))
|
||||||
Ok(ResSumWithItems {
|
.or_insert((v.summary, v.customer, vec![v.reservation]));
|
||||||
summary: s,
|
m
|
||||||
customer,
|
}).into_iter().map(|i| {
|
||||||
reservations
|
ResSumWithItems {
|
||||||
})
|
summary: i.1.0,
|
||||||
})).await.into_iter().collect();
|
customer: i.1.1,
|
||||||
|
reservations: i.1.2
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(res?)
|
ret.sort_by(|a, b| a.summary.date.cmp(&b.summary.date) );
|
||||||
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> {
|
async fn set_state(uuid: Uuid, state: ReservationState) -> Result<(), ServerFnError> {
|
||||||
@@ -185,6 +204,29 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
|||||||
async fn notify_cancel(uuid: Uuid) -> Result<(), AppError> {
|
async fn notify_cancel(uuid: Uuid) -> Result<(), AppError> {
|
||||||
send_notify(uuid, get_message(MessageType::ReservationCanceled).await?).await
|
send_notify(uuid, get_message(MessageType::ReservationCanceled).await?).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn month_chart_data(year: i32) -> Result<Vec<ChartData>, AppError> {
|
||||||
|
let pool = get_pool().await?;
|
||||||
|
let data = query_as::<_, ChartData>(
|
||||||
|
"SELECT count(id) as count, date_part('month', date) as period \
|
||||||
|
FROM reservation_sum \
|
||||||
|
WHERE date_part('year', date) = $1 GROUP BY period ORDER BY period")
|
||||||
|
.bind(year)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn year_chart_data() -> Result<Vec<ChartData>, AppError> {
|
||||||
|
let pool = get_pool().await?;
|
||||||
|
let data = query_as::<_, ChartData>(
|
||||||
|
"SELECT count(id) as count, date_part('year', date) as period FROM reservation_sum GROUP BY period ORDER BY period")
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
@@ -327,6 +369,25 @@ pub async fn get_next_reservations() -> Result<ApiResponse<Vec<ResSumWithItems>>
|
|||||||
Some(ReservationState::Approved)).await?))
|
Some(ReservationState::Approved)).await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
fn num_days(month: u32, year: i32) -> i64 {
|
||||||
|
if month == 12 {
|
||||||
|
NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap()
|
||||||
|
} else {
|
||||||
|
NaiveDate::from_ymd_opt(year, month + 1, 1).unwrap()
|
||||||
|
}.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
|
||||||
|
.num_days()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
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?;
|
||||||
|
|
||||||
|
Ok(ApiResponse::Data(data))
|
||||||
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn approve(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
|
pub async fn approve(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
|
||||||
use crate::perm_check;
|
use crate::perm_check;
|
||||||
@@ -358,3 +419,92 @@ pub async fn cancel(uuid: String) -> Result<ApiResponse<()>, ServerFnError> {
|
|||||||
|
|
||||||
Ok(ApiResponse::Data(()))
|
Ok(ApiResponse::Data(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
fn chart(data: &Vec<ChartData>, title: &str, month: bool) -> String {
|
||||||
|
use charts_rs::{BarChart, THEME_ANT};
|
||||||
|
use base64::Engine;
|
||||||
|
use base64::prelude::BASE64_STANDARD;
|
||||||
|
use std::ops::Add;
|
||||||
|
use chrono::Month;
|
||||||
|
|
||||||
|
let mut chart = BarChart::new_with_theme(
|
||||||
|
vec![("Bookings", data.iter().map(|d| d.count.to_f32().unwrap_or_default()).collect()).into()],
|
||||||
|
if month {
|
||||||
|
data.iter().map(|d| Month::try_from(d.period.to_u8().unwrap_or_default()).unwrap_or(Month::January).name().to_string()).collect()
|
||||||
|
} else {
|
||||||
|
data.iter().map(|d| d.period.to_string()).collect()
|
||||||
|
},
|
||||||
|
THEME_ANT);
|
||||||
|
chart.title_text = title.to_string();
|
||||||
|
chart.legend_show = Some(false);
|
||||||
|
chart.series_label_font_size = 8.0;
|
||||||
|
chart.x_axis_font_size = 8.0;
|
||||||
|
chart.title_font_size = 11.0;
|
||||||
|
chart.font_family = "Arial".to_string();
|
||||||
|
chart.y_axis_configs[0].axis_font_size = 8.0;
|
||||||
|
|
||||||
|
|
||||||
|
chart.width = 300.0;
|
||||||
|
chart.height = 150.0;
|
||||||
|
|
||||||
|
"data:image/svg+xml;base64,".to_string().add(&BASE64_STANDARD.encode(&chart.svg().unwrap_or_default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn month_chart(year: i32) -> Result<ApiResponse<String>, ServerFnError> {
|
||||||
|
use crate::perm_check;
|
||||||
|
perm_check!(is_logged_in);
|
||||||
|
|
||||||
|
let data = month_chart_data(year).await?;
|
||||||
|
Ok(ApiResponse::Data(chart(&data, "Month bookings", true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn year_chart() -> Result<ApiResponse<String>, ServerFnError> {
|
||||||
|
use crate::perm_check;
|
||||||
|
perm_check!(is_logged_in);
|
||||||
|
|
||||||
|
let data = year_chart_data().await?;
|
||||||
|
Ok(ApiResponse::Data(chart(&data, "Year bookings", false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn years() -> Result<Vec<i32>, ServerFnError> {
|
||||||
|
let pool = get_pool().await?;
|
||||||
|
|
||||||
|
let data: Result<Vec<(f64,)>, Error> = query_as("SELECT DISTINCT date_part('year', date) as year FROM reservation_sum ORDER BY year DESC")
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(data?.into_iter().map(|d| d.0.to_i32().unwrap_or_default()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn months(year: i32) -> Result<Vec<u32>, ServerFnError> {
|
||||||
|
let pool = get_pool().await?;
|
||||||
|
|
||||||
|
let data: Result<Vec<(f64,)>, Error> = query_as("SELECT DISTINCT date_part('month', date) as month \
|
||||||
|
FROM reservation_sum \
|
||||||
|
WHERE date_part('year', date) = $1 \
|
||||||
|
ORDER BY month DESC")
|
||||||
|
.bind(year)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(data?.into_iter().map(|d| d.0.to_u32().unwrap_or_default()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn reservations_in_month(year: i32, month: u32) -> Result<ApiResponse<Vec<ResSumWithItems>>, ServerFnError> {
|
||||||
|
use crate::perm_check;
|
||||||
|
perm_check!(is_logged_in);
|
||||||
|
|
||||||
|
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)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(ApiResponse::Data(ret))
|
||||||
|
}
|
||||||
@@ -69,13 +69,13 @@ pub fn AdminPortal(children: Children) -> impl IntoView {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="/bookings" class="menu-link">
|
<a href="/admin/bookings" class="menu-link">
|
||||||
<i class="menu-icon tf-icons bx bx-layer"></i>
|
<i class="menu-icon tf-icons bx bx-layer"></i>
|
||||||
<div data-i18n="Analytics">"Booking summary"</div>
|
<div data-i18n="Analytics">"Booking summary"</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<a href="/customers" class="menu-link">
|
<a href="/admin/customers" class="menu-link">
|
||||||
<i class="menu-icon tf-icons bx bx-face"></i>
|
<i class="menu-icon tf-icons bx bx-face"></i>
|
||||||
<div data-i18n="Analytics">"Customers"</div>
|
<div data-i18n="Analytics">"Customers"</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
use chrono::{Datelike, Local};
|
||||||
|
use leptos::*;
|
||||||
|
use crate::backend::data::ApiResponse;
|
||||||
|
use crate::backend::reservation::{month_chart, reservations_in_month, year_chart, years};
|
||||||
|
use crate::locales::{loc_date, trl};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn bookings() -> impl IntoView {
|
||||||
|
let year_chart = create_blocking_resource(||(), |_| year_chart());
|
||||||
|
let years = create_blocking_resource(||(), |_| years());
|
||||||
|
let year = create_rw_signal(Local::now().year());
|
||||||
|
let month = create_rw_signal(Local::now().month());
|
||||||
|
let chart = create_blocking_resource(move || year.get(),move |y| month_chart(y));
|
||||||
|
let reservations = create_blocking_resource(move || (year.get(), month.get()), move |p| reservations_in_month(p.0, p.1));
|
||||||
|
let all_months: Vec<u32> = vec![1,2,3,4,5,6,7,8,9,10,11,12];
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<h1>{trl("Booking overview")}</h1>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-5">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="nav-link">
|
||||||
|
"Rok: "
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<Transition fallback=move || view! {<div>"Loading"</div>} >
|
||||||
|
{
|
||||||
|
years.get().map(|y| match y {
|
||||||
|
Ok(y) => {
|
||||||
|
view! {
|
||||||
|
<select class="form-select" on:change=move |ev| {
|
||||||
|
let new_value = event_target_value(&ev).parse::<i32>().unwrap_or_default();
|
||||||
|
year.set(new_value);
|
||||||
|
}>
|
||||||
|
<For each=move || y.clone() key=|i| *i let:y>
|
||||||
|
<option prop:value=move || y prop:selected=move || year.get() == y>{y}</option>
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {view! {<select><option></option></select>}}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Transition>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<div class="nav-link">
|
||||||
|
"Měsíc: "
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<select class="form-select" on:change=move |ev| {
|
||||||
|
let new_value = event_target_value(&ev).parse::<u32>().unwrap_or_default();
|
||||||
|
month.set(new_value);
|
||||||
|
}>
|
||||||
|
<For each=move || all_months.clone() key=|i| *i let:m>
|
||||||
|
<option prop:value=move || m prop:selected=move || month.get() == m>{m}</option>
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md">
|
||||||
|
<div class="card md-3">
|
||||||
|
<Transition fallback=move || view! {<div>"Loading"</div>}>
|
||||||
|
{
|
||||||
|
chart.get().map(move |c| match c {
|
||||||
|
Ok(c) => { match c {
|
||||||
|
ApiResponse::Data(c) => {view! {<img style="margin: 1em" src={c.clone()} /> }}
|
||||||
|
ApiResponse::Error(_) => {view! {<img src=""/> }} }
|
||||||
|
}
|
||||||
|
Err(_) => { view! {<img src=""/> } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
<div class="card md-3">
|
||||||
|
<Transition fallback=move || view! {<div>"Loading"</div>}>
|
||||||
|
{
|
||||||
|
year_chart.get().map(move |c| match c {
|
||||||
|
Ok(c) => { match c {
|
||||||
|
ApiResponse::Data(c) => {view! {<img style="margin: 1em" src={c.clone()} /> }}
|
||||||
|
ApiResponse::Error(_) => {view! {<img src=""/> }} }
|
||||||
|
}
|
||||||
|
Err(_) => { view! {<img src=""/> } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<Transition fallback=move || view! {<div>"Loading"</div>}>
|
||||||
|
<table class="table card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{trl("Date")}</th>
|
||||||
|
<th>{trl("Customer")}</th>
|
||||||
|
<th>{trl("Price")}</th>
|
||||||
|
<th>{trl("State")}</th>
|
||||||
|
<th>{trl("Actions")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{
|
||||||
|
reservations.get().map(|r| match r {
|
||||||
|
Ok(r) => { match r {
|
||||||
|
ApiResponse::Data(r) => {
|
||||||
|
view! {
|
||||||
|
<tbody class="table-border-bottom-0">
|
||||||
|
<For each=move || r.clone()
|
||||||
|
key=|i| i.summary.id()
|
||||||
|
let:data>
|
||||||
|
<tr>
|
||||||
|
<td>{loc_date(data.summary.date)}</td>
|
||||||
|
<td>{data.customer.full_name}</td>
|
||||||
|
<td>{data.summary.price.to_string()}</td>
|
||||||
|
<td>{data.summary.state.to_string()}</td>
|
||||||
|
<td><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></td>
|
||||||
|
</tr>
|
||||||
|
</For>
|
||||||
|
</tbody>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApiResponse::Error(e) => {
|
||||||
|
view! {<tbody class="table-border-bottom-0">
|
||||||
|
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
view! {<tbody class="table-border-bottom-0">
|
||||||
|
<tr><td colspan=5>{trl("Something went wrong")}<br/>{e.to_string()}</td></tr></tbody>}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,4 +19,5 @@ mod today_reservations;
|
|||||||
mod new_reservations;
|
mod new_reservations;
|
||||||
pub mod mail_settings;
|
pub mod mail_settings;
|
||||||
mod mail_view;
|
mod mail_view;
|
||||||
|
pub mod all_reservations;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user