Files
tasksquire/pages/timePage.go
2026-02-02 10:04:54 +01:00

186 lines
4.4 KiB
Go

package pages
import (
"time"
"tasksquire/common"
"tasksquire/components/timetable"
"tasksquire/timewarrior"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
)
type TimePage struct {
common *common.Common
intervals timetable.Model
data timewarrior.Intervals
shouldSelectActive bool
}
func NewTimePage(com *common.Common) *TimePage {
p := &TimePage{
common: com,
}
p.populateTable(timewarrior.Intervals{})
return p
}
func (p *TimePage) Init() tea.Cmd {
return tea.Batch(p.getIntervals(), doTick())
}
func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.SetSize(msg.Width, msg.Height)
case intervalsMsg:
p.data = timewarrior.Intervals(msg)
p.populateTable(p.data)
case RefreshIntervalsMsg:
cmds = append(cmds, p.getIntervals())
case tickMsg:
cmds = append(cmds, p.getIntervals())
cmds = append(cmds, doTick())
case tea.KeyMsg:
switch {
case key.Matches(msg, p.common.Keymap.Quit):
return p, tea.Quit
case key.Matches(msg, p.common.Keymap.StartStop):
row := p.intervals.SelectedRow()
if row != nil {
interval := (*timewarrior.Interval)(row)
if interval.IsActive() {
p.common.TimeW.StopTracking()
} else {
p.common.TimeW.ContinueInterval(interval.ID)
p.shouldSelectActive = true
}
return p, tea.Batch(p.getIntervals(), doTick())
}
case key.Matches(msg, p.common.Keymap.Delete):
row := p.intervals.SelectedRow()
if row != nil {
interval := (*timewarrior.Interval)(row)
p.common.TimeW.DeleteInterval(interval.ID)
return p, tea.Batch(p.getIntervals(), doTick())
}
case key.Matches(msg, p.common.Keymap.Edit):
row := p.intervals.SelectedRow()
if row != nil {
interval := (*timewarrior.Interval)(row)
editor := NewTimeEditorPage(p.common, interval)
p.common.PushPage(p)
return editor, editor.Init()
}
case key.Matches(msg, p.common.Keymap.Add):
interval := timewarrior.NewInterval()
interval.Start = time.Now().UTC().Format("20060102T150405Z")
editor := NewTimeEditorPage(p.common, interval)
p.common.PushPage(p)
return editor, editor.Init()
}
}
var cmd tea.Cmd
p.intervals, cmd = p.intervals.Update(msg)
cmds = append(cmds, cmd)
return p, tea.Batch(cmds...)
}
type RefreshIntervalsMsg struct{}
func refreshIntervals() tea.Msg {
return RefreshIntervalsMsg{}
}
func (p *TimePage) View() string {
if len(p.data) == 0 {
return p.common.Styles.Base.Render("No intervals found for today")
}
return p.intervals.View()
}
func (p *TimePage) SetSize(width int, height int) {
p.common.SetSize(width, height)
p.intervals.SetWidth(width - p.common.Styles.Base.GetHorizontalFrameSize())
p.intervals.SetHeight(height - p.common.Styles.Base.GetVerticalFrameSize())
}
func (p *TimePage) populateTable(intervals timewarrior.Intervals) {
var selectedStart string
currentIdx := p.intervals.Cursor()
if row := p.intervals.SelectedRow(); row != nil {
selectedStart = row.Start
}
columns := []timetable.Column{
{Title: "ID", Name: "id", Width: 4},
{Title: "Start", Name: "start", Width: 16},
{Title: "End", Name: "end", Width: 16},
{Title: "Duration", Name: "duration", Width: 10},
{Title: "Tags", Name: "tags", Width: 0}, // flexible width
}
p.intervals = timetable.New(
p.common,
timetable.WithColumns(columns),
timetable.WithIntervals(intervals),
timetable.WithFocused(true),
timetable.WithWidth(p.common.Width()-p.common.Styles.Base.GetVerticalFrameSize()),
timetable.WithHeight(p.common.Height()-p.common.Styles.Base.GetHorizontalFrameSize()),
timetable.WithStyles(p.common.Styles.TableStyle),
)
if len(intervals) > 0 {
newIdx := -1
if p.shouldSelectActive {
for i, interval := range intervals {
if interval.IsActive() {
newIdx = i
break
}
}
p.shouldSelectActive = false
}
if newIdx == -1 && selectedStart != "" {
for i, interval := range intervals {
if interval.Start == selectedStart {
newIdx = i
break
}
}
}
if newIdx == -1 {
newIdx = currentIdx
}
if newIdx >= len(intervals) {
newIdx = len(intervals) - 1
}
if newIdx < 0 {
newIdx = 0
}
p.intervals.SetCursor(newIdx)
}
}
type intervalsMsg timewarrior.Intervals
func (p *TimePage) getIntervals() tea.Cmd {
return func() tea.Msg {
// ":day" is a timewarrior hint for "today"
intervals := p.common.TimeW.GetIntervals(":day")
return intervalsMsg(intervals)
}
}