├── README.md └── src └── main.go /README.md: -------------------------------------------------------------------------------- 1 | ![github banner](https://github.com/user-attachments/assets/2fb6a32e-b0e7-49bd-a3a9-554affbd3d0d) 2 | 3 | ### Enhanced Task Tracker CLI 🚀 4 | **A feature-rich command-line tool for managing your tasks efficiently!** 5 | With support for priorities, searching, sorting, and much more, this CLI tool helps you organize your tasks like a pro. 6 | 7 | --- 8 | 9 | ### Features ✨ 10 | 1. **Add Tasks** 📝 11 | Easily create new tasks with a title and priority (`High`, `Medium`, or `Low`). 12 | 13 | 2. **Update Task Status** 🔄 14 | Change task statuses between `todo`, `in-progress`, and `done` effortlessly. 15 | 16 | 3. **List Tasks** 📋 17 | View tasks filtered by status or see them all in one go, with clear details about priority. 18 | 19 | 4. **Delete Tasks** 🗑️ 20 | Safely remove tasks with confirmation prompts to avoid accidental deletions. 21 | 22 | 5. **Search Tasks** 🔍 23 | Quickly locate tasks by keywords in their titles. 24 | 25 | 6. **Sort Tasks** ⬆️⬇️ 26 | Organize tasks by: 27 | - **ID** 28 | - **Title** 29 | - **Status** 30 | - **Priority** 31 | 32 | 7. **Persistent Storage** 💾 33 | Save tasks to a file (`tasks.json`) and reload them on startup. 34 | 35 | 8. **User-Friendly Menu** 🎯 36 | Intuitive, easy-to-navigate menu for managing tasks. 37 | 38 | --- 39 | 40 | ### Installation 🛠️ 41 | 42 | 1. **Clone the repository**: 43 | ```bash 44 | git clone https://github.com/your-username/enhanced-task-tracker.git 45 | cd enhanced-task-tracker 46 | ``` 47 | 48 | 2. **Run the program**: 49 | ```bash 50 | go run enhanced_task_tracker.go 51 | ``` 52 | 53 | 3. **(Optional)** Compile for standalone use: 54 | ```bash 55 | go build -o task-tracker 56 | ./task-tracker 57 | ``` 58 | 59 | --- 60 | 61 | ### Usage 🧑‍💻 62 | 63 | Upon running the program, you'll see a menu: 64 | 65 | ```text 66 | Enhanced Task Tracker CLI 67 | 1. Add Task 68 | 2. Update Task Status 69 | 3. List Tasks 70 | 4. Delete Task 71 | 5. Search Tasks 72 | 6. Sort Tasks 73 | 7. Exit 74 | Choose an option: 75 | ``` 76 | 77 | Simply select an option and follow the prompts to manage your tasks. 78 | 79 | --- 80 | 81 | ### Examples 🌟 82 | 83 | #### Adding a Task 84 | ```text 85 | Enter task title: Learn Go 86 | Enter priority (High, Medium, Low): High 87 | Task 'Learn Go' added with ID 1. 88 | ``` 89 | 90 | #### Listing All Tasks 91 | ```text 92 | Enter status to filter by (todo, in-progress, done, all): all 93 | 94 | Tasks: 95 | ID: 1 | Title: Learn Go | Status: todo | Priority: High 96 | ``` 97 | 98 | #### Updating a Task's Status 99 | ```text 100 | Enter task ID to update: 1 101 | Enter new status (todo, in-progress, done): in-progress 102 | Task ID 1 updated to status 'in-progress'. 103 | ``` 104 | 105 | #### Searching Tasks 106 | ```text 107 | Enter keyword to search for tasks: learn 108 | 109 | Search Results: 110 | ID: 1 | Title: Learn Go | Status: in-progress | Priority: High 111 | ``` 112 | 113 | #### Sorting Tasks 114 | ```text 115 | Sort tasks by: 116 | 1. ID 117 | 2. Title 118 | 3. Status 119 | 4. Priority 120 | Choose an option: 4 121 | Tasks sorted successfully. 122 | ``` 123 | 124 | --- 125 | 126 | ### Contributing 🤝 127 | 128 | Want to improve the Enhanced Task Tracker? Contributions are welcome! 129 | 130 | 1. Fork the repository. 131 | 2. Create a feature branch. 132 | 3. Commit your changes. 133 | 4. Submit a pull request. 134 | 135 | --- 136 | 137 | ### License 📜 138 | This project is licensed under the MIT License. 139 | 140 | Enjoy productive task tracking! 🎉 141 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // Task represents a single task in the tracker. 14 | type Task struct { 15 | ID int `json:"id"` 16 | Title string `json:"title"` 17 | Status string `json:"status"` 18 | Priority string `json:"priority"` // New priority field 19 | } 20 | 21 | // TaskList manages a slice of Task and the next ID. 22 | type TaskList struct { 23 | Tasks []Task `json:"tasks"` 24 | NextID int `json:"next_id"` 25 | } 26 | 27 | var taskList TaskList 28 | var filePath = "tasks.json" 29 | 30 | func main() { 31 | if err := loadTasksFromFile(filePath); err != nil { 32 | fmt.Println("Warning: Could not load tasks:", err) 33 | } 34 | defer saveTasksToFile(filePath) 35 | 36 | scanner := bufio.NewScanner(os.Stdin) 37 | for { 38 | displayMenu() 39 | scanner.Scan() 40 | choice := strings.TrimSpace(scanner.Text()) 41 | switch choice { 42 | case "1": 43 | addTask(scanner) 44 | case "2": 45 | updateTaskStatus(scanner) 46 | case "3": 47 | listTasks(scanner) 48 | case "4": 49 | deleteTask(scanner) 50 | case "5": 51 | searchTasks(scanner) 52 | case "6": 53 | sortTasksMenu(scanner) 54 | case "7": 55 | fmt.Println("Exiting... Goodbye!") 56 | return 57 | default: 58 | fmt.Println("Invalid choice. Please select a valid option.") 59 | } 60 | } 61 | } 62 | 63 | func displayMenu() { 64 | fmt.Println("\nEnhanced Task Tracker CLI") 65 | fmt.Println("1. Add Task") 66 | fmt.Println("2. Update Task Status") 67 | fmt.Println("3. List Tasks") 68 | fmt.Println("4. Delete Task") 69 | fmt.Println("5. Search Tasks") 70 | fmt.Println("6. Sort Tasks") 71 | fmt.Println("7. Exit") 72 | fmt.Print("Choose an option: ") 73 | } 74 | 75 | func addTask(scanner *bufio.Scanner) { 76 | fmt.Print("Enter task title: ") 77 | scanner.Scan() 78 | title := strings.TrimSpace(scanner.Text()) 79 | if title == "" { 80 | fmt.Println("Error: Task title cannot be empty.") 81 | return 82 | } 83 | 84 | fmt.Print("Enter priority (High, Medium, Low): ") 85 | scanner.Scan() 86 | priority := strings.TrimSpace(scanner.Text()) 87 | if priority != "High" && priority != "Medium" && priority != "Low" { 88 | fmt.Println("Error: Invalid priority.") 89 | return 90 | } 91 | 92 | newTask := Task{ 93 | ID: taskList.NextID, 94 | Title: title, 95 | Status: "todo", 96 | Priority: priority, 97 | } 98 | taskList.Tasks = append(taskList.Tasks, newTask) 99 | taskList.NextID++ 100 | fmt.Printf("Task '%s' added with ID %d.\n", title, newTask.ID) 101 | } 102 | 103 | func updateTaskStatus(scanner *bufio.Scanner) { 104 | fmt.Print("Enter task ID to update: ") 105 | scanner.Scan() 106 | idStr := strings.TrimSpace(scanner.Text()) 107 | id, err := strconv.Atoi(idStr) 108 | if err != nil || id < 1 { 109 | fmt.Println("Error: Invalid task ID.") 110 | return 111 | } 112 | 113 | for i, task := range taskList.Tasks { 114 | if task.ID == id { 115 | fmt.Print("Enter new status (todo, in-progress, done): ") 116 | scanner.Scan() 117 | status := strings.TrimSpace(scanner.Text()) 118 | if !isValidStatus(status) { 119 | fmt.Println("Error: Invalid status. Valid statuses are: todo, in-progress, done.") 120 | return 121 | } 122 | taskList.Tasks[i].Status = status 123 | fmt.Printf("Task ID %d updated to status '%s'.\n", id, status) 124 | return 125 | } 126 | } 127 | fmt.Println("Error: Task not found.") 128 | } 129 | 130 | func listTasks(scanner *bufio.Scanner) { 131 | fmt.Print("Enter status to filter by (todo, in-progress, done, all): ") 132 | scanner.Scan() 133 | status := strings.TrimSpace(scanner.Text()) 134 | if status != "todo" && status != "in-progress" && status != "done" && status != "all" { 135 | fmt.Println("Error: Invalid status filter.") 136 | return 137 | } 138 | 139 | fmt.Println("\nTasks:") 140 | count := 0 141 | for _, task := range taskList.Tasks { 142 | if status == "all" || task.Status == status { 143 | fmt.Printf("ID: %d | Title: %s | Status: %s | Priority: %s\n", task.ID, task.Title, task.Status, task.Priority) 144 | count++ 145 | } 146 | } 147 | if count == 0 { 148 | fmt.Println("No tasks found.") 149 | } 150 | } 151 | 152 | func deleteTask(scanner *bufio.Scanner) { 153 | fmt.Print("Enter task ID to delete: ") 154 | scanner.Scan() 155 | idStr := strings.TrimSpace(scanner.Text()) 156 | id, err := strconv.Atoi(idStr) 157 | if err != nil || id < 1 { 158 | fmt.Println("Error: Invalid task ID.") 159 | return 160 | } 161 | 162 | for i, task := range taskList.Tasks { 163 | if task.ID == id { 164 | fmt.Printf("Are you sure you want to delete task '%s'? (yes/no): ", task.Title) 165 | scanner.Scan() 166 | confirmation := strings.TrimSpace(scanner.Text()) 167 | if confirmation == "yes" { 168 | taskList.Tasks = append(taskList.Tasks[:i], taskList.Tasks[i+1:]...) 169 | fmt.Printf("Task ID %d deleted.\n", id) 170 | return 171 | } else { 172 | fmt.Println("Deletion cancelled.") 173 | return 174 | } 175 | } 176 | } 177 | fmt.Println("Error: Task not found.") 178 | } 179 | 180 | func searchTasks(scanner *bufio.Scanner) { 181 | fmt.Print("Enter keyword to search for tasks: ") 182 | scanner.Scan() 183 | keyword := strings.TrimSpace(scanner.Text()) 184 | if keyword == "" { 185 | fmt.Println("Error: Keyword cannot be empty.") 186 | return 187 | } 188 | 189 | fmt.Println("\nSearch Results:") 190 | count := 0 191 | for _, task := range taskList.Tasks { 192 | if strings.Contains(strings.ToLower(task.Title), strings.ToLower(keyword)) { 193 | fmt.Printf("ID: %d | Title: %s | Status: %s | Priority: %s\n", task.ID, task.Title, task.Status, task.Priority) 194 | count++ 195 | } 196 | } 197 | if count == 0 { 198 | fmt.Println("No tasks found matching the keyword.") 199 | } 200 | } 201 | 202 | func sortTasks(by string, ascending bool) { 203 | switch by { 204 | case "id": 205 | sort.Slice(taskList.Tasks, func(i, j int) bool { 206 | if ascending { 207 | return taskList.Tasks[i].ID < taskList.Tasks[j].ID 208 | } 209 | return taskList.Tasks[i].ID > taskList.Tasks[j].ID 210 | }) 211 | case "title": 212 | sort.Slice(taskList.Tasks, func(i, j int) bool { 213 | if ascending { 214 | return taskList.Tasks[i].Title < taskList.Tasks[j].Title 215 | } 216 | return taskList.Tasks[i].Title > taskList.Tasks[j].Title 217 | }) 218 | case "status": 219 | sort.Slice(taskList.Tasks, func(i, j int) bool { 220 | if ascending { 221 | return taskList.Tasks[i].Status < taskList.Tasks[j].Status 222 | } 223 | return taskList.Tasks[i].Status > taskList.Tasks[j].Status 224 | }) 225 | case "priority": 226 | sort.Slice(taskList.Tasks, func(i, j int) bool { 227 | if ascending { 228 | return taskList.Tasks[i].Priority < taskList.Tasks[j].Priority 229 | } 230 | return taskList.Tasks[i].Priority > taskList.Tasks[j].Priority 231 | }) 232 | default: 233 | fmt.Println("Error: Invalid sort option.") 234 | return 235 | } 236 | fmt.Println("Tasks sorted successfully.") 237 | } 238 | 239 | func sortTasksMenu(scanner *bufio.Scanner) { 240 | fmt.Println("Sort tasks by:") 241 | fmt.Println("1. ID (Ascending)") 242 | fmt.Println("2. ID (Descending)") 243 | fmt.Println("3. Title (Ascending)") 244 | fmt.Println("4. Title (Descending)") 245 | fmt.Println("5. Status (Ascending)") 246 | fmt.Println("6. Status (Descending)") 247 | fmt.Println("7. Priority (Ascending)") 248 | fmt.Println("8. Priority (Descending)") 249 | fmt.Print("Choose an option: ") 250 | scanner.Scan() 251 | choice := strings.TrimSpace(scanner.Text()) 252 | 253 | var by string 254 | var ascending bool 255 | 256 | switch choice { 257 | case "1": 258 | by, ascending = "id", true 259 | case "2": 260 | by, ascending = "id", false 261 | case "3": 262 | by, ascending = "title", true 263 | case "4": 264 | by, ascending = "title", false 265 | case "5": 266 | by, ascending = "status", true 267 | case "6": 268 | by, ascending = "status", false 269 | case "7": 270 | by, ascending = "priority", true 271 | case "8": 272 | by, ascending = "priority", false 273 | default: 274 | fmt.Println("Invalid choice.") 275 | return 276 | } 277 | sortTasks(by, ascending) 278 | } 279 | 280 | func saveTasksToFile(filename string) { 281 | file, err := os.Create(filename) 282 | if err != nil { 283 | fmt.Println("Error saving tasks:", err) 284 | return 285 | } 286 | defer file.Close() 287 | 288 | encoder := json.NewEncoder(file) 289 | err = encoder.Encode(taskList) 290 | if err != nil { 291 | fmt.Println("Error encoding tasks:", err) 292 | } 293 | } 294 | 295 | func loadTasksFromFile(filename string) error { 296 | file, err := os.Open(filename) 297 | if os.IsNotExist(err) { 298 | taskList = TaskList{NextID: 1} 299 | return nil 300 | } else if err != nil { 301 | return err 302 | } 303 | defer file.Close() 304 | 305 | decoder := json.NewDecoder(file) 306 | err = decoder.Decode(&taskList) 307 | if err != nil { 308 | taskList = TaskList{NextID: 1} 309 | return err 310 | } 311 | return nil 312 | } 313 | 314 | func isValidStatus(status string) bool { 315 | return status == "todo" || status == "in-progress" || status == "done" 316 | } --------------------------------------------------------------------------------