1package cli
2
3import (
4	"strconv"
5	"time"
6
7	"github.com/dominikbraun/timetrace/core"
8	"github.com/dominikbraun/timetrace/out"
9
10	"github.com/spf13/cobra"
11)
12
13func listCommand(t *core.Timetrace) *cobra.Command {
14	list := &cobra.Command{
15		Use:   "list",
16		Short: "List all resources",
17		Run: func(cmd *cobra.Command, args []string) {
18			_ = cmd.Help()
19		},
20	}
21
22	list.AddCommand(listProjectsCommand(t))
23	list.AddCommand(listRecordsCommand(t))
24
25	return list
26}
27
28func listProjectsCommand(t *core.Timetrace) *cobra.Command {
29	listProjects := &cobra.Command{
30		Use:   "projects",
31		Short: "List all projects",
32		Run: func(cmd *cobra.Command, args []string) {
33			allProjects, err := t.ListProjects()
34			if err != nil {
35				out.Err("Failed to list projects: %s", err.Error())
36				return
37			}
38
39			// remove all modules from the project list
40			parentProjects := removeModules(allProjects)
41
42			rows := make([][]string, len(parentProjects))
43
44			for i, project := range parentProjects {
45				allModules, err := t.ListProjectModules(project)
46				if err != nil {
47					out.Err("Failed to load project modules: %s", err.Error())
48					return
49				}
50				rows[i] = make([]string, 3)
51				rows[i][0] = strconv.Itoa(i + 1)
52				rows[i][1] = project.Key
53				rows[i][2] = allModules
54			}
55
56			out.Table([]string{"#", "Key", "Modules"}, rows, nil)
57		},
58	}
59
60	return listProjects
61}
62
63type listRecordsOptions struct {
64	isOnlyDisplayingBillable bool
65	projectKeyFilter         string
66}
67
68func listRecordsCommand(t *core.Timetrace) *cobra.Command {
69	var options listRecordsOptions
70
71	listRecords := &cobra.Command{
72		Use:   "records {<YYYY-MM-DD>|today|yesterday}",
73		Short: "List all records from a date",
74		Args:  cobra.ExactArgs(1),
75		Run: func(cmd *cobra.Command, args []string) {
76			date, err := t.Formatter().ParseDate(args[0])
77			if err != nil {
78				out.Err("failed to parse date: %s", err.Error())
79				return
80			}
81
82			records, err := t.ListRecords(date)
83			if err != nil {
84				out.Err("failed to list records: %s", err.Error())
85				return
86			}
87
88			if len(options.projectKeyFilter) > 0 {
89				records = filterProjectRecords(records, options.projectKeyFilter)
90			}
91
92			if options.isOnlyDisplayingBillable {
93				records = filterBillableRecords(records)
94			}
95
96			rows := make([][]string, len(records))
97
98			for i, record := range records {
99				end := defaultString
100				if record.End != nil {
101					end = t.Formatter().TimeString(*record.End)
102				}
103
104				billable := defaultBool
105
106				if record.IsBillable {
107					billable = "yes"
108				}
109
110				rows[i] = make([]string, 7)
111				rows[i][0] = strconv.Itoa(len(records) - i)
112				rows[i][1] = t.Formatter().RecordKey(record)
113				rows[i][2] = record.Project.Key
114				rows[i][3] = t.Formatter().TimeString(record.Start)
115				rows[i][4] = end
116				rows[i][5] = billable
117				rows[i][6] = t.Formatter().FormatTags(record.Tags)
118			}
119
120			footer := make([]string, 7)
121			footer[len(footer)-2] = "Total: "
122			footer[len(footer)-1] = t.Formatter().FormatDuration(getTotalTrackedTime(records))
123
124			out.Table([]string{"#", "Key", "Project", "Start", "End", "Billable", "Tags"}, rows, footer)
125		},
126	}
127
128	listRecords.Flags().BoolVarP(&options.isOnlyDisplayingBillable, "billable", "b",
129		false, `only display billable records`)
130
131	listRecords.Flags().StringVarP(&options.projectKeyFilter, "project", "p",
132		"", "filter by project key")
133
134	return listRecords
135}
136
137func filterBillableRecords(records []*core.Record) []*core.Record {
138	billableRecords := []*core.Record{}
139	for _, record := range records {
140		if record.IsBillable == true {
141			billableRecords = append(billableRecords, record)
142		}
143	}
144	return billableRecords
145}
146
147func filterProjectRecords(records []*core.Record, key string) []*core.Record {
148	projectRecords := []*core.Record{}
149	for _, record := range records {
150		if record.Project.Key == key || record.Project.Parent() == key {
151			projectRecords = append(projectRecords, record)
152		}
153	}
154	return projectRecords
155}
156
157func removeModules(allProjects []*core.Project) []*core.Project {
158	var parentProjects []*core.Project
159	for _, p := range allProjects {
160		if !p.IsModule() {
161			parentProjects = append(parentProjects, p)
162		}
163	}
164
165	return parentProjects
166}
167
168func getTotalTrackedTime(records []*core.Record) time.Duration {
169	var totalTime time.Duration
170	for _, record := range records {
171		totalTime += record.Duration()
172	}
173	return totalTime
174}
175