feat(ui): colour-code tasting note pills by SCA wheel category
Add view-layer mapping that categorises tasting notes into 9 colour families (floral, fruity, citrus, sweet, nutty, spice, roasted, sour, vegetal) based on the SCA Coffee Tasting Wheel. Matching uses case-insensitive exact lookup against ~90 known terms, then substring fallback, with pill-muted as the default for unrecognised notes. - Add tasting_notes module with NoteCategory enum and categorize() - Add 9 pill CSS variants with light and dark mode support - Update roast list and timeline templates to render coloured pills
This commit is contained in:
parent
dcaac1ee9c
commit
44174b4361
7 changed files with 514 additions and 4 deletions
|
|
@ -5,6 +5,7 @@ mod cups;
|
|||
mod gear;
|
||||
mod roasters;
|
||||
mod roasts;
|
||||
pub mod tasting_notes;
|
||||
mod timeline;
|
||||
|
||||
pub use bags::{BagOptionView, BagView};
|
||||
|
|
@ -14,6 +15,7 @@ pub use cups::CupView;
|
|||
pub use gear::{GearOptionView, GearView};
|
||||
pub use roasters::{RoasterOptionView, RoasterView};
|
||||
pub use roasts::{RoastOptionView, RoastView};
|
||||
pub use tasting_notes::TastingNoteView;
|
||||
pub use timeline::{
|
||||
TimelineBrewDataView, TimelineEventDetailView, TimelineEventView, TimelineMonthView,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::domain::roasts::{Roast, RoastWithRoaster};
|
||||
|
||||
use super::tasting_notes::{self, TastingNoteView};
|
||||
|
||||
pub struct RoastView {
|
||||
pub id: String,
|
||||
pub full_id: String,
|
||||
|
|
@ -13,7 +15,7 @@ pub struct RoastView {
|
|||
pub created_date: String,
|
||||
pub created_time: String,
|
||||
pub created_at_sort_key: i64,
|
||||
pub tasting_notes: Vec<String>,
|
||||
pub tasting_notes: Vec<TastingNoteView>,
|
||||
}
|
||||
|
||||
impl RoastView {
|
||||
|
|
@ -64,6 +66,7 @@ impl RoastView {
|
|||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.map(|note| tasting_notes::categorize(¬e))
|
||||
.collect();
|
||||
let created_date = created_at.format("%Y-%m-%d").to_string();
|
||||
let created_time = created_at.format("%H:%M").to_string();
|
||||
|
|
|
|||
398
src/presentation/web/views/tasting_notes.rs
Normal file
398
src/presentation/web/views/tasting_notes.rs
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
/// Maps tasting note strings to colour categories based on the SCA Coffee
|
||||
/// Tasting Wheel. Unknown notes fall back to the neutral `pill-muted` style.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum NoteCategory {
|
||||
Floral,
|
||||
Fruity,
|
||||
Citrus,
|
||||
Sweet,
|
||||
Nutty,
|
||||
Spice,
|
||||
Roasted,
|
||||
Sour,
|
||||
Vegetal,
|
||||
Default,
|
||||
}
|
||||
|
||||
impl NoteCategory {
|
||||
const fn pill_class(self) -> &'static str {
|
||||
match self {
|
||||
Self::Floral => "pill pill-floral",
|
||||
Self::Fruity => "pill pill-fruity",
|
||||
Self::Citrus => "pill pill-citrus",
|
||||
Self::Sweet => "pill pill-sweet",
|
||||
Self::Nutty => "pill pill-nutty",
|
||||
Self::Spice => "pill pill-spice",
|
||||
Self::Roasted => "pill pill-roasted",
|
||||
Self::Sour => "pill pill-sour",
|
||||
Self::Vegetal => "pill pill-vegetal",
|
||||
Self::Default => "pill pill-muted",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TastingNoteView {
|
||||
pub label: String,
|
||||
pub pill_class: &'static str,
|
||||
}
|
||||
|
||||
/// Categorise a tasting note string and return a view with the appropriate
|
||||
/// pill class. Matching is case-insensitive: first an exact match against
|
||||
/// known SCA wheel terms, then a substring scan for common keywords.
|
||||
pub fn categorize(note: &str) -> TastingNoteView {
|
||||
let lower = note.to_lowercase();
|
||||
let category = exact_match(&lower).unwrap_or_else(|| substring_match(&lower));
|
||||
TastingNoteView {
|
||||
label: note.to_string(),
|
||||
pill_class: category.pill_class(),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Exact matches ────────────────────────────────────────────────────
|
||||
|
||||
fn exact_match(lower: &str) -> Option<NoteCategory> {
|
||||
EXACT_MATCHES
|
||||
.iter()
|
||||
.find(|(term, _)| *term == lower)
|
||||
.map(|(_, cat)| *cat)
|
||||
}
|
||||
|
||||
use NoteCategory::{Citrus, Floral, Fruity, Nutty, Roasted, Sour, Spice, Sweet, Vegetal};
|
||||
|
||||
const EXACT_MATCHES: &[(&str, NoteCategory)] = &[
|
||||
// Floral
|
||||
("floral", Floral),
|
||||
("jasmine", Floral),
|
||||
("rose", Floral),
|
||||
("chamomile", Floral),
|
||||
("lavender", Floral),
|
||||
("hibiscus", Floral),
|
||||
("elderflower", Floral),
|
||||
("violet", Floral),
|
||||
("honeysuckle", Floral),
|
||||
("orange blossom", Floral),
|
||||
// Berry
|
||||
("berry", Fruity),
|
||||
("blueberry", Fruity),
|
||||
("strawberry", Fruity),
|
||||
("raspberry", Fruity),
|
||||
("blackberry", Fruity),
|
||||
("cranberry", Fruity),
|
||||
("boysenberry", Fruity),
|
||||
("currant", Fruity),
|
||||
("blackcurrant", Fruity),
|
||||
("redcurrant", Fruity),
|
||||
("red currant", Fruity),
|
||||
("black currant", Fruity),
|
||||
// Dried fruit
|
||||
("raisin", Fruity),
|
||||
("prune", Fruity),
|
||||
("fig", Fruity),
|
||||
("date", Fruity),
|
||||
("dried fruit", Fruity),
|
||||
// Other fruit
|
||||
("cherry", Fruity),
|
||||
("pomegranate", Fruity),
|
||||
("pineapple", Fruity),
|
||||
("grape", Fruity),
|
||||
("apple", Fruity),
|
||||
("red apple", Fruity),
|
||||
("green apple", Fruity),
|
||||
("peach", Fruity),
|
||||
("pear", Fruity),
|
||||
("plum", Fruity),
|
||||
("apricot", Fruity),
|
||||
("mango", Fruity),
|
||||
("papaya", Fruity),
|
||||
("guava", Fruity),
|
||||
("passion fruit", Fruity),
|
||||
("passionfruit", Fruity),
|
||||
("coconut", Fruity),
|
||||
("melon", Fruity),
|
||||
("watermelon", Fruity),
|
||||
("yellow fruit", Fruity),
|
||||
("stone fruit", Fruity),
|
||||
("tropical", Fruity),
|
||||
("tropical fruit", Fruity),
|
||||
("fruit", Fruity),
|
||||
("fruity", Fruity),
|
||||
("juicy", Fruity),
|
||||
("tomato", Fruity),
|
||||
("rhubarb", Fruity),
|
||||
// Citrus
|
||||
("citrus", Citrus),
|
||||
("lemon", Citrus),
|
||||
("lime", Citrus),
|
||||
("orange", Citrus),
|
||||
("grapefruit", Citrus),
|
||||
("bergamot", Citrus),
|
||||
("tangerine", Citrus),
|
||||
("mandarin", Citrus),
|
||||
("yuzu", Citrus),
|
||||
("clementine", Citrus),
|
||||
("zesty", Citrus),
|
||||
("citric", Citrus),
|
||||
("citric acid", Citrus),
|
||||
// Sweet
|
||||
("sweet", Sweet),
|
||||
("caramel", Sweet),
|
||||
("honey", Sweet),
|
||||
("vanilla", Sweet),
|
||||
("vanillin", Sweet),
|
||||
("brown sugar", Sweet),
|
||||
("chocolate", Sweet),
|
||||
("dark chocolate", Sweet),
|
||||
("milk chocolate", Sweet),
|
||||
("white chocolate", Sweet),
|
||||
("toffee", Sweet),
|
||||
("butterscotch", Sweet),
|
||||
("maple", Sweet),
|
||||
("maple syrup", Sweet),
|
||||
("molasses", Sweet),
|
||||
("caramelized", Sweet),
|
||||
("sugar cane", Sweet),
|
||||
("sugarcane", Sweet),
|
||||
("candy", Sweet),
|
||||
("marshmallow", Sweet),
|
||||
("nougat", Sweet),
|
||||
("lychee", Sweet),
|
||||
("syrupy", Sweet),
|
||||
("black tea", Sweet),
|
||||
("tea", Sweet),
|
||||
("nasturtium", Sweet),
|
||||
("fudge", Sweet),
|
||||
// Nutty / Cocoa
|
||||
("nutty", Nutty),
|
||||
("hazelnut", Nutty),
|
||||
("almond", Nutty),
|
||||
("peanut", Nutty),
|
||||
("peanuts", Nutty),
|
||||
("walnut", Nutty),
|
||||
("pecan", Nutty),
|
||||
("macadamia", Nutty),
|
||||
("pistachio", Nutty),
|
||||
("cashew", Nutty),
|
||||
("cocoa", Nutty),
|
||||
("cacao", Nutty),
|
||||
("praline", Nutty),
|
||||
("marzipan", Nutty),
|
||||
("roasted almond", Nutty),
|
||||
("roasted nuts", Nutty),
|
||||
// Spice
|
||||
("spice", Spice),
|
||||
("spicy", Spice),
|
||||
("cinnamon", Spice),
|
||||
("nutmeg", Spice),
|
||||
("clove", Spice),
|
||||
("anise", Spice),
|
||||
("star anise", Spice),
|
||||
("cardamom", Spice),
|
||||
("ginger", Spice),
|
||||
("pepper", Spice),
|
||||
("black pepper", Spice),
|
||||
("pink pepper", Spice),
|
||||
("allspice", Spice),
|
||||
("brown spice", Spice),
|
||||
("pungent", Spice),
|
||||
// Roasted
|
||||
("roasted", Roasted),
|
||||
("smoky", Roasted),
|
||||
("tobacco", Roasted),
|
||||
("pipe tobacco", Roasted),
|
||||
("ashy", Roasted),
|
||||
("burnt", Roasted),
|
||||
("charred", Roasted),
|
||||
("malt", Roasted),
|
||||
("grain", Roasted),
|
||||
("cereal", Roasted),
|
||||
("toast", Roasted),
|
||||
("toasted", Roasted),
|
||||
("roasty", Roasted),
|
||||
("dark roast", Roasted),
|
||||
// Sour / Fermented
|
||||
("sour", Sour),
|
||||
("fermented", Sour),
|
||||
("winey", Sour),
|
||||
("wine", Sour),
|
||||
("whiskey", Sour),
|
||||
("boozy", Sour),
|
||||
("acetic", Sour),
|
||||
("acetic acid", Sour),
|
||||
("malic acid", Sour),
|
||||
("mead", Sour),
|
||||
("tart", Sour),
|
||||
("tangy", Sour),
|
||||
("vinous", Sour),
|
||||
("overripe", Sour),
|
||||
// Green / Vegetal
|
||||
("green", Vegetal),
|
||||
("vegetal", Vegetal),
|
||||
("vegetative", Vegetal),
|
||||
("herbal", Vegetal),
|
||||
("grassy", Vegetal),
|
||||
("hay", Vegetal),
|
||||
("herb-like", Vegetal),
|
||||
("fresh", Vegetal),
|
||||
("earthy", Vegetal),
|
||||
("woody", Vegetal),
|
||||
("cedar", Vegetal),
|
||||
("pine", Vegetal),
|
||||
("mint", Vegetal),
|
||||
("eucalyptus", Vegetal),
|
||||
("sage", Vegetal),
|
||||
("thyme", Vegetal),
|
||||
("basil", Vegetal),
|
||||
("yoghurt", Vegetal),
|
||||
("yogurt", Vegetal),
|
||||
("cream", Vegetal),
|
||||
];
|
||||
|
||||
// ── Substring fallback ───────────────────────────────────────────────
|
||||
|
||||
fn substring_match(lower: &str) -> NoteCategory {
|
||||
// Order: specific before general to avoid false positives.
|
||||
const KEYWORDS: &[(&str, NoteCategory)] = &[
|
||||
// Floral
|
||||
("floral", Floral),
|
||||
("blossom", Floral),
|
||||
("flower", Floral),
|
||||
("jasmine", Floral),
|
||||
("rose", Floral),
|
||||
// Fruity / Berry
|
||||
("berry", Fruity),
|
||||
("cherry", Fruity),
|
||||
("plum", Fruity),
|
||||
("peach", Fruity),
|
||||
("apricot", Fruity),
|
||||
("mango", Fruity),
|
||||
("grape", Fruity),
|
||||
("apple", Fruity),
|
||||
("pear", Fruity),
|
||||
("melon", Fruity),
|
||||
("fruit", Fruity),
|
||||
("tropical", Fruity),
|
||||
("juicy", Fruity),
|
||||
("raisin", Fruity),
|
||||
("prune", Fruity),
|
||||
("fig", Fruity),
|
||||
// Citrus
|
||||
("citrus", Citrus),
|
||||
("lemon", Citrus),
|
||||
("lime", Citrus),
|
||||
("grapefruit", Citrus),
|
||||
("bergamot", Citrus),
|
||||
("orange", Citrus),
|
||||
("tangerine", Citrus),
|
||||
("zesty", Citrus),
|
||||
// Sweet / Chocolate
|
||||
("chocolate", Sweet),
|
||||
("caramel", Sweet),
|
||||
("honey", Sweet),
|
||||
("vanilla", Sweet),
|
||||
("toffee", Sweet),
|
||||
("butterscotch", Sweet),
|
||||
("maple", Sweet),
|
||||
("molasses", Sweet),
|
||||
("sugar", Sweet),
|
||||
("candy", Sweet),
|
||||
("syrup", Sweet),
|
||||
("sweet", Sweet),
|
||||
// Nutty
|
||||
("nut", Nutty),
|
||||
("cocoa", Nutty),
|
||||
("cacao", Nutty),
|
||||
("praline", Nutty),
|
||||
("marzipan", Nutty),
|
||||
("almond", Nutty),
|
||||
// Spice
|
||||
("cinnamon", Spice),
|
||||
("clove", Spice),
|
||||
("cardamom", Spice),
|
||||
("ginger", Spice),
|
||||
("pepper", Spice),
|
||||
("spice", Spice),
|
||||
("spicy", Spice),
|
||||
// Roasted
|
||||
("smoke", Roasted),
|
||||
("smoky", Roasted),
|
||||
("tobacco", Roasted),
|
||||
("roast", Roasted),
|
||||
("toast", Roasted),
|
||||
("malt", Roasted),
|
||||
("grain", Roasted),
|
||||
("ash", Roasted),
|
||||
("burnt", Roasted),
|
||||
("charred", Roasted),
|
||||
// Sour / Fermented
|
||||
("wine", Sour),
|
||||
("ferment", Sour),
|
||||
("tart", Sour),
|
||||
("sour", Sour),
|
||||
("tangy", Sour),
|
||||
("vinous", Sour),
|
||||
("boozy", Sour),
|
||||
// Vegetal
|
||||
("herbal", Vegetal),
|
||||
("herb", Vegetal),
|
||||
("grass", Vegetal),
|
||||
("green", Vegetal),
|
||||
("earthy", Vegetal),
|
||||
("woody", Vegetal),
|
||||
("cedar", Vegetal),
|
||||
("pine", Vegetal),
|
||||
("mint", Vegetal),
|
||||
];
|
||||
|
||||
for (keyword, category) in KEYWORDS {
|
||||
if lower.contains(keyword) {
|
||||
return *category;
|
||||
}
|
||||
}
|
||||
|
||||
NoteCategory::Default
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn exact_match_known_notes() {
|
||||
assert_eq!(categorize("Jasmine").pill_class, "pill pill-floral");
|
||||
assert_eq!(categorize("Blueberry").pill_class, "pill pill-fruity");
|
||||
assert_eq!(categorize("Lemon").pill_class, "pill pill-citrus");
|
||||
assert_eq!(categorize("Caramel").pill_class, "pill pill-sweet");
|
||||
assert_eq!(categorize("Hazelnut").pill_class, "pill pill-nutty");
|
||||
assert_eq!(categorize("Cinnamon").pill_class, "pill pill-spice");
|
||||
assert_eq!(categorize("Smoky").pill_class, "pill pill-roasted");
|
||||
assert_eq!(categorize("Winey").pill_class, "pill pill-sour");
|
||||
assert_eq!(categorize("Herbal").pill_class, "pill pill-vegetal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_insensitive() {
|
||||
assert_eq!(categorize("JASMINE").pill_class, "pill pill-floral");
|
||||
assert_eq!(categorize("dark chocolate").pill_class, "pill pill-sweet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn substring_fallback() {
|
||||
assert_eq!(categorize("Wild Blueberry").pill_class, "pill pill-fruity");
|
||||
assert_eq!(categorize("Citrus Zest").pill_class, "pill pill-citrus");
|
||||
assert_eq!(categorize("Roasted Almond").pill_class, "pill pill-nutty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_note_falls_back_to_muted() {
|
||||
assert_eq!(categorize("Umami").pill_class, "pill pill-muted");
|
||||
assert_eq!(categorize("Complex").pill_class, "pill pill-muted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserves_original_label() {
|
||||
let view = categorize("Dark Chocolate");
|
||||
assert_eq!(view.label, "Dark Chocolate");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::domain::timeline::{TimelineEvent, TimelineEventDetail};
|
||||
|
||||
use super::relative_date;
|
||||
use super::tasting_notes::{self, TastingNoteView};
|
||||
|
||||
/// Returns `true` if the value is empty or just an em-dash placeholder.
|
||||
fn is_blank(value: &str) -> bool {
|
||||
|
|
@ -54,7 +55,7 @@ pub struct TimelineEventView {
|
|||
pub external_link: Option<String>,
|
||||
pub details: Vec<TimelineEventDetailView>,
|
||||
pub subtitle: Option<String>,
|
||||
pub tasting_notes: Option<Vec<String>>,
|
||||
pub tasting_notes: Option<Vec<TastingNoteView>>,
|
||||
pub brew_data: Option<TimelineBrewDataView>,
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +113,7 @@ impl TimelineEventView {
|
|||
.filter(|segment| !segment.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.map(|note| tasting_notes::categorize(¬e))
|
||||
.collect::<Vec<_>>();
|
||||
Some(notes)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,107 @@ a {
|
|||
background-color: rgba(5, 150, 105, 0.1);
|
||||
}
|
||||
|
||||
/* Tasting-note colour categories (SCA wheel) */
|
||||
|
||||
.pill-floral {
|
||||
border-color: rgba(236, 72, 153, 0.3);
|
||||
color: #9d174d;
|
||||
background-color: #fdf2f8;
|
||||
}
|
||||
|
||||
.pill-fruity {
|
||||
border-color: rgba(220, 38, 38, 0.3);
|
||||
color: #991b1b;
|
||||
background-color: #fef2f2;
|
||||
}
|
||||
|
||||
.pill-citrus {
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
color: #92400e;
|
||||
background-color: #fffbeb;
|
||||
}
|
||||
|
||||
.pill-sweet {
|
||||
border-color: rgba(234, 88, 12, 0.3);
|
||||
color: #9a3412;
|
||||
background-color: #fff7ed;
|
||||
}
|
||||
|
||||
.pill-nutty {
|
||||
border-color: rgba(161, 98, 7, 0.3);
|
||||
color: #713f12;
|
||||
background-color: #fefce8;
|
||||
}
|
||||
|
||||
.pill-spice {
|
||||
border-color: rgba(190, 18, 60, 0.3);
|
||||
color: #9f1239;
|
||||
background-color: #fff1f2;
|
||||
}
|
||||
|
||||
.pill-roasted {
|
||||
border-color: rgba(120, 113, 108, 0.3);
|
||||
color: #44403c;
|
||||
background-color: #f5f5f4;
|
||||
}
|
||||
|
||||
.pill-sour {
|
||||
border-color: rgba(101, 163, 13, 0.3);
|
||||
color: #3f6212;
|
||||
background-color: #f7fee7;
|
||||
}
|
||||
|
||||
.pill-vegetal {
|
||||
border-color: rgba(5, 150, 105, 0.3);
|
||||
color: #065f46;
|
||||
background-color: #ecfdf5;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-floral {
|
||||
color: #f9a8d4;
|
||||
background-color: rgba(236, 72, 153, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-fruity {
|
||||
color: #fca5a5;
|
||||
background-color: rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-citrus {
|
||||
color: #fcd34d;
|
||||
background-color: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-sweet {
|
||||
color: #fdba74;
|
||||
background-color: rgba(234, 88, 12, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-nutty {
|
||||
color: #fde68a;
|
||||
background-color: rgba(161, 98, 7, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-spice {
|
||||
color: #fda4af;
|
||||
background-color: rgba(190, 18, 60, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-roasted {
|
||||
color: #a8a29e;
|
||||
background-color: rgba(120, 113, 108, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-sour {
|
||||
color: #bef264;
|
||||
background-color: rgba(101, 163, 13, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pill-vegetal {
|
||||
color: #6ee7b7;
|
||||
background-color: rgba(5, 150, 105, 0.1);
|
||||
}
|
||||
|
||||
/* ── Tabs ──────────────────────────────────────────────────────── */
|
||||
|
||||
.tab {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
{% if !roast.tasting_notes.is_empty() %}
|
||||
<div class="flex flex-wrap gap-1 md:justify-start justify-end">
|
||||
{% for note in roast.tasting_notes %}
|
||||
<span class="pill pill-muted">{{ note }}</span>
|
||||
<span class="{{ note.pill_class }}">{{ note.label }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,11 @@
|
|||
{% if !notes.is_empty() %}
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="shrink-0 font-medium text-text-muted">Tasting Notes</dt>
|
||||
<dd class="text-right">{{ notes.join(", ") }}</dd>
|
||||
<dd class="flex flex-wrap gap-1 justify-end">
|
||||
{% for note in notes %}
|
||||
<span class="{{ note.pill_class }}">{{ note.label }}</span>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue