Compare commits
3 Commits
b570d1d8e2
...
a4cda71999
Author | SHA1 | Date | |
---|---|---|---|
|
a4cda71999 | ||
fb7d4a028e | |||
52f85f4d02 |
26
CHANGELOG.md
Normal file
26
CHANGELOG.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.0.2] - 2025-03-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- add current hours
|
||||||
|
|
||||||
|
## [0.0.1] - 2025-03-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- make it somewhat pretty
|
||||||
|
- redo ui
|
||||||
|
- rename project
|
||||||
|
- add basic clock in tool
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- test ci
|
||||||
|
- remove timetable
|
||||||
|
- elide lifetimes
|
||||||
|
- remove unused deps
|
@ -3,7 +3,7 @@ members = ["crates/*"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
clock = { path = "crates/clock" }
|
clock = { path = "crates/clock" }
|
||||||
|
@ -61,28 +61,31 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
match cli.command.expect("to have a command available") {
|
match cli.command.expect("to have a command available") {
|
||||||
Commands::List { limit, project } => {
|
Commands::List { limit, project } => {
|
||||||
|
let mut timetable = timetable.clone();
|
||||||
let days = &timetable
|
let days = &timetable
|
||||||
.days
|
.days
|
||||||
.iter()
|
.iter_mut()
|
||||||
.filter(|d| {
|
.map(|d| {
|
||||||
if let Some(project) = &project {
|
if let Some(project) = &project {
|
||||||
Some(project) == d.project.as_ref()
|
d.entry = d
|
||||||
|
.entry
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.project.as_ref() == Some(project))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
d
|
||||||
} else {
|
} else {
|
||||||
true
|
d
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.filter(|d| !d.entry.is_empty())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let days = days.iter().rev().take(limit).collect::<Vec<_>>();
|
let days = days.iter().rev().take(limit).collect::<Vec<_>>();
|
||||||
|
|
||||||
for day in days {
|
for day in days {
|
||||||
println!(
|
println!(
|
||||||
"{}{}{}\n {}{}\n",
|
"{}{}\n{}\n",
|
||||||
day.clock_in.with_timezone(&Local {}).format("%Y-%m-%d"),
|
day.date.format("%Y-%m-%d"),
|
||||||
if let Some(project) = &day.project {
|
|
||||||
format!(" project: {}", project)
|
|
||||||
} else {
|
|
||||||
"".into()
|
|
||||||
},
|
|
||||||
if day.breaks.is_empty() {
|
if day.breaks.is_empty() {
|
||||||
"".into()
|
"".into()
|
||||||
} else {
|
} else {
|
||||||
@ -91,28 +94,64 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
day.breaks.iter().fold(0, |acc, _| acc + 30)
|
day.breaks.iter().fold(0, |acc, _| acc + 30)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
day.clock_in.with_timezone(&Local {}).format("%H:%M"),
|
day.entry
|
||||||
if let Some(clockout) = &day.clock_out {
|
.iter()
|
||||||
format!(" - {}", clockout.with_timezone(&Local {}).format("%H:%M"))
|
.map(|e| {
|
||||||
} else {
|
format!(
|
||||||
" - unclosed".into()
|
" - {} - {}{}",
|
||||||
},
|
e.clock_in.with_timezone(&Local {}).format("%H:%M"),
|
||||||
|
if let Some(clockout) = &e.clock_out {
|
||||||
|
clockout
|
||||||
|
.with_timezone(&Local {})
|
||||||
|
.format("%H:%M")
|
||||||
|
.to_string()
|
||||||
|
} else if day.date == now.date_naive() {
|
||||||
|
let working_hours = e.clock_in - now;
|
||||||
|
format!(
|
||||||
|
"unclosed, current hours: {}h{}m",
|
||||||
|
working_hours.num_hours().abs(),
|
||||||
|
working_hours.num_minutes().abs() % 60
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"unclosed".into()
|
||||||
|
},
|
||||||
|
if let Some(project) = &e.project {
|
||||||
|
format!(": project: {}", project)
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::In { project } => {
|
Commands::In { project } => {
|
||||||
timetable.days.push(Day {
|
match timetable.get_day(now) {
|
||||||
clock_in: now,
|
Some(d) => {
|
||||||
clock_out: None,
|
d.entry.push(ClockIn {
|
||||||
breaks: Vec::default(),
|
clock_in: now,
|
||||||
project,
|
clock_out: None,
|
||||||
});
|
project,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => timetable.days.push(Day {
|
||||||
|
entry: vec![ClockIn {
|
||||||
|
clock_in: now,
|
||||||
|
clock_out: None,
|
||||||
|
project,
|
||||||
|
}],
|
||||||
|
breaks: Vec::default(),
|
||||||
|
date: now.date_naive(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Commands::Out { project } => match timetable.get_day(project, now) {
|
Commands::Out { project } => match timetable.get_day_entry(project, now) {
|
||||||
Some(day) => day.clock_out = Some(now),
|
Some(day) => day.clock_out = Some(now),
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
},
|
},
|
||||||
Commands::Break { project } => match timetable.get_day(project, now) {
|
Commands::Break { project } => match timetable.get_day(now) {
|
||||||
Some(day) => day.breaks.push(Break {}),
|
Some(day) => day.breaks.push(Break {}),
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
},
|
},
|
||||||
@ -120,6 +159,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let to_resolve = timetable
|
let to_resolve = timetable
|
||||||
.days
|
.days
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
.flat_map(|d| &mut d.entry)
|
||||||
.filter(|d| d.clock_out.is_none())
|
.filter(|d| d.clock_out.is_none())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -204,15 +244,18 @@ fn parse_string_to_time(v: &str) -> anyhow::Result<chrono::NaiveTime> {
|
|||||||
})
|
})
|
||||||
.context("failed to parse int to hour")
|
.context("failed to parse int to hour")
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct ClockIn {
|
||||||
|
clock_in: chrono::DateTime<chrono::Utc>,
|
||||||
|
clock_out: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
project: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
struct Day {
|
struct Day {
|
||||||
clock_in: chrono::DateTime<chrono::Utc>,
|
date: chrono::NaiveDate,
|
||||||
clock_out: Option<chrono::DateTime<chrono::Utc>>,
|
entry: Vec<ClockIn>,
|
||||||
|
|
||||||
breaks: Vec<Break>,
|
breaks: Vec<Break>,
|
||||||
|
|
||||||
project: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -224,12 +267,21 @@ struct TimeTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TimeTable {
|
impl TimeTable {
|
||||||
pub fn get_day(
|
pub fn get_day(&mut self, now: chrono::DateTime<chrono::Utc>) -> Option<&mut Day> {
|
||||||
|
let item = self
|
||||||
|
.days
|
||||||
|
.iter_mut()
|
||||||
|
.find(|d| d.date.format("%Y-%m-%d").to_string() == now.format("%Y-%m-%d").to_string());
|
||||||
|
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_day_entry(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<String>,
|
project: Option<String>,
|
||||||
now: chrono::DateTime<chrono::Utc>,
|
now: chrono::DateTime<chrono::Utc>,
|
||||||
) -> Option<&mut Day> {
|
) -> Option<&mut ClockIn> {
|
||||||
let item = self.days.iter_mut().find(|d| {
|
let item = self.days.iter_mut().flat_map(|d| &mut d.entry).find(|d| {
|
||||||
if d.project != project {
|
if d.project != project {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user