feat(timesheet): make hour-output default, decimal with two decimals
This commit is contained in:
parent
7f4d3bb147
commit
22ada87de6
3 changed files with 69 additions and 52 deletions
|
|
@ -51,9 +51,9 @@ pub enum Commands {
|
|||
|
||||
/// Display extracted timesheets
|
||||
Timesheet {
|
||||
/// Display time as minutes (HH:MM) instead of decimal hours (H.Hh)
|
||||
/// Display time as decimal hours (X.XXh) instead of the default HH:MM format
|
||||
#[arg(short, long)]
|
||||
minutes: bool,
|
||||
decimal: bool,
|
||||
|
||||
/// Show all timecards grouped by day instead of the summary report
|
||||
#[arg(short, long)]
|
||||
|
|
|
|||
|
|
@ -42,28 +42,39 @@ fn load_all_shards(base_folder: &Path) -> Result<Vec<LocalizedShard>, StreamdErr
|
|||
Ok(shards)
|
||||
}
|
||||
|
||||
enum DisplayMode {
|
||||
Minutes,
|
||||
Decimal,
|
||||
}
|
||||
|
||||
/// Format minutes with sign for display.
|
||||
fn format_diff(minutes: i64, use_minutes: bool) -> String {
|
||||
fn format_diff(minutes: i64, mode: &DisplayMode) -> String {
|
||||
let sign = if minutes >= 0 { "+" } else { "-" };
|
||||
if use_minutes {
|
||||
match mode {
|
||||
DisplayMode::Minutes => {
|
||||
let h = minutes.unsigned_abs() / 60;
|
||||
let m = minutes.unsigned_abs() % 60;
|
||||
format!("{}{}:{:02}", sign, h, m)
|
||||
} else {
|
||||
}
|
||||
DisplayMode::Decimal => {
|
||||
let hours = minutes.unsigned_abs() as f64 / 60.0;
|
||||
format!("{}{:.1}h", sign, hours)
|
||||
format!("{}{:.2}h", sign, hours)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format minutes for display without sign.
|
||||
fn format_hours(minutes: i64, use_minutes: bool) -> String {
|
||||
if use_minutes {
|
||||
fn format_hours(minutes: i64, mode: &DisplayMode) -> String {
|
||||
match mode {
|
||||
DisplayMode::Minutes => {
|
||||
let h = minutes.unsigned_abs() / 60;
|
||||
let m = minutes.unsigned_abs() % 60;
|
||||
format!("{}:{:02}", h, m)
|
||||
} else {
|
||||
}
|
||||
DisplayMode::Decimal => {
|
||||
let hours = minutes.unsigned_abs() as f64 / 60.0;
|
||||
format!("{:.1}h", hours)
|
||||
format!("{:.2}h", hours)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +101,8 @@ fn print_header() {
|
|||
}
|
||||
|
||||
/// Print a month report.
|
||||
fn print_month(month: &MonthReport, use_minutes: bool) {
|
||||
let diff_str = format_diff(month.diff(), use_minutes);
|
||||
fn print_month(month: &MonthReport, mode: &DisplayMode) {
|
||||
let diff_str = format_diff(month.diff(), mode);
|
||||
let month_title = format!("{} {}", month.month_name(), month.year);
|
||||
|
||||
// Month header with diff
|
||||
|
|
@ -112,9 +123,9 @@ fn print_month(month: &MonthReport, use_minutes: bool) {
|
|||
for day in &month.days {
|
||||
let date_str = day.date.format("%Y-%m-%d").to_string();
|
||||
let weekday = weekday_abbrev(day.date);
|
||||
let expected = format_hours(day.expected_minutes, use_minutes);
|
||||
let actual = format_hours(day.actual_minutes, use_minutes);
|
||||
let diff = format_diff(day.diff(), use_minutes);
|
||||
let expected = format_hours(day.expected_minutes, mode);
|
||||
let actual = format_hours(day.actual_minutes, mode);
|
||||
let diff = format_diff(day.diff(), mode);
|
||||
|
||||
let type_str = match day.day_type {
|
||||
DayType::Regular => String::new(),
|
||||
|
|
@ -147,26 +158,26 @@ fn print_month(month: &MonthReport, use_minutes: bool) {
|
|||
println!(" {}", light_line);
|
||||
println!(
|
||||
" Monthly: {:>7} {:>7} {:>6}",
|
||||
format_hours(month.total_expected(), use_minutes),
|
||||
format_hours(month.total_actual(), use_minutes),
|
||||
format_diff(month.diff(), use_minutes)
|
||||
format_hours(month.total_expected(), mode),
|
||||
format_hours(month.total_actual(), mode),
|
||||
format_diff(month.diff(), mode)
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
/// Print the cumulative balance.
|
||||
fn print_cumulative_balance(balance: i64, use_minutes: bool) {
|
||||
fn print_cumulative_balance(balance: i64, mode: &DisplayMode) {
|
||||
let light_line = "\u{2500}".repeat(SEPARATOR_WIDTH);
|
||||
println!("{}", light_line);
|
||||
println!(
|
||||
" CUMULATIVE BALANCE: {}",
|
||||
format_diff(balance, use_minutes)
|
||||
format_diff(balance, mode)
|
||||
);
|
||||
println!("{}", light_line);
|
||||
}
|
||||
|
||||
/// Print warnings section.
|
||||
fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
|
||||
fn print_warnings(report: &TimesheetReport, mode: &DisplayMode) {
|
||||
if !report.has_warnings() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -231,7 +242,7 @@ fn print_warnings(report: &TimesheetReport, use_minutes: bool) {
|
|||
println!(
|
||||
" - {}: {} worked (no period configured)",
|
||||
w.date.format("%Y-%m-%d"),
|
||||
format_hours(*minutes_worked, use_minutes)
|
||||
format_hours(*minutes_worked, mode)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -297,7 +308,12 @@ fn print_debug(report: &TimesheetReport, timesheets: &[Timesheet]) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run(use_minutes: bool, debug: bool) -> Result<(), StreamdError> {
|
||||
pub fn run(decimal: bool, debug: bool) -> Result<(), StreamdError> {
|
||||
let mode = if decimal {
|
||||
DisplayMode::Decimal
|
||||
} else {
|
||||
DisplayMode::Minutes
|
||||
};
|
||||
let settings = Settings::load()?;
|
||||
let base_folder = Path::new(&settings.base_folder);
|
||||
|
||||
|
|
@ -339,11 +355,11 @@ pub fn run(use_minutes: bool, debug: bool) -> Result<(), StreamdError> {
|
|||
print_header();
|
||||
|
||||
for month in &report.months {
|
||||
print_month(month, use_minutes);
|
||||
print_month(month, &mode);
|
||||
}
|
||||
|
||||
print_cumulative_balance(report.cumulative_balance, use_minutes);
|
||||
print_warnings(&report, use_minutes);
|
||||
print_cumulative_balance(report.cumulative_balance, &mode);
|
||||
print_warnings(&report, &mode);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -355,33 +371,34 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_format_hours_decimal() {
|
||||
assert_eq!(format_hours(480, false), "8.0h");
|
||||
assert_eq!(format_hours(510, false), "8.5h");
|
||||
assert_eq!(format_hours(0, false), "0.0h");
|
||||
assert_eq!(format_hours(480, &DisplayMode::Decimal), "8.00h");
|
||||
assert_eq!(format_hours(510, &DisplayMode::Decimal), "8.50h");
|
||||
assert_eq!(format_hours(507, &DisplayMode::Decimal), "8.45h");
|
||||
assert_eq!(format_hours(0, &DisplayMode::Decimal), "0.00h");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_hours_minutes() {
|
||||
assert_eq!(format_hours(480, true), "8:00");
|
||||
assert_eq!(format_hours(510, true), "8:30");
|
||||
assert_eq!(format_hours(0, true), "0:00");
|
||||
assert_eq!(format_hours(75, true), "1:15");
|
||||
assert_eq!(format_hours(77, true), "1:17");
|
||||
assert_eq!(format_hours(200, true), "3:20");
|
||||
assert_eq!(format_hours(480, &DisplayMode::Minutes), "8:00");
|
||||
assert_eq!(format_hours(510, &DisplayMode::Minutes), "8:30");
|
||||
assert_eq!(format_hours(0, &DisplayMode::Minutes), "0:00");
|
||||
assert_eq!(format_hours(75, &DisplayMode::Minutes), "1:15");
|
||||
assert_eq!(format_hours(77, &DisplayMode::Minutes), "1:17");
|
||||
assert_eq!(format_hours(200, &DisplayMode::Minutes), "3:20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_diff_decimal() {
|
||||
assert_eq!(format_diff(30, false), "+0.5h");
|
||||
assert_eq!(format_diff(-90, false), "-1.5h");
|
||||
assert_eq!(format_diff(0, false), "+0.0h");
|
||||
assert_eq!(format_diff(30, &DisplayMode::Decimal), "+0.50h");
|
||||
assert_eq!(format_diff(-90, &DisplayMode::Decimal), "-1.50h");
|
||||
assert_eq!(format_diff(0, &DisplayMode::Decimal), "+0.00h");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_diff_minutes() {
|
||||
assert_eq!(format_diff(30, true), "+0:30");
|
||||
assert_eq!(format_diff(-90, true), "-1:30");
|
||||
assert_eq!(format_diff(0, true), "+0:00");
|
||||
assert_eq!(format_diff(75, true), "+1:15");
|
||||
assert_eq!(format_diff(30, &DisplayMode::Minutes), "+0:30");
|
||||
assert_eq!(format_diff(-90, &DisplayMode::Minutes), "-1:30");
|
||||
assert_eq!(format_diff(0, &DisplayMode::Minutes), "+0:00");
|
||||
assert_eq!(format_diff(75, &DisplayMode::Minutes), "+1:15");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ 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, debug }) => {
|
||||
streamd::cli::commands::timesheet::run(minutes, debug)?
|
||||
Some(Commands::Timesheet { decimal, debug }) => {
|
||||
streamd::cli::commands::timesheet::run(decimal, debug)?
|
||||
}
|
||||
Some(Commands::Completions { shell }) => {
|
||||
streamd::cli::commands::completions::run(shell);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue