Add companion planting section to species detail page and redesign home page

Species detail now shows a "Companion Plants" card (left column, after
Ecology) with beneficial/antagonistic sub-lists. Each entry links to
the companion species and shows the mechanism.

Home page gains stats cards, quick-filter buttons, and a hero section.
Species list supports URL query params for quick-filter links.
New /api/v1/stats endpoint provides database counts.

i18n keys added for DE/EN: card.companion_plants, companions.beneficial_for,
companions.antagonistic_for, stat.*, filter.*, home.*.
This commit is contained in:
2026-03-16 11:18:10 +01:00
parent 0ef902cc91
commit 00e26b3a84
9 changed files with 518 additions and 10 deletions
+3
View File
@@ -6,6 +6,7 @@ mod health;
mod images;
mod search;
mod species;
mod stats;
mod suppliers;
use axum::http::{header, HeaderValue, StatusCode};
@@ -44,6 +45,8 @@ pub fn router(state: AppState) -> Router {
.route("/api/v1/images/{id}", delete(images::remove))
// Image file serving (S3 proxy)
.route("/img/{*path}", get(images::serve_image))
// Stats
.route("/api/v1/stats", get(stats::get_stats))
// Search
.route("/api/v1/search", get(search::search))
// API docs
+52
View File
@@ -0,0 +1,52 @@
use axum::extract::State;
use axum::Json;
use serde::Serialize;
use crate::error::Result;
use crate::state::AppState;
#[derive(Serialize)]
pub struct DbStats {
pub families: i64,
pub species: i64,
pub cultivars: i64,
pub suppliers: i64,
pub companions: i64,
pub images: i64,
}
pub async fn get_stats(State(state): State<AppState>) -> Result<Json<DbStats>> {
let (families,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM families")
.fetch_one(&state.pool)
.await?;
let (species,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM species")
.fetch_one(&state.pool)
.await?;
let (cultivars,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM cultivars")
.fetch_one(&state.pool)
.await?;
let (suppliers,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM suppliers")
.fetch_one(&state.pool)
.await?;
let (companions,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM companion_relationships")
.fetch_one(&state.pool)
.await?;
let (images,): (i64,) =
sqlx::query_as("SELECT COUNT(*) FROM images")
.fetch_one(&state.pool)
.await?;
Ok(Json(DbStats {
families,
species,
cultivars,
suppliers,
companions,
images,
}))
}