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_scroll: usize,
|
||||
pub log_view_selected: usize,
|
||||
pub log_view_rounded: bool, // Toggle for 15-minute rounding display
|
||||
pub help_scroll: usize,
|
||||
pub clipboard: Option<arboard::Clipboard>,
|
||||
pub status_message: Option<(String, std::time::Instant)>,
|
||||
|
|
@ -81,6 +82,24 @@ pub enum NewEntryMode {
|
|||
}
|
||||
|
||||
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
|
||||
fn is_entry_line(&self, line: &str) -> bool {
|
||||
// Exclude separator lines (time gap markers)
|
||||
|
|
@ -130,6 +149,7 @@ impl App {
|
|||
log_view_frame_indices: Vec::new(),
|
||||
log_view_scroll: 0,
|
||||
log_view_selected: 0,
|
||||
log_view_rounded: false,
|
||||
help_scroll: 0,
|
||||
clipboard: arboard::Clipboard::new().ok(),
|
||||
status_message: None,
|
||||
|
|
@ -560,6 +580,12 @@ impl App {
|
|||
self.format_log_entries();
|
||||
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 => {
|
||||
self.log_view_selected = (self.log_view_selected + 10)
|
||||
.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 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
|
||||
if self.config.show_time_gaps && frame_idx > 0 {
|
||||
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 stop_time = format!("{:02}:{:02}", stop_local.hour(), stop_local.minute());
|
||||
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
|
||||
let stop_time = format!("{:02}:{:02}", display_stop.hour(), display_stop.minute());
|
||||
|
||||
// Calculate duration
|
||||
let duration = stop_local.signed_duration_since(start_local);
|
||||
// Calculate duration (use rounded times if enabled)
|
||||
let duration = display_stop.signed_duration_since(display_start);
|
||||
let hours = duration.num_hours();
|
||||
let minutes = duration.num_minutes() % 60;
|
||||
let duration_str = if hours > 0 {
|
||||
|
|
@ -755,11 +788,18 @@ impl App {
|
|||
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());
|
||||
// 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)
|
||||
};
|
||||
|
||||
// Calculate duration
|
||||
let duration = stop_local.signed_duration_since(start_local);
|
||||
let start_time = format!("{:02}:{:02}", display_start.hour(), display_start.minute());
|
||||
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 minutes = duration.num_minutes() % 60;
|
||||
let duration_str = if hours > 0 {
|
||||
|
|
|
|||
|
|
@ -692,7 +692,8 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
|||
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()
|
||||
.title(title)
|
||||
|
|
@ -939,6 +940,12 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
|||
" - Days are shown in the chosen order",
|
||||
" - 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:",
|
||||
"- j/k or ↑/↓: Navigate selection",
|
||||
" - At Entry level: Move to next/previous entry",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue