From 1c68f67c3f58db89d221f5306413647d2adbe1f7 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Tue, 25 Mar 2025 20:53:07 +0100 Subject: [PATCH] feat: redo ui --- crates/clock/src/main.rs | 155 ++++++++++++++------------------------- timetable.json | 1 + 2 files changed, 56 insertions(+), 100 deletions(-) create mode 100644 timetable.json diff --git a/crates/clock/src/main.rs b/crates/clock/src/main.rs index 2e4f589..1c293a3 100644 --- a/crates/clock/src/main.rs +++ b/crates/clock/src/main.rs @@ -1,6 +1,4 @@ -use std::collections::BTreeMap; - -use chrono::NaiveDate; +use chrono::Timelike; use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; @@ -60,92 +58,54 @@ async fn main() -> anyhow::Result<()> { match cli.command.expect("to have a command available") { Commands::List { limit, project } => { - let days = timetable.group_by_day(); - let days = days.iter().rev().take(limit).collect::>(); - - for (day, pairs) in days.iter() { - let hours = pairs - .iter() - .fold( - (chrono::Duration::default(), None), - |(total, last_in), ev| match ev.r#type { - InOut::In => (total, Some(ev)), - InOut::Out => { - if let Some(in_time) = last_in { - if in_time.project == project { - (total + (ev.timestamp - in_time.timestamp), None) - } else { - (total, None) - } - } else { - (total, None) - } - } - InOut::Break => (total, last_in), - }, - ) - .0; - - let break_time = - pairs - .iter() - .fold(chrono::TimeDelta::zero(), |acc, e| match e.r#type { - InOut::Break => acc + chrono::Duration::minutes(30), - _ => acc, - }); + let days = &timetable + .days + .iter() + .filter(|d| { + if let Some(project) = &project { + Some(project) == d.project.as_ref() + } else { + true + } + }) + .collect::>(); + let days = days.iter().rev().take(limit).collect::>(); + for day in days { println!( - "{}: {}h{}m{} mins\n {}", - day, - hours.num_hours(), - hours.num_minutes() % 60, - if break_time.num_minutes() > 0 { - format!(", break: {}", break_time.num_minutes()) + "day: {}{}\n {}:{}{}", + day.clock_in.format("%Y/%m/%d"), + if let Some(project) = &day.project { + format!(" project: {}", project) } else { "".into() }, - pairs - .iter() - .map(|d| format!( - "{} - {}{}", - d.timestamp.with_timezone(&chrono::Local).format("%H:%M"), - match d.r#type { - InOut::In => "clocked in ", - InOut::Out => "clocked out", - InOut::Break => "break", - }, - if let Some(project) = &d.project { - format!(" - project: {}", project) - } else { - "".into() - } - )) - .collect::>() - .join("\n ") - ); + day.clock_in.hour(), + day.clock_in.minute(), + if let Some(clockout) = &day.clock_out { + format!(" - {}:{}", clockout.hour(), clockout.minute()) + } else { + " - unclosed".into() + } + ) } } - Commands::Break { project } => { - timetable.days.push(Day { - timestamp: now, - r#type: InOut::Break, - project, - }); - } Commands::In { project } => { timetable.days.push(Day { - timestamp: now, - r#type: InOut::In, - project, - }); - } - Commands::Out { project } => { - timetable.days.push(Day { - timestamp: now, - r#type: InOut::Out, + clock_in: now, + clock_out: None, + breaks: Vec::default(), project, }); } + Commands::Out { project } => match timetable.get_day(project, now) { + Some(day) => day.clock_out = Some(now), + None => todo!(), + }, + Commands::Break { project } => match timetable.get_day(project, now) { + Some(day) => day.breaks.push(Break {}), + None => todo!(), + }, } if let Some(parent) = dir.parent() { @@ -160,19 +120,16 @@ async fn main() -> anyhow::Result<()> { #[derive(Clone, Debug, Serialize, Deserialize)] struct Day { - timestamp: chrono::DateTime, - #[serde(rename = "type")] - r#type: InOut, + clock_in: chrono::DateTime, + clock_out: Option>, + + breaks: Vec, project: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] -enum InOut { - In, - Out, - Break, -} +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +struct Break {} #[derive(Default, Clone, Debug, Serialize, Deserialize)] struct TimeTable { @@ -180,21 +137,19 @@ struct TimeTable { } impl TimeTable { - /// Groups entries by calendar day in ascending order by timestamp - pub fn group_by_day(&self) -> BTreeMap> { - let mut grouped: BTreeMap> = BTreeMap::new(); + pub fn get_day<'a>( + &'a mut self, + project: Option, + now: chrono::DateTime, + ) -> Option<&'a mut Day> { + let item = self.days.iter_mut().find(|d| { + if d.project == project { + return false; + } - // First pass: group entries by date - for day in &self.days { - let date = day.timestamp.date_naive(); - grouped.entry(date).or_default().push(day); - } + d.clock_in.format("%Y-%m-%d").to_string() == now.format("%Y-%m-%d").to_string() + }); - // Second pass: sort each day's entries by timestamp - for entries in grouped.values_mut() { - entries.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); - } - - grouped + item } } diff --git a/timetable.json b/timetable.json new file mode 100644 index 0000000..f3ebb25 --- /dev/null +++ b/timetable.json @@ -0,0 +1 @@ +{"days":[{"timestamp":"2025-03-25T08:00:00.055002Z","type":"In","project":null},{"timestamp":"2025-03-25T16:17:27.767509Z","type":"Out","project":null},{"timestamp":"2025-03-25T19:14:40.167673Z","type":"In","project":"spare"},{"timestamp":"2025-03-25T19:15:12.757436Z","type":"Out","project":"spare"}]} \ No newline at end of file