Add time editing
This commit is contained in:
168
pages/timeEditor.go
Normal file
168
pages/timeEditor.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"tasksquire/common"
|
||||||
|
"tasksquire/timewarrior"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeEditorPage struct {
|
||||||
|
common *common.Common
|
||||||
|
interval *timewarrior.Interval
|
||||||
|
form *huh.Form
|
||||||
|
|
||||||
|
startStr string
|
||||||
|
endStr string
|
||||||
|
tagsStr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeEditorPage(com *common.Common, interval *timewarrior.Interval) *TimeEditorPage {
|
||||||
|
p := &TimeEditorPage{
|
||||||
|
common: com,
|
||||||
|
interval: interval,
|
||||||
|
startStr: interval.Start,
|
||||||
|
endStr: interval.End,
|
||||||
|
tagsStr: formatTags(interval.Tags),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.form = huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewInput().
|
||||||
|
Title("Start").
|
||||||
|
Value(&p.startStr).
|
||||||
|
Validate(func(s string) error {
|
||||||
|
return timewarrior.ValidateDate(s)
|
||||||
|
}),
|
||||||
|
huh.NewInput().
|
||||||
|
Title("End").
|
||||||
|
Value(&p.endStr).
|
||||||
|
Validate(func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return nil // End can be empty (active)
|
||||||
|
}
|
||||||
|
return timewarrior.ValidateDate(s)
|
||||||
|
}),
|
||||||
|
huh.NewInput().
|
||||||
|
Title("Tags").
|
||||||
|
Value(&p.tagsStr).
|
||||||
|
Description("Space separated, use \"\" for tags with spaces"),
|
||||||
|
),
|
||||||
|
).WithTheme(com.Styles.Form)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TimeEditorPage) Init() tea.Cmd {
|
||||||
|
return p.form.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TimeEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
if key.Matches(msg, p.common.Keymap.Back) {
|
||||||
|
model, err := p.common.PopPage()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("page stack empty")
|
||||||
|
return nil, tea.Quit
|
||||||
|
}
|
||||||
|
return model, BackCmd
|
||||||
|
}
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
p.SetSize(msg.Width, msg.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
form, cmd := p.form.Update(msg)
|
||||||
|
if f, ok := form.(*huh.Form); ok {
|
||||||
|
p.form = f
|
||||||
|
}
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
if p.form.State == huh.StateCompleted {
|
||||||
|
p.saveInterval()
|
||||||
|
|
||||||
|
model, err := p.common.PopPage()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("page stack empty")
|
||||||
|
return nil, tea.Quit
|
||||||
|
}
|
||||||
|
// Return with a command to refresh the intervals
|
||||||
|
return model, tea.Batch(tea.Batch(cmds...), refreshIntervals)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TimeEditorPage) View() string {
|
||||||
|
return p.form.View()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TimeEditorPage) SetSize(width, height int) {
|
||||||
|
p.common.SetSize(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TimeEditorPage) saveInterval() {
|
||||||
|
// If it's an existing interval (has ID), delete it first so we can replace it with the modified version
|
||||||
|
if p.interval.ID != 0 {
|
||||||
|
err := p.common.TimeW.DeleteInterval(p.interval.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to delete old interval during edit", "err", err)
|
||||||
|
// Proceeding to import anyway, attempting to save user data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.interval.Start = p.startStr
|
||||||
|
p.interval.End = p.endStr
|
||||||
|
|
||||||
|
// Parse tags
|
||||||
|
p.interval.Tags = parseTags(p.tagsStr)
|
||||||
|
|
||||||
|
err := p.common.TimeW.ModifyInterval(p.interval)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to modify interval", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTags(tagsStr string) []string {
|
||||||
|
var tags []string
|
||||||
|
var current strings.Builder
|
||||||
|
inQuotes := false
|
||||||
|
|
||||||
|
for _, r := range tagsStr {
|
||||||
|
switch {
|
||||||
|
case r == '"':
|
||||||
|
inQuotes = !inQuotes
|
||||||
|
case r == ' ' && !inQuotes:
|
||||||
|
if current.Len() > 0 {
|
||||||
|
tags = append(tags, current.String())
|
||||||
|
current.Reset()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
current.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current.Len() > 0 {
|
||||||
|
tags = append(tags, current.String())
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatTags(tags []string) string {
|
||||||
|
var formatted []string
|
||||||
|
for _, t := range tags {
|
||||||
|
if strings.Contains(t, " ") {
|
||||||
|
formatted = append(formatted, "\""+t+"\"")
|
||||||
|
} else {
|
||||||
|
formatted = append(formatted, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(formatted, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"tasksquire/common"
|
"tasksquire/common"
|
||||||
"tasksquire/components/timetable"
|
"tasksquire/components/timetable"
|
||||||
"tasksquire/timewarrior"
|
"tasksquire/timewarrior"
|
||||||
@ -39,6 +41,8 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case intervalsMsg:
|
case intervalsMsg:
|
||||||
p.data = timewarrior.Intervals(msg)
|
p.data = timewarrior.Intervals(msg)
|
||||||
p.populateTable(p.data)
|
p.populateTable(p.data)
|
||||||
|
case RefreshIntervalsMsg:
|
||||||
|
cmds = append(cmds, p.getIntervals())
|
||||||
case tickMsg:
|
case tickMsg:
|
||||||
cmds = append(cmds, p.getIntervals())
|
cmds = append(cmds, p.getIntervals())
|
||||||
cmds = append(cmds, doTick())
|
cmds = append(cmds, doTick())
|
||||||
@ -65,6 +69,20 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
p.common.TimeW.DeleteInterval(interval.ID)
|
p.common.TimeW.DeleteInterval(interval.ID)
|
||||||
return p, tea.Batch(p.getIntervals(), doTick())
|
return p, tea.Batch(p.getIntervals(), doTick())
|
||||||
}
|
}
|
||||||
|
case key.Matches(msg, p.common.Keymap.Edit):
|
||||||
|
row := p.intervals.SelectedRow()
|
||||||
|
if row != nil {
|
||||||
|
interval := (*timewarrior.Interval)(row)
|
||||||
|
editor := NewTimeEditorPage(p.common, interval)
|
||||||
|
p.common.PushPage(p)
|
||||||
|
return editor, editor.Init()
|
||||||
|
}
|
||||||
|
case key.Matches(msg, p.common.Keymap.Add):
|
||||||
|
interval := timewarrior.NewInterval()
|
||||||
|
interval.Start = time.Now().UTC().Format("20060102T150405Z")
|
||||||
|
editor := NewTimeEditorPage(p.common, interval)
|
||||||
|
p.common.PushPage(p)
|
||||||
|
return editor, editor.Init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +93,12 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return p, tea.Batch(cmds...)
|
return p, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RefreshIntervalsMsg struct{}
|
||||||
|
|
||||||
|
func refreshIntervals() tea.Msg {
|
||||||
|
return RefreshIntervalsMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *TimePage) View() string {
|
func (p *TimePage) View() string {
|
||||||
if len(p.data) == 0 {
|
if len(p.data) == 0 {
|
||||||
return p.common.Styles.Base.Render("No intervals found for today")
|
return p.common.Styles.Base.Render("No intervals found for today")
|
||||||
|
|||||||
@ -145,6 +145,8 @@ func formatDate(date string, format string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dt = dt.Local()
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case "formatted", "":
|
case "formatted", "":
|
||||||
return dt.Format("2006-01-02 15:04")
|
return dt.Format("2006-01-02 15:04")
|
||||||
|
|||||||
Reference in New Issue
Block a user