fix(step16.1): review fixes — acceptance, filters, sorting, export
- Remove dead acceptSuggestion, unify into refreshAfterSuggestion() - Journal: nodeID picker, includeChildren only with selected node - Journal: billable/approximate filters (all/yes/no selects) - Summary: ByDay sorted by date desc, ByNode by minutes desc - CSV: proper encoding/csv writer (was manual fmt.Sprintf) - Markdown: escape pipes and newlines via escMD() - After suggestion: refresh suggestions + count + worklog + journal - Add GetNodeTitle binding - i18n: common.all/no/date/search
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package worklog
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -231,9 +233,18 @@ func (s *Service) Summary(f ReportFilter) (*ReportSummary, error) {
|
||||
for day, min := range dayMap {
|
||||
sm.ByDay = append(sm.ByDay, SummaryGroup{Label: day, Minutes: min, Count: dayCount[day]})
|
||||
}
|
||||
sort.Slice(sm.ByDay, func(i, j int) bool {
|
||||
return sm.ByDay[i].Label > sm.ByDay[j].Label // descending date
|
||||
})
|
||||
for node, min := range nodeMap {
|
||||
sm.ByNode = append(sm.ByNode, SummaryGroup{Label: node, Minutes: min, Count: nodeCount[node]})
|
||||
}
|
||||
sort.Slice(sm.ByNode, func(i, j int) bool {
|
||||
if sm.ByNode[i].Minutes != sm.ByNode[j].Minutes {
|
||||
return sm.ByNode[i].Minutes > sm.ByNode[j].Minutes
|
||||
}
|
||||
return sm.ByNode[i].Label < sm.ByNode[j].Label
|
||||
})
|
||||
|
||||
return sm, nil
|
||||
}
|
||||
@@ -247,7 +258,8 @@ func (s *Service) ExportCSV(f ReportFilter) (string, error) {
|
||||
s.BuildReportPaths(rows)
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("Date,Node,Path,Summary,Minutes,Approximate,Billable,Created\n")
|
||||
w := csv.NewWriter(&b)
|
||||
w.Write([]string{"Date", "Node", "Path", "Summary", "Minutes", "Approximate", "Billable", "Created"})
|
||||
for _, r := range rows {
|
||||
approx := "0"
|
||||
if r.Approximate {
|
||||
@@ -257,11 +269,18 @@ func (s *Service) ExportCSV(f ReportFilter) (string, error) {
|
||||
if r.Billable {
|
||||
bill = "1"
|
||||
}
|
||||
summary := strings.ReplaceAll(r.Summary, "\"", "\"\"")
|
||||
b.WriteString(fmt.Sprintf("%s,\"%s\",\"%s\",\"%s\",%d,%s,%s,%s\n",
|
||||
r.Date, r.NodeTitle, r.NodePath, summary, r.Minutes, approx, bill, r.CreatedAt))
|
||||
w.Write([]string{r.Date, r.NodeTitle, r.NodePath, r.Summary,
|
||||
fmt.Sprintf("%d", r.Minutes), approx, bill, r.CreatedAt})
|
||||
}
|
||||
return b.String(), nil
|
||||
w.Flush()
|
||||
return b.String(), w.Error()
|
||||
}
|
||||
|
||||
func escMD(s string) string {
|
||||
s = strings.ReplaceAll(s, "|", "\\|")
|
||||
s = strings.ReplaceAll(s, "\n", " ")
|
||||
s = strings.ReplaceAll(s, "\r", "")
|
||||
return s
|
||||
}
|
||||
|
||||
// ExportMarkdown returns a Markdown report.
|
||||
@@ -295,7 +314,7 @@ func (s *Service) ExportMarkdown(f ReportFilter) (string, error) {
|
||||
approx = " ~"
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("| %s | %s | %s | %s | %d%s |\n",
|
||||
r.Date, r.NodeTitle, r.NodePath, r.Summary, r.Minutes, approx))
|
||||
escMD(r.Date), escMD(r.NodeTitle), escMD(r.NodePath), escMD(r.Summary), r.Minutes, approx))
|
||||
}
|
||||
|
||||
if sm != nil {
|
||||
@@ -307,7 +326,7 @@ func (s *Service) ExportMarkdown(f ReportFilter) (string, error) {
|
||||
b.WriteString("| Date | Minutes | Entries |\n")
|
||||
b.WriteString("|------|---------|--------|\n")
|
||||
for _, d := range sm.ByDay {
|
||||
b.WriteString(fmt.Sprintf("| %s | %dh %dm | %d |\n", d.Label, d.Minutes/60, d.Minutes%60, d.Count))
|
||||
b.WriteString(fmt.Sprintf("| %s | %dh %dm | %d |\n", escMD(d.Label), d.Minutes/60, d.Minutes%60, d.Count))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user