From 987d4023b0e79966b3b1aebf37039a90f9312518 Mon Sep 17 00:00:00 2001 From: Jonas Lima de Amorim Date: Thu, 23 Jul 2026 18:45:45 -0300 Subject: [PATCH] feat(summary): add --output json/csv --- README.md | 4 ++ Timetracker.Console/Options/SummaryOptions.cs | 3 ++ Timetracker.Console/Program.cs | 50 +++++++++++++++---- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ac59086..2d3bc43 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ Accepts the same period options as `list`. | `--to` | `-t` | no | End date (default: today) | | `--period` | `-p` | no | Specific month in `YYYY/MM` format | | `--work-item` | `-w` | no | Filter by Work Item ID | +| `--output` | `-o` | no | Output format: `json` or `csv`. Defaults to a table | | `--today` | | no | Today | | `--yesterday` | | no | Yesterday | | `--week` | | no | Current week (Mon–Sun) | @@ -222,6 +223,9 @@ Accepts the same period options as `list`. # Daily totals for the current week timetracker summary --week +# Export the monthly breakdown as CSV +timetracker summary --month --output csv > breakdown.csv + # Current month timetracker summary --month diff --git a/Timetracker.Console/Options/SummaryOptions.cs b/Timetracker.Console/Options/SummaryOptions.cs index 5f13dce..f7208fd 100644 --- a/Timetracker.Console/Options/SummaryOptions.cs +++ b/Timetracker.Console/Options/SummaryOptions.cs @@ -34,4 +34,7 @@ public class SummaryOptions : IPeriodOptions [Option('w', "work-item", Required = false, HelpText = "Filter entries by Work Item ID.")] public int? WorkItemId { get; set; } + + [Option('o', "output", Required = false, HelpText = "Output format: 'json' or 'csv'. Defaults to a table.")] + public string Output { get; set; } } diff --git a/Timetracker.Console/Program.cs b/Timetracker.Console/Program.cs index 4e7555b..6c83b9d 100644 --- a/Timetracker.Console/Program.cs +++ b/Timetracker.Console/Program.cs @@ -382,21 +382,53 @@ static async Task SummaryAction(SummaryOptions opts, CancellationToken canc var totalHours = Math.Round(workLogs.Sum(x => x.Length) / 3600m, 2); + var days = workLogs + .GroupBy(x => x.TimeStamp.Date) + .OrderBy(g => g.Key) + .Select(g => new + { + Date = g.Key, + Hours = Math.Round(g.Sum(x => x.Length) / 3600m, 2), + Count = g.Count() + }) + .ToList(); + + if (!string.IsNullOrEmpty(opts.Output)) + { + switch (opts.Output.ToLowerInvariant()) + { + case "json": + var payload = days.Select(d => new + { + date = d.Date.ToString("yyyy/MM/dd"), + weekday = d.Date.DayOfWeek.ToString(), + hours = d.Hours, + entries = d.Count + }); + Console.WriteLine(JsonConvert.SerializeObject(payload, Formatting.Indented)); + return 0; + case "csv": + Console.WriteLine("date,weekday,hours,entries"); + foreach (var d in days) + Console.WriteLine($"{d.Date:yyyy/MM/dd},{d.Date.DayOfWeek},{d.Hours},{d.Count}"); + return 0; + default: + ConsoleHelper.WriteError($"Unknown output format '{opts.Output}'. Use 'json' or 'csv'."); + return 1; + } + } + TableHelper.WriteMuted($"Summary from {from:yyyy/MM/dd} to {to:yyyy/MM/dd}:"); Console.WriteLine(); var table = TableHelper.NewTable("DATE", "WEEKDAY", "HOURS", "ENTRIES"); - foreach (var day in workLogs.GroupBy(x => x.TimeStamp.Date).OrderBy(g => g.Key)) - { - var dayHours = Math.Round(day.Sum(x => x.Length) / 3600m, 2); - var count = day.Count(); + foreach (var d in days) table.AddRow( - $"{day.Key:yyyy/MM/dd}", - Markup.Escape(day.Key.DayOfWeek.ToString()), - $"{dayHours}h", - count.ToString()); - } + $"{d.Date:yyyy/MM/dd}", + Markup.Escape(d.Date.DayOfWeek.ToString()), + $"{d.Hours}h", + d.Count.ToString()); AnsiConsole.Write(table);