Fifteen-minute rounding
This commit is contained in:
parent
7b6d24f955
commit
f3dcd5acbc
2 changed files with 56 additions and 9 deletions
56
src/app.rs
56
src/app.rs
|
|
@ -68,6 +68,7 @@ pub struct App {
|
||||||
pub log_view_frame_indices: Vec<usize>, // Maps display order to frame index
|
pub log_view_frame_indices: Vec<usize>, // Maps display order to frame index
|
||||||
pub log_view_scroll: usize,
|
pub log_view_scroll: usize,
|
||||||
pub log_view_selected: usize,
|
pub log_view_selected: usize,
|
||||||
|
pub log_view_rounded: bool, // Toggle for 15-minute rounding display
|
||||||
pub help_scroll: usize,
|
pub help_scroll: usize,
|
||||||
pub clipboard: Option<arboard::Clipboard>,
|
pub clipboard: Option<arboard::Clipboard>,
|
||||||
pub status_message: Option<(String, std::time::Instant)>,
|
pub status_message: Option<(String, std::time::Instant)>,
|
||||||
|
|
@ -81,6 +82,24 @@ pub enum NewEntryMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
// Helper to round a time to the nearest 15-minute interval
|
||||||
|
fn round_to_15min(dt: &chrono::DateTime<chrono::Local>) -> chrono::DateTime<chrono::Local> {
|
||||||
|
use chrono::{Timelike, Duration};
|
||||||
|
|
||||||
|
let minutes = dt.minute();
|
||||||
|
let rounded_minutes = ((minutes + 7) / 15) * 15; // Round to nearest 15
|
||||||
|
|
||||||
|
let mut rounded = dt.with_minute(0).unwrap().with_second(0).unwrap().with_nanosecond(0).unwrap();
|
||||||
|
rounded = rounded + Duration::minutes(rounded_minutes as i64);
|
||||||
|
|
||||||
|
// Handle hour overflow (e.g., 50 minutes rounds to 60)
|
||||||
|
if rounded_minutes >= 60 {
|
||||||
|
rounded = rounded.with_minute(0).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
rounded
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to determine if a line is an entry line based on grouping mode
|
// Helper to determine if a line is an entry line based on grouping mode
|
||||||
fn is_entry_line(&self, line: &str) -> bool {
|
fn is_entry_line(&self, line: &str) -> bool {
|
||||||
// Exclude separator lines (time gap markers)
|
// Exclude separator lines (time gap markers)
|
||||||
|
|
@ -130,6 +149,7 @@ impl App {
|
||||||
log_view_frame_indices: Vec::new(),
|
log_view_frame_indices: Vec::new(),
|
||||||
log_view_scroll: 0,
|
log_view_scroll: 0,
|
||||||
log_view_selected: 0,
|
log_view_selected: 0,
|
||||||
|
log_view_rounded: false,
|
||||||
help_scroll: 0,
|
help_scroll: 0,
|
||||||
clipboard: arboard::Clipboard::new().ok(),
|
clipboard: arboard::Clipboard::new().ok(),
|
||||||
status_message: None,
|
status_message: None,
|
||||||
|
|
@ -560,6 +580,12 @@ impl App {
|
||||||
self.format_log_entries();
|
self.format_log_entries();
|
||||||
self.needs_clear = true;
|
self.needs_clear = true;
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('R') => {
|
||||||
|
// Toggle 15-minute rounding for display
|
||||||
|
self.log_view_rounded = !self.log_view_rounded;
|
||||||
|
self.format_log_entries();
|
||||||
|
self.needs_clear = true;
|
||||||
|
}
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
self.log_view_selected = (self.log_view_selected + 10)
|
self.log_view_selected = (self.log_view_selected + 10)
|
||||||
.min(self.log_view_frame_indices.len().saturating_sub(1));
|
.min(self.log_view_frame_indices.len().saturating_sub(1));
|
||||||
|
|
@ -644,6 +670,13 @@ impl App {
|
||||||
let start_local: DateTime<Local> = start_dt.into();
|
let start_local: DateTime<Local> = start_dt.into();
|
||||||
let stop_local: DateTime<Local> = stop_dt.into();
|
let stop_local: DateTime<Local> = stop_dt.into();
|
||||||
|
|
||||||
|
// Apply rounding if enabled
|
||||||
|
let (display_start, display_stop) = if self.log_view_rounded {
|
||||||
|
(Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local))
|
||||||
|
} else {
|
||||||
|
(start_local, stop_local)
|
||||||
|
};
|
||||||
|
|
||||||
// Check for time gap and add separator line if enabled
|
// Check for time gap and add separator line if enabled
|
||||||
if self.config.show_time_gaps && frame_idx > 0 {
|
if self.config.show_time_gaps && frame_idx > 0 {
|
||||||
if let Some(prev) = prev_stop {
|
if let Some(prev) = prev_stop {
|
||||||
|
|
@ -655,11 +688,11 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_time = format!("{:02}:{:02}", start_local.hour(), start_local.minute());
|
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
|
||||||
let stop_time = format!("{:02}:{:02}", stop_local.hour(), stop_local.minute());
|
let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute());
|
||||||
|
|
||||||
// Calculate duration
|
// Calculate duration (use rounded times if enabled)
|
||||||
let duration = stop_local.signed_duration_since(start_local);
|
let duration = display_stop.signed_duration_since(display_start);
|
||||||
let hours = duration.num_hours();
|
let hours = duration.num_hours();
|
||||||
let minutes = duration.num_minutes() % 60;
|
let minutes = duration.num_minutes() % 60;
|
||||||
let duration_str = if hours > 0 {
|
let duration_str = if hours > 0 {
|
||||||
|
|
@ -755,11 +788,18 @@ impl App {
|
||||||
let start_local: DateTime<Local> = start_dt.into();
|
let start_local: DateTime<Local> = start_dt.into();
|
||||||
let stop_local: DateTime<Local> = stop_dt.into();
|
let stop_local: DateTime<Local> = stop_dt.into();
|
||||||
|
|
||||||
let start_time = format!("{:02}:{:02}", start_local.hour(), start_local.minute());
|
// Apply rounding if enabled
|
||||||
let stop_time = format!("{:02}:{:02}", stop_local.hour(), stop_local.minute());
|
let (display_start, display_stop) = if self.log_view_rounded {
|
||||||
|
(Self::round_to_15min(&start_local), Self::round_to_15min(&stop_local))
|
||||||
|
} else {
|
||||||
|
(start_local, stop_local)
|
||||||
|
};
|
||||||
|
|
||||||
// Calculate duration
|
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
|
||||||
let duration = stop_local.signed_duration_since(start_local);
|
let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute());
|
||||||
|
|
||||||
|
// Calculate duration (use rounded times if enabled)
|
||||||
|
let duration = display_stop.signed_duration_since(display_start);
|
||||||
let hours = duration.num_hours();
|
let hours = duration.num_hours();
|
||||||
let minutes = duration.num_minutes() % 60;
|
let minutes = duration.num_minutes() % 60;
|
||||||
let duration_str = if hours > 0 {
|
let duration_str = if hours > 0 {
|
||||||
|
|
|
||||||
|
|
@ -692,7 +692,8 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
LogViewDayOrder::ReverseChronological => "↓",
|
LogViewDayOrder::ReverseChronological => "↓",
|
||||||
};
|
};
|
||||||
|
|
||||||
let title = format!("Watson Log - {} View ({}) [{}]", period_str, grouping_str, order_str);
|
let rounded_indicator = if app.log_view_rounded { " [ROUNDED]" } else { "" };
|
||||||
|
let title = format!("Watson Log - {} View ({}) [{}]{}", period_str, grouping_str, order_str, rounded_indicator);
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(title)
|
.title(title)
|
||||||
|
|
@ -939,6 +940,12 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
||||||
" - Days are shown in the chosen order",
|
" - Days are shown in the chosen order",
|
||||||
" - Entries within each day are always chronological (earliest to latest)",
|
" - Entries within each day are always chronological (earliest to latest)",
|
||||||
"",
|
"",
|
||||||
|
"Display Mode:",
|
||||||
|
"- R: Toggle 15-minute rounding (for estimates/reports)",
|
||||||
|
" - When enabled, all times are rounded to nearest 15-minute interval",
|
||||||
|
" - Shows [ROUNDED] indicator in title bar",
|
||||||
|
" - Affects display and clipboard copy, does not modify Watson data",
|
||||||
|
"",
|
||||||
"Navigation:",
|
"Navigation:",
|
||||||
"- j/k or ↑/↓: Navigate selection",
|
"- j/k or ↑/↓: Navigate selection",
|
||||||
" - At Entry level: Move to next/previous entry",
|
" - At Entry level: Move to next/previous entry",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue