From cdf90d1ff3c7728cbf80a52e5b20cd1960cee11b Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Wed, 4 Feb 2026 19:03:05 +0000 Subject: [PATCH] feat(home): add relative dates to timeline and brew cards - Add relative_date() helper to views module for humanized timestamps - Show relative dates on home page timeline preview and brew cards - Format: "Just now", "Xm ago", "Xh ago", "Yesterday", "Xd/Xw ago" --- src/presentation/web/views/brews.rs | 4 +++ src/presentation/web/views/mod.rs | 41 ++++++++++++++++++++++++++ src/presentation/web/views/timeline.rs | 4 +++ templates/home.html | 11 ++++--- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/presentation/web/views/brews.rs b/src/presentation/web/views/brews.rs index 264d0de..0eda295 100644 --- a/src/presentation/web/views/brews.rs +++ b/src/presentation/web/views/brews.rs @@ -1,5 +1,7 @@ use crate::domain::brews::{Brew, BrewWithDetails}; +use super::relative_date; + #[derive(Clone)] pub struct BrewView { pub id: String, @@ -20,6 +22,7 @@ pub struct BrewView { pub water_temp: String, pub ratio: String, pub created_at: String, + pub relative_date_label: String, // Raw values for "brew again" feature pub coffee_weight_raw: f64, pub grind_setting_raw: f64, @@ -60,6 +63,7 @@ impl BrewView { water_temp: format!("{:.1}\u{00B0}C", brew.brew.water_temp), ratio, created_at: brew.brew.created_at.format("%Y-%m-%d %H:%M").to_string(), + relative_date_label: relative_date(brew.brew.created_at), coffee_weight_raw: brew.brew.coffee_weight, grind_setting_raw: brew.brew.grind_setting, water_volume_raw: brew.brew.water_volume, diff --git a/src/presentation/web/views/mod.rs b/src/presentation/web/views/mod.rs index 5b99925..b072afa 100644 --- a/src/presentation/web/views/mod.rs +++ b/src/presentation/web/views/mod.rs @@ -28,8 +28,49 @@ pub struct StatsView { pub gear: u64, } +use chrono::{DateTime, Utc}; + use crate::domain::listing::{DEFAULT_PAGE_SIZE, ListRequest, Page, PageSize, SortKey}; +fn relative_date(dt: DateTime) -> String { + let now = Utc::now(); + let delta = now.signed_duration_since(dt); + let secs = delta.num_seconds(); + + if secs < 60 { + return "Just now".to_string(); + } + + let mins = delta.num_minutes(); + if mins < 60 { + return format!("{mins}m ago"); + } + + let hours = delta.num_hours(); + if hours < 24 { + return format!("{hours}h ago"); + } + + let days = delta.num_days(); + if days == 1 { + return "Yesterday".to_string(); + } + if days < 7 { + return format!("{days}d ago"); + } + + let weeks = days / 7; + if days < 30 { + return format!("{weeks}w ago"); + } + + if dt.format("%Y").to_string() == now.format("%Y").to_string() { + dt.format("%b %d").to_string() + } else { + dt.format("%b %d, %Y").to_string() + } +} + pub struct Paginated { pub items: Vec, pub page: u32, diff --git a/src/presentation/web/views/timeline.rs b/src/presentation/web/views/timeline.rs index eedd986..73cd942 100644 --- a/src/presentation/web/views/timeline.rs +++ b/src/presentation/web/views/timeline.rs @@ -1,5 +1,7 @@ use crate::domain::timeline::{TimelineEvent, TimelineEventDetail}; +use super::relative_date; + #[derive(Clone)] pub struct TimelineEventDetailView { pub label: String, @@ -25,6 +27,7 @@ pub struct TimelineEventView { pub id: String, pub kind_label: &'static str, pub date_label: String, + pub relative_date_label: String, pub time_label: Option, pub iso_timestamp: String, pub title: String, @@ -116,6 +119,7 @@ impl TimelineEventView { id: id.to_string(), kind_label, date_label: occurred_at.format("%B %d, %Y").to_string(), + relative_date_label: relative_date(occurred_at), time_label: Some(occurred_at.format("%H:%M UTC").to_string()), iso_timestamp: occurred_at.to_rfc3339(), title, diff --git a/templates/home.html b/templates/home.html index 127db42..3989f74 100644 --- a/templates/home.html +++ b/templates/home.html @@ -192,9 +192,12 @@
{% for brew in recent_brews %}
-
- {{ brew.roast_name }} -

{{ brew.roaster_name }}

+
+
+ {{ brew.roast_name }} +

{{ brew.roaster_name }}

+
+

{{ brew.coffee_weight }} / {{ brew.water_volume }} · {{ brew.ratio }} · {{ brew.water_temp }}

@@ -262,7 +265,7 @@ {{ event.kind_label }} {{ event.title }}
- +
{% endfor %}