From 0533c7777aa23cde36416ad3a72b85d2af9624c9 Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Tue, 7 Apr 2026 08:42:14 +0200 Subject: [PATCH] feat: add timesheet debug --- src/cli/args.rs | 4 ++ src/cli/commands/timesheet.rs | 81 +++++++++++++++++++++++++++++++---- src/main.rs | 4 +- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 83c762c..b0da540 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -54,6 +54,10 @@ pub enum Commands { /// Display time as minutes (HH:MM) instead of decimal hours (H.Hh) #[arg(short, long)] minutes: bool, + + /// Show all timecards grouped by day instead of the summary report + #[arg(short, long)] + debug: bool, }, /// Generate shell completions diff --git a/src/cli/commands/timesheet.rs b/src/cli/commands/timesheet.rs index 665e7d4..2cea3ae 100644 --- a/src/cli/commands/timesheet.rs +++ b/src/cli/commands/timesheet.rs @@ -1,7 +1,9 @@ +use std::collections::HashMap; use std::fs; use std::path::Path; use chrono::Datelike; +use chrono::NaiveDate; use walkdir::WalkDir; use crate::config::Settings; @@ -11,7 +13,7 @@ const COLUMN_SEPARATOR_WIDTH: usize = 65; use crate::error::StreamdError; use crate::extract::parse_markdown_file; use crate::localize::localize_stream_file; -use crate::models::LocalizedShard; +use crate::models::{LocalizedShard, Timesheet}; use crate::timesheet::{ extract_timesheets, generate_report, load_repository_config, BasicTimesheetConfiguration, DayType, DayWarning, MonthReport, TimesheetReport, @@ -237,7 +239,65 @@ fn print_warnings(report: &TimesheetReport, use_minutes: bool) { } } -pub fn run(use_minutes: bool) -> Result<(), StreamdError> { +/// Print debug view: all timecards grouped and sorted by day. +fn print_debug(report: &TimesheetReport, timesheets: &[Timesheet]) { + let timesheets_by_date: HashMap = + timesheets.iter().map(|ts| (ts.date, ts)).collect(); + + for month in &report.months { + let month_title = format!("{} {}", month.month_name(), month.year); + let separator = "\u{2550}".repeat(SEPARATOR_WIDTH); + println!("{}", separator); + println!(" {}", month_title); + println!("{}", separator); + println!(); + + for day in &month.days { + let date_str = day.date.format("%Y-%m-%d").to_string(); + let weekday = weekday_abbrev(day.date); + + let mut parts: Vec = Vec::new(); + + // Add day type label for non-regular days + let type_label = match day.day_type { + DayType::Regular => None, + DayType::SickLeave => Some("Sick Leave"), + DayType::Vacation => Some("Vacation"), + DayType::Holiday => Some("Holiday"), + DayType::FlexDay => Some("Flex Day"), + DayType::Weekend => Some("Weekend"), + DayType::Missing => Some("Missing"), + DayType::OutsidePeriod => Some("Outside Period"), + }; + if let Some(label) = type_label { + parts.push(label.to_string()); + } + + // Add timecards + if let Some(ts) = timesheets_by_date.get(&day.date) { + for tc in &ts.timecards { + parts.push(format!( + "{} - {}", + tc.from_time.format("%H:%M"), + tc.to_time.format("%H:%M") + )); + } + } + + let content = if parts.is_empty() { + String::new() + } else { + parts.join(", ") + }; + + println!(" {} ({}): {}", date_str, weekday, content); + } + + println!(); + } +} + +pub fn run(use_minutes: bool, debug: bool) -> Result<(), StreamdError> { let settings = Settings::load()?; let base_folder = Path::new(&settings.base_folder); @@ -273,16 +333,19 @@ pub fn run(use_minutes: bool) -> Result<(), StreamdError> { return Ok(()); } - // Print the report - print_header(); + if debug { + print_debug(&report, ×heets); + } else { + print_header(); - for month in &report.months { - print_month(month, use_minutes); + for month in &report.months { + print_month(month, use_minutes); + } + + print_cumulative_balance(report.cumulative_balance, use_minutes); + print_warnings(&report, use_minutes); } - print_cumulative_balance(report.cumulative_balance, use_minutes); - print_warnings(&report, use_minutes); - Ok(()) } diff --git a/src/main.rs b/src/main.rs index 2a7e1f6..354b8dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,9 @@ fn main() -> miette::Result<()> { Some(TodoAction::Done { number }) => streamd::cli::commands::todo::run_done(number)?, }, Some(Commands::Edit { number }) => streamd::cli::commands::edit::run(number)?, - Some(Commands::Timesheet { minutes }) => streamd::cli::commands::timesheet::run(minutes)?, + Some(Commands::Timesheet { minutes, debug }) => { + streamd::cli::commands::timesheet::run(minutes, debug)? + } Some(Commands::Completions { shell }) => { streamd::cli::commands::completions::run(shell); }