Files
herbapi/herbapi-ui/src/pages/cultivars.rs
T
florian.berthold 484979ad53 Initial HerbAPI implementation
Rust/Axum REST API (herbapi-api) with PostgreSQL, S3/Garage, OIDC auth.
Dioxus 0.7 WASM frontend (herbapi-ui) with sidebar layout and botanical reference style.
9 SQL migrations covering families, species, cultivars, suppliers, companions, images, users, API tokens.
2026-03-14 00:02:29 +01:00

155 lines
5.8 KiB
Rust

use dioxus::prelude::*;
use crate::api;
use crate::app::Route;
use crate::components::planting_calendar::PlantingCalendar;
#[component]
pub fn CultivarList() -> Element {
let mut page = use_signal(|| 1i64);
let mut search = use_signal(|| String::new());
let current_page = *page.read();
let search_str = search.read().clone();
let cultivars = use_resource(move || {
let s = search_str.clone();
async move {
let q = if s.is_empty() { None } else { Some(s.as_str()) };
api::list_cultivars(current_page, None, q).await
}
});
rsx! {
div { class: "page",
h1 { "Cultivars" }
div { class: "search-bar",
input {
r#type: "text",
placeholder: "Search cultivars...",
value: "{search}",
oninput: move |e| {
search.set(e.value());
page.set(1);
},
}
}
match &*cultivars.read() {
None => rsx! { p { "Loading..." } },
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
Some(Ok(data)) => rsx! {
div { class: "table-wrap",
table {
thead {
tr {
th { "Name" }
th { "Organic" }
th { "Perennial" }
th { "Frost Tolerance" }
}
}
tbody {
for c in data.data.iter() {
tr {
td {
Link { to: Route::CultivarDetail { slug: c.slug.clone() },
strong { "{c.name}" }
}
}
td { if c.is_organic { "Yes" } else { "-" } }
td { if c.perennial { "Yes" } else { "Annual" } }
td { "{c.frost_tolerance.as_deref().unwrap_or(\"-\")}" }
}
}
}
}
}
if data.total > data.per_page {
div { class: "pagination",
button {
disabled: current_page <= 1,
onclick: move |_| page.set(current_page - 1),
"Previous"
}
span { "Page {current_page}" }
button {
disabled: current_page * data.per_page >= data.total,
onclick: move |_| page.set(current_page + 1),
"Next"
}
}
}
},
}
}
}
}
#[component]
pub fn CultivarDetail(slug: String) -> Element {
let slug_clone = slug.clone();
let cultivar = use_resource(move || {
let s = slug_clone.clone();
async move { api::get_cultivar(&s).await }
});
rsx! {
div { class: "page cultivar-detail",
match &*cultivar.read() {
None => rsx! { p { "Loading..." } },
Some(Err(e)) => rsx! { p { class: "error", "Error: {e}" } },
Some(Ok(c)) => rsx! {
h1 { "{c.name}" }
if let Some(ref en) = c.name_en {
p { class: "name-common", "{en}" }
}
if let Some(ref desc) = c.description {
div { class: "description", "{desc}" }
}
div { class: "badges",
if c.is_organic {
span { class: "badge organic", "Organic" }
}
if c.perennial {
span { class: "badge", "Perennial" }
}
if let Some(ref ft) = c.frost_tolerance {
span { class: "badge", "Frost: {ft}" }
}
}
if let Some(ref dtg) = c.days_to_germination {
p { "Days to germination: {dtg}" }
}
if let Some(ref gtd) = c.growing_time_days {
p { "Growing time: {gtd} days" }
}
// Planting calendar
h2 { "Planting Calendar" }
PlantingCalendar {
indoor_sowing: c.indoor_sowing_months.clone(),
direct_sowing: c.direct_sowing_months.clone(),
transplanting: c.transplanting_months.clone(),
glasshouse: c.glasshouse_months.clone(),
harvesting: c.harvesting_months.clone(),
}
if let Some(ref pg) = c.pollination_group {
p { "Pollination group: {pg}" }
}
if let Some(sf) = c.self_fertile {
if sf {
p { "Self-fertile: Yes" }
} else {
p { "Self-fertile: No" }
}
}
},
}
}
}
}