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

167 lines
5.5 KiB

use perseus::prelude::*;
use serde::Deserialize;
use sycamore::prelude::*;
use crate::{
api::types::paginated_response::{IntoTableData, PaginatedResponse},
components::{
base_table::{BaseTable, TableContentRx},
loading::Loading,
},
};
#[derive(Prop)]
pub struct PaginatedTableStateRx<'a, M, F, C>
where
M: 'a,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>> + 'a,
{
pub record_label: String,
pub route: &'a C,
pub filter: Option<String>,
pub table_class: &'a String,
pub refresh: &'a Signal<bool>,
}
impl<'a, M, F, C> PaginatedTableStateRx<'a, M, F, C>
where
M: 'a,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>> + 'a,
{
async fn get_data(&self, page: i64, size: i64) -> Result<PaginatedResponse<M>, ()> {
(self.route)(self.filter.clone(), page, size).await
}
}
/// This is a generic component that will display a paginated table given a
/// function of the generic signature C and a filter represented by an Option<String>
#[component]
pub fn PaginatedTable<'a, G, M, F, C>(
cx: Scope<'a>,
props: PaginatedTableStateRx<'a, M, F, C>,
) -> View<G>
where
G: Html,
M: 'a + Clone,
PaginatedResponse<M>: IntoTableData<G>,
for<'de> M: Deserialize<'de>,
C: Fn(Option<String>, i64, i64) -> F,
F: std::future::Future<Output = Result<PaginatedResponse<M>, ()>> + 'a,
{
let paginated_data = create_signal(cx, None);
let table_prop: TableContentRx<G> = TableContentRx {
headers_view: create_signal(cx, vec![]),
data_view: create_signal(cx, vec![vec![]]),
table_class: props.table_class,
};
let page = create_signal(cx, 0);
let n_page = create_signal(cx, 1);
let n_rows = create_signal(cx, 0);
let page_up = move |_| {
n_page.set(
(*paginated_data.get())
.clone()
.map_or(0, |t: PaginatedResponse<M>| t.num_pages),
);
if *page.get() + 1 < *n_page.get() {
page.set((*page.get()).min(*n_page.get() - 1) + 1)
}
};
let page_down = |_| {
if *page.get() > 0 {
page.set((*page.get() - 1).max(0));
}
};
let page_size_string = create_signal(cx, "20".to_string());
create_effect(cx, move || {
page_size_string.track();
page.set(0);
});
let props_sig = create_signal(cx, props);
#[cfg(client)]
let data_fetch = move || {
spawn_local_scoped(cx, async move {
let res = props_sig
.get()
.get_data(*page.get(), page_size_string.get().parse().unwrap_or(20))
.await
.unwrap();
paginated_data.set(Some(res.clone()));
n_rows.set(res.count);
let table_content = res.into_table_data(cx);
table_prop.data_view.set(table_content.data_view);
table_prop.headers_view.set(table_content.headers_view);
n_page.set((*paginated_data.get()).as_ref().map_or(0, |t| t.num_pages));
})
};
#[cfg(client)]
create_effect(cx, move || {
if *props_sig.get().refresh.get() {
props_sig.get().refresh.set(false);
data_fetch()
}
});
#[cfg(client)]
create_effect(cx, move || {
page.track();
page_size_string.track();
data_fetch();
});
view! { cx,
(if paginated_data.get().is_some() {
if *n_rows.get() == 0 {
view!{cx,
div (class="text-center rounded-md bg-slate-200 dark:bg-slate-800") {
(format!("No {}", props_sig.get().record_label))
}
}
}
else {
view! {cx,
p (class="text-right") { (format!("{} {}", n_rows.get(), props_sig.get().record_label)) }
div (class="flex flex-row justify-between") {
select (bind:value=page_size_string,
class="justify-end p-2 rounded-md text-slate-700 bg-slate-200 dark:text-slate-100 dark:bg-slate-800",
id="size-select",
) {
option (value="10", selected=(*page_size_string.get()).eq("10")) { "10" }
option (value="20", selected=(*page_size_string.get()).eq("20")) { "20" }
option (value="30", selected=(*page_size_string.get()).eq("30")) { "30" }
option (value="40", selected=(*page_size_string.get()).eq("40")) { "40" }
option (value="50", selected=(*page_size_string.get()).eq("50")) { "50" }
}
div (id="page_buttons", class="flex flex-row p-2 rounded-md bg-slate-200 dark:bg-slate-800") {
button (on:click=page_down,class="m-1 hover:font-bold") {
"<<"
}
div (class="m-1 text-center align-middle") {
(format!("{}/{}",*page.get() + 1, *n_page.get()) )
}
button (on:click=page_up, class="m-1 hover:font-bold") {
">>"
}
}
}
BaseTable(headers_view=table_prop.headers_view, data_view=table_prop.data_view, table_class=table_prop.table_class)
}
}
} else {
view! {cx,
div (class="flex flex-row justify-center") {
Loading()
}
}
})
}
}