View filtering, task name refactor
Weird issues where projects were mapping to tags. Also group everything by project in view with 'g'
This commit is contained in:
parent
4f57c01693
commit
353d422730
3 changed files with 123 additions and 44 deletions
109
src/app.rs
109
src/app.rs
|
|
@ -29,6 +29,11 @@ pub enum LogViewPeriod {
|
|||
Month,
|
||||
}
|
||||
|
||||
pub enum LogViewGrouping {
|
||||
ByDate,
|
||||
ByProject,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub state: AppState,
|
||||
pub config: Config,
|
||||
|
|
@ -41,6 +46,7 @@ pub struct App {
|
|||
pub reassign_project_buffer: String,
|
||||
pub reassign_project_cursor: usize,
|
||||
pub log_view_period: LogViewPeriod,
|
||||
pub log_view_grouping: LogViewGrouping,
|
||||
pub log_view_frames: Vec<WatsonFrame>,
|
||||
pub log_view_content: Vec<String>,
|
||||
pub log_view_scroll: usize,
|
||||
|
|
@ -68,6 +74,7 @@ impl App {
|
|||
reassign_project_buffer: String::new(),
|
||||
reassign_project_cursor: 0,
|
||||
log_view_period: LogViewPeriod::Day,
|
||||
log_view_grouping: LogViewGrouping::ByDate,
|
||||
log_view_frames: Vec::new(),
|
||||
log_view_content: Vec::new(),
|
||||
log_view_scroll: 0,
|
||||
|
|
@ -144,15 +151,13 @@ impl App {
|
|||
(KeyCode::Enter, _) => {
|
||||
match self.new_entry_mode {
|
||||
NewEntryMode::Task => {
|
||||
if !self.new_entry_buffer.is_empty() {
|
||||
self.new_entry_mode = NewEntryMode::Project;
|
||||
self.new_entry_cursor = self.new_entry_project.len();
|
||||
}
|
||||
// Move from Tag to Project
|
||||
self.new_entry_mode = NewEntryMode::Project;
|
||||
self.new_entry_cursor = self.new_entry_buffer.len();
|
||||
}
|
||||
NewEntryMode::Project => {
|
||||
if self.new_entry_project.is_empty()
|
||||
|| self.config.is_valid_project(&self.new_entry_project)
|
||||
{
|
||||
// Project is required, tag is optional
|
||||
if !self.new_entry_buffer.is_empty() {
|
||||
// Create new time item
|
||||
let item = TimeItem {
|
||||
name: self.new_entry_buffer.clone(),
|
||||
|
|
@ -184,15 +189,15 @@ impl App {
|
|||
(KeyCode::Backspace, _) => match self.new_entry_mode {
|
||||
NewEntryMode::Task => {
|
||||
if self.new_entry_cursor > 0 {
|
||||
self.new_entry_buffer.remove(self.new_entry_cursor - 1);
|
||||
self.new_entry_project.remove(self.new_entry_cursor - 1);
|
||||
self.new_entry_cursor -= 1;
|
||||
}
|
||||
}
|
||||
NewEntryMode::Project => {
|
||||
if self.new_entry_cursor > 0 {
|
||||
let idx = self.new_entry_cursor - 1;
|
||||
if idx < self.new_entry_project.len() {
|
||||
self.new_entry_project.remove(idx);
|
||||
if idx < self.new_entry_buffer.len() {
|
||||
self.new_entry_buffer.remove(idx);
|
||||
self.new_entry_cursor -= 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -200,12 +205,12 @@ impl App {
|
|||
},
|
||||
(KeyCode::Char(c), m) if m.is_empty() => match self.new_entry_mode {
|
||||
NewEntryMode::Task => {
|
||||
self.new_entry_buffer.insert(self.new_entry_cursor, c);
|
||||
self.new_entry_project.insert(self.new_entry_cursor, c);
|
||||
self.new_entry_cursor += 1;
|
||||
}
|
||||
NewEntryMode::Project => {
|
||||
if self.new_entry_cursor <= self.new_entry_project.len() {
|
||||
self.new_entry_project.insert(self.new_entry_cursor, c);
|
||||
if self.new_entry_cursor <= self.new_entry_buffer.len() {
|
||||
self.new_entry_buffer.insert(self.new_entry_cursor, c);
|
||||
self.new_entry_cursor += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -334,6 +339,16 @@ impl App {
|
|||
KeyCode::Char('c') => {
|
||||
self.copy_log_to_clipboard()?;
|
||||
}
|
||||
KeyCode::Char('g') => {
|
||||
// Toggle grouping mode
|
||||
self.log_view_grouping = match self.log_view_grouping {
|
||||
LogViewGrouping::ByDate => LogViewGrouping::ByProject,
|
||||
LogViewGrouping::ByProject => LogViewGrouping::ByDate,
|
||||
};
|
||||
self.log_view_selected = 0;
|
||||
self.format_log_entries();
|
||||
self.needs_clear = true;
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
self.log_view_selected = (self.log_view_selected + 10)
|
||||
.min(self.log_view_frames.len().saturating_sub(1));
|
||||
|
|
@ -357,6 +372,16 @@ impl App {
|
|||
return;
|
||||
}
|
||||
|
||||
match self.log_view_grouping {
|
||||
LogViewGrouping::ByDate => self.format_by_date(),
|
||||
LogViewGrouping::ByProject => self.format_by_project(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_by_date(&mut self) {
|
||||
use chrono::{DateTime, Local, Timelike};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// Group frames by date
|
||||
let mut by_date: BTreeMap<String, Vec<&WatsonFrame>> = BTreeMap::new();
|
||||
|
||||
|
|
@ -402,6 +427,64 @@ impl App {
|
|||
self.log_view_content = lines;
|
||||
}
|
||||
|
||||
fn format_by_project(&mut self) {
|
||||
use chrono::{DateTime, Local, Timelike};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// Group frames by date, then by project within each date
|
||||
let mut by_date: BTreeMap<String, BTreeMap<String, Vec<&WatsonFrame>>> = BTreeMap::new();
|
||||
|
||||
for frame in &self.log_view_frames {
|
||||
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
|
||||
let local_dt: DateTime<Local> = start_dt.into();
|
||||
let date_key = local_dt.format("%A %d %B %Y").to_string();
|
||||
by_date
|
||||
.entry(date_key)
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.entry(frame.project.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (date, projects) in by_date.iter().rev() {
|
||||
lines.push(date.clone());
|
||||
|
||||
for (project, frames) in projects.iter() {
|
||||
lines.push(format!(" {}", project)); // Project header with indent
|
||||
|
||||
for frame in frames {
|
||||
if let (Ok(start_dt), Ok(stop_dt)) = (
|
||||
DateTime::parse_from_rfc3339(&frame.start),
|
||||
DateTime::parse_from_rfc3339(&frame.stop),
|
||||
) {
|
||||
let start_local: DateTime<Local> = start_dt.into();
|
||||
let stop_local: DateTime<Local> = stop_dt.into();
|
||||
|
||||
let start_time = format!("{:02}:{:02}", start_local.hour(), start_local.minute());
|
||||
let stop_time = format!("{:02}:{:02}", stop_local.hour(), stop_local.minute());
|
||||
|
||||
let tags_str = if frame.tags.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" [{}]", frame.tags.join(", "))
|
||||
};
|
||||
|
||||
lines.push(format!(
|
||||
"\t\t{} to {}{}",
|
||||
start_time, stop_time, tags_str
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.push(String::new()); // Empty line between dates
|
||||
}
|
||||
|
||||
self.log_view_content = lines;
|
||||
}
|
||||
|
||||
fn edit_selected_frame(&mut self) -> anyhow::Result<()> {
|
||||
if self.log_view_frames.is_empty() || self.log_view_selected >= self.log_view_frames.len() {
|
||||
return Ok(());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue