From d11a35c15736c26d03c9e5d6b4aac64127f2e265 Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Thu, 2 Apr 2026 17:07:36 +0200 Subject: [PATCH] refactor(timesheet): use repeat() for separator lines and sort points before grouping --- src/cli/commands/timesheet.rs | 30 ++++++++++++------------------ src/timesheet/extract.rs | 6 +++++- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/cli/commands/timesheet.rs b/src/cli/commands/timesheet.rs index f116a82..80346b4 100644 --- a/src/cli/commands/timesheet.rs +++ b/src/cli/commands/timesheet.rs @@ -5,6 +5,9 @@ use chrono::Datelike; use walkdir::WalkDir; use crate::config::Settings; + +const SEPARATOR_WIDTH: usize = 71; +const COLUMN_SEPARATOR_WIDTH: usize = 65; use crate::error::StreamdError; use crate::extract::parse_markdown_file; use crate::localize::localize_stream_file; @@ -66,13 +69,10 @@ fn weekday_abbrev(date: chrono::NaiveDate) -> &'static str { /// Print the timesheet report header. fn print_header() { - println!( - "\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}" - ); + let double_line = "\u{2550}".repeat(SEPARATOR_WIDTH); + println!("{}", double_line); println!(" TIMESHEET REPORT"); - println!( - "\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}" - ); + println!("{}", double_line); println!(); } @@ -91,10 +91,9 @@ fn print_month(month: &MonthReport) { println!(); // Column headers + let light_line = "\u{2500}".repeat(COLUMN_SEPARATOR_WIDTH); println!(" Date Day Expected Actual Diff Type"); - println!( - " \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}" - ); + println!(" {}", light_line); // Day rows for day in &month.days { @@ -132,9 +131,7 @@ fn print_month(month: &MonthReport) { } // Monthly totals - println!( - " \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}" - ); + println!(" {}", light_line); println!( " Monthly: {:>7} {:>7} {:>6}", format_hours(month.total_expected()), @@ -146,16 +143,13 @@ fn print_month(month: &MonthReport) { /// Print the cumulative balance. fn print_cumulative_balance(balance: f64) { - println!( - "\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}" - ); + let light_line = "\u{2500}".repeat(SEPARATOR_WIDTH); + println!("{}", light_line); println!( " CUMULATIVE BALANCE: {}", format_diff(balance) ); - println!( - "\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}" - ); + println!("{}", light_line); } /// Print warnings section. diff --git a/src/timesheet/extract.rs b/src/timesheet/extract.rs index e11131e..017434f 100644 --- a/src/timesheet/extract.rs +++ b/src/timesheet/extract.rs @@ -136,8 +136,12 @@ fn aggregate_timecard_day(points: &[TimesheetPoint]) -> Result fn aggregate_timecards(points: &[TimesheetPoint]) -> Result, StreamdError> { let mut timesheets = Vec::new(); + // Sort points by moment to ensure proper grouping + let mut sorted_points = points.to_vec(); + sorted_points.sort_by_key(|p| p.moment); + // Group by date - for (_date, group) in &points.iter().chunk_by(|p| p.moment.date_naive()) { + for (_date, group) in &sorted_points.iter().chunk_by(|p| p.moment.date_naive()) { let day_points: Vec<_> = group.cloned().collect(); if let Some(timesheet) = aggregate_timecard_day(&day_points)? { timesheets.push(timesheet);