โ– humdrum codex / ticktock v0.0.2
license AGPL-3.0
3.1 KB raw
 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),
	))
}