Add task count to task list panel header

Show 'X todo / Y done' in the Tasks panel title bar.

Also includes prior uncommitted work:
- Pagination in fetch_tasks (maxResults=100 + pageToken loop)
- fetch_tasks_since for incremental pull sync
- SyncStats struct with version/last_sync/last_pull/changed counts
- Periodic push (30s) and pull (5min) sync engine
- event::poll(100ms) for non-blocking UI refresh
- Ctrl+R full sync (push + pull)
- refresh_if_needed() to reload data after background sync
- Retry mechanism (MAX_SYNC_RETRIES=3) for sync queue items
- HTTP status code checks in fetch_lists/fetch_tasks/fetch_tasks_since
- Fix move_task URL to use reqwest query()
- Remove CASCADE via replace_all_lists (use insert_list instead)
- has_pending_sync() to prevent pull during pending push
This commit is contained in:
Ruben Rosario
2026-06-21 14:21:14 +01:00
parent ae9910bcbc
commit 6eee90f128
7 changed files with 394 additions and 69 deletions
+30 -3
View File
@@ -5,6 +5,7 @@ use ratatui::layout::{Alignment, Rect};
use ratatui::Frame;
use crate::domain::models::*;
use crate::app::SyncStats;
use super::NetworkStatus;
const TAB_COLOR: Color = Color::Cyan;
@@ -56,6 +57,10 @@ pub fn render_task_list(
focused: bool,
_scroll: u16,
) {
let total = tasks.len();
let done = tasks.iter().filter(|t| t.status == TaskStatus::Completed).count();
let todo = total - done;
let items: Vec<ListItem> = tasks
.iter()
.map(|task| {
@@ -100,7 +105,7 @@ pub fn render_task_list(
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(if focused { FOCUS_COLOR } else { Color::DarkGray }))
.title(" Tasks ")
.title(format!(" Tasks ({} todo / {} done) ", todo, done))
.title_alignment(Alignment::Left);
let list = List::new(items)
@@ -185,18 +190,40 @@ pub fn render_status_bar(
frame: &mut Frame,
area: Rect,
status: &NetworkStatus,
sync_stats: &SyncStats,
) {
let (text, color) = match status {
let (status_text, color) = match status {
NetworkStatus::Online => (" ONLINE ", STATUS_ONLINE),
NetworkStatus::Offline => (" OFFLINE ", STATUS_OFFLINE),
NetworkStatus::Syncing => (" SYNCING... ", STATUS_SYNC),
};
let right_text = match sync_stats.last_sync_time {
Some(time) => {
let time_str = time.format("%H:%M:%S").to_string();
let mut parts: Vec<String> = Vec::new();
if sync_stats.lists_changed > 0 {
parts.push(format!("{} lists", sync_stats.lists_changed));
}
if sync_stats.tasks_changed > 0 {
parts.push(format!("{} tasks", sync_stats.tasks_changed));
}
if parts.is_empty() {
format!(" {} last sync ", time_str)
} else {
format!(" {} last sync: {} ", time_str, parts.join(", "))
}
}
None => String::new(),
};
let full_text = format!("{}{}", status_text, right_text);
let block = Block::default()
.style(Style::default().bg(color).fg(Color::Black));
let paragraph = Paragraph::new(Line::from(Span::styled(
text,
full_text,
Style::default().fg(Color::Black).add_modifier(Modifier::BOLD),
)))
.block(block)