Command to backfill to last entry

This commit is contained in:
Ian Keane 2025-11-25 19:49:58 -05:00
parent 2abc99ff3b
commit e158378a13
2 changed files with 95 additions and 0 deletions

View file

@ -493,6 +493,17 @@ impl App {
self.delete_selected_frame()?; self.delete_selected_frame()?;
} }
} }
KeyCode::Char('b') => {
// Backfill - adjust start time to match previous entry's end time
// Only works in ByDate mode at Entry level
if !matches!(self.log_view_selection_level, LogViewSelection::Entry) {
self.set_status_message("Backfill only works at Entry level (use h/l)");
} else if !matches!(self.log_view_grouping, LogViewGrouping::ByDate) {
self.set_status_message("Backfill only works in By Date view (press g)");
} else {
self.fix_entry_gap()?;
}
}
KeyCode::Char('h') | KeyCode::Left => { KeyCode::Char('h') | KeyCode::Left => {
// Zoom out selection level // Zoom out selection level
self.log_view_selection_level = match (&self.log_view_selection_level, &self.log_view_grouping) { self.log_view_selection_level = match (&self.log_view_selection_level, &self.log_view_grouping) {
@ -824,6 +835,88 @@ impl App {
Ok(()) Ok(())
} }
fn fix_entry_gap(&mut self) -> anyhow::Result<()> {
use chrono::DateTime;
use serde_json::Value;
self.set_status_message("Backfill function called");
// Check if selection is valid
if self.log_view_selected >= self.log_view_frame_indices.len() {
self.set_status_message("Invalid selection");
return Ok(());
}
// Can't fix the first entry
if self.log_view_selected == 0 {
self.set_status_message("No previous entry!");
return Ok(());
}
// Check if previous entry is on the same day
let current_frame_idx = self.log_view_frame_indices[self.log_view_selected];
let prev_frame_idx = self.log_view_frame_indices[self.log_view_selected - 1];
if let (Some(current), Some(prev)) = (
self.log_view_frames.get(current_frame_idx),
self.log_view_frames.get(prev_frame_idx),
) {
// Parse timestamps
if let (Ok(curr_start), Ok(prev_stop)) = (
DateTime::parse_from_rfc3339(&current.start),
DateTime::parse_from_rfc3339(&prev.stop),
) {
// Check if they're on the same day
let curr_date = curr_start.format("%Y-%m-%d").to_string();
let prev_date = prev_stop.format("%Y-%m-%d").to_string();
if curr_date != prev_date {
self.set_status_message("No previous entry on same day!");
return Ok(());
}
// Read watson frames file
let frames_path = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
.join("watson")
.join("frames");
let frames_content = std::fs::read_to_string(&frames_path)?;
let mut frames: Value = serde_json::from_str(&frames_content)?;
// Find and update the frame with matching id
if let Some(frames_array) = frames.as_array_mut() {
for frame in frames_array {
if let Some(frame_array) = frame.as_array_mut() {
// Frame format: [start, stop, project, id, tags, ...]
if frame_array.len() > 3 {
if let Some(id) = frame_array[3].as_str() {
if id == current.id {
// Update start timestamp (index 0)
let new_start_timestamp = prev_stop.timestamp();
frame_array[0] = Value::Number(new_start_timestamp.into());
break;
}
}
}
}
}
}
// Write back to frames file
let updated_content = serde_json::to_string_pretty(&frames)?;
std::fs::write(&frames_path, updated_content)?;
// Reload log content
self.load_log_content()?;
self.set_status_message("Entry start time adjusted");
}
}
self.needs_clear = true;
Ok(())
}
fn copy_log_to_clipboard(&mut self) -> anyhow::Result<()> { fn copy_log_to_clipboard(&mut self) -> anyhow::Result<()> {
if self.log_view_content.is_empty() { if self.log_view_content.is_empty() {
return Ok(()); return Ok(());

View file

@ -955,6 +955,8 @@ fn render_log_view_help(frame: &mut Frame, app: &App) {
"Actions:", "Actions:",
"- e: Edit the selected entry (Entry level only)", "- e: Edit the selected entry (Entry level only)",
"- x: Delete the selected entry (Entry level only)", "- x: Delete the selected entry (Entry level only)",
"- b: Backfill - set entry start time to previous entry's end time",
" (Entry level, By Date view only)",
"- c: Copy selection to clipboard (works at all levels)", "- c: Copy selection to clipboard (works at all levels)",
" Copies based on current selection level", " Copies based on current selection level",
"", "",