use dioxus::prelude::*; /// Month boundaries as week ranges (approximate ISO weeks). /// Jan=1-4, Feb=5-8, Mar=9-13, Apr=14-17, May=18-22, Jun=23-26, /// Jul=27-30, Aug=31-35, Sep=36-39, Oct=40-44, Nov=45-48, Dec=49-52 const MONTH_WEEK_RANGES: [(u8, u8); 12] = [ (1, 4), (5, 8), (9, 13), (14, 17), (18, 22), (23, 26), (27, 30), (31, 35), (36, 39), (40, 44), (45, 48), (49, 52), ]; const MONTH_LABELS: [&str; 12] = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; /// Expand month data (1-12) into week numbers (1-52) for fallback. fn months_to_weeks(months: &Option>) -> Option> { let m = months.as_ref()?; if m.is_empty() { return None; } let mut weeks = Vec::new(); for &month in m { if month >= 1 && month <= 12 { let (start, end) = MONTH_WEEK_RANGES[(month - 1) as usize]; for w in start..=end { weeks.push(w as i32); } } } weeks.sort(); weeks.dedup(); Some(weeks) } /// Resolve: prefer week data, fall back to expanding month data. fn resolve_weeks(weeks: &Option>, months: &Option>) -> Option> { if let Some(w) = weeks { if !w.is_empty() { return Some(w.clone()); } } months_to_weeks(months) } #[component] pub fn PlantingCalendar( indoor_sowing_months: Option>, direct_sowing_months: Option>, transplanting_months: Option>, glasshouse_months: Option>, harvesting_months: Option>, indoor_sowing_weeks: Option>, direct_sowing_weeks: Option>, transplanting_weeks: Option>, glasshouse_weeks: Option>, harvesting_weeks: Option>, ) -> Element { let rows: Vec<(&str, &str, Option>)> = vec![ ("Indoor Sowing", "cal-indoor", resolve_weeks(&indoor_sowing_weeks, &indoor_sowing_months)), ("Direct Sowing", "cal-direct", resolve_weeks(&direct_sowing_weeks, &direct_sowing_months)), ("Transplanting", "cal-transplant", resolve_weeks(&transplanting_weeks, &transplanting_months)), ("Glasshouse", "cal-glass", resolve_weeks(&glasshouse_weeks, &glasshouse_months)), ("Harvesting", "cal-harvest", resolve_weeks(&harvesting_weeks, &harvesting_months)), ]; let has_data = rows.iter().any(|(_, _, w)| w.is_some()); if !has_data { return rsx! { p { class: "empty", "No planting calendar data." } }; } rsx! { div { class: "planting-calendar week-calendar", // Month header spanning groups of weeks div { class: "wcal-month-row", div { class: "wcal-label" } for (i, label) in MONTH_LABELS.iter().enumerate() { { let (start, end) = MONTH_WEEK_RANGES[i]; let span = (end - start + 1) as usize; rsx! { div { class: "wcal-month-header", style: "grid-column: span {span};", "{label}" } } } } } // Data rows for (name, class, weeks) in rows.iter() { if weeks.is_some() { div { class: "wcal-row", div { class: "wcal-label", "{name}" } for week in 1..=52i32 { { let active = weeks.as_ref() .map(|w| w.contains(&week)) .unwrap_or(false); rsx! { div { class: if active { format!("wcal-cell {class} active") } else { "wcal-cell".to_string() }, title: "W{week}", } } } } } } } // Legend div { class: "wcal-legend", div { class: "wcal-legend-item", div { class: "wcal-legend-swatch cal-indoor" } span { "Indoor Sowing" } } div { class: "wcal-legend-item", div { class: "wcal-legend-swatch cal-direct" } span { "Direct Sowing" } } div { class: "wcal-legend-item", div { class: "wcal-legend-swatch cal-transplant" } span { "Transplanting" } } div { class: "wcal-legend-item", div { class: "wcal-legend-swatch cal-glass" } span { "Glasshouse" } } div { class: "wcal-legend-item", div { class: "wcal-legend-swatch cal-harvest" } span { "Harvesting" } } } } } }