Add 52-week planting calendar with month fallback

Add indoor_sowing_weeks, direct_sowing_weeks, transplanting_weeks,
glasshouse_weeks, harvesting_weeks (integer[]) to backend Cultivar and
CreateCultivar structs with INSERT/UPDATE SQL bindings.

Frontend PlantingCalendar component rewritten as a compact 52-column
Gantt-style grid grouped by month headers. Prefers week data when
available, falls back to expanding month data into week ranges.
Calendar displayed full-width on cultivar detail page with color legend.
This commit is contained in:
2026-03-15 14:36:17 +01:00
parent 3ecfdfadf2
commit 170aa84a0f
6 changed files with 245 additions and 39 deletions
+25 -11
View File
@@ -127,7 +127,10 @@ pub async fn create(pool: &PgPool, req: &CreateCultivar) -> Result<Cultivar> {
min_light_hours_day, optimal_light_hours_day, greenhouse_min_temp_c,
indoor_season_extension_weeks, ventilation_requirement, heating_required,
indoor_sowing_months, direct_sowing_months, transplanting_months, glasshouse_months,
harvesting_months, succession_planting_days, planting_notes,
harvesting_months,
indoor_sowing_weeks, direct_sowing_weeks, transplanting_weeks, glasshouse_weeks,
harvesting_weeks,
succession_planting_days, planting_notes,
propagation_methods, cutting_season, rootstock_species_id,
years_to_first_harvest, productive_lifespan_years,
expected_yield_kg_per_m2, yield_unit, expected_yield_value,
@@ -137,7 +140,7 @@ pub async fn create(pool: &PgPool, req: &CreateCultivar) -> Result<Cultivar> {
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,
$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31::frost_tolerance,$32,$33,$34,$35,$36,$37,
$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,
$57,$58,$59,$60,$61,$62,$63)
$57,$58,$59,$60,$61,$62,$63,$64,$65,$66,$67,$68)
RETURNING *"
)
.bind(id).bind(&slug).bind(req.species_id).bind(&req.name)
@@ -155,7 +158,11 @@ pub async fn create(pool: &PgPool, req: &CreateCultivar) -> Result<Cultivar> {
.bind(req.indoor_season_extension_weeks).bind(&req.ventilation_requirement).bind(req.heating_required)
.bind(&req.indoor_sowing_months).bind(&req.direct_sowing_months)
.bind(&req.transplanting_months).bind(&req.glasshouse_months)
.bind(&req.harvesting_months).bind(req.succession_planting_days).bind(&req.planting_notes)
.bind(&req.harvesting_months)
.bind(&req.indoor_sowing_weeks).bind(&req.direct_sowing_weeks)
.bind(&req.transplanting_weeks).bind(&req.glasshouse_weeks)
.bind(&req.harvesting_weeks)
.bind(req.succession_planting_days).bind(&req.planting_notes)
.bind(&req.propagation_methods).bind(&req.cutting_season).bind(req.rootstock_species_id)
.bind(req.years_to_first_harvest).bind(req.productive_lifespan_years)
.bind(req.expected_yield_kg_per_m2).bind(&req.yield_unit).bind(req.expected_yield_value)
@@ -192,13 +199,16 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateCultivar) -> Result<Cul
min_light_hours_day=$32, optimal_light_hours_day=$33, greenhouse_min_temp_c=$34,
indoor_season_extension_weeks=$35, ventilation_requirement=$36, heating_required=$37,
indoor_sowing_months=$38, direct_sowing_months=$39, transplanting_months=$40,
glasshouse_months=$41, harvesting_months=$42, succession_planting_days=$43,
planting_notes=$44, propagation_methods=$45, cutting_season=$46,
rootstock_species_id=$47, years_to_first_harvest=$48, productive_lifespan_years=$49,
expected_yield_kg_per_m2=$50, yield_unit=$51, expected_yield_value=$52,
harvest_window_days=$53, storage_method=$54, shelf_life_days=$55, cold_storage_days=$56,
pollination_group=$57, self_fertile=$58, rootstock_compatibility=$59,
wikidata_qid=$60, gbif_id=$61, pfaf_url=$62, source_urls=$63, updated_at=NOW()
glasshouse_months=$41, harvesting_months=$42,
indoor_sowing_weeks=$43, direct_sowing_weeks=$44, transplanting_weeks=$45,
glasshouse_weeks=$46, harvesting_weeks=$47,
succession_planting_days=$48,
planting_notes=$49, propagation_methods=$50, cutting_season=$51,
rootstock_species_id=$52, years_to_first_harvest=$53, productive_lifespan_years=$54,
expected_yield_kg_per_m2=$55, yield_unit=$56, expected_yield_value=$57,
harvest_window_days=$58, storage_method=$59, shelf_life_days=$60, cold_storage_days=$61,
pollination_group=$62, self_fertile=$63, rootstock_compatibility=$64,
wikidata_qid=$65, gbif_id=$66, pfaf_url=$67, source_urls=$68, updated_at=NOW()
WHERE id=$1 RETURNING *"
)
.bind(id).bind(&slug).bind(req.species_id).bind(&req.name)
@@ -216,7 +226,11 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateCultivar) -> Result<Cul
.bind(req.indoor_season_extension_weeks).bind(&req.ventilation_requirement).bind(req.heating_required)
.bind(&req.indoor_sowing_months).bind(&req.direct_sowing_months)
.bind(&req.transplanting_months).bind(&req.glasshouse_months)
.bind(&req.harvesting_months).bind(req.succession_planting_days).bind(&req.planting_notes)
.bind(&req.harvesting_months)
.bind(&req.indoor_sowing_weeks).bind(&req.direct_sowing_weeks)
.bind(&req.transplanting_weeks).bind(&req.glasshouse_weeks)
.bind(&req.harvesting_weeks)
.bind(req.succession_planting_days).bind(&req.planting_notes)
.bind(&req.propagation_methods).bind(&req.cutting_season).bind(req.rootstock_species_id)
.bind(req.years_to_first_harvest).bind(req.productive_lifespan_years)
.bind(req.expected_yield_kg_per_m2).bind(&req.yield_unit).bind(req.expected_yield_value)
+10
View File
@@ -250,6 +250,11 @@ pub struct Cultivar {
pub transplanting_months: Option<Vec<i32>>,
pub glasshouse_months: Option<Vec<i32>>,
pub harvesting_months: Option<Vec<i32>>,
pub indoor_sowing_weeks: Option<Vec<i32>>,
pub direct_sowing_weeks: Option<Vec<i32>>,
pub transplanting_weeks: Option<Vec<i32>>,
pub glasshouse_weeks: Option<Vec<i32>>,
pub harvesting_weeks: Option<Vec<i32>>,
pub succession_planting_days: Option<i32>,
pub planting_notes: Option<String>,
pub propagation_methods: Option<Vec<String>>,
@@ -318,6 +323,11 @@ pub struct CreateCultivar {
pub transplanting_months: Option<Vec<i32>>,
pub glasshouse_months: Option<Vec<i32>>,
pub harvesting_months: Option<Vec<i32>>,
pub indoor_sowing_weeks: Option<Vec<i32>>,
pub direct_sowing_weeks: Option<Vec<i32>>,
pub transplanting_weeks: Option<Vec<i32>>,
pub glasshouse_weeks: Option<Vec<i32>>,
pub harvesting_weeks: Option<Vec<i32>>,
pub succession_planting_days: Option<i32>,
pub planting_notes: Option<String>,
pub propagation_methods: Option<Vec<String>>,