Month -> 31 trailing days
This commit is contained in:
parent
e62832fde6
commit
360af2f0cb
2 changed files with 45 additions and 33 deletions
32
src/app.rs
32
src/app.rs
|
|
@ -28,7 +28,7 @@ pub enum Screen {
|
||||||
pub enum LogViewPeriod {
|
pub enum LogViewPeriod {
|
||||||
Day,
|
Day,
|
||||||
Week,
|
Week,
|
||||||
Month,
|
Month, // Represents the last 31 days
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum LogViewDayOrder {
|
pub enum LogViewDayOrder {
|
||||||
|
|
@ -1587,17 +1587,29 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_log_content(&mut self) -> anyhow::Result<()> {
|
fn load_log_content(&mut self) -> anyhow::Result<()> {
|
||||||
let flag = match self.log_view_period {
|
let mut command = Command::new("watson");
|
||||||
LogViewPeriod::Day => "--day",
|
command.arg("log").arg("--json");
|
||||||
LogViewPeriod::Week => "--week",
|
|
||||||
LogViewPeriod::Month => "--month",
|
// Handle different period options
|
||||||
|
match self.log_view_period {
|
||||||
|
LogViewPeriod::Day => {
|
||||||
|
command.arg("--day");
|
||||||
|
},
|
||||||
|
LogViewPeriod::Week => {
|
||||||
|
command.arg("--week");
|
||||||
|
},
|
||||||
|
LogViewPeriod::Month => {
|
||||||
|
// Use --from with date 31 days ago for month view
|
||||||
|
let thirty_one_days_ago = chrono::Local::now()
|
||||||
|
.checked_sub_signed(chrono::Duration::days(31))
|
||||||
|
.expect("Failed to calculate date from 31 days ago");
|
||||||
|
|
||||||
|
command.arg("--from");
|
||||||
|
command.arg(thirty_one_days_ago.format("%Y-%m-%d").to_string());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = Command::new("watson")
|
let output = command.output()?;
|
||||||
.arg("log")
|
|
||||||
.arg(flag)
|
|
||||||
.arg("--json")
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
let json_str = String::from_utf8_lossy(&output.stdout);
|
let json_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
|
||||||
46
src/ui.rs
46
src/ui.rs
|
|
@ -33,10 +33,10 @@ fn render_main(frame: &mut Frame, app: &App) {
|
||||||
let has_active_timer = app.state.active_timer.is_some();
|
let has_active_timer = app.state.active_timer.is_some();
|
||||||
let has_status = app.status_message.is_some();
|
let has_status = app.status_message.is_some();
|
||||||
let show_help_hint = app.config.show_help_hint;
|
let show_help_hint = app.config.show_help_hint;
|
||||||
|
|
||||||
// Show bottom bar if we have any of: timer, status, or help hint
|
// Show bottom bar if we have any of: timer, status, or help hint
|
||||||
let show_bottom_bar = has_active_timer || has_status || show_help_hint;
|
let show_bottom_bar = has_active_timer || has_status || show_help_hint;
|
||||||
|
|
||||||
let bottom_height = if show_bottom_bar {
|
let bottom_height = if show_bottom_bar {
|
||||||
if has_status {
|
if has_status {
|
||||||
2 // Need extra line for status
|
2 // Need extra line for status
|
||||||
|
|
@ -63,41 +63,41 @@ fn render_main(frame: &mut Frame, app: &App) {
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title("WAT")
|
.title("WAT")
|
||||||
.style(Style::default().fg(ACTIVE_COLOR));
|
.style(Style::default().fg(ACTIVE_COLOR));
|
||||||
|
|
||||||
let text = Paragraph::new("No sections enabled. Edit config (press 'c') to enable sections.")
|
let text = Paragraph::new("No sections enabled. Edit config (press 'c') to enable sections.")
|
||||||
.block(block)
|
.block(block)
|
||||||
.style(Style::default().fg(Color::Yellow))
|
.style(Style::default().fg(Color::Yellow))
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
frame.render_widget(text, frame.size());
|
frame.render_widget(text, frame.size());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build constraints for enabled sections
|
// Build constraints for enabled sections
|
||||||
let mut constraints = Vec::new();
|
let mut constraints = Vec::new();
|
||||||
|
|
||||||
if bottom_height > 0 {
|
if bottom_height > 0 {
|
||||||
// Reserve space for bottom bar first, then split remainder among sections
|
// Reserve space for bottom bar first, then split remainder among sections
|
||||||
constraints.push(Constraint::Min(0)); // Sections get remaining space
|
constraints.push(Constraint::Min(0)); // Sections get remaining space
|
||||||
constraints.push(Constraint::Length(bottom_height)); // Bottom bar gets fixed height
|
constraints.push(Constraint::Length(bottom_height)); // Bottom bar gets fixed height
|
||||||
|
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(constraints)
|
.constraints(constraints)
|
||||||
.split(frame.size());
|
.split(frame.size());
|
||||||
|
|
||||||
// Split the top area among enabled sections
|
// Split the top area among enabled sections
|
||||||
let section_percentage = 100 / enabled_sections as u16;
|
let section_percentage = 100 / enabled_sections as u16;
|
||||||
let section_constraints = vec![Constraint::Percentage(section_percentage); enabled_sections];
|
let section_constraints = vec![Constraint::Percentage(section_percentage); enabled_sections];
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(section_constraints)
|
.constraints(section_constraints)
|
||||||
.split(layout[0]);
|
.split(layout[0]);
|
||||||
|
|
||||||
// Render enabled sections
|
// Render enabled sections
|
||||||
let mut chunk_idx = 0;
|
let mut chunk_idx = 0;
|
||||||
|
|
||||||
if app.config.show_permanent {
|
if app.config.show_permanent {
|
||||||
render_section(
|
render_section(
|
||||||
frame,
|
frame,
|
||||||
|
|
@ -145,14 +145,14 @@ fn render_main(frame: &mut Frame, app: &App) {
|
||||||
// No bottom bar - just render sections
|
// No bottom bar - just render sections
|
||||||
let section_percentage = 100 / enabled_sections as u16;
|
let section_percentage = 100 / enabled_sections as u16;
|
||||||
let constraints = vec![Constraint::Percentage(section_percentage); enabled_sections];
|
let constraints = vec![Constraint::Percentage(section_percentage); enabled_sections];
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(constraints)
|
.constraints(constraints)
|
||||||
.split(frame.size());
|
.split(frame.size());
|
||||||
|
|
||||||
let mut chunk_idx = 0;
|
let mut chunk_idx = 0;
|
||||||
|
|
||||||
if app.config.show_permanent {
|
if app.config.show_permanent {
|
||||||
render_section(
|
render_section(
|
||||||
frame,
|
frame,
|
||||||
|
|
@ -679,7 +679,7 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
let period_str = match app.log_view_period {
|
let period_str = match app.log_view_period {
|
||||||
LogViewPeriod::Day => "Day",
|
LogViewPeriod::Day => "Day",
|
||||||
LogViewPeriod::Week => "Week",
|
LogViewPeriod::Week => "Week",
|
||||||
LogViewPeriod::Month => "Month",
|
LogViewPeriod::Month => "31 Days",
|
||||||
};
|
};
|
||||||
|
|
||||||
let grouping_str = match app.log_view_grouping {
|
let grouping_str = match app.log_view_grouping {
|
||||||
|
|
@ -812,14 +812,14 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
|
|
||||||
// Third pass: render with highlighting and overlap detection
|
// Third pass: render with highlighting and overlap detection
|
||||||
let is_by_date = matches!(app.log_view_grouping, LogViewGrouping::ByDate);
|
let is_by_date = matches!(app.log_view_grouping, LogViewGrouping::ByDate);
|
||||||
|
|
||||||
// Pre-calculate overlaps for efficiency - check consecutive entries within same day
|
// Pre-calculate overlaps for efficiency - check consecutive entries within same day
|
||||||
let mut overlap_entry_indices = std::collections::HashSet::new();
|
let mut overlap_entry_indices = std::collections::HashSet::new();
|
||||||
if is_by_date && app.log_view_frame_indices.len() > 1 {
|
if is_by_date && app.log_view_frame_indices.len() > 1 {
|
||||||
// Track which day we're in to avoid comparing across days
|
// Track which day we're in to avoid comparing across days
|
||||||
let mut current_day_start_entry = 0;
|
let mut current_day_start_entry = 0;
|
||||||
let mut in_day = false;
|
let mut in_day = false;
|
||||||
|
|
||||||
for (line_idx, line) in app.log_view_content.iter().enumerate() {
|
for (line_idx, line) in app.log_view_content.iter().enumerate() {
|
||||||
// New day header
|
// New day header
|
||||||
if !line.starts_with(" ") && !line.is_empty() && !line.starts_with(" ") {
|
if !line.starts_with(" ") && !line.is_empty() && !line.starts_with(" ") {
|
||||||
|
|
@ -835,12 +835,12 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
.filter(|l| l.starts_with(" ") && !l.is_empty() && l.trim() != "---")
|
.filter(|l| l.starts_with(" ") && !l.is_empty() && l.trim() != "---")
|
||||||
.count()
|
.count()
|
||||||
.saturating_sub(1);
|
.saturating_sub(1);
|
||||||
|
|
||||||
// Only check overlap if not the first entry of the day
|
// Only check overlap if not the first entry of the day
|
||||||
if entry_idx > current_day_start_entry {
|
if entry_idx > current_day_start_entry {
|
||||||
let current_frame_idx = app.log_view_frame_indices[entry_idx];
|
let current_frame_idx = app.log_view_frame_indices[entry_idx];
|
||||||
let prev_frame_idx = app.log_view_frame_indices[entry_idx - 1];
|
let prev_frame_idx = app.log_view_frame_indices[entry_idx - 1];
|
||||||
|
|
||||||
if let (Some(current), Some(prev)) = (
|
if let (Some(current), Some(prev)) = (
|
||||||
app.log_view_frames.get(current_frame_idx),
|
app.log_view_frames.get(current_frame_idx),
|
||||||
app.log_view_frames.get(prev_frame_idx),
|
app.log_view_frames.get(prev_frame_idx),
|
||||||
|
|
@ -859,16 +859,16 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.log_view_content
|
app.log_view_content
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, line)| {
|
.map(|(idx, line)| {
|
||||||
let is_selected = idx >= selected_line_start && idx <= selected_line_end;
|
let is_selected = idx >= selected_line_start && idx <= selected_line_end;
|
||||||
|
|
||||||
// Skip styling for separator lines
|
// Skip styling for separator lines
|
||||||
let is_separator = line.trim() == "---";
|
let is_separator = line.trim() == "---";
|
||||||
|
|
||||||
// Check if this line corresponds to an overlapping entry
|
// Check if this line corresponds to an overlapping entry
|
||||||
let has_overlap = if !is_separator && is_by_date && line.starts_with(" ") && !line.is_empty() {
|
let has_overlap = if !is_separator && is_by_date && line.starts_with(" ") && !line.is_empty() {
|
||||||
// Count which entry this is (0-based in display order)
|
// Count which entry this is (0-based in display order)
|
||||||
|
|
@ -878,7 +878,7 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
.filter(|l| l.starts_with(" ") && !l.is_empty() && l.trim() != "---")
|
.filter(|l| l.starts_with(" ") && !l.is_empty() && l.trim() != "---")
|
||||||
.count()
|
.count()
|
||||||
.saturating_sub(1);
|
.saturating_sub(1);
|
||||||
|
|
||||||
overlap_entry_indices.contains(&entry_idx)
|
overlap_entry_indices.contains(&entry_idx)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -904,7 +904,7 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
let paragraph = Paragraph::new(text_lines)
|
let paragraph = Paragraph::new(text_lines)
|
||||||
.block(block)
|
.block(block)
|
||||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||||
|
|
||||||
frame.render_widget(paragraph, chunks[0]);
|
frame.render_widget(paragraph, chunks[0]);
|
||||||
|
|
||||||
// Render help hint at bottom if enabled
|
// Render help hint at bottom if enabled
|
||||||
|
|
@ -928,7 +928,7 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
||||||
"Time Periods:",
|
"Time Periods:",
|
||||||
"- d: Switch to Day view (current day)",
|
"- d: Switch to Day view (current day)",
|
||||||
"- w: Switch to Week view (current week)",
|
"- w: Switch to Week view (current week)",
|
||||||
"- m: Switch to Month view (current month)",
|
"- m: Switch to Month view (last 31 days)",
|
||||||
"",
|
"",
|
||||||
"Grouping:",
|
"Grouping:",
|
||||||
"- g: Toggle between grouping by Date or by Project",
|
"- g: Toggle between grouping by Date or by Project",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue