Compare commits

..

2 commits

Author SHA1 Message Date
b322c0307d
chore: bump version to 0.2.1
Some checks failed
Continuous Integration / Build Package (push) Failing after 28s
Release / Build and Release (push) Failing after 29s
Continuous Integration / Lint, Check & Test (push) Failing after 49s
2026-04-07 08:42:42 +02:00
0533c7777a
feat: add timesheet debug 2026-04-07 08:42:14 +02:00
4 changed files with 80 additions and 11 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "streamd" name = "streamd"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
description = "Personal knowledge management and time-tracking CLI using @Tag annotations" description = "Personal knowledge management and time-tracking CLI using @Tag annotations"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"

View file

@ -54,6 +54,10 @@ pub enum Commands {
/// Display time as minutes (HH:MM) instead of decimal hours (H.Hh) /// Display time as minutes (HH:MM) instead of decimal hours (H.Hh)
#[arg(short, long)] #[arg(short, long)]
minutes: bool, minutes: bool,
/// Show all timecards grouped by day instead of the summary report
#[arg(short, long)]
debug: bool,
}, },
/// Generate shell completions /// Generate shell completions

View file

@ -1,7 +1,9 @@
use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use chrono::Datelike; use chrono::Datelike;
use chrono::NaiveDate;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::config::Settings; use crate::config::Settings;
@ -11,7 +13,7 @@ const COLUMN_SEPARATOR_WIDTH: usize = 65;
use crate::error::StreamdError; use crate::error::StreamdError;
use crate::extract::parse_markdown_file; use crate::extract::parse_markdown_file;
use crate::localize::localize_stream_file; use crate::localize::localize_stream_file;
use crate::models::LocalizedShard; use crate::models::{LocalizedShard, Timesheet};
use crate::timesheet::{ use crate::timesheet::{
extract_timesheets, generate_report, load_repository_config, BasicTimesheetConfiguration, extract_timesheets, generate_report, load_repository_config, BasicTimesheetConfiguration,
DayType, DayWarning, MonthReport, TimesheetReport, 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<NaiveDate, &Timesheet> =
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<String> = 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 settings = Settings::load()?;
let base_folder = Path::new(&settings.base_folder); let base_folder = Path::new(&settings.base_folder);
@ -273,16 +333,19 @@ pub fn run(use_minutes: bool) -> Result<(), StreamdError> {
return Ok(()); return Ok(());
} }
// Print the report if debug {
print_header(); print_debug(&report, &timesheets);
} else {
print_header();
for month in &report.months { for month in &report.months {
print_month(month, use_minutes); 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(()) Ok(())
} }

View file

@ -15,7 +15,9 @@ fn main() -> miette::Result<()> {
Some(TodoAction::Done { number }) => streamd::cli::commands::todo::run_done(number)?, Some(TodoAction::Done { number }) => streamd::cli::commands::todo::run_done(number)?,
}, },
Some(Commands::Edit { number }) => streamd::cli::commands::edit::run(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 }) => { Some(Commands::Completions { shell }) => {
streamd::cli::commands::completions::run(shell); streamd::cli::commands::completions::run(shell);
} }