blob: 2b0c34acb7b0b6e3cc379dcfd04f7dc59cef8f4b (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
package tui
import (
"time"
"git.tjp.lol/punchcard/internal/queries"
"github.com/charmbracelet/lipgloss/v2"
)
// TimerInfo holds information about the current or most recent timer state
type TimerInfo struct {
IsActive bool
EntryID int64
StartTime time.Time
Duration time.Duration
ClientID int64
ClientName string
ProjectID *int64
ProjectName string
Description *string
BillableRate *float64
}
func (ti *TimerInfo) setNames(clients []queries.Client, projects map[int64][]queries.Project) {
for _, cl := range clients {
if cl.ID == ti.ClientID {
ti.ClientName = cl.Name
break
}
}
if ti.ProjectID == nil {
return
}
for _, group := range projects {
for _, proj := range group {
if proj.ID == *ti.ProjectID {
ti.ProjectName = proj.Name
return
}
}
}
}
// Box models for the three main components
type TimerBoxModel struct {
timerInfo TimerInfo
currentTime time.Time
}
// NewTimerBoxModel creates a new timer box model
func NewTimerBoxModel() TimerBoxModel {
return TimerBoxModel{
currentTime: time.Now(),
}
}
// View renders the timer box
func (m TimerBoxModel) View(width, height int, isSelected bool) string {
var content string
if m.timerInfo.IsActive {
content = m.renderActiveTimer()
} else {
content = m.renderInactiveTimer()
}
// Apply box styling
var style lipgloss.Style
if isSelected {
style = selectedBoxStyle
} else if m.timerInfo.IsActive {
style = activeBoxStyle
} else {
style = unselectedBoxStyle
}
return style.Width(width).Height(height).Render(content)
}
// renderActiveTimer renders the active timer display
func (m TimerBoxModel) renderActiveTimer() string {
statusStyle := lipgloss.NewStyle().Foreground(colorTimerActive).Bold(true)
content := statusStyle.Render("TRACKING") + "\n\n"
// Timer duration - big and bold
dur := FormatDuration(m.currentTime.Sub(m.timerInfo.StartTime))
content += activeTimerStyle.Render(dur) + "\n\n"
// Project/Client info
dimLabel := lipgloss.NewStyle().Foreground(colorDimmed)
if m.timerInfo.ProjectName != "" {
content += dimLabel.Render(m.timerInfo.ClientName+" /") + "\n"
content += m.timerInfo.ProjectName + "\n"
} else {
content += m.timerInfo.ClientName + "\n"
}
// Start time
localStartTime := m.timerInfo.StartTime.Local()
content += dimLabel.Render("since " + localStartTime.Format("3:04 PM"))
return content
}
// renderInactiveTimer renders the inactive timer display
func (m TimerBoxModel) renderInactiveTimer() string {
statusStyle := lipgloss.NewStyle().Foreground(colorDimmed)
content := statusStyle.Render("IDLE") + "\n\n"
if m.timerInfo.EntryID == 0 {
content += inactiveTimerStyle.Render("No entries yet.\nPunch in to start.")
return content
}
content += inactiveTimerStyle.Render(FormatDuration(m.timerInfo.Duration)) + "\n\n"
dimLabel := lipgloss.NewStyle().Foreground(colorDimmed)
if m.timerInfo.ProjectName != "" {
content += dimLabel.Render(m.timerInfo.ClientName+" /") + "\n"
content += inactiveTimerStyle.Render(m.timerInfo.ProjectName)
} else {
content += inactiveTimerStyle.Render(m.timerInfo.ClientName)
}
return content
}
func (m TimerBoxModel) activeTime() time.Duration {
if !m.timerInfo.IsActive {
return 0
}
return m.currentTime.Sub(m.timerInfo.StartTime)
}
|