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) } }