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 }