1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
// Package form holds the shared huh entry-form builders used by both the
// day list view and the grid view. Pure builders only โ each view keeps its
// own edit state machine (coupled to the Bubble Tea value-copy pointer fix).
package form
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/huh"
"ticktock/internal/store"
)
// Values holds the raw string fields the huh form edits.
type Values struct {
Start, End, Project, Desc, Tags, Note string
}
// SplitTags splits a comma-separated tag string, trimming blanks.
func SplitTags(s string) []string {
var out []string
for _, p := range strings.Split(s, ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}
// BuildEntry turns form Values into an Entry on date (HH:MM times). The
// returned Entry has no Key; the caller decides Add vs SafeReplace.
func BuildEntry(date string, base store.Entry, v Values) (store.Entry, error) {
st, err := time.ParseInLocation("2006-01-02 15:04", date+" "+v.Start, time.Local)
if err != nil {
return store.Entry{}, fmt.Errorf("start time %q: %w", v.Start, err)
}
et, err := time.ParseInLocation("2006-01-02 15:04", date+" "+v.End, time.Local)
if err != nil {
return store.Entry{}, fmt.Errorf("end time %q: %w", v.End, err)
}
return store.Entry{
Start: st, End: et, Project: v.Project, Description: v.Desc,
Tags: SplitTags(v.Tags), Notes: v.Note,
}, nil
}
// Suggest carries autocomplete candidates for the form's inputs.
type Suggest struct {
Projects, Descriptions, Tags []string
}
// New prefills *v from e and returns the edit form bound to v, with history
// suggestions attached to project/description/tags.
func New(e store.Entry, v *Values, sug Suggest) *huh.Form {
v.Start = e.Start.Format("15:04")
if e.Running() {
v.End = ""
} else {
v.End = e.End.Format("15:04")
}
v.Project, v.Desc = e.Project, e.Description
v.Tags, v.Note = strings.Join(e.Tags, ", "), e.Notes
return huh.NewForm(huh.NewGroup(
huh.NewInput().Title("start (HH:MM)").Value(&v.Start),
huh.NewInput().Title("end (HH:MM)").Value(&v.End),
huh.NewInput().Title("project").Value(&v.Project).Suggestions(sug.Projects),
huh.NewInput().Title("description").Value(&v.Desc).Suggestions(sug.Descriptions),
huh.NewInput().Title("tags (comma-sep)").Value(&v.Tags).Suggestions(sug.Tags),
huh.NewInput().Title("note").Value(&v.Note),
))
}
// AddForm builds a form for a new entry whose start/end are already fixed by a
// grid selection. Start/end are prefilled and placed last so the cursor lands
// on project, yet the times remain editable if the selection needs tweaking.
func AddForm(start, end, project string, v *Values, sug Suggest) *huh.Form {
*v = Values{Start: start, End: end, Project: project}
return huh.NewForm(huh.NewGroup(
huh.NewInput().Title("project").Value(&v.Project).Suggestions(sug.Projects),
huh.NewInput().Title("description").Value(&v.Desc).Suggestions(sug.Descriptions),
huh.NewInput().Title("tags (comma-sep)").Value(&v.Tags).Suggestions(sug.Tags),
huh.NewInput().Title("note").Value(&v.Note),
huh.NewInput().Title("start (HH:MM)").Value(&v.Start),
huh.NewInput().Title("end (HH:MM)").Value(&v.End),
))
}
|