From e661ad8ba11649533c623e512124d9ec97393404 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Sun, 23 Nov 2025 12:50:31 -0500 Subject: [PATCH] Fix dates, create order toggle --- src/app.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++------- src/ui.rs | 15 +++++++-- 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index b593b53..8bf8512 100644 --- a/src/app.rs +++ b/src/app.rs @@ -30,6 +30,11 @@ pub enum LogViewPeriod { Month, } +pub enum LogViewDayOrder { + Chronological, // Oldest first + ReverseChronological, // Newest first (default) +} + pub enum LogViewGrouping { ByDate, ByProject, @@ -55,6 +60,7 @@ pub struct App { pub reassign_project_cursor: usize, pub log_view_period: LogViewPeriod, pub log_view_grouping: LogViewGrouping, + pub log_view_day_order: LogViewDayOrder, pub log_view_selection_level: LogViewSelection, pub log_view_frames: Vec, pub log_view_content: Vec, @@ -94,6 +100,7 @@ impl App { reassign_project_cursor: 0, log_view_period: LogViewPeriod::Day, log_view_grouping: LogViewGrouping::ByDate, + log_view_day_order: LogViewDayOrder::ReverseChronological, log_view_selection_level: LogViewSelection::Entry, log_view_frames: Vec::new(), log_view_content: Vec::new(), @@ -501,6 +508,15 @@ impl App { self.format_log_entries(); self.needs_clear = true; } + KeyCode::Char('r') => { + // Toggle day order (chronological vs reverse chronological) + self.log_view_day_order = match self.log_view_day_order { + LogViewDayOrder::Chronological => LogViewDayOrder::ReverseChronological, + LogViewDayOrder::ReverseChronological => LogViewDayOrder::Chronological, + }; + 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)); @@ -532,21 +548,48 @@ impl App { use std::collections::BTreeMap; // Group frames by date, tracking their original indices - let mut by_date: BTreeMap> = BTreeMap::new(); + // Use YYYY-MM-DD as the key for proper sorting + let mut by_date: BTreeMap)> = BTreeMap::new(); + + // Choose date format based on view period + let date_format = match self.log_view_period { + LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday) + _ => "%A %d %B %Y", // "Saturday 23 November 2025" + }; for (idx, frame) in self.log_view_frames.iter().enumerate() { if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) { let local_dt: DateTime = start_dt.into(); - let date_key = local_dt.format("%A %d %B %Y").to_string(); - by_date.entry(date_key).or_insert_with(Vec::new).push((idx, frame)); + let sort_key = local_dt.format("%Y-%m-%d").to_string(); // For sorting + let display_date = local_dt.format(date_format).to_string(); // For display + by_date + .entry(sort_key) + .or_insert_with(|| (display_date.clone(), Vec::new())) + .1 + .push((idx, frame)); } } let mut lines = Vec::new(); let mut frame_indices = Vec::new(); - for (date, frames) in by_date.iter().rev() { - lines.push(date.clone()); + // Sort each day's frames chronologically + for (_, frames) in by_date.values_mut() { + frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start)); + } + + // Collect dates in the desired order + let dates: Vec<_> = match self.log_view_day_order { + LogViewDayOrder::ReverseChronological => { + by_date.iter().rev().collect() + } + LogViewDayOrder::Chronological => { + by_date.iter().collect() + } + }; + + for (_sort_key, (display_date, frames)) in dates { + lines.push(display_date.clone()); for (idx, frame) in frames { if let (Ok(start_dt), Ok(stop_dt)) = ( @@ -584,15 +627,24 @@ impl App { use std::collections::BTreeMap; // Group frames by date, then by project within each date, tracking indices - let mut by_date: BTreeMap>> = BTreeMap::new(); + // Use YYYY-MM-DD as the key for proper sorting + let mut by_date: BTreeMap>)> = BTreeMap::new(); + + // Choose date format based on view period + let date_format = match self.log_view_period { + LogViewPeriod::Month => "%d %B %Y", // "23 November 2025" (no weekday) + _ => "%A %d %B %Y", // "Saturday 23 November 2025" + }; for (idx, frame) in self.log_view_frames.iter().enumerate() { if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) { let local_dt: DateTime = start_dt.into(); - let date_key = local_dt.format("%A %d %B %Y").to_string(); + let sort_key = local_dt.format("%Y-%m-%d").to_string(); // For sorting + let display_date = local_dt.format(date_format).to_string(); // For display by_date - .entry(date_key) - .or_insert_with(BTreeMap::new) + .entry(sort_key) + .or_insert_with(|| (display_date.clone(), BTreeMap::new())) + .1 .entry(frame.project.clone()) .or_insert_with(Vec::new) .push((idx, frame)); @@ -602,8 +654,25 @@ impl App { let mut lines = Vec::new(); let mut frame_indices = Vec::new(); - for (date, projects) in by_date.iter().rev() { - lines.push(date.clone()); + // Sort frames within each project chronologically + for (_, projects) in by_date.values_mut() { + for frames in projects.values_mut() { + frames.sort_by(|(_, a), (_, b)| a.start.cmp(&b.start)); + } + } + + // Collect dates in the desired order + let dates: Vec<_> = match self.log_view_day_order { + LogViewDayOrder::ReverseChronological => { + by_date.iter().rev().collect() + } + LogViewDayOrder::Chronological => { + by_date.iter().collect() + } + }; + + for (_sort_key, (display_date, projects)) in dates { + lines.push(display_date.clone()); for (project, frames) in projects.iter() { lines.push(format!(" {}", project)); // Project header with indent diff --git a/src/ui.rs b/src/ui.rs index a35478e..946db90 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -7,7 +7,7 @@ use ratatui::{ }; use crate::{ - app::{App, LogViewGrouping, LogViewPeriod, LogViewSelection, NewEntryMode, Screen}, + app::{App, LogViewDayOrder, LogViewGrouping, LogViewPeriod, LogViewSelection, NewEntryMode, Screen}, state::{AppState, TimeItem}, }; @@ -252,6 +252,7 @@ fn render_help(frame: &mut Frame, app: &App) { "Once in log view, you can:", "- Switch time periods: d (day), w (week), m (month)", "- Toggle grouping: g (by date or by project)", + "- Toggle day order: r (newest first ↔ oldest first)", "- Change selection: h/l (entry → project → day → all)", "- Navigate: j/k to move through selections", "- Edit entry: e (entry level only)", @@ -474,7 +475,12 @@ fn render_log_view(frame: &mut Frame, app: &App) { LogViewGrouping::ByProject => "by Project", }; - let title = format!("Watson Log - {} View ({})", period_str, grouping_str); + let order_str = match app.log_view_day_order { + LogViewDayOrder::Chronological => "↑", + LogViewDayOrder::ReverseChronological => "↓", + }; + + let title = format!("Watson Log - {} View ({}) [{}]", period_str, grouping_str, order_str); let block = Block::default() .title(title) @@ -641,6 +647,11 @@ fn render_log_view_help(frame: &mut Frame, app: &App) { " - By Date: Shows all entries chronologically", " - By Project: Groups entries by project within each date", "", + "Day Order:", + "- r: Toggle day order between newest first (↓) and oldest first (↑)", + " - Days are shown in the chosen order", + " - Entries within each day are always chronological (earliest to latest)", + "", "Navigation:", "- j/k or ↑/↓: Navigate selection", " - At Entry level: Move to next/previous entry",