Skip to content

Commit

Permalink
feat: add report and active commands
Browse files Browse the repository at this point in the history
  • Loading branch information
dhth committed Jun 10, 2024
1 parent e3758ae commit 7784b94
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 28 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
✨ Overview
---

"hours" is a simple CLI app that allows you to track time on tasks you care
about.
"hours" is a CLI app that allows you to track time on tasks you care about.

"hours" is intended for users who want to do some sort of time tracking for
their projects, but don't want to use an overly complicated app or website to do
so. It has a simple and minimalistic UI; almost everything in it can be achieved
with one or two keypresses.

💾 Install
---
Expand All @@ -14,3 +18,21 @@ about.
```sh
go install github.com/dhth/hours@latest
```

⚡️ Usage
---

```
Usage:
hours [flags] [command]
Flags:
-db-path string
location where hours should create its DB file (default "/Users/dhruvthakur/hours.v1.db")
Commands:
report
outputs a report of recently added log entries
active
shows the task currently being tracked
```
27 changes: 25 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,22 @@ func Execute() {
dbPath := flag.String("db-path", defaultDBPath, "location where hours should create its DB file")

flag.Usage = func() {
fmt.Fprintf(os.Stdout, "Track time on your tasks.\n\nFlags:\n")
fmt.Fprintf(os.Stdout, `Track time on your tasks via a simple TUI.
Usage:
hours [flags] [command]
Flags:
`)
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
fmt.Fprintf(os.Stdout, `
Commands:
report
outputs a report of recently added log entries
active
shows the task currently being tracked
`)
}
flag.Parse()

Expand All @@ -43,6 +56,16 @@ func Execute() {
os.Exit(1)
}

ui.RenderUI(db)
args := os.Args[1:]
out := os.Stdout

if len(args) > 0 {
if args[0] == "report" {
ui.RenderTaskLogReport(db, out)
} else if args[0] == "active" {
ui.ShowActiveTask(db, out)
}
} else {
ui.RenderUI(db)
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
Expand All @@ -47,6 +48,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
Expand Down
23 changes: 23 additions & 0 deletions internal/ui/active.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ui

import (
"database/sql"
"fmt"
"io"
"os"
)

func ShowActiveTask(db *sql.DB, writer io.Writer) {
activeTaskDetails, err := fetchActiveTaskFromDB(db)

if err != nil {
fmt.Fprintf(os.Stdout, "Something went wrong:\n%s", err)
os.Exit(1)
}

if activeTaskDetails.taskId == -1 {
return
}

fmt.Fprintf(writer, "%s", activeTaskDetails.taskSummary)
}
11 changes: 7 additions & 4 deletions internal/ui/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,20 @@ func insertManualEntry(db *sql.DB, taskId int, beginTS time.Time, endTS time.Tim

func fetchActiveTask(db *sql.DB) tea.Cmd {
return func() tea.Msg {
id, beginTs, err := fetchActiveTaskFromDB(db)
activeTaskDetails, err := fetchActiveTaskFromDB(db)

if err != nil {
return activeTaskFetchedMsg{err: err}
}

if id == -1 {
if activeTaskDetails.taskId == -1 {
return activeTaskFetchedMsg{noneActive: true}
}

return activeTaskFetchedMsg{activeTaskId: id, beginTs: beginTs}
return activeTaskFetchedMsg{
activeTaskId: activeTaskDetails.taskId,
beginTs: activeTaskDetails.lastLogEntryBeginTS,
}
}
}

Expand All @@ -91,7 +94,7 @@ func updateTaskRep(db *sql.DB, t *task) tea.Cmd {

func fetchTaskLogEntries(db *sql.DB) tea.Cmd {
return func() tea.Msg {
entries, err := fetchTLEntriesFromDB(db)
entries, err := fetchTLEntriesFromDB(db, 50)
return taskLogEntriesFetchedMsg{
entries: entries,
err: err,
Expand Down
4 changes: 0 additions & 4 deletions internal/ui/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ var (
helpSectionStyle.Render(`
(scroll line by line with j/k/arrow keys or by half a page with <c-d>/<c-u>)
"hours" is intended for those who want to do some sort of time tracking for their projects,
but don't want to use an overly complicated app or website to do so. "hours" has a simple
and minimalistic UI; almost everything in it can be achieved with one or two keypresses.
"hours" has 5 panes:
- Tasks List View Shows your tasks
- Task Management View Allows you to create/update tasks
Expand Down
29 changes: 15 additions & 14 deletions internal/ui/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,26 +122,27 @@ WHERE id = ?;
return nil
}

func fetchActiveTaskFromDB(db *sql.DB) (int, time.Time, error) {
func fetchActiveTaskFromDB(db *sql.DB) (activeTaskDetails, error) {

row := db.QueryRow(`
SELECT task_id, begin_ts
FROM task_log
WHERE active=true;
SELECT t.id, t.summary, tl.begin_ts
FROM task_log tl left join task t on tl.task_id = t.id
WHERE tl.active=true;
`)

var activeTaskId int
var beginTs time.Time
var activeTaskDetails activeTaskDetails
err := row.Scan(
&activeTaskId,
&beginTs,
&activeTaskDetails.taskId,
&activeTaskDetails.taskSummary,
&activeTaskDetails.lastLogEntryBeginTS,
)
if errors.Is(err, sql.ErrNoRows) {
return -1, beginTs, nil
activeTaskDetails.taskId = -1
return activeTaskDetails, nil
} else if err != nil {
return -1, beginTs, err
return activeTaskDetails, err
}
return activeTaskId, beginTs, nil
return activeTaskDetails, nil
}

func insertTaskInDB(db *sql.DB, summary string) error {
Expand Down Expand Up @@ -255,16 +256,16 @@ LIMIT 100;
return tasks, nil
}

func fetchTLEntriesFromDB(db *sql.DB) ([]taskLogEntry, error) {
func fetchTLEntriesFromDB(db *sql.DB, limit int) ([]taskLogEntry, error) {

var logEntries []taskLogEntry

rows, err := db.Query(`
SELECT tl.id, tl.task_id, t.summary, tl.begin_ts, tl.end_ts, tl.comment
FROM task_log tl left join task t on tl.task_id=t.id
WHERE tl.active=false
ORDER by tl.begin_ts DESC LIMIT 30;
`)
ORDER by tl.begin_ts DESC LIMIT ?;
`, limit)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion internal/ui/render_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ func (tl *taskLogEntry) updateDesc() {
secsSpent := int(tl.endTS.Sub(tl.beginTS).Seconds())
timeSpentStr := humanizeDuration(secsSpent)

tl.desc = fmt.Sprintf("%s (spent %s)", RightPadTrim(humanize.Time(tl.beginTS), 60), timeSpentStr)
timeStr := fmt.Sprintf("%s (spent %s)", RightPadTrim(humanize.Time(tl.beginTS), 30), timeSpentStr)

tl.desc = fmt.Sprintf("%s %s", RightPadTrim("["+tl.taskSummary+"]", 60), timeStr)
}
43 changes: 43 additions & 0 deletions internal/ui/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ui

import (
"database/sql"
"fmt"
"io"
"os"

"github.com/olekukonko/tablewriter"
)

func RenderTaskLogReport(db *sql.DB, writer io.Writer) {
taskLogEntries, err := fetchTLEntriesFromDB(db, 20)
if err != nil {
fmt.Fprintf(writer, "Something went wrong generating the report:\n%s", err)
os.Exit(1)
}

if len(taskLogEntries) == 0 {
return
}

data := make([][]string, len(taskLogEntries))
var secsSpent int
var timeSpentStr string

for i, entry := range taskLogEntries {
secsSpent = int(entry.endTS.Sub(entry.beginTS).Seconds())
timeSpentStr = humanizeDuration(secsSpent)
data[i] = []string{
fmt.Sprintf("%d", entry.id),
entry.taskSummary,
entry.beginTS.Format(timeFormat),
timeSpentStr,
}
}

table := tablewriter.NewWriter(writer)
table.SetHeader([]string{"ID", "Task", "Begin TS", "Time Spent"})

table.AppendBulk(data)
table.Render()
}
6 changes: 6 additions & 0 deletions internal/ui/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type taskLogEntry struct {
desc string
}

type activeTaskDetails struct {
taskId int
taskSummary string
lastLogEntryBeginTS time.Time
}

func (t task) Title() string {
return t.title
}
Expand Down
12 changes: 11 additions & 1 deletion internal/ui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if fs == list.Filtering || fs == list.FilterApplied {
m.taskLogList.ResetFilter()
} else {
return m, tea.Quit
m.activeView = activeTaskListView
}
case inactiveTaskListView:
fs := m.inactiveTasksList.FilterState()
if fs == list.Filtering || fs == list.FilterApplied {
m.inactiveTasksList.ResetFilter()
} else {
m.activeView = activeTaskListView
}
case helpView:
m.activeView = activeTaskListView
Expand All @@ -242,6 +249,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case taskLogView:
cmds = append(cmds, fetchTaskLogEntries(m.db))
m.taskLogList.ResetSelected()
case inactiveTaskListView:
cmds = append(cmds, fetchTasks(m.db, false))
m.inactiveTasksList.ResetSelected()
}
case "ctrl+t":
if m.activeView == activeTaskListView {
Expand Down

0 comments on commit 7784b94

Please sign in to comment.