package engine import ( "fmt" "strings" "time" "github.com/fatih/color" ) // FormatTaskList formats a list of tasks for display func FormatTaskList(tasks []*Task, ws *WorkingSet) string { if len(tasks) == 0 { return "No tasks found." } var sb strings.Builder // Header sb.WriteString(fmt.Sprintf("%-3s %-8s %-3s %-12s %-40s %-12s %s\n", "ID", "Status", "Pri", "Project", "Description", "Due", "Tags")) sb.WriteString(strings.Repeat("-", 100) + "\n") // Tasks 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 } } } status := formatStatus(task.Status) priority := formatPriority(task.Priority) project := formatProject(task.Project) description := truncate(task.Description, 40) due := formatDue(task.Due) tags := formatTags(task.Tags) sb.WriteString(fmt.Sprintf("%-3d %-8s %-3s %-12s %-40s %-12s %s\n", displayID, status, priority, project, description, due, tags)) } return sb.String() } // FormatTaskDetail formats detailed task information func FormatTaskDetail(task *Task) string { var sb strings.Builder sb.WriteString(color.New(color.Bold).Sprint("Task Details") + "\n") sb.WriteString(strings.Repeat("=", 50) + "\n") sb.WriteString(fmt.Sprintf("UUID: %s\n", task.UUID)) sb.WriteString(fmt.Sprintf("Status: %s\n", formatStatus(task.Status))) sb.WriteString(fmt.Sprintf("Description: %s\n", task.Description)) sb.WriteString(fmt.Sprintf("Priority: %s\n", formatPriority(task.Priority))) sb.WriteString(fmt.Sprintf("Project: %s\n", formatProject(task.Project))) sb.WriteString(fmt.Sprintf("\nCreated: %s\n", formatTime(task.Created))) sb.WriteString(fmt.Sprintf("Modified: %s\n", formatTime(task.Modified))) if task.Start != nil { sb.WriteString(fmt.Sprintf("Started: %s\n", formatTime(*task.Start))) } if task.End != nil { sb.WriteString(fmt.Sprintf("Ended: %s\n", formatTime(*task.End))) } if task.Due != nil { sb.WriteString(fmt.Sprintf("Due: %s\n", formatTimeWithColor(*task.Due))) } if task.Scheduled != nil { sb.WriteString(fmt.Sprintf("Scheduled: %s\n", formatTime(*task.Scheduled))) } if task.Wait != nil { sb.WriteString(fmt.Sprintf("Wait: %s\n", formatTime(*task.Wait))) } if task.Until != nil { sb.WriteString(fmt.Sprintf("Until: %s\n", formatTime(*task.Until))) } if task.RecurrenceDuration != nil { sb.WriteString(fmt.Sprintf("Recurrence: %s\n", FormatRecurrenceDuration(*task.RecurrenceDuration))) } if task.ParentUUID != nil { sb.WriteString(fmt.Sprintf("Parent: %s (recurring instance)\n", *task.ParentUUID)) } if len(task.Tags) > 0 { sb.WriteString(fmt.Sprintf("\nTags: %s\n", formatTags(task.Tags))) } return sb.String() } // FormatProjects formats project list with counts func FormatProjects(projectCounts map[string]int) string { if len(projectCounts) == 0 { return "No projects found." } var sb strings.Builder sb.WriteString(fmt.Sprintf("%-20s %s\n", "Project", "Count")) sb.WriteString(strings.Repeat("-", 30) + "\n") for project, count := range projectCounts { sb.WriteString(fmt.Sprintf("%-20s %d\n", project, count)) } return sb.String() } // FormatTags formats tag list with counts func FormatTagCounts(tagCounts map[string]int) string { if len(tagCounts) == 0 { return "No tags found." } var sb strings.Builder sb.WriteString(fmt.Sprintf("%-20s %s\n", "Tag", "Count")) sb.WriteString(strings.Repeat("-", 30) + "\n") for tag, count := range tagCounts { sb.WriteString(fmt.Sprintf("%-20s %d\n", tag, count)) } return sb.String() } // 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("template") 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) } return strings.Join(formatted, " ") } func truncate(s string, maxLen int) string { if len(s) <= maxLen { return s } return s[:maxLen-3] + "..." } // 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 }