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:
@@ -7,8 +7,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"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)
|
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),
|
cDim.Sprint(n.ID),
|
||||||
render(prioRM, n.GetProperty("prio"), true),
|
render(prioRM, n.GetProperty("prio"), true),
|
||||||
render(statusRM, n.GetProperty("status"), true),
|
render(statusRM, n.GetProperty("status"), true),
|
||||||
render(typeRM, n.GetProperty("type"), true),
|
render(typeRM, n.GetProperty("type"), true),
|
||||||
cTitle.Sprint(truncate(n.Title, 80)),
|
cTitle.Sprint(truncate(n.Title, 80)),
|
||||||
cDim.Sprint("["+strings.Join(ns_rel_node_titles, ",")+"]"),
|
cDim.Sprint("["+strings.Join(ns_rel_node_titles, ",")+"]"),
|
||||||
|
dueDateShort(n.DueDate),
|
||||||
)
|
)
|
||||||
tags := n.GetDisplayTags()
|
tags := n.GetDisplayTags()
|
||||||
if len(tags) > 0 {
|
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, " Status: %s\n", render(statusRM, n.GetProperty("status"), false))
|
||||||
fmt.Fprintf(w, " Priority: %s\n", render(prioRM, n.GetProperty("prio"), false))
|
fmt.Fprintf(w, " Priority: %s\n", render(prioRM, n.GetProperty("prio"), false))
|
||||||
if n.DueDate != nil {
|
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, " Created: %s\n", cDim.Sprint(n.CreatedAt))
|
||||||
fmt.Fprintf(w, " Updated: %s\n", cDim.Sprint(n.UpdatedAt))
|
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)
|
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 {
|
func truncate(s string, max int) string {
|
||||||
if len(s) <= max {
|
if len(s) <= max {
|
||||||
return s
|
return s
|
||||||
|
|||||||
Reference in New Issue
Block a user