use dioxus::prelude::*; use std::collections::HashMap; use uuid::Uuid; use crate::api; use crate::app::{Lang, Route}; use crate::components::planting_calendar::PlantingCalendar; use crate::components::table_controls::*; use crate::i18n::{pick_desc, pick_name, t, t_val}; /// Convert a month-number array (1=Jan..12=Dec) to a comma-separated string of abbreviations. fn months_display(months: &Option>) -> String { const NAMES: [&str; 12] = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; match months { Some(v) if !v.is_empty() => v .iter() .filter_map(|&m| NAMES.get((m - 1) as usize).copied()) .collect::>() .join(", "), _ => "\u{2014}".to_string(), // em dash } } /// Format an Option for display: value or em dash placeholder. fn opt_str(val: &Option) -> String { match val { Some(s) if !s.is_empty() => s.clone(), _ => "\u{2014}".to_string(), } } /// Format an Option with a suffix. fn opt_f64_suffix(val: Option, suffix: &str) -> String { match val { Some(v) => format!("{v}{suffix}"), None => "\u{2014}".to_string(), } } /// Format an Option with a suffix. fn opt_i32_suffix(val: Option, suffix: &str) -> String { match val { Some(v) => format!("{v}{suffix}"), None => "\u{2014}".to_string(), } } /// Format an Option as Yes / No / em dash (translated). fn opt_bool_t(val: Option, lang: &str) -> String { match val { Some(true) => t(lang, "yes").to_string(), Some(false) => t(lang, "no").to_string(), None => "\u{2014}".to_string(), } } /// Bool field: always present (non-Option), translated. fn bool_display_t(val: bool, lang: &str) -> String { if val { t(lang, "yes").to_string() } else { t(lang, "no").to_string() } } /// Format an Option> as comma-separated or em dash. fn opt_vec_str(val: &Option>) -> String { match val { Some(v) if !v.is_empty() => v.join(", "), _ => "\u{2014}".to_string(), } } /// Infer a light requirement string from a species plant_layer value. fn infer_light_from_layer(layer: &Option) -> Option { match layer.as_deref() { Some(l) => { let lower = l.to_lowercase(); if lower.contains("canopy") || lower.contains("tall tree") { Some("Full sun".to_string()) } else if lower.contains("understory") || lower.contains("small tree") || lower.contains("shrub") { Some("Partial shade".to_string()) } else if lower.contains("ground") || lower.contains("creep") { Some("Shade tolerant".to_string()) } else if lower.contains("herb") || lower.contains("climber") || lower.contains("vine") { Some("Full sun to partial shade".to_string()) } else { None } } None => None, } } const STORAGE_KEY_COLS: &str = "herbapi_cultivars_cols"; const STORAGE_KEY_PP: &str = "herbapi_cultivars_pp"; fn cultivar_columns() -> Vec { vec![ ColumnDef { key: "name", label: "Name", default_visible: true }, ColumnDef { key: "species", label: "Species", default_visible: true }, ColumnDef { key: "organic", label: "Organic", default_visible: true }, ColumnDef { key: "perennial", label: "Perennial", default_visible: true }, ColumnDef { key: "description", label: "Description", default_visible: false }, ColumnDef { key: "frost_tolerance", label: "Frost Tol.", default_visible: false }, ColumnDef { key: "growing_time", label: "Growing Time", default_visible: false }, ColumnDef { key: "days_germ", label: "Days to Germ.", default_visible: false }, ] } #[component] pub fn CultivarList() -> Element { let lang = use_context::().0; let columns = cultivar_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, &cultivar_columns())); // Fetch species list once to map species_id -> name let species_map = use_resource(move || async move { let mut map = HashMap::::new(); if let Ok(resp) = api::list_species(1, 100, None, None).await { for s in resp.data { map.insert(s.id, s.name_scientific); } // Fetch remaining pages if needed let total_pages = (resp.total + resp.per_page - 1) / resp.per_page; for p in 2..=total_pages { if let Ok(r) = api::list_species(p, 100, None, None).await { for s in r.data { map.insert(s.id, s.name_scientific); } } } } map }); let cultivars = use_resource(move || { let p = *page.read(); let pp = *per_page.read(); let s = search.read().clone(); async move { let q = if s.is_empty() { None } else { Some(s.as_str()) }; api::list_cultivars(p, pp, None, q).await } }); let current_page = *page.read(); let l = lang.read().clone(); rsx! { div { class: "page", h1 { "{t(&l, \"page.cultivars\")}" } div { class: "table-toolbar", div { class: "search-bar", input { r#type: "text", placeholder: "{t(&l, \"search.placeholder_cultivars\")}", 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 &*cultivars.read() { None => rsx! { p { "{t(&l, \"loading\")}" } }, Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } }, Some(Ok(data)) => { let smap = species_map.read(); let empty_map: HashMap = HashMap::new(); let sm = match &*smap { Some(m) => m, _ => &empty_map, }; let vis = visible_cols.read(); rsx! { p { class: "result-count", "{data.total} {t(&l, \"nav.cultivars\").to_lowercase()}" } div { class: "table-wrap", table { thead { tr { if is_col_visible(&vis, "name") { th { "{t(&l, \"field.name\")}" } } if is_col_visible(&vis, "species") { th { "{t(&l, \"field.species\")}" } } if is_col_visible(&vis, "organic") { th { "{t(&l, \"field.organic\")}" } } if is_col_visible(&vis, "perennial") { th { "{t(&l, \"field.perennial\")}" } } if is_col_visible(&vis, "description") { th { "{t(&l, \"field.description\")}" } } if is_col_visible(&vis, "frost_tolerance") { th { "{t(&l, \"field.frost_tol\")}" } } if is_col_visible(&vis, "growing_time") { th { "{t(&l, \"field.growing_time\")}" } } if is_col_visible(&vis, "days_germ") { th { "{t(&l, \"field.days_germ\")}" } } } } tbody { for c in data.data.iter() { { let species_name: &str = sm.get(&c.species_id).map(String::as_str).unwrap_or("-"); rsx! { tr { if is_col_visible(&vis, "name") { td { Link { to: Route::CultivarDetail { slug: c.slug.clone() }, strong { "{c.name}" } } } } if is_col_visible(&vis, "species") { td { class: "species-name", em { "{species_name}" } } } if is_col_visible(&vis, "organic") { td { if c.is_organic { span { class: "badge organic", "{t(&l, \"yes\")}" } } else { "-" } } } if is_col_visible(&vis, "perennial") { td { if c.perennial { "{t(&l, \"yes\")}" } else { "{t(&l, \"annual\")}" } } } if is_col_visible(&vis, "description") { td { class: "cell-truncated", "{c.description.as_deref().map(|d| truncate(d, 60)).unwrap_or_else(|| \"-\".to_string())}" } } if is_col_visible(&vis, "frost_tolerance") { td { "{c.frost_tolerance.as_deref().unwrap_or(\"-\")}" } } if is_col_visible(&vis, "growing_time") { td { match c.growing_time_days { Some(d) => rsx! { "{d} days" }, None => rsx! { "-" }, } } } if is_col_visible(&vis, "days_germ") { td { match c.days_to_germination { Some(d) => rsx! { "{d}" }, None => rsx! { "-" }, } } } } } } } } } } 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 CultivarDetail(slug: String) -> Element { let lang = use_context::().0; let slug_clone = slug.clone(); let cultivar = use_resource(move || { let s = slug_clone.clone(); async move { api::get_cultivar(&s).await } }); // Fetch species info once we have the cultivar let species_data = use_resource(move || { let cv = cultivar.read().clone(); async move { if let Some(Ok(c)) = cv { let resp = api::list_species(1, 200, None, None).await.ok(); if let Some(r) = resp { for s in &r.data { if s.id == c.species_id { return Some(s.clone()); } } } } None } }); // Fetch supplier links once we have the cultivar let suppliers_data = use_resource(move || { let cv = cultivar.read().clone(); async move { if let Some(Ok(c)) = cv { api::get_cultivar_suppliers(c.id).await.ok() } else { None } } }); // Fetch all suppliers for name lookup let all_suppliers = use_resource(move || async move { api::list_suppliers().await.ok().unwrap_or_default() }); // Fetch species images (to show species image on cultivar page) let species_images = use_resource(move || { let cv = cultivar.read().clone(); async move { if let Some(Ok(c)) = cv { api::get_images("species", c.species_id).await.ok().unwrap_or_default() } else { vec![] } } }); rsx! { div { class: "page cultivar-detail", match &*cultivar.read() { None => rsx! { p { "{t(&lang.read(), \"loading\")}" } }, Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } }, Some(Ok(c)) => { let current_lang = lang.read().clone(); let l = &*current_lang; // Pre-compute display strings outside of rsx let common_name = pick_name(¤t_lang, &c.name_de, &c.name_en, &c.name); let name_en = opt_str(&c.name_en); let name_de = opt_str(&c.name_de); let name_sci = opt_str(&c.name_scientific); let desc = pick_desc(¤t_lang, &c.description_de, &c.description_en, &c.description); let organic = bool_display_t(c.is_organic, l); let perennial = bool_display_t(c.perennial, l); // Planting schedule let indoor = months_display(&c.indoor_sowing_months); let direct = months_display(&c.direct_sowing_months); let transplant = months_display(&c.transplanting_months); let glasshouse = months_display(&c.glasshouse_months); let harvest = months_display(&c.harvesting_months); let succession = opt_i32_suffix(c.succession_planting_days, " days"); let planting_notes = opt_str(&c.planting_notes); // Growing information let growing_time = opt_i32_suffix(c.growing_time_days, " days"); let planting_depth = opt_f64_suffix(c.planting_depth_cm, " cm"); let row_spacing = opt_f64_suffix(c.row_spacing_cm, " cm"); let plant_spacing = opt_f64_suffix(c.plant_spacing_cm, " cm"); let propagation = opt_vec_str(&c.propagation_methods); // Germination let days_germ = opt_i32_suffix(c.days_to_germination, ""); let germ_temp = opt_f64_suffix(c.germination_temp_c, " \u{00b0}C"); let light_req = opt_str(&c.light_requirement); let strat_req = opt_bool_t(c.stratification_required, l); let strat_days = opt_i32_suffix(c.stratification_days, ""); let scar_req = opt_bool_t(c.scarification_required, l); // Climate let min_temp = opt_f64_suffix(c.min_temp, " \u{00b0}C"); let max_temp = opt_f64_suffix(c.max_temp, " \u{00b0}C"); let humidity = opt_str(&c.humidity); let light = opt_str(&c.light); let frost_tol = opt_str(&c.frost_tolerance); let min_light_h = opt_f64_suffix(c.min_light_hours_day, " h"); let opt_light_h = opt_f64_suffix(c.optimal_light_hours_day, " h"); // --- Species fallback values --- let (sp_frost_fb, sp_light_fb, sp_drought_fb, sp_usda_fb) = { let sp_read = species_data.read(); match &*sp_read { Some(Some(sp)) => { let frost = sp.hardiness_zone_usda.as_ref() .filter(|s| !s.is_empty()) .map(|z| format!("USDA Zone {z}")); let light = infer_light_from_layer(&sp.plant_layer); let drought = sp.drought_tolerance.as_deref() .filter(|s| !s.is_empty()) .map(|v| t_val(l, v).to_string()); let usda = sp.hardiness_zone_usda.clone() .filter(|s| !s.is_empty()); (frost, light, drought, usda) } _ => (None, None, None, None), } }; let has_any_fallback = (frost_tol == "\u{2014}" && sp_frost_fb.is_some()) || (light_req == "\u{2014}" && sp_light_fb.is_some()) || (light == "\u{2014}" && sp_light_fb.is_some()) || sp_drought_fb.is_some() || sp_usda_fb.is_some(); // Species image let sp_primary_img: Option = { let imgs = species_images.read(); match &*imgs { Some(v) if !v.is_empty() => { v.iter().find(|i| i.is_primary).or(v.first()).cloned() } _ => None, } }; let sp_img_key: Option = sp_primary_img.as_ref().map(|i| i.s3_key.clone()) .or_else(|| { let sp_read = species_data.read(); match &*sp_read { Some(Some(sp)) => sp.primary_image_key.clone(), _ => None, } }); rsx! { h1 { "{c.name}" } if common_name != c.name { p { class: "name-common", "{common_name}" } } // Week-based planting calendar (full width) div { class: "detail-card", style: "margin-top: 1.25rem;", div { class: "detail-card-header", "{t(l, \"card.planting_calendar\")}" } div { style: "padding: 0.75rem;", PlantingCalendar { indoor_sowing_months: c.indoor_sowing_months.clone(), direct_sowing_months: c.direct_sowing_months.clone(), transplanting_months: c.transplanting_months.clone(), glasshouse_months: c.glasshouse_months.clone(), harvesting_months: c.harvesting_months.clone(), indoor_sowing_weeks: c.indoor_sowing_weeks.clone(), direct_sowing_weeks: c.direct_sowing_weeks.clone(), transplanting_weeks: c.transplanting_weeks.clone(), glasshouse_weeks: c.glasshouse_weeks.clone(), harvesting_weeks: c.harvesting_weeks.clone(), } } } div { class: "detail-row", // === LEFT COLUMN === div { class: "detail-col", // Card 1: Cultivar Details div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.cultivar_details\")}" } table { class: "attr-table", tbody { tr { th { "{t(l, \"field.name\")}" } td { "{c.name}" } } tr { th { "{t(l, \"field.common_name\")}" } td { "{common_name}" } } tr { th { "{t(l, \"field.name_en\")}" } td { class: if name_en == "\u{2014}" { "placeholder" } else { "" }, "{name_en}" } } tr { th { "{t(l, \"field.name_de\")}" } td { class: if name_de == "\u{2014}" { "placeholder" } else { "" }, "{name_de}" } } tr { th { "{t(l, \"field.scientific_name\")}" } td { class: if name_sci == "\u{2014}" { "placeholder" } else { "" }, if name_sci != "\u{2014}" { em { "{name_sci}" } } else { span { "\u{2014}" } } } } tr { th { "{t(l, \"field.species\")}" } td { if let Some(Some(ref sp)) = *species_data.read() { Link { to: Route::SpeciesDetail { slug: sp.slug.clone() }, em { "{sp.name_scientific}" } } if let Some(ref de) = sp.name_de { if !de.is_empty() { " ({de})" } } } else { span { class: "placeholder", "\u{2014}" } } } } tr { th { "{t(l, \"field.description\")}" } td { class: if desc == "\u{2014}" { "placeholder" } else { "" }, "{desc}" } } tr { th { "{t(l, \"field.organic\")}" } td { "{organic}" } } tr { th { "{t(l, \"field.perennial\")}" } td { "{perennial}" } } } } } // Card 2: Planting Schedule div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.planting_schedule\")}" } table { class: "attr-table", tbody { tr { th { "{t(l, \"cal.indoor_sowing\")}" } td { class: if indoor == "\u{2014}" { "placeholder" } else { "" }, "{indoor}" } } tr { th { "{t(l, \"cal.direct_sowing\")}" } td { class: if direct == "\u{2014}" { "placeholder" } else { "" }, "{direct}" } } tr { th { "{t(l, \"cal.transplanting\")}" } td { class: if transplant == "\u{2014}" { "placeholder" } else { "" }, "{transplant}" } } tr { th { "{t(l, \"cal.glasshouse\")}" } td { class: if glasshouse == "\u{2014}" { "placeholder" } else { "" }, "{glasshouse}" } } tr { th { "{t(l, \"cal.harvesting\")}" } td { class: if harvest == "\u{2014}" { "placeholder" } else { "" }, "{harvest}" } } tr { th { "{t(l, \"field.succession_planting\")}" } td { class: if succession == "\u{2014}" { "placeholder" } else { "" }, "{succession}" } } tr { th { "{t(l, \"field.planting_notes\")}" } td { class: if planting_notes == "\u{2014}" { "placeholder" } else { "" }, "{planting_notes}" } } } } } // Card 3: Where to Buy div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.where_to_buy\")}" } { let sup_links = suppliers_data.read(); let sups = all_suppliers.read(); let sup_list: Vec = match &*sups { Some(v) => v.clone(), None => vec![], }; match &*sup_links { Some(Some(links)) if !links.is_empty() => { rsx! { table { class: "attr-table", thead { tr { th { "{t(l, \"field.supplier\")}" } th { "{t(l, \"field.sku\")}" } th { "{t(l, \"field.price\")}" } th { "{t(l, \"field.link\")}" } } } tbody { for link in links.iter() { { let sup_name = sup_list.iter() .find(|s| s.id == link.supplier_id) .map(|s| s.name.clone()) .unwrap_or_else(|| "\u{2014}".to_string()); let sku = link.article_number.as_deref().unwrap_or("\u{2014}").to_string(); let price = match link.price_eur { Some(p) if p > 0.0 => format!("EUR {p:.2}"), _ => "\u{2014}".to_string(), }; let url = link.product_url.clone(); rsx! { tr { td { "{sup_name}" } td { class: if sku == "\u{2014}" { "placeholder" } else { "" }, "{sku}" } td { class: if price == "\u{2014}" { "placeholder" } else { "" }, "{price}" } td { if let Some(ref u) = url { if !u.is_empty() { a { href: "{u}", target: "_blank", class: "external-link", "{t(l, \"btn.view\")}" } } else { span { class: "placeholder", "\u{2014}" } } } else { span { class: "placeholder", "\u{2014}" } } } } } } } } } } }, _ => rsx! { p { class: "placeholder detail-card-empty", "\u{2014} {t(l, \"no_suppliers_linked\")}" } }, } } } } // === RIGHT COLUMN === div { class: "detail-col", // Image card if let Some(ref key) = sp_img_key { div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.image\")}" } div { class: "image-card-body", img { class: "species-image", src: "/img/{key}", alt: "{c.name}" } if let Some(ref img) = sp_primary_img { div { class: "image-attribution", if let Some(ref caption) = img.caption { span { "{caption}" } } if let Some(ref lic) = img.license { if img.caption.is_some() { " | " } span { "{lic}" } } if let Some(ref url) = img.source_url { if img.caption.is_some() || img.license.is_some() { " | " } a { href: "{url}", target: "_blank", class: "attribution-link", "{t(l, \"field.source\")}" } } } } } } } // Card 4: Growing Information div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.growing_info\")}" } table { class: "attr-table", tbody { tr { th { "{t(l, \"field.growing_time\")}" } td { class: if growing_time == "\u{2014}" { "placeholder" } else { "" }, "{growing_time}" } } tr { th { "{t(l, \"field.planting_depth\")}" } td { class: if planting_depth == "\u{2014}" { "placeholder" } else { "" }, "{planting_depth}" } } tr { th { "{t(l, \"field.row_spacing\")}" } td { class: if row_spacing == "\u{2014}" { "placeholder" } else { "" }, "{row_spacing}" } } tr { th { "{t(l, \"field.plant_spacing\")}" } td { class: if plant_spacing == "\u{2014}" { "placeholder" } else { "" }, "{plant_spacing}" } } tr { th { "{t(l, \"field.propagation\")}" } td { class: if propagation == "\u{2014}" { "placeholder" } else { "" }, "{propagation}" } } } } } // Card 5: Germination div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.germination\")}" } table { class: "attr-table", tbody { tr { th { "{t(l, \"field.days_to_germ\")}" } td { class: if days_germ == "\u{2014}" { "placeholder" } else { "" }, "{days_germ}" } } tr { th { "{t(l, \"field.germ_temp\")}" } td { class: if germ_temp == "\u{2014}" { "placeholder" } else { "" }, "{germ_temp}" } } tr { th { "{t(l, \"field.light_req\")}" } if light_req != "\u{2014}" { td { "{light_req}" } } else if let Some(ref fb) = sp_light_fb { td { span { class: "estimated-value", "{fb}" } span { class: "estimated-tag", "~ species" } } } else { td { class: "placeholder", "\u{2014}" } } } tr { th { "{t(l, \"field.stratification\")}" } td { class: if strat_req == "\u{2014}" { "placeholder" } else { "" }, "{strat_req}" } } tr { th { "{t(l, \"field.stratification_days\")}" } td { class: if strat_days == "\u{2014}" { "placeholder" } else { "" }, "{strat_days}" } } tr { th { "{t(l, \"field.scarification\")}" } td { class: if scar_req == "\u{2014}" { "placeholder" } else { "" }, "{scar_req}" } } } } } // Card 6: Climate & Environment div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.climate\")}" } table { class: "attr-table", tbody { tr { th { "{t(l, \"field.min_temp\")}" } td { class: if min_temp == "\u{2014}" { "placeholder" } else { "" }, "{min_temp}" } } tr { th { "{t(l, \"field.max_temp\")}" } td { class: if max_temp == "\u{2014}" { "placeholder" } else { "" }, "{max_temp}" } } tr { th { "{t(l, \"field.humidity\")}" } td { class: if humidity == "\u{2014}" { "placeholder" } else { "" }, "{humidity}" } } tr { th { "{t(l, \"field.light\")}" } if light != "\u{2014}" { td { "{light}" } } else if let Some(ref fb) = sp_light_fb { td { span { class: "estimated-value", "{fb}" } span { class: "estimated-tag", "~ species" } } } else { td { class: "placeholder", "\u{2014}" } } } tr { th { "{t(l, \"field.frost_tolerance\")}" } if frost_tol != "\u{2014}" { td { "{frost_tol}" } } else if let Some(ref fb) = sp_frost_fb { td { span { class: "estimated-value", "{fb}" } span { class: "estimated-tag", "~ species" } } } else { td { class: "placeholder", "\u{2014}" } } } tr { th { "{t(l, \"field.min_light_hours\")}" } td { class: if min_light_h == "\u{2014}" { "placeholder" } else { "" }, "{min_light_h}" } } tr { th { "{t(l, \"field.opt_light_hours\")}" } td { class: if opt_light_h == "\u{2014}" { "placeholder" } else { "" }, "{opt_light_h}" } } if let Some(ref fb) = sp_drought_fb { tr { th { "{t(l, \"field.drought_tolerance\")}" } td { span { class: "estimated-value", "{fb}" } span { class: "estimated-tag", "~ species" } } } } if let Some(ref fb) = sp_usda_fb { tr { th { "{t(l, \"field.usda_hardiness_zone\")}" } td { span { class: "estimated-value", "{fb}" } span { class: "estimated-tag", "~ species" } } } } } } } // Card 7: Species Information div { class: "detail-card", div { class: "detail-card-header", "{t(l, \"card.species_info\")}" } { let sp_read = species_data.read(); match &*sp_read { Some(Some(sp)) => { let ph_range = match (sp.ph_min, sp.ph_max) { (Some(mn), Some(mx)) => format!("{mn} \u{2013} {mx}"), (Some(mn), None) => format!("{mn}+"), (None, Some(mx)) => format!("< {mx}"), _ => "\u{2014}".to_string(), }; let sp_layer = sp.plant_layer.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| "\u{2014}".to_string()); let sp_drought = sp.drought_tolerance.as_deref().map(|v| t_val(l, v).to_string()).unwrap_or_else(|| "\u{2014}".to_string()); let sp_usda = opt_str(&sp.hardiness_zone_usda); let sp_nfix = opt_bool_t(sp.nitrogen_fixer, l); let sp_dynacc = opt_bool_t(sp.dynamic_accumulator, l); let sp_food = pick_desc(¤t_lang, &sp.food_uses_de, &sp.food_uses_en, &sp.food_uses); let sp_med = pick_desc(¤t_lang, &sp.medicinal_uses_de, &sp.medicinal_uses_en, &sp.medicinal_uses); let sp_other = pick_desc(¤t_lang, &sp.other_uses_de, &sp.other_uses_en, &sp.other_uses); let sp_wildlife = opt_str(&sp.wildlife_value); let sp_native = opt_str(&sp.native_range); rsx! { table { class: "attr-table", tbody { tr { th { "{t(l, \"field.plant_layer\")}" } td { class: if sp_layer == "\u{2014}" { "placeholder" } else { "" }, "{sp_layer}" } } tr { th { "{t(l, \"field.drought_tolerance\")}" } td { class: if sp_drought == "\u{2014}" { "placeholder" } else { "" }, "{sp_drought}" } } tr { th { "{t(l, \"field.usda_zone\")}" } td { class: if sp_usda == "\u{2014}" { "placeholder" } else { "" }, "{sp_usda}" } } tr { th { "{t(l, \"field.ph_range\")}" } td { class: if ph_range == "\u{2014}" { "placeholder" } else { "" }, "{ph_range}" } } tr { th { "{t(l, \"field.nitrogen_fixer\")}" } td { class: if sp_nfix == "\u{2014}" { "placeholder" } else { "" }, "{sp_nfix}" } } tr { th { "{t(l, \"field.dynamic_acc\")}" } td { class: if sp_dynacc == "\u{2014}" { "placeholder" } else { "" }, "{sp_dynacc}" } } tr { th { "{t(l, \"field.food_uses\")}" } td { class: if sp_food == "\u{2014}" { "placeholder" } else { "" }, "{sp_food}" } } tr { th { "{t(l, \"field.medicinal_uses\")}" } td { class: if sp_med == "\u{2014}" { "placeholder" } else { "" }, "{sp_med}" } } tr { th { "{t(l, \"field.other_uses\")}" } td { class: if sp_other == "\u{2014}" { "placeholder" } else { "" }, "{sp_other}" } } tr { th { "{t(l, \"field.wildlife_value\")}" } td { class: if sp_wildlife == "\u{2014}" { "placeholder" } else { "" }, "{sp_wildlife}" } } tr { th { "{t(l, \"field.native_range\")}" } td { class: if sp_native == "\u{2014}" { "placeholder" } else { "" }, "{sp_native}" } } } } } }, _ => rsx! { p { class: "placeholder detail-card-empty", "{t(l, \"loading_species_data\")}" } }, } } } } } if has_any_fallback { p { class: "species-fallback-note", "{t(l, \"estimated_note\")}" } } } }, } } } }