diff options
author | T <t@tjp.lol> | 2025-08-05 11:37:02 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-08-05 11:37:08 -0600 |
commit | 665bd389a0a1c8adadcaa1122e846cc81f5ead31 (patch) | |
tree | f34f9ec77891308c600c680683f60951599429c3 /internal/tui/timer.go | |
parent | dc895cec9d8a84af89ce2501db234dff33c757e2 (diff) |
WIP TUI
Diffstat (limited to 'internal/tui/timer.go')
-rw-r--r-- | internal/tui/timer.go | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/internal/tui/timer.go b/internal/tui/timer.go new file mode 100644 index 0000000..827951d --- /dev/null +++ b/internal/tui/timer.go @@ -0,0 +1,150 @@ +package tui + +import ( + "context" + "fmt" + "time" + + "punchcard/internal/queries" + + tea "github.com/charmbracelet/bubbletea" +) + +// TimerModel represents the timer view model +type TimerModel struct { + ctx context.Context + queries *queries.Queries + timerInfo TimerInfo + stats TimeStats + lastTick time.Time +} + +// NewTimerModel creates a new timer model +func NewTimerModel(ctx context.Context, q *queries.Queries) TimerModel { + return TimerModel{ + ctx: ctx, + queries: q, + } +} + +// Init initializes the timer model +func (m TimerModel) Init() tea.Cmd { + return tea.Batch( + m.updateData(), + m.tickCmd(), + ) +} + +// Update handles messages for the timer model +func (m TimerModel) Update(msg tea.Msg) (TimerModel, tea.Cmd) { + switch msg := msg.(type) { + case TickMsg: + // Update timer duration if active + if m.timerInfo.IsActive { + m.timerInfo.Duration = time.Since(m.timerInfo.StartTime) + } + m.lastTick = time.Time(msg) + return m, m.tickCmd() + } + + return m, nil +} + +// View renders the timer view +func (m TimerModel) View(width, height int) string { + var content string + + if m.timerInfo.IsActive { + content += m.renderActiveTimer() + } else { + content += m.renderInactiveTimer() + } + + return content +} + +// renderActiveTimer renders the active timer display +func (m TimerModel) renderActiveTimer() string { + var content string + + // Timer status + timerLine := fmt.Sprintf("⏱ Tracking: %s", FormatDuration(m.timerInfo.Duration)) + content += activeTimerStyle.Render(timerLine) + "\n" + + // Project/Client info + if m.timerInfo.ProjectName != "" { + projectLine := fmt.Sprintf("Project: %s / %s", m.timerInfo.ClientName, m.timerInfo.ProjectName) + content += projectLine + "\n" + } else { + clientLine := fmt.Sprintf("Client: %s", m.timerInfo.ClientName) + content += clientLine + "\n" + } + + // Description if available + if m.timerInfo.Description != "" { + descLine := fmt.Sprintf("Description: %s", m.timerInfo.Description) + content += descLine + "\n" + } + + // Billable rate if available + if m.timerInfo.BillableRate != nil { + rateLine := fmt.Sprintf("Rate: $%.2f/hr", *m.timerInfo.BillableRate) + content += rateLine + "\n" + } + + // Start time (convert from UTC to local) + localStartTime := m.timerInfo.StartTime.Local() + startLine := fmt.Sprintf("Started: %s", localStartTime.Format("3:04 PM")) + content += startLine + "\n" + + return content +} + +// renderInactiveTimer renders the inactive timer display +func (m TimerModel) renderInactiveTimer() string { + var content string + + content += inactiveTimerStyle.Render("⚪ No active timer") + "\n" + content += "\n" + content += "Ready to start tracking time.\n" + + return content +} + +// updateData fetches fresh data from the database +func (m TimerModel) updateData() tea.Cmd { + return func() tea.Msg { + // Get timer info + timerInfo, err := GetTimerInfo(m.ctx, m.queries) + if err != nil { + // Handle error silently for now + return nil + } + + // Get time stats + stats, err := GetTimeStats(m.ctx, m.queries) + if err != nil { + // Handle error silently for now + return nil + } + + return dataUpdatedMsg{ + timerInfo: timerInfo, + stats: stats, + } + } +} + +// tickCmd returns a command that sends a tick message every second +func (m TimerModel) tickCmd() tea.Cmd { + return tea.Tick(time.Second, func(t time.Time) tea.Msg { + return TickMsg(t) + }) +} + +// UpdateData updates the model with fresh data +func (m TimerModel) UpdateData(timerInfo TimerInfo, stats TimeStats) TimerModel { + m.timerInfo = timerInfo + m.stats = stats + return m +} |