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, i64, i64) -> F, F: std::future::Future, ()>> + 'a, { pub record_label: String, pub route: &'a C, pub filter: Option, pub table_class: &'a String, pub refresh: &'a Signal, } impl<'a, M, F, C> PaginatedTableStateRx<'a, M, F, C> where M: 'a, C: Fn(Option, i64, i64) -> F, F: std::future::Future, ()>> + 'a, { async fn get_data(&self, page: i64, size: i64) -> Result, ()> { (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 #[component] pub fn PaginatedTable<'a, G, M, F, C>( cx: Scope<'a>, props: PaginatedTableStateRx<'a, M, F, C>, ) -> View where G: Html, M: 'a + Clone, PaginatedResponse: IntoTableData, for<'de> M: Deserialize<'de>, C: Fn(Option, i64, i64) -> F, F: std::future::Future, ()>> + 'a, { let paginated_data = create_signal(cx, None); let table_prop: TableContentRx = 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| 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() } } }) } }