summaryrefslogtreecommitdiff
path: root/internal/tui/timer.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui/timer.go')
-rw-r--r--internal/tui/timer.go150
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
+}