diff --git a/build.rs b/build.rs
index bb45018..1bd3a21 100644
--- a/build.rs
+++ b/build.rs
@@ -1,3 +1,4 @@
+use std::path::Path;
use std::process::Command;
fn main() {
@@ -18,6 +19,9 @@ fn git_hash() {
}
fn tailwind() {
+ println!("cargo:rerun-if-changed=static/css/input.css");
+ println!("cargo:rerun-if-changed=templates/");
+
let mut cmd = Command::new("tailwindcss");
cmd.args(["-i", "static/css/input.css", "-o", "static/css/styles.css"]);
@@ -25,6 +29,8 @@ fn tailwind() {
cmd.arg("--minify");
}
+ let output = Path::new("static/css/styles.css");
+
match cmd.status() {
Ok(status) if status.success() => {}
Ok(status) => {
@@ -37,4 +43,9 @@ fn tailwind() {
eprintln!("cargo:warning=failed to run tailwindcss: {e}");
}
}
+
+ // Ensure the file exists so include_str!() doesn't break the build
+ if !output.exists() {
+ std::fs::write(output, "/* tailwindcss not available */").ok();
+ }
}
diff --git a/src/domain/bags.rs b/src/domain/bags.rs
index 49c4eee..f35457f 100644
--- a/src/domain/bags.rs
+++ b/src/domain/bags.rs
@@ -89,6 +89,7 @@ pub enum BagSortKey {
UpdatedAt,
Roaster,
Roast,
+ Status,
FinishedAt,
}
@@ -104,6 +105,7 @@ impl SortKey for BagSortKey {
"updated-at" => Some(BagSortKey::UpdatedAt),
"roaster" => Some(BagSortKey::Roaster),
"roast" => Some(BagSortKey::Roast),
+ "status" => Some(BagSortKey::Status),
"finished-at" => Some(BagSortKey::FinishedAt),
_ => None,
}
@@ -116,13 +118,14 @@ impl SortKey for BagSortKey {
BagSortKey::UpdatedAt => "updated-at",
BagSortKey::Roaster => "roaster",
BagSortKey::Roast => "roast",
+ BagSortKey::Status => "status",
BagSortKey::FinishedAt => "finished-at",
}
}
fn default_direction(self) -> SortDirection {
match self {
- BagSortKey::Roaster | BagSortKey::Roast => SortDirection::Asc,
+ BagSortKey::Roaster | BagSortKey::Roast | BagSortKey::Status => SortDirection::Asc,
_ => SortDirection::Desc,
}
}
diff --git a/src/domain/cups.rs b/src/domain/cups.rs
index 51f598a..eee27af 100644
--- a/src/domain/cups.rs
+++ b/src/domain/cups.rs
@@ -24,6 +24,7 @@ pub struct CupWithDetails {
pub roaster_slug: String,
pub cafe_name: String,
pub cafe_slug: String,
+ pub cafe_city: String,
}
impl CupWithDetails {
@@ -96,7 +97,9 @@ impl CupFilter {
pub enum CupSortKey {
CreatedAt,
CafeName,
+ CafeCity,
RoastName,
+ RoasterName,
}
impl SortKey for CupSortKey {
@@ -108,7 +111,9 @@ impl SortKey for CupSortKey {
match value {
"created-at" => Some(CupSortKey::CreatedAt),
"cafe" => Some(CupSortKey::CafeName),
+ "city" => Some(CupSortKey::CafeCity),
"roast" => Some(CupSortKey::RoastName),
+ "roaster" => Some(CupSortKey::RoasterName),
_ => None,
}
}
@@ -117,7 +122,9 @@ impl SortKey for CupSortKey {
match self {
CupSortKey::CreatedAt => "created-at",
CupSortKey::CafeName => "cafe",
+ CupSortKey::CafeCity => "city",
CupSortKey::RoastName => "roast",
+ CupSortKey::RoasterName => "roaster",
}
}
diff --git a/src/infrastructure/repositories/bags.rs b/src/infrastructure/repositories/bags.rs
index 05a8130..91f5778 100644
--- a/src/infrastructure/repositories/bags.rs
+++ b/src/infrastructure/repositories/bags.rs
@@ -42,6 +42,7 @@ impl SqlBagRepository {
BagSortKey::UpdatedAt => format!("b.updated_at {dir_sql}, b.id DESC"),
BagSortKey::Roaster => format!("LOWER(rr.name) {dir_sql}, b.created_at DESC"),
BagSortKey::Roast => format!("LOWER(r.name) {dir_sql}, b.created_at DESC"),
+ BagSortKey::Status => format!("b.closed {dir_sql}, b.created_at DESC"),
BagSortKey::FinishedAt => format!("b.finished_at {dir_sql}, b.created_at DESC"),
}
}
diff --git a/src/infrastructure/repositories/cups.rs b/src/infrastructure/repositories/cups.rs
index 1591c41..d6695ca 100644
--- a/src/infrastructure/repositories/cups.rs
+++ b/src/infrastructure/repositories/cups.rs
@@ -15,7 +15,8 @@ const BASE_SELECT: &str = r"
c.created_at, c.updated_at,
r.name as roast_name, r.slug as roast_slug,
rr.name as roaster_name, rr.slug as roaster_slug,
- ca.name as cafe_name, ca.slug as cafe_slug
+ ca.name as cafe_name, ca.slug as cafe_slug,
+ ca.city as cafe_city
FROM cups c
JOIN roasts r ON c.roast_id = r.id
JOIN roasters rr ON r.roaster_id = rr.id
@@ -43,9 +44,15 @@ impl SqlCupRepository {
CupSortKey::CafeName => {
format!("LOWER(ca.name) {dir_sql}, c.created_at DESC")
}
+ CupSortKey::CafeCity => {
+ format!("LOWER(ca.city) {dir_sql}, c.created_at DESC")
+ }
CupSortKey::RoastName => {
format!("LOWER(r.name) {dir_sql}, c.created_at DESC")
}
+ CupSortKey::RoasterName => {
+ format!("LOWER(rr.name) {dir_sql}, c.created_at DESC")
+ }
}
}
@@ -74,6 +81,7 @@ impl SqlCupRepository {
roaster_slug: record.roaster_slug,
cafe_name: record.cafe_name,
cafe_slug: record.cafe_slug,
+ cafe_city: record.cafe_city,
}
}
@@ -219,4 +227,5 @@ struct CupWithDetailsRecord {
roaster_slug: String,
cafe_name: String,
cafe_slug: String,
+ cafe_city: String,
}
diff --git a/src/presentation/web/views/bags.rs b/src/presentation/web/views/bags.rs
index 9d10c16..1b9329a 100644
--- a/src/presentation/web/views/bags.rs
+++ b/src/presentation/web/views/bags.rs
@@ -8,7 +8,7 @@ pub struct BagView {
pub amount: String,
pub remaining: String,
pub closed: bool,
- pub finished_at: String,
+ pub finished_date: String,
pub created_date: String,
pub created_time: String,
pub roast_name: String,
@@ -26,10 +26,10 @@ impl BagView {
amount: format!("{:.1}", bag.bag.amount),
remaining: format!("{:.1}", bag.bag.remaining),
closed: bag.bag.closed,
- finished_at: bag
+ finished_date: bag
.bag
.finished_at
- .map_or_else(|| "—".to_string(), |d| d.to_string()),
+ .map_or_else(|| "—".to_string(), |d| d.format("%Y-%m-%d").to_string()),
created_date: bag.bag.created_at.format("%Y-%m-%d").to_string(),
created_time: bag.bag.created_at.format("%H:%M").to_string(),
roast_name: bag.roast_name,
diff --git a/src/presentation/web/views/cups.rs b/src/presentation/web/views/cups.rs
index 4ed977a..70cd36e 100644
--- a/src/presentation/web/views/cups.rs
+++ b/src/presentation/web/views/cups.rs
@@ -9,6 +9,7 @@ pub struct CupView {
pub roaster_slug: String,
pub cafe_name: String,
pub cafe_slug: String,
+ pub cafe_city: String,
pub created_date: String,
pub created_time: String,
}
@@ -23,6 +24,7 @@ impl CupView {
roaster_slug: cup.roaster_slug,
cafe_name: cup.cafe_name,
cafe_slug: cup.cafe_slug,
+ cafe_city: cup.cafe_city,
created_date: cup.cup.created_at.format("%Y-%m-%d").to_string(),
created_time: cup.cup.created_at.format("%H:%M").to_string(),
}
diff --git a/static/css/input.css b/static/css/input.css
index 2e6e56c..28dbfc0 100644
--- a/static/css/input.css
+++ b/static/css/input.css
@@ -548,14 +548,44 @@ a {
border-bottom-width: 1px;
}
+ /* Hide Actions column on desktop — whole row is clickable */
+ .responsive-table .actions-col,
+ .responsive-table td.card-actions {
+ display: none;
+ }
+
+ /* Pointer cursor on clickable rows */
+ .responsive-table tbody > tr[onclick] {
+ cursor: pointer;
+ }
+
/* Hover: accent tint + left bar */
.responsive-table tbody > tr:not(.detail-row):hover {
background-color: var(--accent-subtle);
box-shadow: inset 3px 0 0 var(--accent);
}
+ /* Expanded row: persistent accent highlight */
+ .responsive-table tbody > tr.expanded {
+ background-color: var(--accent-subtle);
+ box-shadow: inset 3px 0 0 var(--accent);
+ }
+
+ /* Expanded row: remove bottom border so detail row merges */
+ .responsive-table tbody > tr.expanded {
+ border-bottom-width: 0;
+ }
+
+ /* Detail row: light grey bg + left accent bar, no top border */
+ .responsive-table tbody > tr.expanded + tr.detail-row {
+ background-color: var(--surface-alt);
+ box-shadow: inset 3px 0 0 var(--accent);
+ border-top-width: 0;
+ }
+
/* Detail row: keep compact */
.responsive-table .detail-row td {
+ background-color: transparent;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
diff --git a/templates/partials/lists/bag_list.html b/templates/partials/lists/bag_list.html
index 6c81544..8c6c7ed 100644
--- a/templates/partials/lists/bag_list.html
+++ b/templates/partials/lists/bag_list.html
@@ -27,16 +27,18 @@
{% call table::sortable_header("Roaster", "roaster", navigator, "#bag-list") %}
{% call table::sortable_header("Roast", "roast", navigator, "#bag-list") %}
Weight |
- Status |
+ {% call table::sortable_header("Status", "status", navigator, "#bag-list") %}
{% call table::sortable_header("Finished", "finished-at", navigator, "#bag-list") %}
{% if is_authenticated %}
- Actions |
+ Actions |
{% endif %}
{% for bag in bags.items %}
-
+
|
{{ bag.created_date }}
{{ bag.created_time }}
@@ -44,20 +46,24 @@
|
{{ bag.roaster_name }}
|
- {{ bag.roast_name }} |
- {{ bag.amount }}g |
-
+ |
+ {{ bag.roast_name }}
+ |
+ {{ bag.amount }}g |
+
{% if bag.closed %}
- Closed
+ Closed
{% else %}
- Open
- {{ bag.remaining }}g left
+ Open
+ {{ bag.remaining }}g left
{% endif %}
|
{% if !bag.closed %}
{{ bag.remaining }}g left |
{% endif %}
- {{ bag.finished_at }} |
+
+ {{ bag.finished_date }}
+ |
{% if is_authenticated %}
@@ -76,13 +82,21 @@
|
-
+
+
+ {% if bag.closed %}
+ Closed
+ {% else %}
+ Open
+ {% endif %}
+
+
+
|
{% endif %}
diff --git a/templates/partials/lists/brew_list.html b/templates/partials/lists/brew_list.html
index 73e8608..8c3dc06 100644
--- a/templates/partials/lists/brew_list.html
+++ b/templates/partials/lists/brew_list.html
@@ -58,13 +58,15 @@
Brewer |
Notes |
{% if is_authenticated %}
- Actions |
+ Actions |
{% endif %}
{% for brew in brews.items %}
-
+
|
{{ brew.created_date }}
{{ brew.created_time }}
@@ -80,7 +82,7 @@
|
{{ brew.grinder_name }} |
- {{ brew.coffee_weight }} / {{ brew.water_volume }}
+ {{ brew.coffee_weight }} · {{ brew.water_volume }}
{{ brew.ratio }} · {{ brew.water_temp }}
|
{{ brew.ratio }} |
@@ -94,11 +96,15 @@
{% if let Some(fp_name) = brew.filter_paper_name %}
{{ fp_name }} |
{% endif %}
-
- {% if !brew.quick_notes_label.is_empty() %}
- {{ brew.quick_notes_label }}
+ |
+ {% if !brew.quick_notes.is_empty() %}
+
+ {% for qn in brew.quick_notes %}
+ {{ qn.label }}
+ {% endfor %}
+
{% else %}
- —
+ No Notes
{% endif %}
|
{% if is_authenticated %}
@@ -108,7 +114,6 @@
|