this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

enhance: improve task list display with colors, grouping, and details

bnjox 81686a17 7e45aaf9

+163 -14
+5 -4
src/core/models.zig
··· 1 1 pub const Task = struct { 2 + pub const Status = enum { pending, in_progress, completed }; 3 + pub const Priority = enum { low, medium, high }; 4 + 2 5 id: []const u8, 3 6 title: []const u8, 4 7 description: ?[]const u8 = null, 5 - /// pending, in_progress, completed 6 - status: enum { pending, in_progress, completed } = .pending, 7 - /// low, medium, high 8 - priority: ?enum { low, medium, high } = null, 8 + status: Status = .pending, 9 + priority: ?Priority = .low, 9 10 due_date: ?i64 = null, 10 11 assigned_to: ?[]const u8 = null, 11 12 created_at: i64,
+158 -10
src/core/task.zig
··· 3 3 const storage = @import("../storage/json.zig"); 4 4 const generate = @import("../utils/generate.zig"); 5 5 6 + const Color = enum { 7 + red, 8 + green, 9 + yellow, 10 + cyan, 11 + reset, 12 + }; 13 + 14 + fn color(c: Color) []const u8 { 15 + return switch (c) { 16 + .red => "\x1b[31m", 17 + .green => "\x1b[32m", 18 + .yellow => "\x1b[33m", 19 + .cyan => "\x1b[36m", 20 + .reset => "\x1b[0m", 21 + }; 22 + } 23 + 24 + fn priority_label(priority: ?models.Task.Priority) []const u8 { 25 + if (priority) |p| { 26 + return switch (p) { 27 + .high => "↑", 28 + .medium => "-", 29 + .low => "↓", 30 + }; 31 + } 32 + return ""; 33 + } 34 + 35 + fn priority_color(priority: ?models.Task.Priority) Color { 36 + if (priority) |p| { 37 + return switch (p) { 38 + .high => .red, 39 + .medium => .yellow, 40 + .low => .green, 41 + }; 42 + } 43 + return .reset; 44 + } 45 + 46 + fn status_icon(status: models.Task.Status) []const u8 { 47 + return switch (status) { 48 + .pending => "○", 49 + .in_progress => "⟳", 50 + .completed => "✓", 51 + }; 52 + } 53 + 54 + fn status_color(status: models.Task.Status) Color { 55 + return switch (status) { 56 + .pending => .reset, 57 + .in_progress => .cyan, 58 + .completed => .green, 59 + }; 60 + } 61 + 6 62 pub const TaskArgs = struct { 7 63 list: bool = false, 8 64 subcommand: ?union(enum) { ··· 114 170 else => return, 115 171 } 116 172 }; 117 - std.debug.print("({d}) Tasks:\n", .{tasks.len}); 173 + 174 + if (tasks.len == 0) { 175 + std.debug.print("No tasks\n", .{}); 176 + return; 177 + } 178 + 179 + var pending: std.ArrayList(models.Task) = .empty; 180 + var in_progress: std.ArrayList(models.Task) = .empty; 181 + var completed: std.ArrayList(models.Task) = .empty; 182 + defer { 183 + pending.deinit(allocator); 184 + in_progress.deinit(allocator); 185 + completed.deinit(allocator); 186 + } 118 187 119 188 for (tasks) |task| { 120 - if (task.status == .completed) { 121 - std.debug.print(" [x]: ({s}) - {s}\n", .{ task.id, task.title }); 189 + switch (task.status) { 190 + .pending => try pending.append(allocator, task), 191 + .in_progress => try in_progress.append(allocator, task), 192 + .completed => try completed.append(allocator, task), 193 + } 194 + } 195 + 196 + if (pending.items.len > 0) { 197 + std.debug.print("{s}Pending{s} ({d})\n", .{ color(.cyan), color(.reset), pending.items.len }); 198 + for (pending.items) |task| { 199 + try print_task_details(task); 200 + } 201 + std.debug.print("\n", .{}); 202 + } 203 + 204 + if (in_progress.items.len > 0) { 205 + std.debug.print("{s}In Progress{s} ({d})\n", .{ color(.cyan), color(.reset), in_progress.items.len }); 206 + for (in_progress.items) |task| { 207 + try print_task_details(task); 208 + } 209 + std.debug.print("\n", .{}); 210 + } 211 + 212 + if (completed.items.len > 0) { 213 + std.debug.print("{s}Completed{s} ({d})\n", .{ color(.green), color(.reset), completed.items.len }); 214 + for (completed.items) |task| { 215 + try print_task_details(task); 216 + } 217 + } 218 + } 219 + 220 + fn print_task_details(task: models.Task) !void { 221 + const c_status = status_color(task.status); 222 + const c_reset = color(.reset); 223 + 224 + const compact_id = if (task.id.len > 8) task.id[0..8] else task.id; 225 + 226 + std.debug.print(" {s}{s}{s} ", .{ color(c_status), status_icon(task.status), c_reset }); 227 + if (task.priority) |p| { 228 + std.debug.print("{s} ", .{priority_label(p)}); 229 + } 230 + std.debug.print("{s}\n", .{task.title}); 231 + 232 + if (task.description) |desc| { 233 + std.debug.print(" {s}📝{s} {s}\n", .{ color(.yellow), c_reset, desc }); 234 + } 235 + 236 + if (task.due_date) |due| { 237 + const now = std.time.timestamp(); 238 + if (due < now) { 239 + std.debug.print(" {s}📅 Due: {d} (overdue){s}\n", .{ color(.red), due, c_reset }); 122 240 } else { 123 - std.debug.print(" [ ]: ({s}) - {s}\n", .{ task.id, task.title }); 241 + std.debug.print(" {s}📅 Due: {d}{s}\n", .{ color(.yellow), due, c_reset }); 124 242 } 125 243 } 244 + 245 + if (task.status == .completed) { 246 + if (task.completed_at) |completed| { 247 + std.debug.print(" {s}✓ Completed: {d}{s}\n", .{ color(.green), completed, c_reset }); 248 + } 249 + } 250 + 251 + std.debug.print(" {s}ID: {s}{s}\n", .{ color(.yellow), compact_id, c_reset }); 126 252 } 127 253 128 254 /// Marks the task matching `task_id` as completed. Returns `error.InvalidItem` ··· 150 276 } 151 277 152 278 /// Removes the task matching `task_id` from storage. Returns `error.InvalidItem` 153 - /// if no task with that id exists. 279 + /// if no task with that id exists. Supports partial ID matching (min 4 chars). 154 280 fn delete_task(allocator: std.mem.Allocator, task_id: []const u8, dir: std.fs.Dir) !void { 155 281 var arena = std.heap.ArenaAllocator.init(allocator); 156 282 defer arena.deinit(); ··· 162 288 }; 163 289 164 290 var remaining: std.ArrayList(models.Task) = .empty; 165 - var found = false; 291 + defer remaining.deinit(arena_alloc); 166 292 167 - for (tasks) |task| { 168 - if (std.mem.eql(u8, task.id, task_id)) { 169 - found = true; 293 + var found_indices: std.ArrayList(usize) = .empty; 294 + defer found_indices.deinit(arena_alloc); 295 + 296 + for (tasks, 0..) |task, i| { 297 + const match = if (task_id.len >= 4 and task.id.len >= task_id.len) 298 + std.mem.eql(u8, task.id[0..task_id.len], task_id) 299 + else 300 + std.mem.eql(u8, task.id, task_id); 301 + 302 + if (match) { 303 + try found_indices.append(arena_alloc, i); 170 304 } else { 171 305 try remaining.append(arena_alloc, task); 172 306 } 173 307 } 174 308 175 - if (!found) return error.InvalidItem; 309 + if (found_indices.items.len == 0) { 310 + std.debug.print("No task found matching '{s}'\n", .{task_id}); 311 + return error.InvalidItem; 312 + } 313 + 314 + if (found_indices.items.len > 1) { 315 + std.debug.print("Multiple tasks match '{s}':\n", .{task_id}); 316 + for (found_indices.items) |idx| { 317 + const task = tasks[idx]; 318 + std.debug.print(" - {s} [{s}]\n", .{ task.id[0..@min(8, task.id.len)], task.title }); 319 + } 320 + std.debug.print("Use a longer ID to disambiguate.\n", .{}); 321 + return error.AmbiguousMatch; 322 + } 176 323 177 324 try storage.save_tasks(arena_alloc, dir, remaining.items); 325 + std.debug.print("Task deleted: {s}\n", .{tasks[found_indices.items[0]].title}); 178 326 } 179 327 180 328 test "add and list tasks" {