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:
2026-06-03 10:30:48 +08:00
parent c25e75f839
commit 5732264fc5
8 changed files with 143 additions and 58 deletions
+26 -7
View File
@@ -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")
}