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.
167 lines
5.5 KiB
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()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|