From 13f7678a9fd092ac20eec10e4d2196f5bd1ae107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20B=C3=BCtow?= <1224732+snonux@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:40:56 +0300 Subject: Add cell navigation, editor hotkey, stats and filter --- internal/ui/table.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 3 deletions(-) (limited to 'internal/ui/table.go') diff --git a/internal/ui/table.go b/internal/ui/table.go index 4300034..4293500 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -1,19 +1,43 @@ package ui import ( + "fmt" + "strconv" + "strings" + "time" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + atable "tasksamurai/internal/atable" + "tasksamurai/internal/task" ) // Model wraps a Bubble Tea table.Model to display tasks. type Model struct { tbl atable.Model showHelp bool + + filter string + tasks []task.Task + + total int + inProgress int + due int } // New creates a new UI model with the provided rows. -func New(rows []atable.Row) Model { +func New(filter string) (Model, error) { + m := Model{filter: filter} + + if err := m.reload(); err != nil { + return Model{}, err + } + + return m, nil +} + +func newTable(rows []atable.Row) atable.Model { cols := []atable.Column{ {Title: "ID", Width: 4}, {Title: "Task", Width: 30}, @@ -36,7 +60,36 @@ func New(rows []atable.Row) Model { styles.Selected = styles.Selected.Foreground(lipgloss.Color("229")).Background(lipgloss.Color("57")) styles.Cell = styles.Cell.Padding(0, 1) t.SetStyles(styles) - return Model{tbl: t} + return t +} + +func (m *Model) reload() error { + tasks, err := task.Export(strings.Fields(m.filter)...) + if err != nil { + return err + } + + var rows []atable.Row + var filtered []task.Task + for _, tsk := range tasks { + if tsk.Status == "completed" { + continue + } + filtered = append(filtered, tsk) + rows = append(rows, taskToRow(tsk)) + } + + m.tasks = filtered + m.total = task.TotalTasks(filtered) + m.inProgress = task.InProgressTasks(filtered) + m.due = task.DueTasks(filtered, time.Now()) + + if m.tbl.Columns() == nil { + m.tbl = newTable(rows) + } else { + m.tbl.SetRows(rows) + } + return nil } // Init implements tea.Model. @@ -60,6 +113,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } return m, tea.Quit + case "E": + if row := m.tbl.SelectedRow(); row != nil { + id, err := strconv.Atoi(row[0]) + if err == nil { + _ = task.Edit(id) + m.reload() + } + } } } @@ -81,5 +142,64 @@ func (m Model) View() string { "?: help", // show help toggle line ) } - return m.tbl.View() + return lipgloss.JoinVertical(lipgloss.Left, + m.tbl.View(), + m.statusLine(), + ) +} + +func (m Model) statusLine() string { + return fmt.Sprintf("Total:%d InProgress:%d Due:%d", m.total, m.inProgress, m.due) +} + +func taskToRow(t task.Task) atable.Row { + active := "" + if t.Start != "" { + active = "yes" + } + + age := "" + if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil { + days := int(time.Since(ts).Hours() / 24) + age = fmt.Sprintf("%dd", days) + } + + tags := strings.Join(t.Tags, ",") + urg := fmt.Sprintf("%.1f", t.Urgency) + + var anns []string + for _, a := range t.Annotations { + anns = append(anns, a.Description) + } + + return atable.Row{ + strconv.Itoa(t.ID), + t.Description, + active, + age, + t.Priority, + tags, + t.Recur, + formatDue(t.Due), + urg, + strings.Join(anns, "; "), + } +} + +func formatDue(s string) string { + if s == "" { + return "" + } + ts, err := time.Parse("20060102T150405Z", s) + if err != nil { + return s + } + + days := int(time.Until(ts).Hours() / 24) + val := fmt.Sprintf("%dd", days) + style := lipgloss.NewStyle() + if days < 0 { + style = style.Background(lipgloss.Color("1")) + } + return style.Render(val) } -- cgit v1.2.3