From da206c193870c27ab2ea5ed8df4064eadb695dca Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Mon, 9 Feb 2026 19:56:47 +0000 Subject: [PATCH] feat(ui): add entity links to detail page cards Add roaster and roast slug parameters to coffee_card and roaster_card macros, rendering entity names as links to their detail pages. Add cafe link in the cup detail page. Pass slug fields through template structs and route handlers. --- src/application/routes/app/bags.rs | 2 ++ src/application/routes/app/brews.rs | 2 ++ src/application/routes/app/cups.rs | 3 +++ src/application/routes/app/roasts.rs | 1 + src/presentation/web/templates.rs | 8 +++++++ src/presentation/web/views/bags.rs | 5 +++++ src/presentation/web/views/cups.rs | 7 ++++++ templates/pages/bag.html | 13 ++++++++--- templates/pages/brew.html | 4 ++-- templates/pages/cup.html | 12 ++++++++--- templates/pages/roast.html | 4 ++-- templates/partials/detail_cards.html | 32 +++++++++++++++++++++++----- 12 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/application/routes/app/bags.rs b/src/application/routes/app/bags.rs index 9cb60ba..790c1b9 100644 --- a/src/application/routes/app/bags.rs +++ b/src/application/routes/app/bags.rs @@ -44,6 +44,8 @@ pub(crate) async fn bag_detail_page( version_info: &crate::VERSION_INFO, base_url: crate::base_url(), bag: view, + roaster_slug: roaster.slug.clone(), + roast_slug: roast.slug.clone(), }; render_html(template).map(IntoResponse::into_response) diff --git a/src/application/routes/app/brews.rs b/src/application/routes/app/brews.rs index 8d11ba7..b367e1b 100644 --- a/src/application/routes/app/brews.rs +++ b/src/application/routes/app/brews.rs @@ -50,6 +50,8 @@ pub(crate) async fn brew_detail_page( version_info: &crate::VERSION_INFO, base_url: crate::base_url(), brew: view, + roaster_slug: roaster.slug.clone(), + roast_slug: roast.slug.clone(), }; render_html(template).map(IntoResponse::into_response) diff --git a/src/application/routes/app/cups.rs b/src/application/routes/app/cups.rs index e0a514e..0ade4c7 100644 --- a/src/application/routes/app/cups.rs +++ b/src/application/routes/app/cups.rs @@ -55,6 +55,9 @@ pub(crate) async fn cup_detail_page( version_info: &crate::VERSION_INFO, base_url: crate::base_url(), cup: view, + roaster_slug: roaster.slug.clone(), + roast_slug: roast.slug.clone(), + cafe_slug: cafe.slug.clone(), }; render_html(template).map(IntoResponse::into_response) diff --git a/src/application/routes/app/roasts.rs b/src/application/routes/app/roasts.rs index b1bf46f..b7dc6d1 100644 --- a/src/application/routes/app/roasts.rs +++ b/src/application/routes/app/roasts.rs @@ -37,6 +37,7 @@ pub(crate) async fn roast_detail_page( version_info: &crate::VERSION_INFO, base_url: crate::base_url(), roast: view, + roaster_slug, }; render_html(template).map(IntoResponse::into_response) diff --git a/src/presentation/web/templates.rs b/src/presentation/web/templates.rs index 57c205c..2092a0f 100644 --- a/src/presentation/web/templates.rs +++ b/src/presentation/web/templates.rs @@ -219,6 +219,8 @@ pub struct BagDetailTemplate { pub version_info: &'static crate::VersionInfo, pub base_url: &'static str, pub bag: BagDetailView, + pub roaster_slug: String, + pub roast_slug: String, } #[derive(Template)] @@ -229,6 +231,8 @@ pub struct BrewDetailTemplate { pub version_info: &'static crate::VersionInfo, pub base_url: &'static str, pub brew: BrewDetailView, + pub roaster_slug: String, + pub roast_slug: String, } #[derive(Template)] @@ -239,6 +243,9 @@ pub struct CupDetailTemplate { pub version_info: &'static crate::VersionInfo, pub base_url: &'static str, pub cup: CupDetailView, + pub roaster_slug: String, + pub roast_slug: String, + pub cafe_slug: String, } #[derive(Template)] @@ -249,6 +256,7 @@ pub struct RoastDetailTemplate { pub version_info: &'static crate::VersionInfo, pub base_url: &'static str, pub roast: RoastDetailView, + pub roaster_slug: String, } #[derive(Template)] diff --git a/src/presentation/web/views/bags.rs b/src/presentation/web/views/bags.rs index 6c6128c..6d36d8e 100644 --- a/src/presentation/web/views/bags.rs +++ b/src/presentation/web/views/bags.rs @@ -89,6 +89,9 @@ pub struct BagDetailView { // Map pub map_countries: String, pub map_max: u32, + // Slugs (for breadcrumbs) + pub roaster_slug: String, + pub roast_slug: String, // Dates pub created_date: String, pub created_time: String, @@ -112,6 +115,8 @@ impl BagDetailView { id: bag.bag.id.to_string(), roast_name: bag.roast_name, roaster_name: bag.roaster_name, + roaster_slug: roaster.slug.clone(), + roast_slug: roast.slug.clone(), origin: coffee.origin, origin_flag: coffee.origin_flag, region: coffee.region, diff --git a/src/presentation/web/views/cups.rs b/src/presentation/web/views/cups.rs index 4d08a7b..e135d6a 100644 --- a/src/presentation/web/views/cups.rs +++ b/src/presentation/web/views/cups.rs @@ -63,6 +63,10 @@ pub struct CupDetailView { // Map pub map_countries: String, pub map_max: u32, + // Slugs (for breadcrumbs) + pub roaster_slug: String, + pub roast_slug: String, + pub cafe_slug: String, // Dates pub created_date: String, pub created_time: String, @@ -106,6 +110,9 @@ impl CupDetailView { cafe_country: cafe.country.clone(), cafe_country_flag, cafe_website: cafe.website.clone(), + roaster_slug: roaster.slug.clone(), + roast_slug: roast.slug.clone(), + cafe_slug: cafe.slug.clone(), map_countries, map_max, created_date: cup.cup.created_at.format("%Y-%m-%d").to_string(), diff --git a/templates/pages/bag.html b/templates/pages/bag.html index d592f64..9ed9ce7 100644 --- a/templates/pages/bag.html +++ b/templates/pages/bag.html @@ -27,13 +27,13 @@ {# ── Coffee + map ── #}
- {{ detail::coffee_card(bag.roast_name, bag.roaster_name, bag.origin, bag.origin_flag, bag.region, bag.producer, bag.process, bag.tasting_notes) }} + {{ detail::coffee_card(bag.roast_name, bag.roaster_name, bag.origin, bag.origin_flag, bag.region, bag.producer, bag.process, bag.tasting_notes, roaster_slug, roast_slug) }} {{ detail::map_with_legend_2(bag.map_countries, bag.map_max, "Origin", "", "Roaster", "opacity-50") }}
{# ── Roaster & bag info ── #}
- {{ detail::roaster_card(bag.roaster_name, bag.roaster_country, bag.roaster_country_flag, bag.roaster_city, bag.roaster_homepage) }} + {{ detail::roaster_card(bag.roaster_name, bag.roaster_country, bag.roaster_country_flag, bag.roaster_city, bag.roaster_homepage, roaster_slug) }}

Bag

@@ -49,7 +49,14 @@ Closed {% else %}
-
+
- {{ detail::coffee_card(brew.roast_name, brew.roaster_name, brew.origin, brew.origin_flag, brew.region, brew.producer, brew.process, brew.tasting_notes) }} + {{ detail::coffee_card(brew.roast_name, brew.roaster_name, brew.origin, brew.origin_flag, brew.region, brew.producer, brew.process, brew.tasting_notes, roaster_slug, roast_slug) }} {{ detail::map_with_legend_2(brew.map_countries, brew.map_max, "Origin", "", "Roaster", "opacity-50") }}
{# ── Roaster & gear ── #}
- {{ detail::roaster_card(brew.roaster_name, brew.roaster_country, brew.roaster_country_flag, brew.roaster_city, brew.roaster_homepage) }} + {{ detail::roaster_card(brew.roaster_name, brew.roaster_country, brew.roaster_country_flag, brew.roaster_city, brew.roaster_homepage, roaster_slug) }}

Gear

diff --git a/templates/pages/cup.html b/templates/pages/cup.html index 333102c..375a80d 100644 --- a/templates/pages/cup.html +++ b/templates/pages/cup.html @@ -27,20 +27,26 @@ {# ── Coffee + map ── #}
- {{ detail::coffee_card(cup.roast_name, cup.roaster_name, cup.origin, cup.origin_flag, cup.region, cup.producer, cup.process, cup.tasting_notes) }} + {{ detail::coffee_card(cup.roast_name, cup.roaster_name, cup.origin, cup.origin_flag, cup.region, cup.producer, cup.process, cup.tasting_notes, roaster_slug, roast_slug) }} {{ detail::map_with_legend_3(cup.map_countries, cup.map_max, "Cafe", "", "Origin", "opacity-65", "Roaster", "opacity-35") }}
{# ── Roaster & cafe ── #}
- {{ detail::roaster_card(cup.roaster_name, cup.roaster_country, cup.roaster_country_flag, cup.roaster_city, cup.roaster_homepage) }} + {{ detail::roaster_card(cup.roaster_name, cup.roaster_country, cup.roaster_country_flag, cup.roaster_city, cup.roaster_homepage, roaster_slug) }}

Cafe

Name
-
{{ cup.cafe_name }}
+
+ {{ cup.cafe_name }} +
Country
diff --git a/templates/pages/roast.html b/templates/pages/roast.html index 3482476..07ebb52 100644 --- a/templates/pages/roast.html +++ b/templates/pages/roast.html @@ -22,12 +22,12 @@
- {{ detail::coffee_card(roast.name, roast.roaster_name, roast.origin, roast.origin_flag, roast.region, roast.producer, roast.process, roast.tasting_notes) }} + {{ detail::coffee_card(roast.name, roast.roaster_name, roast.origin, roast.origin_flag, roast.region, roast.producer, roast.process, roast.tasting_notes, roaster_slug, "") }} {{ detail::map_with_legend_2(roast.map_countries, roast.map_max, "Origin", "", "Roaster", "opacity-50") }}
- {{ detail::roaster_card(roast.roaster_name, roast.roaster_country, roast.roaster_country_flag, roast.roaster_city, roast.roaster_homepage) }} + {{ detail::roaster_card(roast.roaster_name, roast.roaster_country, roast.roaster_country_flag, roast.roaster_city, roast.roaster_homepage, roaster_slug) }}
{% if is_authenticated %} diff --git a/templates/partials/detail_cards.html b/templates/partials/detail_cards.html index 42da7af..e256746 100644 --- a/templates/partials/detail_cards.html +++ b/templates/partials/detail_cards.html @@ -30,17 +30,33 @@ {% endmacro %} -{% macro coffee_card(roast_name, roaster_name, origin, origin_flag, region, producer, process, tasting_notes) %} +{% macro coffee_card(roast_name, roaster_name, origin, origin_flag, region, producer, process, tasting_notes, roaster_slug, roast_slug) %}

Coffee

Roast
-
{{ roast_name }}
+
+ {% if !roast_slug.is_empty() %} + {{ roast_name }} + {% else %} + {{ roast_name }} + {% endif %} +
Roaster
-
{{ roaster_name }}
+
+ {{ roaster_name }} +
{% if origin != "\u{2014}" %}
@@ -169,13 +185,19 @@ {% endif %} {% endmacro %} -{% macro roaster_card(name, country, country_flag, city, homepage) %} +{% macro roaster_card(name, country, country_flag, city, homepage, roaster_slug) %}

Roaster

Name
-
{{ name }}
+
+ {{ name }} +
Country