Files
task_app_rust/src/ui/mod.rs
T
Ruben Rosario 6eee90f128 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
2026-06-21 14:21:14 +01:00

103 lines
2.7 KiB
Rust

pub mod components;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Frame;
use crate::app::SyncStats;
use crate::domain::models::*;
use components::*;
#[derive(Debug, Clone, PartialEq)]
pub enum Focus {
Tabs,
TaskList,
Detail,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Popup {
Input,
DatePicker,
ConfirmDelete,
DeviceAuth { url: String, code: String },
}
#[derive(Debug, Clone, PartialEq)]
pub enum NetworkStatus {
Online,
Offline,
Syncing,
}
pub struct AppView<'a> {
pub lists: &'a [TaskList],
pub tasks: &'a [Task],
pub selected_list: usize,
pub selected_task: usize,
pub focus: Focus,
pub show_popup: Option<&'a Popup>,
pub popup_input: &'a str,
pub popup_cursor: usize,
pub draft_date: chrono::NaiveDateTime,
pub network_status: &'a NetworkStatus,
pub task_list_scroll: u16,
pub detail_scroll: u16,
pub auth_error: Option<&'a str>,
pub sync_stats: &'a SyncStats,
}
pub fn draw(frame: &mut Frame, view: AppView) {
let area = frame.area();
let main_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(1),
])
.split(area);
let tabs_area = main_layout[0];
let body_area = main_layout[1];
let status_area = main_layout[2];
let is_tabs_focused = view.focus == Focus::Tabs;
render_tabs_bar(frame, tabs_area, view.lists, view.selected_list, is_tabs_focused);
let body_layout = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(body_area);
let is_task_list_focused = view.focus == Focus::TaskList;
render_task_list(
frame,
body_layout[0],
view.tasks,
view.selected_task,
is_task_list_focused,
view.task_list_scroll,
);
let is_detail_focused = view.focus == Focus::Detail;
render_detail(
frame,
body_layout[1],
view.tasks.get(view.selected_task),
is_detail_focused,
view.detail_scroll,
);
render_status_bar(frame, status_area, view.network_status, view.sync_stats);
if let Some(popup) = view.show_popup {
match popup {
Popup::Input => render_input_popup(frame, area, view.popup_input, view.popup_cursor),
Popup::DatePicker => render_date_picker(frame, area, view.draft_date),
Popup::ConfirmDelete => render_confirm_popup(frame, area),
Popup::DeviceAuth { url, code } => render_device_auth_popup(frame, area, url, code, view.auth_error),
}
}
}