Add bilingual uses fields (food/medicinal/other _de/_en) to species
Backend: add 6 new columns to Species and CreateSpecies structs, update INSERT/UPDATE queries, add migration 010. Frontend: add fields to Species type, use pick_desc() for language-aware display in species detail and cultivar species-info card.
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS food_uses_de TEXT;
|
||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS food_uses_en TEXT;
|
||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS medicinal_uses_de TEXT;
|
||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS medicinal_uses_en TEXT;
|
||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS other_uses_de TEXT;
|
||||||
|
ALTER TABLE species ADD COLUMN IF NOT EXISTS other_uses_en TEXT;
|
||||||
@@ -101,8 +101,14 @@ pub struct Species {
|
|||||||
pub salt_tolerance: Option<String>,
|
pub salt_tolerance: Option<String>,
|
||||||
pub edibility_rating: Option<i16>,
|
pub edibility_rating: Option<i16>,
|
||||||
pub food_uses: Option<String>,
|
pub food_uses: Option<String>,
|
||||||
|
pub food_uses_de: Option<String>,
|
||||||
|
pub food_uses_en: Option<String>,
|
||||||
pub medicinal_uses: Option<String>,
|
pub medicinal_uses: Option<String>,
|
||||||
|
pub medicinal_uses_de: Option<String>,
|
||||||
|
pub medicinal_uses_en: Option<String>,
|
||||||
pub other_uses: Option<String>,
|
pub other_uses: Option<String>,
|
||||||
|
pub other_uses_de: Option<String>,
|
||||||
|
pub other_uses_en: Option<String>,
|
||||||
pub native_range: Option<String>,
|
pub native_range: Option<String>,
|
||||||
pub invasiveness: Option<String>,
|
pub invasiveness: Option<String>,
|
||||||
pub pollination_type: Option<String>,
|
pub pollination_type: Option<String>,
|
||||||
@@ -164,8 +170,14 @@ pub struct CreateSpecies {
|
|||||||
pub salt_tolerance: Option<String>,
|
pub salt_tolerance: Option<String>,
|
||||||
pub edibility_rating: Option<i16>,
|
pub edibility_rating: Option<i16>,
|
||||||
pub food_uses: Option<String>,
|
pub food_uses: Option<String>,
|
||||||
|
pub food_uses_de: Option<String>,
|
||||||
|
pub food_uses_en: Option<String>,
|
||||||
pub medicinal_uses: Option<String>,
|
pub medicinal_uses: Option<String>,
|
||||||
|
pub medicinal_uses_de: Option<String>,
|
||||||
|
pub medicinal_uses_en: Option<String>,
|
||||||
pub other_uses: Option<String>,
|
pub other_uses: Option<String>,
|
||||||
|
pub other_uses_de: Option<String>,
|
||||||
|
pub other_uses_en: Option<String>,
|
||||||
pub native_range: Option<String>,
|
pub native_range: Option<String>,
|
||||||
pub invasiveness: Option<String>,
|
pub invasiveness: Option<String>,
|
||||||
pub pollination_type: Option<String>,
|
pub pollination_type: Option<String>,
|
||||||
|
|||||||
@@ -106,7 +106,10 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result<Species> {
|
|||||||
soil_moisture, drainage_requirement, ph_min, ph_max, soil_texture_preference,
|
soil_moisture, drainage_requirement, ph_min, ph_max, soil_texture_preference,
|
||||||
hardiness_zone_usda, hardiness_zone_at, min_temp, max_temp,
|
hardiness_zone_usda, hardiness_zone_at, min_temp, max_temp,
|
||||||
drought_tolerance, salt_tolerance, edibility_rating,
|
drought_tolerance, salt_tolerance, edibility_rating,
|
||||||
food_uses, medicinal_uses, other_uses, native_range, invasiveness, pollination_type,
|
food_uses, food_uses_de, food_uses_en,
|
||||||
|
medicinal_uses, medicinal_uses_de, medicinal_uses_en,
|
||||||
|
other_uses, other_uses_de, other_uses_en,
|
||||||
|
native_range, invasiveness, pollination_type,
|
||||||
plant_layer, nitrogen_fixer, dynamic_accumulator, dynamic_accumulator_nutrients,
|
plant_layer, nitrogen_fixer, dynamic_accumulator, dynamic_accumulator_nutrients,
|
||||||
attracts_pollinators, attracts_beneficial_insects, wildlife_value, mulch_plant,
|
attracts_pollinators, attracts_beneficial_insects, wildlife_value, mulch_plant,
|
||||||
ground_cover_quality, allelopathic, guild_role, succession_stage, heavy_metal_tolerance,
|
ground_cover_quality, allelopathic, guild_role, succession_stage, heavy_metal_tolerance,
|
||||||
@@ -116,10 +119,10 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result<Species> {
|
|||||||
hoverfly_count, beetle_count, bird_count, mammal_count,
|
hoverfly_count, beetle_count, bird_count, mammal_count,
|
||||||
native_status, naturadb_tags)
|
native_status, naturadb_tags)
|
||||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
|
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,
|
$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,
|
||||||
$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,
|
$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,
|
||||||
$39,$40,$41,$42,$43,$44,$45,
|
$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,
|
||||||
$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58)
|
$58,$59,$60,$61,$62,$63,$64)
|
||||||
RETURNING *"
|
RETURNING *"
|
||||||
)
|
)
|
||||||
.bind(id).bind(&slug).bind(req.family_id).bind(&req.name_scientific)
|
.bind(id).bind(&slug).bind(req.family_id).bind(&req.name_scientific)
|
||||||
@@ -130,7 +133,9 @@ pub async fn create(pool: &PgPool, req: &CreateSpecies) -> Result<Species> {
|
|||||||
.bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at)
|
.bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at)
|
||||||
.bind(req.min_temp).bind(req.max_temp)
|
.bind(req.min_temp).bind(req.max_temp)
|
||||||
.bind(&req.drought_tolerance).bind(&req.salt_tolerance).bind(req.edibility_rating)
|
.bind(&req.drought_tolerance).bind(&req.salt_tolerance).bind(req.edibility_rating)
|
||||||
.bind(&req.food_uses).bind(&req.medicinal_uses).bind(&req.other_uses)
|
.bind(&req.food_uses).bind(&req.food_uses_de).bind(&req.food_uses_en)
|
||||||
|
.bind(&req.medicinal_uses).bind(&req.medicinal_uses_de).bind(&req.medicinal_uses_en)
|
||||||
|
.bind(&req.other_uses).bind(&req.other_uses_de).bind(&req.other_uses_en)
|
||||||
.bind(&req.native_range).bind(&req.invasiveness).bind(&req.pollination_type)
|
.bind(&req.native_range).bind(&req.invasiveness).bind(&req.pollination_type)
|
||||||
.bind(&req.plant_layer).bind(req.nitrogen_fixer).bind(req.dynamic_accumulator)
|
.bind(&req.plant_layer).bind(req.nitrogen_fixer).bind(req.dynamic_accumulator)
|
||||||
.bind(&req.dynamic_accumulator_nutrients)
|
.bind(&req.dynamic_accumulator_nutrients)
|
||||||
@@ -165,17 +170,20 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateSpecies) -> Result<Spec
|
|||||||
soil_texture_preference=$14, hardiness_zone_usda=$15, hardiness_zone_at=$16,
|
soil_texture_preference=$14, hardiness_zone_usda=$15, hardiness_zone_at=$16,
|
||||||
min_temp=$17, max_temp=$18,
|
min_temp=$17, max_temp=$18,
|
||||||
drought_tolerance=$19, salt_tolerance=$20,
|
drought_tolerance=$19, salt_tolerance=$20,
|
||||||
edibility_rating=$21, food_uses=$22, medicinal_uses=$23, other_uses=$24,
|
edibility_rating=$21,
|
||||||
native_range=$25, invasiveness=$26, pollination_type=$27,
|
food_uses=$22, food_uses_de=$23, food_uses_en=$24,
|
||||||
plant_layer=$28, nitrogen_fixer=$29, dynamic_accumulator=$30,
|
medicinal_uses=$25, medicinal_uses_de=$26, medicinal_uses_en=$27,
|
||||||
dynamic_accumulator_nutrients=$31, attracts_pollinators=$32, attracts_beneficial_insects=$33,
|
other_uses=$28, other_uses_de=$29, other_uses_en=$30,
|
||||||
wildlife_value=$34, mulch_plant=$35, ground_cover_quality=$36, allelopathic=$37,
|
native_range=$31, invasiveness=$32, pollination_type=$33,
|
||||||
guild_role=$38, succession_stage=$39, heavy_metal_tolerance=$40,
|
plant_layer=$34, nitrogen_fixer=$35, dynamic_accumulator=$36,
|
||||||
wikidata_qid=$41, gbif_id=$42, eppo_code=$43, pfaf_url=$44, source_urls=$45,
|
dynamic_accumulator_nutrients=$37, attracts_pollinators=$38, attracts_beneficial_insects=$39,
|
||||||
nectar_value=$46, pollen_value=$47, wild_bee_count=$48, wild_bee_specialist_count=$49,
|
wildlife_value=$40, mulch_plant=$41, ground_cover_quality=$42, allelopathic=$43,
|
||||||
butterfly_moth_count=$50, caterpillar_host_count=$51, caterpillar_specialist_count=$52,
|
guild_role=$44, succession_stage=$45, heavy_metal_tolerance=$46,
|
||||||
hoverfly_count=$53, beetle_count=$54, bird_count=$55, mammal_count=$56,
|
wikidata_qid=$47, gbif_id=$48, eppo_code=$49, pfaf_url=$50, source_urls=$51,
|
||||||
native_status=$57, naturadb_tags=$58,
|
nectar_value=$52, pollen_value=$53, wild_bee_count=$54, wild_bee_specialist_count=$55,
|
||||||
|
butterfly_moth_count=$56, caterpillar_host_count=$57, caterpillar_specialist_count=$58,
|
||||||
|
hoverfly_count=$59, beetle_count=$60, bird_count=$61, mammal_count=$62,
|
||||||
|
native_status=$63, naturadb_tags=$64,
|
||||||
updated_at=NOW()
|
updated_at=NOW()
|
||||||
WHERE id=$1 RETURNING *"
|
WHERE id=$1 RETURNING *"
|
||||||
)
|
)
|
||||||
@@ -187,7 +195,9 @@ pub async fn update(pool: &PgPool, id: Uuid, req: &CreateSpecies) -> Result<Spec
|
|||||||
.bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at)
|
.bind(&req.hardiness_zone_usda).bind(&req.hardiness_zone_at)
|
||||||
.bind(req.min_temp).bind(req.max_temp)
|
.bind(req.min_temp).bind(req.max_temp)
|
||||||
.bind(&req.drought_tolerance).bind(&req.salt_tolerance).bind(req.edibility_rating)
|
.bind(&req.drought_tolerance).bind(&req.salt_tolerance).bind(req.edibility_rating)
|
||||||
.bind(&req.food_uses).bind(&req.medicinal_uses).bind(&req.other_uses)
|
.bind(&req.food_uses).bind(&req.food_uses_de).bind(&req.food_uses_en)
|
||||||
|
.bind(&req.medicinal_uses).bind(&req.medicinal_uses_de).bind(&req.medicinal_uses_en)
|
||||||
|
.bind(&req.other_uses).bind(&req.other_uses_de).bind(&req.other_uses_en)
|
||||||
.bind(&req.native_range).bind(&req.invasiveness).bind(&req.pollination_type)
|
.bind(&req.native_range).bind(&req.invasiveness).bind(&req.pollination_type)
|
||||||
.bind(&req.plant_layer).bind(req.nitrogen_fixer).bind(req.dynamic_accumulator)
|
.bind(&req.plant_layer).bind(req.nitrogen_fixer).bind(req.dynamic_accumulator)
|
||||||
.bind(&req.dynamic_accumulator_nutrients)
|
.bind(&req.dynamic_accumulator_nutrients)
|
||||||
|
|||||||
@@ -843,9 +843,9 @@ pub fn CultivarDetail(slug: String) -> Element {
|
|||||||
let sp_usda = opt_str(&sp.hardiness_zone_usda);
|
let sp_usda = opt_str(&sp.hardiness_zone_usda);
|
||||||
let sp_nfix = opt_bool(sp.nitrogen_fixer);
|
let sp_nfix = opt_bool(sp.nitrogen_fixer);
|
||||||
let sp_dynacc = opt_bool(sp.dynamic_accumulator);
|
let sp_dynacc = opt_bool(sp.dynamic_accumulator);
|
||||||
let sp_food = opt_str(&sp.food_uses);
|
let sp_food = pick_desc(¤t_lang, &sp.food_uses_de, &sp.food_uses_en, &sp.food_uses);
|
||||||
let sp_med = opt_str(&sp.medicinal_uses);
|
let sp_med = pick_desc(¤t_lang, &sp.medicinal_uses_de, &sp.medicinal_uses_en, &sp.medicinal_uses);
|
||||||
let sp_other = opt_str(&sp.other_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_wildlife = opt_str(&sp.wildlife_value);
|
||||||
let sp_native = opt_str(&sp.native_range);
|
let sp_native = opt_str(&sp.native_range);
|
||||||
rsx! {
|
rsx! {
|
||||||
|
|||||||
@@ -496,9 +496,9 @@ pub fn SpeciesDetail(slug: String) -> Element {
|
|||||||
let desc = pick_desc(¤t_lang, &s.description_de, &s.description_en, &s.description);
|
let desc = pick_desc(¤t_lang, &s.description_de, &s.description_en, &s.description);
|
||||||
|
|
||||||
// Uses
|
// Uses
|
||||||
let food = os(&s.food_uses);
|
let food = pick_desc(¤t_lang, &s.food_uses_de, &s.food_uses_en, &s.food_uses);
|
||||||
let med = os(&s.medicinal_uses);
|
let med = pick_desc(¤t_lang, &s.medicinal_uses_de, &s.medicinal_uses_en, &s.medicinal_uses);
|
||||||
let other = os(&s.other_uses);
|
let other = pick_desc(¤t_lang, &s.other_uses_de, &s.other_uses_en, &s.other_uses);
|
||||||
let edibility = match s.edibility_rating {
|
let edibility = match s.edibility_rating {
|
||||||
Some(r) => format!("{r}/5"),
|
Some(r) => format!("{r}/5"),
|
||||||
None => em.to_string(),
|
None => em.to_string(),
|
||||||
|
|||||||
@@ -43,8 +43,14 @@ pub struct Species {
|
|||||||
pub salt_tolerance: Option<String>,
|
pub salt_tolerance: Option<String>,
|
||||||
pub edibility_rating: Option<i16>,
|
pub edibility_rating: Option<i16>,
|
||||||
pub food_uses: Option<String>,
|
pub food_uses: Option<String>,
|
||||||
|
pub food_uses_de: Option<String>,
|
||||||
|
pub food_uses_en: Option<String>,
|
||||||
pub medicinal_uses: Option<String>,
|
pub medicinal_uses: Option<String>,
|
||||||
|
pub medicinal_uses_de: Option<String>,
|
||||||
|
pub medicinal_uses_en: Option<String>,
|
||||||
pub other_uses: Option<String>,
|
pub other_uses: Option<String>,
|
||||||
|
pub other_uses_de: Option<String>,
|
||||||
|
pub other_uses_en: Option<String>,
|
||||||
pub native_range: Option<String>,
|
pub native_range: Option<String>,
|
||||||
pub plant_layer: Option<String>,
|
pub plant_layer: Option<String>,
|
||||||
pub nitrogen_fixer: Option<bool>,
|
pub nitrogen_fixer: Option<bool>,
|
||||||
|
|||||||
Reference in New Issue
Block a user