use crate::model::SectionKind;
use crate::tui::app::App;
pub struct RecentEntry {
pub track_id: String,
pub id: String,
pub title: String,
pub resolved: String,
pub track_name: String,
pub task: crate::model::task::Task,
pub is_archived: bool,
}
pub fn build_recent_entries(app: &App) -> Vec<RecentEntry> {
let mut entries: Vec<RecentEntry> = Vec::new();
for (track_id, track) in &app.project.tracks {
let track_name = app.track_name(track_id).to_string();
for task in track.section_tasks(SectionKind::Done) {
push_done_entry(&mut entries, task, track_id, &track_name, false);
}
}
let archive_dir = app.project.frame_dir.join("archive");
if archive_dir.is_dir() {
for tc in &app.project.config.tracks {
let archive_path = archive_dir.join(format!("{}.md", tc.id));
if let Ok(text) = std::fs::read_to_string(&archive_path) {
let lines: Vec<String> = text.lines().map(String::from).collect();
let start = lines
.iter()
.position(|l| {
let t = l.trim_start();
t.starts_with("- [") && t.len() >= 5 && t.as_bytes()[4] == b']'
})
.unwrap_or(lines.len());
let (tasks, _) = crate::parse::parse_tasks(&lines, start, 0, 0);
let track_name = app.track_name(&tc.id).to_string();
for task in &tasks {
push_done_entry(&mut entries, task, &tc.id, &track_name, true);
}
}
}
}
entries.sort_by(|a, b| b.resolved.cmp(&a.resolved));
entries
}
fn resolved_date(task: &crate::model::task::Task) -> String {
task.metadata
.iter()
.find_map(|m| {
if let crate::model::task::Metadata::Resolved(d) = m {
Some(d.clone())
} else {
None
}
})
.unwrap_or_default()
}
fn push_done_entry(
entries: &mut Vec<RecentEntry>,
task: &crate::model::task::Task,
track_id: &str,
track_name: &str,
is_archived: bool,
) {
entries.push(RecentEntry {
track_id: track_id.to_string(),
id: task.id.clone().unwrap_or_default(),
title: task.title.clone(),
resolved: resolved_date(task),
track_name: track_name.to_string(),
task: task.clone(),
is_archived,
});
}
pub(super) fn open_recent_detail(app: &mut App) {
let entries = build_recent_entries(app);
let cursor = app.recent_cursor;
if let Some(entry) = entries.get(cursor) {
if entry.id.is_empty() {
app.status_message = Some("No task to view".to_string());
return;
}
if entry.is_archived {
app.status_message = Some("Cannot view archived task".to_string());
return;
}
app.open_detail(entry.track_id.clone(), entry.id.clone());
}
}
pub(super) fn expand_recent(app: &mut App) {
let entries = build_recent_entries(app);
let cursor = app.recent_cursor;
if let Some(entry) = entries.get(cursor)
&& !entry.task.subtasks.is_empty()
{
app.recent_expanded.insert(entry.id.clone());
}
}
pub(super) fn collapse_recent(app: &mut App) {
let entries = build_recent_entries(app);
let cursor = app.recent_cursor;
if let Some(entry) = entries.get(cursor) {
app.recent_expanded.remove(&entry.id);
}
}