b02c40f716
Add relative date formatting (today, tomorrow, in 3d, etc.) for list and detail views. Add structured feedback helpers for add/complete/delete operations showing display IDs and parsed modifiers. Change Complete() to return spawned recurring instance so callers can display recurrence info. Add AppendTask to working set for immediate display ID assignment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.2 KiB
Go
98 lines
2.2 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.jnss.me/joakim/opal/internal/engine"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var doneCmd = &cobra.Command{
|
|
Use: "done [filter...]",
|
|
Short: "Mark tasks as completed",
|
|
Long: `Mark one or more tasks as completed.
|
|
|
|
Examples:
|
|
opal done 1 # Complete task with display ID 1
|
|
opal 1 done # Flexible syntax (same as above)
|
|
opal done +urgent # Complete all urgent tasks
|
|
opal +urgent done # Flexible syntax (same as above)
|
|
opal done project:backend`,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
parsed := getParsedArgs(cmd)
|
|
if err := completeTasks(parsed.Filters); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
},
|
|
}
|
|
|
|
func completeTasks(args []string) error {
|
|
// Parse filter
|
|
filter, err := engine.ParseFilter(args)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse filter: %w", err)
|
|
}
|
|
|
|
// Load working set to resolve IDs
|
|
ws, err := engine.LoadWorkingSet()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load working set: %w", err)
|
|
}
|
|
|
|
// Get matching tasks
|
|
var tasks []*engine.Task
|
|
|
|
if len(filter.IDs) > 0 {
|
|
// Resolve display IDs
|
|
for _, id := range filter.IDs {
|
|
task, err := ws.GetTaskByDisplayID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasks = append(tasks, task)
|
|
}
|
|
} else {
|
|
// Use filter to get tasks
|
|
matched, err := engine.GetTasks(filter)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get tasks: %w", err)
|
|
}
|
|
tasks = matched
|
|
}
|
|
|
|
if len(tasks) == 0 {
|
|
return fmt.Errorf("no tasks matched filter")
|
|
}
|
|
|
|
if len(tasks) > 1 {
|
|
fmt.Print(engine.FormatTaskConfirmList("complete", tasks, ws))
|
|
fmt.Printf("Proceed? (y/N): ")
|
|
var confirm string
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "y" && confirm != "Y" {
|
|
fmt.Println("Cancelled.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Complete tasks
|
|
completed := 0
|
|
for _, task := range tasks {
|
|
if _, err := task.Complete(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Warning: failed to complete task %s: %v\n", task.UUID, err)
|
|
} else {
|
|
completed++
|
|
}
|
|
}
|
|
|
|
if len(tasks) == 1 {
|
|
fmt.Printf("Completed task %s\n", engine.FormatTaskSummary(tasks[0], ws))
|
|
} else {
|
|
fmt.Printf("Completed %d task(s).\n", completed)
|
|
}
|
|
|
|
return nil
|
|
}
|