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:
+38
@@ -10,6 +10,15 @@ use crate::infrastructure::api::ApiClient;
|
||||
use crate::infrastructure::db::Db;
|
||||
use crate::ui::{Focus, NetworkStatus, Popup};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SyncStats {
|
||||
pub last_sync_time: Option<chrono::NaiveDateTime>,
|
||||
pub last_pull_time: Option<chrono::NaiveDateTime>,
|
||||
pub lists_changed: usize,
|
||||
pub tasks_changed: usize,
|
||||
pub version: u64,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub lists: Vec<TaskList>,
|
||||
pub tasks: Vec<Task>,
|
||||
@@ -29,6 +38,8 @@ pub struct App {
|
||||
pub api_client: Arc<ApiClient>,
|
||||
pub needs_auth: bool,
|
||||
pub auth_error: Option<String>,
|
||||
pub sync_stats: SyncStats,
|
||||
last_sync_version: u64,
|
||||
auth_tx: std_mpsc::Sender<AuthEvent>,
|
||||
auth_rx: std_mpsc::Receiver<AuthEvent>,
|
||||
sync_tx: mpsc::Sender<SyncCommand>,
|
||||
@@ -42,6 +53,7 @@ enum AuthEvent {
|
||||
#[allow(dead_code)]
|
||||
pub enum SyncCommand {
|
||||
TriggerSync,
|
||||
FullSync,
|
||||
InitialSync,
|
||||
Shutdown,
|
||||
}
|
||||
@@ -86,6 +98,8 @@ impl App {
|
||||
api_client,
|
||||
needs_auth: !has_token,
|
||||
auth_error: None,
|
||||
sync_stats: SyncStats::default(),
|
||||
last_sync_version: 0,
|
||||
auth_tx,
|
||||
auth_rx,
|
||||
sync_tx,
|
||||
@@ -142,10 +156,31 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_if_needed(&mut self) {
|
||||
if self.sync_stats.version != self.last_sync_version {
|
||||
self.last_sync_version = self.sync_stats.version;
|
||||
self.load_lists();
|
||||
if !self.lists.is_empty() && self.selected_list < self.lists.len() {
|
||||
self.tasks = self.db.get_tasks(&self.lists[self.selected_list].id);
|
||||
} else {
|
||||
self.tasks.clear();
|
||||
}
|
||||
if self.selected_task >= self.tasks.len() && !self.tasks.is_empty() {
|
||||
self.selected_task = self.tasks.len() - 1;
|
||||
} else if self.tasks.is_empty() {
|
||||
self.selected_task = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger_sync(&self) {
|
||||
let _ = self.sync_tx.try_send(SyncCommand::TriggerSync);
|
||||
}
|
||||
|
||||
fn trigger_full_sync(&self) {
|
||||
let _ = self.sync_tx.try_send(SyncCommand::FullSync);
|
||||
}
|
||||
|
||||
pub fn handle_key(&mut self, key: KeyEvent) {
|
||||
if let Some(ref popup) = self.show_popup.clone() {
|
||||
self.handle_popup_key(key, popup);
|
||||
@@ -238,6 +273,9 @@ impl App {
|
||||
KeyCode::Esc => {
|
||||
self.show_popup = None;
|
||||
}
|
||||
KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
self.trigger_full_sync();
|
||||
}
|
||||
KeyCode::Char('q') | KeyCode::Char('Q') => {
|
||||
self.should_quit = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user