diff options
| author | Paul Bütow <1224732+snonux@users.noreply.github.com> | 2025-06-20 09:40:56 +0300 |
|---|---|---|
| committer | Paul Bütow <1224732+snonux@users.noreply.github.com> | 2025-06-20 09:40:56 +0300 |
| commit | 13f7678a9fd092ac20eec10e4d2196f5bd1ae107 (patch) | |
| tree | 512a6b9f8017f8cf0cc24158acc154034c121225 /internal/ui/table.go | |
| parent | 13ec0a6ec615b4c7e7ddc461a7a6a623109826f9 (diff) | |
Add cell navigation, editor hotkey, stats and filter
Diffstat (limited to 'internal/ui/table.go')
| -rw-r--r-- | internal/ui/table.go | 126 |
1 files changed, 123 insertions, 3 deletions
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) } |
