feat: add colored due date indicators to list and show views

- List view: show relative due date (+12d, -3d, today) after namespace
- Show view: colorize full due date based on urgency
- Color scheme: red (overdue), yellow (due within 3 days), green (4+ days)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 04:54:15 +02:00
parent 30f802b5ac
commit a30c703efa

View File

@@ -7,8 +7,10 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"sort"
"strings"
"time"
"github.com/fatih/color"
)
@@ -89,13 +91,14 @@ func PrintNodes(w io.Writer, svc service.NodeService, nodes []*models.Node, json
}
ns_rel_node_titles = append(ns_rel_node_titles, ns_rel_node.Title)
}
fmt.Fprintf(w, " %s %s %s %s %s %s",
fmt.Fprintf(w, " %s %s %s %s %s %s %s",
cDim.Sprint(n.ID),
render(prioRM, n.GetProperty("prio"), true),
render(statusRM, n.GetProperty("status"), true),
render(typeRM, n.GetProperty("type"), true),
cTitle.Sprint(truncate(n.Title, 80)),
cDim.Sprint("["+strings.Join(ns_rel_node_titles, ",")+"]"),
dueDateShort(n.DueDate),
)
tags := n.GetDisplayTags()
if len(tags) > 0 {
@@ -117,7 +120,7 @@ func PrintNode(w io.Writer, svc service.NodeService, n *models.Node, jsonOut boo
fmt.Fprintf(w, " Status: %s\n", render(statusRM, n.GetProperty("status"), false))
fmt.Fprintf(w, " Priority: %s\n", render(prioRM, n.GetProperty("prio"), false))
if n.DueDate != nil {
fmt.Fprintf(w, " Due: %s %s\n", iconCalendar, n.DueDate.Format("2006-01-02"))
fmt.Fprintf(w, " Due: %s\n", dueDateLong(n.DueDate))
}
fmt.Fprintf(w, " Created: %s\n", cDim.Sprint(n.CreatedAt))
fmt.Fprintf(w, " Updated: %s\n", cDim.Sprint(n.UpdatedAt))
@@ -202,6 +205,57 @@ func render(rm RenderMap, key string, short bool) string {
return v.c.Sprint(v.l)
}
// dueDateShort returns a "+12d" / "-3d" style string colored by urgency.
func dueDateShort(d *models.Date) string {
if d == nil {
return ""
}
now := time.Now().UTC().Truncate(24 * time.Hour)
due := d.UTC().Truncate(24 * time.Hour)
days := int(math.Round(due.Sub(now).Hours() / 24))
var label string
if days == 0 {
label = "today"
} else if days > 0 {
label = fmt.Sprintf("+%dd", days)
} else {
label = fmt.Sprintf("%dd", days)
}
var c *color.Color
switch {
case days < 0:
c = cBad
case days <= 3:
c = cWarn
default:
c = cGood
}
return c.Sprint(label)
}
// dueDateLong returns the full date string colored by urgency.
func dueDateLong(d *models.Date) string {
if d == nil {
return ""
}
now := time.Now().UTC().Truncate(24 * time.Hour)
due := d.UTC().Truncate(24 * time.Hour)
days := int(math.Round(due.Sub(now).Hours() / 24))
var c *color.Color
switch {
case days < 0:
c = cBad
case days <= 3:
c = cWarn
default:
c = cGood
}
return c.Sprintf("%s %s", iconCalendar, d.Format("2006-01-02"))
}
func truncate(s string, max int) string {
if len(s) <= max {
return s