feat(models): derive order state from status + tracking
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2026-05-09 16:48:10 +02:00
parent c5f4e492c0
commit 700ad4926b
4 changed files with 154 additions and 9 deletions

25
cog.toml Normal file
View File

@@ -0,0 +1,25 @@
from_latest_tag = false
ignore_merge_commits = false
ignore_fixup_commits = false
disable_changelog = false
disable_bump_commit = false
generate_mono_repository_global_tag = true
generate_mono_repository_package_tags = true
branch_whitelist = []
pre = "alpha.*"
skip_ci = "[skip ci]"
skip_untracked = false
pre_bump_hooks = []
post_bump_hooks = []
pre_package_bump_hooks = []
post_package_bump_hooks = []
[git_hooks]
[commit_types]
[changelog]
path = "CHANGELOG.md"
authors = []
[bump_profiles]

View File

@@ -0,0 +1,76 @@
package models
// DisplayKind is a ui agnostic color/severity token.
// The TUI (or any other renderer) maps this to a concrete style
// the model stays free of UI deps.
type DisplayKind string
const (
DisplayKindNeutral DisplayKind = "neutral" // pending, default
DisplayKindAccent DisplayKind = "accent" // paid, awaiting fulfillment
DisplayKindBrand DisplayKind = "brand" // shipped / in transit
DisplayKindSuccess DisplayKind = "success" // delivered
DisplayKindError DisplayKind = "error" // cancelled, failed, refunded
DisplayKindWarning DisplayKind = "warning" // refund pending, delivery issue
)
func (order *Order) DisplayState() string {
switch order.Status {
case OrderStatusPending:
return "Awaiting payment"
case OrderStatusPaid:
if order.TrackingNumber == "" {
return "Paid * awaiting label"
}
// !IMPORTANT
// Reachable only if UpdateTracking auto-promote is removed later
if order.TrackingStatus == TrackingStatusPreTransit {
return "Paid * ready to ship"
}
return "Paid"
case OrderStatusShipped:
switch order.TrackingStatus {
case TrackingStatusTransit:
return "Shipped * in transit"
case TrackingStatusFailure:
return "Shipped * delivery issue"
case TrackingStatusReturned:
return "Shipped * returned to sender"
case TrackingStatusDelivered:
// Webhook hasn't promoted Status yet, trust tracking
return "Delivered"
}
return "Shipped"
case OrderStatusDelivered:
return "Delivered"
case OrderStatusCancelled:
return "Cancelled"
case OrderStatusRefunded:
return "Refunded"
case OrderStatusFailed:
return "Payment failed"
}
return string(order.Status)
}
func (order *Order) DisplayKind() DisplayKind {
switch order.Status {
case OrderStatusPending:
return DisplayKindNeutral
case OrderStatusPaid:
return DisplayKindAccent
case OrderStatusShipped:
switch order.TrackingStatus {
case TrackingStatusFailure, TrackingStatusReturned:
return DisplayKindWarning
case TrackingStatusDelivered:
return DisplayKindSuccess
}
return DisplayKindBrand
case OrderStatusDelivered:
return DisplayKindSuccess
case OrderStatusCancelled, OrderStatusRefunded, OrderStatusFailed:
return DisplayKindError
}
return DisplayKindNeutral
}

View File

@@ -0,0 +1,36 @@
package models
import "testing"
func TestOrderDisplayState(t *testing.T) {
tests := []struct {
name string
order Order
want string
kind DisplayKind
}{
{"pending", Order{Status: OrderStatusPending}, "Awaiting payment", DisplayKindNeutral},
{"paid no tracking", Order{Status: OrderStatusPaid}, "Paid * awaiting label", DisplayKindAccent},
{"paid with tracking, no event", Order{Status: OrderStatusPaid, TrackingNumber: "12999"}, "Paid", DisplayKindAccent},
{"Paid pre-transit", Order{Status: OrderStatusPaid, TrackingNumber: "12999", TrackingStatus: TrackingStatusPreTransit}, "Paid * ready to ship", DisplayKindAccent},
{"shipped no tracking event", Order{Status: OrderStatusShipped}, "Shipped", DisplayKindBrand},
{"shipped in transit", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusTransit}, "Shipped * in transit", DisplayKindBrand},
{"shipped failure", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusFailure}, "Shipped * delivery issue", DisplayKindWarning},
{"shipped returned", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusReturned}, "Shipped * returned to sender", DisplayKindWarning},
{"shipped tracking-delivered (webhook lag)", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusDelivered}, "Delivered", DisplayKindSuccess},
{"delivered", Order{Status: OrderStatusDelivered}, "Delivered", DisplayKindSuccess},
{"cancelled", Order{Status: OrderStatusCancelled}, "Cancelled", DisplayKindError},
{"refunded", Order{Status: OrderStatusRefunded}, "Refunded", DisplayKindError},
{"failed", Order{Status: OrderStatusFailed}, "Payment failed", DisplayKindError},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tc.order.DisplayState(); got != tc.want {
t.Errorf("DisplayState() = %q, want %q", got, tc.want)
}
if got := tc.order.DisplayKind(); got != tc.kind {
t.Errorf("DisplayKind() = %q, want %q", got, tc.kind)
}
})
}
}

View File

@@ -74,9 +74,9 @@ func (m Model) buildOrderCard(order models.Order, boxWidth int, isSelected bool)
}
line1 := orderLabel + lipgloss.NewStyle().Width(spacing).Render("") + total
// Line 2: date + status
// Line 2: date + derived status
date := dimStyle.Render(order.CreatedAt.Format("Jan 02 2006"))
status := m.statusStyle(order.Status).Bold(true).Render(strings.ToUpper(string(order.Status)))
status := m.displayStyle(order.DisplayKind()).Bold(true).Render(order.DisplayState())
line2 := date + " " + status
boxContent := line1 + "\n" + line2
@@ -102,7 +102,7 @@ func (m Model) buildOrderDetailView(order models.Order, _ int) string {
// Status and date
b.WriteString(labelStyle.Render("Status: "))
b.WriteString(m.statusStyle(order.Status).Render(strings.ToUpper(string(order.Status))))
b.WriteString(m.displayStyle(order.DisplayKind()).Render(order.DisplayState()))
b.WriteString("\n")
b.WriteString(labelStyle.Render("Date: "))
b.WriteString(valueStyle.Render(order.CreatedAt.Format("Jan 02 2006 3:04 PM")))
@@ -183,15 +183,23 @@ func (m Model) buildOrderDetailView(order models.Order, _ int) string {
return b.String()
}
func (m Model) statusStyle(s models.OrderStatus) lipgloss.Style {
switch s {
case models.OrderStatusDelivered:
// displayStyle maps a model-layer DisplayKind token to a themed lipgloss style.
// Keeps pkg/models free of UI deps.
func (m Model) displayStyle(k models.DisplayKind) lipgloss.Style {
switch k {
case models.DisplayKindSuccess:
return m.theme.TextSuccess()
case models.OrderStatusShipped:
case models.DisplayKindBrand:
return m.theme.TextBrand()
case models.OrderStatusFailed, models.OrderStatusCancelled, models.OrderStatusRefunded:
case models.DisplayKindAccent:
return m.theme.TextAccent()
case models.DisplayKindError:
return m.theme.TextError()
case models.DisplayKindWarning:
// !TODO add refunds and change this
// No warning color in theme yet, reuse loading pink as visual flag
// for now
return m.theme.TextLoading()
}
return m.theme.TextHighlight()
}