From aa91fb7a9d943d821dd595c6d6c68f8dd742aafd Mon Sep 17 00:00:00 2001 From: Ruben Rosario Date: Sun, 21 Jun 2026 16:24:54 +0100 Subject: [PATCH] Feature 3: relative due dates right-aligned in task list - Replace absolute date with relative string (Overdue, Xh left, X days left) - Overdue in Red, <24h in Yellow, >=24h in DarkGray - Due string right-aligned via padding calculation --- src/ui/components.rs | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/ui/components.rs b/src/ui/components.rs index 04a0128..4fc6e17 100644 --- a/src/ui/components.rs +++ b/src/ui/components.rs @@ -49,6 +49,24 @@ pub fn render_tabs_bar( frame.render_widget(tabs, area); } +fn relative_due_str(due: chrono::NaiveDateTime) -> (String, Color) { + let now = chrono::Local::now().naive_local(); + let diff = due - now; + + if diff < chrono::Duration::zero() { + (" Overdue ".to_string(), Color::Red) + } else if diff < chrono::Duration::hours(24) { + let hours = diff.num_hours(); + (format!(" {}h left ", hours), Color::Yellow) + } else { + let days = diff.num_days(); + ( + format!(" {} day{} left ", days, if days == 1 { "" } else { "s" }), + Color::DarkGray, + ) + } +} + pub fn render_task_list( frame: &mut Frame, area: Rect, @@ -61,6 +79,8 @@ pub fn render_task_list( let done = tasks.iter().filter(|t| t.status == TaskStatus::Completed).count(); let todo = total - done; + let content_width = (area.width as usize).saturating_sub(5); + let items: Vec = tasks .iter() .map(|task| { @@ -69,12 +89,12 @@ pub fn render_task_list( TaskStatus::NeedsAction => "[ ]", }; - let due_str = task + let (due_text, due_color) = task .due - .map(|d| d.format(" %d/%m/%Y %H:%M").to_string()) - .unwrap_or_default(); + .map(relative_due_str) + .unwrap_or((String::new(), Color::DarkGray)); - let content = Line::from(vec![ + let mut spans = vec![ Span::styled( format!("{} ", checkbox), Style::default().fg(if task.status == TaskStatus::Completed { @@ -93,12 +113,16 @@ pub fn render_task_list( }, ), ), - Span::styled( - due_str, - Style::default().fg(Color::DarkGray), - ), - ]); - ListItem::new(content) + ]; + + if !due_text.is_empty() { + let left_len: usize = spans.iter().map(|s| s.content.len()).sum(); + let pad = content_width.saturating_sub(left_len + due_text.len()); + spans.push(Span::raw(" ".repeat(pad))); + spans.push(Span::styled(due_text, Style::default().fg(due_color))); + } + + ListItem::new(Line::from(spans)) }) .collect();