Fix dates, create order toggle
This commit is contained in:
parent
f10c1ddfff
commit
e661ad8ba1
2 changed files with 93 additions and 13 deletions
91
src/app.rs
91
src/app.rs
|
|
@ -30,6 +30,11 @@ pub enum LogViewPeriod {
|
||||||
Month,
|
Month,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum LogViewDayOrder {
|
||||||
|
Chronological, // Oldest first
|
||||||
|
ReverseChronological, // Newest first (default)
|
||||||
|
}
|
||||||
|
|
||||||
pub enum LogViewGrouping {
|
pub enum LogViewGrouping {
|
||||||
ByDate,
|
ByDate,
|
||||||
ByProject,
|
ByProject,
|
||||||
|
|
@ -55,6 +60,7 @@ pub struct App {
|
||||||
pub reassign_project_cursor: usize,
|
pub reassign_project_cursor: usize,
|
||||||
pub log_view_period: LogViewPeriod,
|
pub log_view_period: LogViewPeriod,
|
||||||
pub log_view_grouping: LogViewGrouping,
|
pub log_view_grouping: LogViewGrouping,
|
||||||
|
pub log_view_day_order: LogViewDayOrder,
|
||||||
pub log_view_selection_level: LogViewSelection,
|
pub log_view_selection_level: LogViewSelection,
|
||||||
pub log_view_frames: Vec<WatsonFrame>,
|
pub log_view_frames: Vec<WatsonFrame>,
|
||||||
pub log_view_content: Vec<String>,
|
pub log_view_content: Vec<String>,
|
||||||
|
|
@ -94,6 +100,7 @@ impl App {
|
||||||
reassign_project_cursor: 0,
|
reassign_project_cursor: 0,
|
||||||
log_view_period: LogViewPeriod::Day,
|
log_view_period: LogViewPeriod::Day,
|
||||||
log_view_grouping: LogViewGrouping::ByDate,
|
log_view_grouping: LogViewGrouping::ByDate,
|
||||||
|
log_view_day_order: LogViewDayOrder::ReverseChronological,
|
||||||
log_view_selection_level: LogViewSelection::Entry,
|
log_view_selection_level: LogViewSelection::Entry,
|
||||||
log_view_frames: Vec::new(),
|
log_view_frames: Vec::new(),
|
||||||
log_view_content: Vec::new(),
|
log_view_content: Vec::new(),
|
||||||
|
|
@ -501,6 +508,15 @@ impl App {
|
||||||
self.format_log_entries();
|
self.format_log_entries();
|
||||||
self.needs_clear = true;
|
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 => {
|
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));
|
||||||
|
|
@ -532,21 +548,48 @@ impl App {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
// Group frames by date, tracking their original indices
|
// Group frames by date, tracking their original indices
|
||||||
let mut by_date: BTreeMap<String, Vec<(usize, &WatsonFrame)>> = BTreeMap::new();
|
// Use YYYY-MM-DD as the key for proper sorting
|
||||||
|
let mut by_date: BTreeMap<String, (String, Vec<(usize, &WatsonFrame)>)> = 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() {
|
for (idx, frame) in self.log_view_frames.iter().enumerate() {
|
||||||
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
|
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
|
||||||
let local_dt: DateTime<Local> = start_dt.into();
|
let local_dt: DateTime<Local> = 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
|
||||||
by_date.entry(date_key).or_insert_with(Vec::new).push((idx, frame));
|
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 lines = Vec::new();
|
||||||
let mut frame_indices = Vec::new();
|
let mut frame_indices = Vec::new();
|
||||||
|
|
||||||
for (date, frames) in by_date.iter().rev() {
|
// Sort each day's frames chronologically
|
||||||
lines.push(date.clone());
|
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 {
|
for (idx, frame) in frames {
|
||||||
if let (Ok(start_dt), Ok(stop_dt)) = (
|
if let (Ok(start_dt), Ok(stop_dt)) = (
|
||||||
|
|
@ -584,15 +627,24 @@ impl App {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
// Group frames by date, then by project within each date, tracking indices
|
// Group frames by date, then by project within each date, tracking indices
|
||||||
let mut by_date: BTreeMap<String, BTreeMap<String, Vec<(usize, &WatsonFrame)>>> = BTreeMap::new();
|
// Use YYYY-MM-DD as the key for proper sorting
|
||||||
|
let mut by_date: BTreeMap<String, (String, BTreeMap<String, Vec<(usize, &WatsonFrame)>>)> = 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() {
|
for (idx, frame) in self.log_view_frames.iter().enumerate() {
|
||||||
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
|
if let Ok(start_dt) = DateTime::parse_from_rfc3339(&frame.start) {
|
||||||
let local_dt: DateTime<Local> = start_dt.into();
|
let local_dt: DateTime<Local> = 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
|
by_date
|
||||||
.entry(date_key)
|
.entry(sort_key)
|
||||||
.or_insert_with(BTreeMap::new)
|
.or_insert_with(|| (display_date.clone(), BTreeMap::new()))
|
||||||
|
.1
|
||||||
.entry(frame.project.clone())
|
.entry(frame.project.clone())
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
.push((idx, frame));
|
.push((idx, frame));
|
||||||
|
|
@ -602,8 +654,25 @@ impl App {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
let mut frame_indices = Vec::new();
|
let mut frame_indices = Vec::new();
|
||||||
|
|
||||||
for (date, projects) in by_date.iter().rev() {
|
// Sort frames within each project chronologically
|
||||||
lines.push(date.clone());
|
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() {
|
for (project, frames) in projects.iter() {
|
||||||
lines.push(format!(" {}", project)); // Project header with indent
|
lines.push(format!(" {}", project)); // Project header with indent
|
||||||
|
|
|
||||||
15
src/ui.rs
15
src/ui.rs
|
|
@ -7,7 +7,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{App, LogViewGrouping, LogViewPeriod, LogViewSelection, NewEntryMode, Screen},
|
app::{App, LogViewDayOrder, LogViewGrouping, LogViewPeriod, LogViewSelection, NewEntryMode, Screen},
|
||||||
state::{AppState, TimeItem},
|
state::{AppState, TimeItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -252,6 +252,7 @@ fn render_help(frame: &mut Frame, app: &App) {
|
||||||
"Once in log view, you can:",
|
"Once in log view, you can:",
|
||||||
"- Switch time periods: d (day), w (week), m (month)",
|
"- Switch time periods: d (day), w (week), m (month)",
|
||||||
"- Toggle grouping: g (by date or by project)",
|
"- Toggle grouping: g (by date or by project)",
|
||||||
|
"- Toggle day order: r (newest first ↔ oldest first)",
|
||||||
"- Change selection: h/l (entry → project → day → all)",
|
"- Change selection: h/l (entry → project → day → all)",
|
||||||
"- Navigate: j/k to move through selections",
|
"- Navigate: j/k to move through selections",
|
||||||
"- Edit entry: e (entry level only)",
|
"- Edit entry: e (entry level only)",
|
||||||
|
|
@ -474,7 +475,12 @@ fn render_log_view(frame: &mut Frame, app: &App) {
|
||||||
LogViewGrouping::ByProject => "by Project",
|
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()
|
let block = Block::default()
|
||||||
.title(title)
|
.title(title)
|
||||||
|
|
@ -641,6 +647,11 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
|
||||||
" - By Date: Shows all entries chronologically",
|
" - By Date: Shows all entries chronologically",
|
||||||
" - By Project: Groups entries by project within each date",
|
" - 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:",
|
"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