Nicer status command
This commit is contained in:
parent
88138dc783
commit
227e29e9c4
File diff suppressed because it is too large
Load Diff
|
@ -11,6 +11,7 @@ reqwest = {version="0.12.9", features = ["json"]}
|
|||
serde = {version="1.0.214", features = ["derive"]}
|
||||
serde_json = "1.0.132"
|
||||
tokio = {version="1.41.0", features = ["full"]}
|
||||
colored = "2.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "timer"
|
||||
|
|
136
src/commands.rs
136
src/commands.rs
|
@ -10,6 +10,7 @@ use std::{
|
|||
io::Read,
|
||||
process::Command,
|
||||
};
|
||||
use colored::*;
|
||||
|
||||
pub struct Settings {
|
||||
pub url: String,
|
||||
|
@ -205,14 +206,141 @@ pub struct Status {
|
|||
pub active: String,
|
||||
}
|
||||
|
||||
fn format_bar(seconds: i64, max_value: i64, width: usize) -> String {
|
||||
const BLOCKS: &[&str] = &["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
|
||||
let ratio = (seconds as f64) / (max_value as f64);
|
||||
let full_blocks = (ratio * (width as f64)) as usize;
|
||||
let remainder = ((ratio * (width as f64) - full_blocks as f64) * 8.0) as usize;
|
||||
|
||||
let mut bar = String::new();
|
||||
for _ in 0..full_blocks {
|
||||
bar.push_str(BLOCKS[7]);
|
||||
}
|
||||
if remainder > 0 && full_blocks < width {
|
||||
bar.push_str(BLOCKS[remainder - 1]);
|
||||
}
|
||||
while bar.len() < width {
|
||||
bar.push(' ');
|
||||
}
|
||||
|
||||
// Color based on hours worked
|
||||
let hours = seconds as f64 / 3600.0;
|
||||
let colored_bar = match hours {
|
||||
h if h >= 8.0 => bar.green(),
|
||||
h if h >= 4.0 => bar.yellow(),
|
||||
_ => bar.red(),
|
||||
};
|
||||
|
||||
format!("│{}", colored_bar)
|
||||
}
|
||||
|
||||
pub async fn status(settings: Settings) -> Result<(), reqwest::Error> {
|
||||
// Get additional metrics from status endpoint
|
||||
let client = Client::new();
|
||||
let response = client.get(settings.url.to_string() + "/")
|
||||
let status_response = client.get(settings.url.to_string() + "/")
|
||||
.send()
|
||||
.await?;
|
||||
let status: Status = serde_json::from_str(&status_response.text().await?).unwrap();
|
||||
|
||||
let response_text = response.text().await.unwrap();
|
||||
let status: Status = serde_json::from_str(&response_text).unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&status).unwrap());
|
||||
// Compute current week boundaries (Monday to Sunday)
|
||||
let now = chrono::Local::now();
|
||||
let today = now.date_naive();
|
||||
let weekday = today.weekday().num_days_from_monday() as i64;
|
||||
let week_start_date = today - chrono::Duration::days(weekday);
|
||||
let week_start = week_start_date.and_hms(0, 0, 0);
|
||||
let week_end = week_start_date.and_hms(23, 59, 59) + chrono::Duration::days(6);
|
||||
|
||||
// Build query URL for history API
|
||||
let query_params = [
|
||||
("since", week_start.format("%Y-%m-%dT%H:%M:%S").to_string()),
|
||||
("until", week_end.format("%Y-%m-%dT%H:%M:%S").to_string()),
|
||||
("count", "100".to_string()),
|
||||
("project", settings.project.clone()),
|
||||
];
|
||||
let url = reqwest::Url::parse_with_params(&(settings.url + "/api/history"), &query_params).unwrap();
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get(url).send().await?;
|
||||
let body = response.text().await?;
|
||||
let periods = parse_periods(body).unwrap_or_default();
|
||||
|
||||
// Check for active session first
|
||||
if let Some(active_period) = periods.iter().find(|p| p.end_time.is_none()) {
|
||||
let duration = now.naive_local().signed_duration_since(active_period.start_time);
|
||||
let duration_str = format_duration(duration.num_seconds());
|
||||
println!("\n{}", "Current Session".bold());
|
||||
println!("Active for: {} (started at {})",
|
||||
duration_str.yellow(),
|
||||
active_period.start_time.format("%H:%M").to_string().yellow()
|
||||
);
|
||||
}
|
||||
|
||||
// Group durations by day (assume each period is within a single day)
|
||||
use std::collections::HashMap;
|
||||
let mut daily: HashMap<chrono::NaiveDate, i64> = HashMap::new();
|
||||
for period in &periods {
|
||||
let start = period.start_time;
|
||||
let end = period.end_time.unwrap_or(now.naive_local());
|
||||
let duration = (end - start).num_seconds();
|
||||
let day = start.date();
|
||||
*daily.entry(day).or_insert(0) += duration;
|
||||
}
|
||||
|
||||
// Find maximum hours for scaling
|
||||
let max_seconds = daily.values().cloned().max().unwrap_or(8 * 3600);
|
||||
let max_seconds = std::cmp::max(max_seconds, 8 * 3600); // At least 8 hours for scale
|
||||
|
||||
const BOX_WIDTH: usize = 50;
|
||||
const BAR_WIDTH: usize = 20;
|
||||
|
||||
// Print active session if exists
|
||||
if status.active != "0" && status.active != "" {
|
||||
println!("\n{}", "Current Session".bold());
|
||||
println!("Active for: {}", status.active.yellow());
|
||||
}
|
||||
|
||||
// Print weekly overview with visual bars
|
||||
println!("\n{}", "Weekly Work Overview".bold());
|
||||
println!("╭{}╮", "─".repeat(BOX_WIDTH));
|
||||
|
||||
let mut total = 0;
|
||||
for i in 0..7 {
|
||||
let date = week_start_date + chrono::Duration::days(i);
|
||||
let seconds = daily.get(&date).cloned().unwrap_or(0);
|
||||
total += seconds;
|
||||
|
||||
let day = date.format("%a").to_string().bold();
|
||||
let duration = format_duration(seconds);
|
||||
// Ensure the day and duration part is exactly 15 characters wide
|
||||
let day_str = format!("{:<3} {:<10}", day, duration);
|
||||
|
||||
println!("{:<15} {:<width$}│",
|
||||
day_str,
|
||||
format_bar(seconds, max_seconds, BAR_WIDTH),
|
||||
width = BOX_WIDTH - 15
|
||||
);
|
||||
}
|
||||
|
||||
println!("╰{}╯", "─".repeat(BOX_WIDTH));
|
||||
println!("\nTotal Worked: {}", format_duration(total).bold());
|
||||
|
||||
let weekly_target = 39 * 3600;
|
||||
if total < weekly_target {
|
||||
println!("Remaining: {}", format_duration(weekly_target - total).red());
|
||||
} else {
|
||||
println!("Overtime: {}", format_duration(total - weekly_target).green());
|
||||
}
|
||||
|
||||
// Print monthly and yearly totals
|
||||
println!("\n{}", "Extended Statistics".bold());
|
||||
println!("This Month: {}", status.month.cyan());
|
||||
println!("This Year: {}", status.year.cyan());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_duration(seconds: i64) -> String {
|
||||
let hours = seconds / 3600;
|
||||
let minutes = (seconds % 3600) / 60;
|
||||
format!("{}h {}m", hours, minutes)
|
||||
}
|
Loading…
Reference in New Issue