You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fast-insiders/client/src/components/paginated_data_table.rs

132 lines
5.0 KiB

use std::rc::Rc;
use serde::Deserialize;
use sycamore::prelude::*;
use crate::{
api::types::paginated_response::{IntoTableData, PaginatedResponse},
components::{
base_table::{BaseTable, TableContentRx},
loading::Loading,
},
};
#[derive(Clone)]
pub struct PaginatedTableStateRx<M, F, C>
where
M: 'static,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>>,
{
pub route: C,
pub filter: Option<String>,
}
impl<M, F, C> PaginatedTableStateRx<M, F, C>
where
M: 'static,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>>,
{
async fn get_data(&self, page: i64, size: i64) -> Result<PaginatedResponse<M>, ()> {
(self.route)(self.filter.clone(), page, size).await
}
}
#[component(PaginatedTable<G>)]
pub fn component<M, F, C>(state: PaginatedTableStateRx<M, F, C>) -> View<G>
where
M: 'static + Clone,
PaginatedResponse<M>: IntoTableData<G>,
for<'de> M: Deserialize<'de>,
C: Fn(Option<String>, i64, i64) -> F + 'static,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>> + 'static,
{
let paginated_data: Signal<Option<PaginatedResponse<M>>> = Signal::new(None);
let table_prop: TableContentRx<G> = TableContentRx {
headers_view: Signal::new(vec![]),
data_view: Signal::new(vec![vec![]]),
};
let table_prop2 = table_prop.clone();
let page: Signal<i64> = Signal::new(0);
let n_page: Signal<i64> = Signal::new(1);
let n_rows: Signal<i64> = Signal::new(0);
let page_up = cloned!((page, paginated_data, n_page) => move |_| {
n_page.set((*paginated_data.get()).as_ref().map_or(0, |t| t.num_pages));
if *page.get() + 1 < *n_page.get() {
page.set((*page.get()).min(*n_page.get() - 1) + 1)
}
});
let page_down = cloned!((page) => move |_| {
if *page.get() > 0 {
page.set((*page.get()-1).max(0));
}
});
let page_size_string = Signal::new("20".to_string());
let page_size_string2 = page_size_string.clone();
let state_rc = Rc::new(state);
create_effect(
cloned!((page_size_string, paginated_data, page, n_page, n_rows, state_rc) => move || {
let page = *page.get();
let page_size_s = page_size_string.get();
let page_size = page_size_s.parse().unwrap_or(20);
if G::IS_BROWSER {
perseus::spawn_local(
cloned!((table_prop2, page, paginated_data, n_page, n_rows, state_rc) => async move {
let res = state_rc.get_data(page, page_size).await.unwrap();
paginated_data.set(Some(res.clone()));
n_rows.set(res.count);
let table_content = res.into_table_data();
table_prop2.data_view.set(table_content.data_view);
table_prop2.headers_view.set(table_content.headers_view);
n_page.set((*paginated_data.get()).as_ref().map_or(0, |t| t.num_pages));
}),
);
}
}),
);
view! {
(cloned!((n_rows, page_size_string, page_down, page_up, page, n_page, page_size_string2) =>
view! {
p (class="text-right") { (format!("{} transactions", n_rows.get())) }
div (class="flex flex-row justify-between") {
select (bind:value=page_size_string,
class="p-2 justify-end text-slate-700 dark:text-slate-100 bg-slate-200 dark:bg-slate-800 rounded-md",
id="size-select",
) {
option (value="20") { "20" }
option (value="10") { "10" }
option (value="30", selected=page_size_string2.get().eq(&Rc::new("30".to_string()))) { "30" }
option (value="40") { "40" }
option (value="50") { "50" }
}
div (class="flex flex-row p-2 bg-slate-200 dark:bg-slate-800 rounded-md") {
button (on:click=page_down,class="m-1 hover:font-bold") {
"<<"
}
div (class="m-1 align-middle text-center") {
(format!("{}/{}",*page.get() + 1, *n_page.get()) )
}
button (on:click=page_up, class="m-1 hover:font-bold") {
">>"
}
}
}
}))
(if paginated_data.get().is_some() {
view! {
BaseTable(table_prop.clone())
}
} else {
view! {
div (class="flex flex-row justify-center") {
Loading()
}
}
})
}
}