diff --git a/pages/report.go b/pages/report.go index 37c038a..9e4f013 100644 --- a/pages/report.go +++ b/pages/report.go @@ -137,6 +137,7 @@ func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, p.common.Keymap.StartStop): if p.selectedTask != nil && p.selectedTask.Status == "pending" { if p.selectedTask.Start == "" { + p.common.TW.StopActiveTasks() p.common.TW.StartTask(p.selectedTask) } else { p.common.TW.StopTask(p.selectedTask) diff --git a/pages/timePage.go b/pages/timePage.go index 70451d2..4cabaeb 100644 --- a/pages/timePage.go +++ b/pages/timePage.go @@ -214,7 +214,6 @@ func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { "hasUUID", timewarrior.ExtractUUID(interval.Tags) != "", "uuid", timewarrior.ExtractUUID(interval.Tags)) p.common.TimeW.ContinueInterval(interval.ID) - common.SyncIntervalToTask(interval, p.common.TW, "start") p.shouldSelectActive = true // Set pending sync action instead of syncing immediately // This ensures we sync AFTER intervals are refreshed diff --git a/taskwarrior/models.go b/taskwarrior/models.go index 17f5d00..dc9dabe 100644 --- a/taskwarrior/models.go +++ b/taskwarrior/models.go @@ -47,8 +47,8 @@ type Task struct { Uuid string `json:"uuid,omitempty"` Description string `json:"description,omitempty"` Project string `json:"project"` - // Priority string `json:"priority"` - Status string `json:"status,omitempty"` + Priority string `json:"priority,omitempty"` + Status string `json:"status,omitempty"` Tags []string `json:"tags"` VirtualTags []string `json:"-"` Depends []string `json:"depends,omitempty"` @@ -149,8 +149,8 @@ func (t *Task) GetString(fieldWFormat string) string { } return t.Project - // case "priority": - // return t.Priority + case "priority": + return t.Priority case "status": return t.Status @@ -233,7 +233,33 @@ func (t *Task) GetString(fieldWFormat string) string { return "" } -func (t *Task) GetDate(dateString string) time.Time { +func (t *Task) GetDate(field string) time.Time { + var dateString string + switch field { + case "due": + dateString = t.Due + case "wait": + dateString = t.Wait + case "scheduled": + dateString = t.Scheduled + case "until": + dateString = t.Until + case "start": + dateString = t.Start + case "end": + dateString = t.End + case "entry": + dateString = t.Entry + case "modified": + dateString = t.Modified + default: + return time.Time{} + } + + if dateString == "" { + return time.Time{} + } + dt, err := time.Parse(dtformat, dateString) if err != nil { slog.Error("Failed to parse time:", err) diff --git a/taskwarrior/models_test.go b/taskwarrior/models_test.go new file mode 100644 index 0000000..8862ef2 --- /dev/null +++ b/taskwarrior/models_test.go @@ -0,0 +1,81 @@ +package taskwarrior + +import ( + "testing" + "time" +) + +func TestTask_GetString(t *testing.T) { + tests := []struct { + name string + task Task + fieldWFormat string + want string + }{ + { + name: "Priority", + task: Task{ + Priority: "H", + }, + fieldWFormat: "priority", + want: "H", + }, + { + name: "Description", + task: Task{ + Description: "Buy milk", + }, + fieldWFormat: "description.desc", + want: "Buy milk", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.task.GetString(tt.fieldWFormat); got != tt.want { + t.Errorf("Task.GetString() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTask_GetDate(t *testing.T) { + validDate := "20230101T120000Z" + parsedValid, _ := time.Parse("20060102T150405Z", validDate) + + tests := []struct { + name string + task Task + field string + want time.Time + }{ + { + name: "Due date valid", + task: Task{ + Due: validDate, + }, + field: "due", + want: parsedValid, + }, + { + name: "Due date empty", + task: Task{}, + field: "due", + want: time.Time{}, + }, + { + name: "Unknown field", + task: Task{Due: validDate}, + field: "unknown", + want: time.Time{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.task.GetDate(tt.field); !got.Equal(tt.want) { + t.Errorf("Task.GetDate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/taskwarrior/taskwarrior.go b/taskwarrior/taskwarrior.go index f908f35..1d10792 100644 --- a/taskwarrior/taskwarrior.go +++ b/taskwarrior/taskwarrior.go @@ -98,6 +98,7 @@ type TaskWarrior interface { DeleteTask(task *Task) StartTask(task *Task) StopTask(task *Task) + StopActiveTasks() GetInformation(task *Task) string AddTaskAnnotation(uuid string, annotation string) @@ -159,7 +160,12 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks { args = append(args, filter...) } - cmd := exec.Command(twBinary, append(args, []string{"export", report.Name}...)...) + exportArgs := []string{"export"} + if report.Name != "" { + exportArgs = append(exportArgs, report.Name) + } + + cmd := exec.Command(twBinary, append(args, exportArgs...)...) output, err := cmd.CombinedOutput() if err != nil { slog.Error("Failed getting report:", err) @@ -490,6 +496,33 @@ func (ts *TaskSquire) StopTask(task *Task) { } } +func (ts *TaskSquire) StopActiveTasks() { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"+ACTIVE", "export"}...)...) + output, err := cmd.CombinedOutput() + if err != nil { + slog.Error("Failed getting active tasks:", "error", err, "output", string(output)) + return + } + + tasks := make(Tasks, 0) + err = json.Unmarshal(output, &tasks) + if err != nil { + slog.Error("Failed unmarshalling active tasks:", err) + return + } + + for _, task := range tasks { + cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"stop", task.Uuid}...)...) + err := cmd.Run() + if err != nil { + slog.Error("Failed stopping task:", err) + } + } +} + func (ts *TaskSquire) GetInformation(task *Task) string { ts.mutex.Lock() defer ts.mutex.Unlock()