feat & fix: Add status bar with sync button and fix filter bugs
Added a status bar at the bottom with a sync button that updates the task list from the local database. The button shows different states (idle, syncing, success, error) with colors and icons, and displays status messages. Fixed the layout shift bug when clearing filters by giving dropdowns a minimum width. Also fixed sidebar counts not updating when filters change - now the sidebar shows correct project and tag counts based on filtered tasks. Extracted reusable button and dropdown styles to keep the UI consistent across components.
This commit is contained in:
+101
-7
@@ -1,11 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use gpui::prelude::*;
|
||||
|
||||
use crate::models::{FilterState, ProjectTree};
|
||||
use crate::task::{TaskOverview, TaskService};
|
||||
use crate::task::{self, TaskFilter, TaskOverview, TaskService};
|
||||
use crate::theme::ActiveTheme;
|
||||
use crate::ui::{card_style, SECTION_GAP, ROOT_PADDING};
|
||||
use crate::ui::{ROOT_PADDING, SECTION_GAP, card_style};
|
||||
use crate::view::sidebar::{Sidebar, TagItem};
|
||||
use crate::view::status_bar::StatusBar;
|
||||
use crate::view::status_bar::{StatusBar, StatusBarEvent, SyncState};
|
||||
use crate::view::task_table::TaskTable;
|
||||
use gpui::div;
|
||||
|
||||
@@ -61,11 +63,96 @@ impl gpui::Render for App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn build_sidebar_data(tasks: &[task::Task]) -> (Vec<(String, usize)>, Vec<TagItem>) {
|
||||
let mut project_counts: HashMap<String, usize> = HashMap::new();
|
||||
let mut tag_counts: HashMap<String, usize> = HashMap::new();
|
||||
|
||||
for task in tasks {
|
||||
if let Some(project) = &task.project {
|
||||
*project_counts.entry(project.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
for tag in &task.tags {
|
||||
*tag_counts.entry(tag.clone()).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut projects: Vec<(String, usize)> = project_counts.into_iter().collect();
|
||||
projects.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||
|
||||
let mut tags: Vec<(String, usize)> = tag_counts.into_iter().collect();
|
||||
tags.sort_by(|a, b| a.0.to_lowercase().cmp(&b.0.to_lowercase()));
|
||||
|
||||
let tag_items = tags
|
||||
.into_iter()
|
||||
.map(|(name, task_count)| TagItem { name, task_count })
|
||||
.collect();
|
||||
|
||||
(projects, tag_items)
|
||||
}
|
||||
|
||||
fn reload_tasks(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
let task_service = &mut self.task_service;
|
||||
self.task_table.update(cx, |table, cx| {
|
||||
table.reload_tasks(task_service, cx);
|
||||
let all_tasks = self.task_service.get_all_tasks().unwrap_or_else(|e| {
|
||||
log::error!("[App] Failed to load tasks: {}", e);
|
||||
vec![]
|
||||
});
|
||||
|
||||
let filter_state = self.filter_state.read(cx).clone();
|
||||
let task_filter = TaskFilter::from(&filter_state);
|
||||
let filtered_tasks = task_filter.apply(&all_tasks);
|
||||
let (projects, tags) = Self::build_sidebar_data(&filtered_tasks);
|
||||
|
||||
let mut project_tree = ProjectTree::new();
|
||||
project_tree.build_from_projects(&projects);
|
||||
|
||||
self.sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.update_projects(project_tree, cx);
|
||||
sidebar.update_tags(tags, cx);
|
||||
});
|
||||
|
||||
self.task_table.update(cx, |table, cx| {
|
||||
table.reload_tasks_from_all(all_tasks, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_sync(&mut self, cx: &mut gpui::Context<Self>) {
|
||||
self.status_bar.update(cx, |bar, cx| {
|
||||
bar.set_sync_state(SyncState::Syncing, cx);
|
||||
bar.set_last_sync_message("Syncing...".to_string(), cx);
|
||||
});
|
||||
|
||||
match self.task_service.get_all_tasks() {
|
||||
Ok(all_tasks) => {
|
||||
let filter_state = self.filter_state.read(cx).clone();
|
||||
let task_filter = TaskFilter::from(&filter_state);
|
||||
let filtered_tasks = task_filter.apply(&all_tasks);
|
||||
let (projects, tags) = Self::build_sidebar_data(&filtered_tasks);
|
||||
|
||||
let mut project_tree = ProjectTree::new();
|
||||
project_tree.build_from_projects(&projects);
|
||||
|
||||
self.sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.update_projects(project_tree, cx);
|
||||
sidebar.update_tags(tags, cx);
|
||||
});
|
||||
|
||||
self.task_table.update(cx, |table, cx| {
|
||||
table.reload_tasks_from_all(all_tasks, cx);
|
||||
});
|
||||
|
||||
self.status_bar.update(cx, |bar, cx| {
|
||||
bar.set_sync_state(SyncState::Success, cx);
|
||||
bar.set_last_sync_message("Synced".to_string(), cx);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[App] Sync failed: {}", e);
|
||||
self.status_bar.update(cx, |bar, cx| {
|
||||
bar.set_sync_state(SyncState::Error, cx);
|
||||
bar.set_last_sync_message(format!("Error: {}", e), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> () {
|
||||
@@ -118,7 +205,7 @@ impl App {
|
||||
let app = App {
|
||||
sidebar,
|
||||
filter_state: filter_state.clone(),
|
||||
status_bar,
|
||||
status_bar: status_bar.clone(),
|
||||
task_table,
|
||||
task_service,
|
||||
};
|
||||
@@ -128,6 +215,13 @@ impl App {
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.subscribe(&status_bar, |app, _bar, event, cx| match event {
|
||||
StatusBarEvent::SyncRequested => {
|
||||
app.handle_sync(cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
app
|
||||
})
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user