package engine import ( "fmt" "time" "github.com/fatih/color" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" ) // FormatTaskList formats a list of tasks for display func FormatTaskList(tasks []*Task, ws *WorkingSet) string { if len(tasks) == 0 { return "No tasks found." } t := table.NewWriter() // Configure style t.SetStyle(table.StyleLight) t.Style().Options.SeparateRows = false t.Style().Options.DrawBorder = false // Configure columns with proper widths t.SetColumnConfigs([]table.ColumnConfig{ {Number: 1, WidthMin: 3, WidthMax: 3, Align: text.AlignRight}, // ID {Number: 2, WidthMin: 8, WidthMax: 8, Align: text.AlignLeft}, // Status {Number: 3, WidthMin: 3, WidthMax: 3, Align: text.AlignCenter}, // Pri {Number: 4, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft}, // Project {Number: 5, WidthMin: 40, WidthMax: 40, Align: text.AlignLeft, // Description WidthMaxEnforcer: text.Trim}, {Number: 6, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft}, // Due {Number: 7, Align: text.AlignLeft}, // Tags (variable) }) // Add header t.AppendHeader(table.Row{"ID", "Status", "Pri", "Project", "Description", "Due", "Tags"}) // Add rows for i, task := range tasks { displayID := i + 1 if ws != nil { // Use working set display ID if available for id, uuid := range ws.byID { if uuid == task.UUID { displayID = id break } } } t.AppendRow(table.Row{ displayID, formatStatus(task.Status), formatPriority(task.Priority), formatProject(task.Project), task.Description, formatDue(task.Due), formatTags(task.Tags), }) } return t.Render() } // FormatTaskDetail formats detailed task information func FormatTaskDetail(task *Task) string { t := table.NewWriter() // Configure style t.SetStyle(table.StyleLight) t.Style().Options.SeparateRows = false t.Style().Options.DrawBorder = false t.Style().Options.SeparateHeader = true // Configure columns - field name and value t.SetColumnConfigs([]table.ColumnConfig{ {Number: 1, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft}, {Number: 2, Align: text.AlignLeft}, }) // Add title as header t.SetTitle(color.New(color.Bold).Sprint("Task Details")) // Core fields t.AppendRow(table.Row{"UUID", task.UUID}) t.AppendRow(table.Row{"Status", formatStatus(task.Status)}) t.AppendRow(table.Row{"Description", task.Description}) t.AppendRow(table.Row{"Priority", formatPriority(task.Priority)}) t.AppendRow(table.Row{"Project", formatProject(task.Project)}) t.AppendSeparator() // Timestamps t.AppendRow(table.Row{"Created", formatTime(task.Created)}) t.AppendRow(table.Row{"Modified", formatTime(task.Modified)}) if task.Start != nil { t.AppendRow(table.Row{"Started", formatTime(*task.Start)}) } if task.End != nil { t.AppendRow(table.Row{"Ended", formatTime(*task.End)}) } if task.Due != nil { t.AppendRow(table.Row{"Due", formatTimeWithColor(*task.Due)}) } if task.Scheduled != nil { t.AppendRow(table.Row{"Scheduled", formatTime(*task.Scheduled)}) } if task.Wait != nil { t.AppendRow(table.Row{"Wait", formatTime(*task.Wait)}) } if task.Until != nil { t.AppendRow(table.Row{"Until", formatTime(*task.Until)}) } if task.RecurrenceDuration != nil { t.AppendRow(table.Row{"Recurrence", FormatRecurrenceDuration(*task.RecurrenceDuration)}) } if task.ParentUUID != nil { t.AppendRow(table.Row{"Parent", fmt.Sprintf("%s (recurring instance)", *task.ParentUUID)}) } if len(task.Tags) > 0 { t.AppendSeparator() t.AppendRow(table.Row{"Tags", formatTags(task.Tags)}) } return t.Render() } // FormatProjects formats project list with counts func FormatProjects(projectCounts map[string]int) string { if len(projectCounts) == 0 { return "No projects found." } t := table.NewWriter() // Configure style t.SetStyle(table.StyleLight) t.Style().Options.SeparateRows = false t.Style().Options.DrawBorder = false // Configure columns t.SetColumnConfigs([]table.ColumnConfig{ {Number: 1, WidthMin: 20, WidthMax: 20, Align: text.AlignLeft}, {Number: 2, Align: text.AlignRight}, }) // Add header t.AppendHeader(table.Row{"Project", "Count"}) // Add rows for project, count := range projectCounts { t.AppendRow(table.Row{project, count}) } return t.Render() } // FormatTagCounts formats tag list with counts func FormatTagCounts(tagCounts map[string]int) string { if len(tagCounts) == 0 { return "No tags found." } t := table.NewWriter() // Configure style t.SetStyle(table.StyleLight) t.Style().Options.SeparateRows = false t.Style().Options.DrawBorder = false // Configure columns t.SetColumnConfigs([]table.ColumnConfig{ {Number: 1, WidthMin: 20, WidthMax: 20, Align: text.AlignLeft}, {Number: 2, Align: text.AlignRight}, }) // Add header t.AppendHeader(table.Row{"Tag", "Count"}) // Add rows for tag, count := range tagCounts { t.AppendRow(table.Row{tag, count}) } return t.Render() } // Helper functions func formatStatus(status Status) string { switch status { case StatusPending: return color.YellowString("pending") case StatusCompleted: return color.GreenString("done") case StatusDeleted: return color.RedString("deleted") case StatusRecurring: return color.BlueString("recurring") default: return "unknown" } } func formatPriority(priority Priority) string { switch priority { case PriorityHigh: return color.RedString("H") case PriorityMedium: return color.YellowString("M") case PriorityLow: return color.CyanString("L") case PriorityDefault: return "D" default: return "D" } } func formatProject(project *string) string { if project == nil { return "-" } return *project } func formatDue(due *time.Time) string { if due == nil { return "" } now := time.Now() if due.Before(now) { return color.RedString(due.Format("2006-01-02")) } if due.Before(now.Add(24 * time.Hour)) { return color.YellowString(due.Format("2006-01-02")) } return due.Format("2006-01-02") } func formatTimeWithColor(t time.Time) string { now := time.Now() if t.Before(now) { return color.RedString(t.Format("2006-01-02 15:04")) } return t.Format("2006-01-02 15:04") } func formatTime(t time.Time) string { return t.Format("2006-01-02 15:04") } func formatTags(tags []string) string { if len(tags) == 0 { return "" } formatted := make([]string, len(tags)) for i, tag := range tags { formatted[i] = color.CyanString("+" + tag) } // Join tags with spaces result := formatted[0] for i := 1; i < len(formatted); i++ { result += " " + formatted[i] } return result } // GetProjectCounts returns a map of project names to task counts func GetProjectCounts() (map[string]int, error) { tasks, err := GetTasks(DefaultFilter()) if err != nil { return nil, err } counts := make(map[string]int) for _, task := range tasks { if task.Project != nil { counts[*task.Project]++ } } return counts, nil } // GetTagCounts returns a map of tags to task counts func GetTagCounts() (map[string]int, error) { tasks, err := GetTasks(DefaultFilter()) if err != nil { return nil, err } counts := make(map[string]int) for _, task := range tasks { for _, tag := range task.Tags { counts[tag]++ } } return counts, nil }