2020-10-16 15:36:44 +00:00
|
|
|
package z
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
2020-10-17 00:33:38 +00:00
|
|
|
"strings"
|
2020-10-16 15:36:44 +00:00
|
|
|
"github.com/shopspring/decimal"
|
2020-10-16 20:41:19 +00:00
|
|
|
"github.com/jinzhu/now"
|
2020-10-17 00:33:38 +00:00
|
|
|
// "github.com/gookit/color"
|
2020-10-16 15:36:44 +00:00
|
|
|
)
|
|
|
|
|
2020-10-16 17:11:33 +00:00
|
|
|
type Statistic struct {
|
|
|
|
Hours decimal.Decimal
|
|
|
|
Project string
|
|
|
|
Color (func(...interface {}) string)
|
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
type WeekStatistics map[string][]Statistic
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
type Week struct {
|
|
|
|
Statistics WeekStatistics
|
2020-10-16 15:36:44 +00:00
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
type Month struct {
|
|
|
|
Name string
|
|
|
|
Weeks [5]Week
|
2020-10-16 15:36:44 +00:00
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
type Calendar struct {
|
|
|
|
Months [12]Month
|
2020-10-16 21:37:48 +00:00
|
|
|
Distribution map[string]Statistic
|
|
|
|
TotalHours decimal.Decimal
|
2020-10-16 20:41:19 +00:00
|
|
|
}
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
func NewCalendar(entries []Entry) (Calendar, error) {
|
|
|
|
cal := Calendar{}
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 21:37:48 +00:00
|
|
|
cal.Distribution = make(map[string]Statistic)
|
|
|
|
|
2020-10-17 00:33:38 +00:00
|
|
|
projects := make(map[string]Project)
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
for _, entry := range entries {
|
2021-02-07 18:22:16 +00:00
|
|
|
var entryFinish time.Time
|
2020-10-16 20:41:19 +00:00
|
|
|
endOfBeginDay := now.With(entry.Begin).EndOfDay()
|
|
|
|
sameDayHours := decimal.NewFromInt(0)
|
|
|
|
nextDayHours := decimal.NewFromInt(0)
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-17 12:14:58 +00:00
|
|
|
projectId := GetIdFromName(entry.Project)
|
2020-10-17 00:33:38 +00:00
|
|
|
|
|
|
|
if projects[projectId].Name == "" {
|
|
|
|
project, err := database.GetProject(entry.User, entry.Project)
|
|
|
|
if err != nil {
|
|
|
|
return cal, err
|
|
|
|
}
|
|
|
|
|
|
|
|
projects[projectId] = project
|
|
|
|
}
|
|
|
|
|
2021-02-07 18:22:16 +00:00
|
|
|
if entry.Finish.IsZero() {
|
|
|
|
entryFinish = time.Now()
|
|
|
|
} else {
|
|
|
|
entryFinish = entry.Finish
|
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
/*
|
|
|
|
* Apparently the activity end is on a new day.
|
|
|
|
* This means we have to split the activity across two days.
|
|
|
|
*/
|
2021-02-07 18:22:16 +00:00
|
|
|
if endOfBeginDay.Before(entryFinish) == true {
|
|
|
|
startOfFinishDay := now.With(entryFinish).BeginningOfDay()
|
2020-10-16 17:11:33 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
sameDayDuration := endOfBeginDay.Sub(entry.Begin)
|
|
|
|
sameDay := sameDayDuration.Hours()
|
|
|
|
sameDayHours = decimal.NewFromFloat(sameDay)
|
2020-10-16 17:11:33 +00:00
|
|
|
|
2021-02-07 18:22:16 +00:00
|
|
|
nextDayDuration := entryFinish.Sub(startOfFinishDay)
|
2020-10-16 20:41:19 +00:00
|
|
|
nextDay := nextDayDuration.Hours()
|
|
|
|
nextDayHours = decimal.NewFromFloat(nextDay)
|
2020-10-16 17:11:33 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
} else {
|
2021-02-07 18:22:16 +00:00
|
|
|
sameDayDuration := entryFinish.Sub(entry.Begin)
|
2020-10-16 20:41:19 +00:00
|
|
|
sameDay := sameDayDuration.Hours()
|
|
|
|
sameDayHours = decimal.NewFromFloat(sameDay)
|
2020-10-16 17:11:33 +00:00
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
if sameDayHours.GreaterThan(decimal.NewFromInt(0)) {
|
|
|
|
month, weeknumber := GetISOWeekInMonth(entry.Begin)
|
|
|
|
month0 := month - 1
|
|
|
|
weeknumber0 := weeknumber - 1
|
|
|
|
weekday := entry.Begin.Weekday()
|
|
|
|
weekdayName := weekday.String()[:2]
|
2020-10-16 18:38:00 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
stat := Statistic{
|
|
|
|
Hours: sameDayHours,
|
|
|
|
Project: entry.Project,
|
2020-10-17 00:33:38 +00:00
|
|
|
Color: GetColorFnFromHex(projects[projectId].Color),
|
2020-10-16 20:41:19 +00:00
|
|
|
}
|
2020-10-16 18:38:00 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
|
|
|
|
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
|
|
|
|
}
|
2020-10-16 17:11:33 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat)
|
2020-10-16 17:11:33 +00:00
|
|
|
}
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
if nextDayHours.GreaterThan(decimal.NewFromInt(0)) {
|
2021-02-07 18:22:16 +00:00
|
|
|
month, weeknumber := GetISOWeekInMonth(entryFinish)
|
2020-10-16 20:41:19 +00:00
|
|
|
month0 := month - 1
|
|
|
|
weeknumber0 := weeknumber - 1
|
|
|
|
weekday := entry.Begin.Weekday()
|
|
|
|
weekdayName := weekday.String()[:2]
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
stat := Statistic{
|
|
|
|
Hours: nextDayHours,
|
|
|
|
Project: entry.Project,
|
2020-10-17 00:33:38 +00:00
|
|
|
Color: GetColorFnFromHex(projects[projectId].Color),
|
2020-10-16 20:41:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if cal.Months[month0].Weeks[weeknumber0].Statistics == nil {
|
|
|
|
cal.Months[month0].Weeks[weeknumber0].Statistics = make(WeekStatistics)
|
|
|
|
}
|
|
|
|
|
|
|
|
cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName] = append(cal.Months[month0].Weeks[weeknumber0].Statistics[weekdayName], stat)
|
|
|
|
}
|
2020-10-16 21:37:48 +00:00
|
|
|
|
|
|
|
var dist = cal.Distribution[entry.Project]
|
|
|
|
dist.Project = entry.Project
|
|
|
|
dist.Hours = dist.Hours.Add(sameDayHours)
|
|
|
|
dist.Hours = dist.Hours.Add(nextDayHours)
|
2020-10-17 00:33:38 +00:00
|
|
|
dist.Color = GetColorFnFromHex(projects[projectId].Color)
|
2020-10-16 21:37:48 +00:00
|
|
|
cal.Distribution[entry.Project] = dist
|
|
|
|
|
|
|
|
// fmt.Printf("Same Day: %s \n Next Day: %s \n Project Hours: %s\n", sameDayHours.String(), nextDayHours.String(), dist.Hours.String())
|
|
|
|
cal.TotalHours = cal.TotalHours.Add(sameDayHours)
|
|
|
|
cal.TotalHours = cal.TotalHours.Add(nextDayHours)
|
2020-10-16 20:41:19 +00:00
|
|
|
}
|
2020-10-16 15:36:44 +00:00
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
return cal, nil
|
2020-10-16 15:36:44 +00:00
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
func (calendar *Calendar) GetOutputForWeekCalendar(date time.Time, month int, week int) (string) {
|
2020-10-16 15:36:44 +00:00
|
|
|
var output string = ""
|
|
|
|
var bars [][]string
|
|
|
|
var totalHours = decimal.NewFromInt(0)
|
|
|
|
|
|
|
|
var days = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
|
|
|
|
for _, day := range days {
|
2020-10-16 17:11:33 +00:00
|
|
|
var dayHours = decimal.NewFromInt(0)
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
for _, stat := range calendar.Months[month].Weeks[week].Statistics[day] {
|
2020-10-16 17:11:33 +00:00
|
|
|
dayHours = dayHours.Add(stat.Hours)
|
|
|
|
totalHours = totalHours.Add(stat.Hours)
|
|
|
|
}
|
|
|
|
|
2020-10-17 20:14:22 +00:00
|
|
|
if dayHours.GreaterThan(decimal.NewFromInt(24)) {
|
|
|
|
fmt.Printf("%s %s of week %d in month %d has more than 24h tracked; cutting at 24h now\n", CharError, day, (month+1), (week+1))
|
|
|
|
dayHours = decimal.NewFromInt(24)
|
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
bar := GetOutputBarForHours(dayHours, calendar.Months[month].Weeks[week].Statistics[day])
|
2020-10-16 15:36:44 +00:00
|
|
|
bars = append(bars, bar)
|
|
|
|
}
|
|
|
|
|
2020-10-16 20:41:19 +00:00
|
|
|
output = fmt.Sprintf("CW %02d %s H\n", GetISOCalendarWeek(date), totalHours.StringFixed(2))
|
2020-10-16 15:36:44 +00:00
|
|
|
for row := 0; row < len(bars[0]); row++ {
|
|
|
|
output = fmt.Sprintf("%s%2d │", output, ((6 - row) * 4))
|
|
|
|
for col := 0; col < len(bars); col++ {
|
|
|
|
output = fmt.Sprintf("%s%s", output, bars[col][row])
|
|
|
|
}
|
|
|
|
output = fmt.Sprintf("%s\n", output)
|
|
|
|
}
|
2020-10-16 16:02:41 +00:00
|
|
|
output = fmt.Sprintf("%s └────────────────────────────\n %s %s %s %s %s %s %s\n",
|
2020-10-16 15:36:44 +00:00
|
|
|
output, days[0], days[1], days[2], days[3], days[4], days[5], days[6])
|
|
|
|
|
2020-10-16 16:02:41 +00:00
|
|
|
return output
|
2020-10-16 15:36:44 +00:00
|
|
|
}
|
2020-10-16 21:37:48 +00:00
|
|
|
|
|
|
|
func (calendar *Calendar) GetOutputForDistribution() (string) {
|
|
|
|
var output string = ""
|
|
|
|
|
|
|
|
// fmt.Printf("%s\n", calendar.TotalHours.String())
|
|
|
|
|
2020-10-17 00:33:38 +00:00
|
|
|
var bar string = ""
|
2020-10-16 21:37:48 +00:00
|
|
|
for _, stat := range calendar.Distribution {
|
|
|
|
divided := stat.Hours.Div(calendar.TotalHours)
|
|
|
|
percentage := divided.Mul(decimal.NewFromInt(100))
|
|
|
|
hoursStr := stat.Hours.StringFixed(2)
|
|
|
|
percentageStr := percentage.StringFixed(2)
|
2020-10-17 00:33:38 +00:00
|
|
|
|
|
|
|
dividedByBarLength := percentage.Div(decimal.NewFromInt(100))
|
|
|
|
percentageForBar := dividedByBarLength.Mul(decimal.NewFromInt(80))
|
|
|
|
percentageForBarInt := int(percentageForBar.Round(0).IntPart())
|
|
|
|
|
|
|
|
bar = fmt.Sprintf("%s%s", bar, stat.Color(strings.Repeat("█", percentageForBarInt)))
|
|
|
|
|
|
|
|
output = fmt.Sprintf("%s%s%*s H / %*s %%\n", output, stat.Color(stat.Project), (68 - len(stat.Project)), hoursStr, 5, percentageStr)
|
2020-10-16 21:37:48 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 00:33:38 +00:00
|
|
|
output = fmt.Sprintf("DISTRIBUTION\n\n%s\n\n%s\n", bar, output)
|
2020-10-16 21:37:48 +00:00
|
|
|
return output
|
|
|
|
}
|