use dioxus::prelude::*; use crate::api; use crate::app::{Lang, Route}; use crate::components::table_controls::*; use crate::i18n::{pick_name, t}; const STORAGE_KEY_COLS: &str = "herbapi_families_cols"; const STORAGE_KEY_PP: &str = "herbapi_families_pp"; fn family_columns() -> Vec { vec![ ColumnDef { key: "name_scientific", label: "Scientific Name", default_visible: true }, ColumnDef { key: "common_name", label: "Common Name", default_visible: true }, ColumnDef { key: "name_en", label: "English", default_visible: false }, ColumnDef { key: "name_de", label: "German", default_visible: false }, ColumnDef { key: "description", label: "Description", default_visible: false }, ] } #[component] pub fn FamilyList() -> Element { let lang = use_context::().0; let columns = family_columns(); let mut page = use_signal(|| 1i64); let per_page = use_signal(|| load_per_page(STORAGE_KEY_PP, 25)); let mut search = use_signal(|| String::new()); let visible_cols = use_signal(|| load_visible_columns(STORAGE_KEY_COLS, &family_columns())); let families = use_resource(move || { let s = search.read().clone(); let p = *page.read(); let pp = *per_page.read(); async move { let q = if s.is_empty() { None } else { Some(s.as_str()) }; api::list_families(p, pp, q).await } }); let current_page = *page.read(); let l = lang.read().clone(); rsx! { div { class: "page", h1 { "{t(&l, \"page.families\")}" } div { class: "table-toolbar", div { class: "search-bar", input { r#type: "text", placeholder: "{t(&l, \"search.placeholder_families\")}", value: "{search}", oninput: move |e| { search.set(e.value()); page.set(1); }, } } PerPageSelector { per_page: per_page, page: page, storage_key: STORAGE_KEY_PP.to_string(), } } ColumnToggle { columns: columns.clone(), visible: visible_cols, storage_key: STORAGE_KEY_COLS.to_string(), } match &*families.read() { None => rsx! { p { "{t(&l, \"loading\")}" } }, Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } }, Some(Ok(data)) => { let vis = visible_cols.read(); rsx! { p { class: "result-count", "{data.total} {t(&l, \"nav.families\").to_lowercase()}" } div { class: "table-wrap", table { thead { tr { if is_col_visible(&vis, "name_scientific") { th { "{t(&l, \"field.scientific_name\")}" } } if is_col_visible(&vis, "common_name") { th { "{t(&l, \"field.common_name\")}" } } if is_col_visible(&vis, "name_en") { th { "{t(&l, \"field.name_en\")}" } } if is_col_visible(&vis, "name_de") { th { "{t(&l, \"field.name_de\")}" } } if is_col_visible(&vis, "description") { th { "{t(&l, \"field.description\")}" } } } } tbody { for f in data.data.iter() { { let common = pick_name(&lang.read(), &f.name_de, &f.name_en, &f.name_scientific); rsx! { tr { if is_col_visible(&vis, "name_scientific") { td { Link { to: Route::FamilyDetail { slug: f.slug.clone() }, em { "{f.name_scientific}" } } } } if is_col_visible(&vis, "common_name") { td { "{common}" } } if is_col_visible(&vis, "name_en") { td { "{f.name_en.as_deref().unwrap_or(\"-\")}" } } if is_col_visible(&vis, "name_de") { td { "{f.name_de.as_deref().unwrap_or(\"-\")}" } } if is_col_visible(&vis, "description") { td { class: "cell-truncated", "{f.description.as_deref().map(|d| truncate(d, 80)).unwrap_or_else(|| \"-\".to_string())}" } } } } } } } } } if data.total > data.per_page { div { class: "pagination", button { disabled: current_page <= 1, onclick: move |_| page.set(current_page - 1), "{t(&l, \"btn.previous\")}" } span { "{t(&l, \"page\")} {current_page} {t(&l, \"of\")} {(data.total + data.per_page - 1) / data.per_page}" } button { disabled: current_page * data.per_page >= data.total, onclick: move |_| page.set(current_page + 1), "{t(&l, \"btn.next\")}" } } } } }, } } } } #[component] pub fn FamilyDetail(slug: String) -> Element { let lang = use_context::().0; let slug_clone = slug.clone(); let family = use_resource(move || { let s = slug_clone.clone(); async move { api::get_family(&s).await } }); let slug_for_species = slug.clone(); let species = use_resource(move || { let s = slug_for_species.clone(); async move { api::list_species(1, 100, Some(&s), None).await } }); let l = lang.read().clone(); rsx! { div { class: "page", match &*family.read() { None => rsx! { p { "{t(&l, \"loading\")}" } }, Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } }, Some(Ok(f)) => { let common = pick_name(&lang.read(), &f.name_de, &f.name_en, &f.name_scientific); rsx! { h1 { em { "{f.name_scientific}" } } if common != f.name_scientific { p { class: "name-common", "{common}" } } if let Some(ref desc) = f.description { p { "{desc}" } } } }, } h2 { "{t(&l, \"page.species_in_family\")}" } match &*species.read() { None => rsx! { p { "{t(&l, \"loading\")}" } }, Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } }, Some(Ok(data)) => rsx! { div { class: "card-grid", for s in data.data.iter() { { let sp_common = pick_name(&lang.read(), &s.name_de, &s.name_en, &s.name_scientific); let show_common = if sp_common == s.name_scientific { None } else { Some(sp_common) }; rsx! { div { class: "plant-card", Link { to: Route::SpeciesDetail { slug: s.slug.clone() }, em { "{s.name_scientific}" } } if let Some(ref common) = show_common { p { class: "card-common", "{common}" } } } } } } } }, } } } }