Enhanced BulkAction: 7 options, arrow navigation, PickList, visual selection
- Added: Mark as uncomplete, Set due Tomorrow, Set due Next Week - Added: Move to existing list (PickList popup) - ↑/↓ navigate options, Enter executes, 1-7 for number shortcuts - Selected tasks now show bg(DarkGray) for clearer visual feedback - BulkAction popup with 7 options, PickList popup for list selection
This commit is contained in:
+177
-11
@@ -54,6 +54,9 @@ pub struct App {
|
||||
pending_new_key: bool,
|
||||
pending_bulk_move: bool,
|
||||
pub selected_tasks: BTreeSet<usize>,
|
||||
pub bulk_action_selected: usize,
|
||||
pub popup_list_indices: Vec<(String, String)>,
|
||||
pub popup_list_selected: usize,
|
||||
auth_tx: std_mpsc::Sender<AuthEvent>,
|
||||
auth_rx: std_mpsc::Receiver<AuthEvent>,
|
||||
sync_tx: mpsc::Sender<SyncCommand>,
|
||||
@@ -127,6 +130,9 @@ impl App {
|
||||
pending_new_key: false,
|
||||
pending_bulk_move: false,
|
||||
selected_tasks: BTreeSet::new(),
|
||||
bulk_action_selected: 0,
|
||||
popup_list_indices: Vec::new(),
|
||||
popup_list_selected: 0,
|
||||
auth_tx,
|
||||
auth_rx,
|
||||
sync_tx,
|
||||
@@ -849,19 +855,52 @@ impl App {
|
||||
KeyCode::Esc => {
|
||||
self.show_popup = None;
|
||||
}
|
||||
KeyCode::Char('1') => {
|
||||
self.bulk_mark_completed();
|
||||
self.show_popup = None;
|
||||
KeyCode::Up => {
|
||||
self.bulk_action_selected = self.bulk_action_selected.saturating_sub(1);
|
||||
}
|
||||
KeyCode::Char('2') => {
|
||||
self.bulk_set_due_today();
|
||||
self.show_popup = None;
|
||||
KeyCode::Down => {
|
||||
if self.bulk_action_selected < 6 {
|
||||
self.bulk_action_selected += 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Char('3') => {
|
||||
self.popup_input.clear();
|
||||
self.popup_cursor = 0;
|
||||
self.pending_bulk_move = true;
|
||||
self.show_popup = Some(Popup::Input);
|
||||
KeyCode::Enter => {
|
||||
let action = self.bulk_action_selected;
|
||||
self.execute_bulk_action(action);
|
||||
if action <= 4 {
|
||||
self.show_popup = None;
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
if let Some(n) = c.to_digit(10) {
|
||||
let idx = n as usize - 1;
|
||||
if idx <= 6 {
|
||||
self.execute_bulk_action(idx);
|
||||
if idx <= 4 {
|
||||
self.show_popup = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Popup::PickList => match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.show_popup = Some(Popup::BulkAction);
|
||||
}
|
||||
KeyCode::Up => {
|
||||
self.popup_list_selected = self.popup_list_selected.saturating_sub(1);
|
||||
}
|
||||
KeyCode::Down => {
|
||||
if self.popup_list_selected + 1 < self.popup_list_indices.len() {
|
||||
self.popup_list_selected += 1;
|
||||
}
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if !self.popup_list_indices.is_empty() {
|
||||
let list_id = self.popup_list_indices[self.popup_list_selected].1.clone();
|
||||
self.bulk_move_to_existing_list(&list_id);
|
||||
self.show_popup = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -963,6 +1002,133 @@ impl App {
|
||||
self.load_tasks();
|
||||
}
|
||||
|
||||
fn bulk_mark_uncomplete(&mut self) {
|
||||
let indices: Vec<usize> = self.selected_tasks.iter().copied().collect();
|
||||
for &i in &indices {
|
||||
if i >= self.tasks.len() { continue; }
|
||||
let task = &mut self.tasks[i];
|
||||
task.status = TaskStatus::NeedsAction;
|
||||
self.db.update_task(task).ok();
|
||||
self.db.push_sync(
|
||||
SyncAction::Update,
|
||||
&task.id,
|
||||
&task.list_id,
|
||||
&serde_json::to_string(task).unwrap_or_default(),
|
||||
).ok();
|
||||
}
|
||||
self.clear_selection();
|
||||
self.trigger_sync();
|
||||
self.load_tasks();
|
||||
}
|
||||
|
||||
fn bulk_set_due_tomorrow(&mut self) {
|
||||
let tomorrow = chrono::Local::now().naive_local() + chrono::Duration::days(1);
|
||||
let time = NaiveTime::from_hms_opt(9, 0, 0).unwrap();
|
||||
let due = chrono::NaiveDateTime::new(tomorrow.date(), time);
|
||||
let indices: Vec<usize> = self.selected_tasks.iter().copied().collect();
|
||||
for &i in &indices {
|
||||
if i >= self.tasks.len() { continue; }
|
||||
let task = &mut self.tasks[i];
|
||||
task.due = Some(due);
|
||||
self.db.update_task(task).ok();
|
||||
self.db.push_sync(
|
||||
SyncAction::Update,
|
||||
&task.id,
|
||||
&task.list_id,
|
||||
&serde_json::to_string(task).unwrap_or_default(),
|
||||
).ok();
|
||||
}
|
||||
self.clear_selection();
|
||||
self.trigger_sync();
|
||||
self.load_tasks();
|
||||
}
|
||||
|
||||
fn bulk_set_due_next_week(&mut self) {
|
||||
let next_week = chrono::Local::now().naive_local() + chrono::Duration::days(7);
|
||||
let time = NaiveTime::from_hms_opt(9, 0, 0).unwrap();
|
||||
let due = chrono::NaiveDateTime::new(next_week.date(), time);
|
||||
let indices: Vec<usize> = self.selected_tasks.iter().copied().collect();
|
||||
for &i in &indices {
|
||||
if i >= self.tasks.len() { continue; }
|
||||
let task = &mut self.tasks[i];
|
||||
task.due = Some(due);
|
||||
self.db.update_task(task).ok();
|
||||
self.db.push_sync(
|
||||
SyncAction::Update,
|
||||
&task.id,
|
||||
&task.list_id,
|
||||
&serde_json::to_string(task).unwrap_or_default(),
|
||||
).ok();
|
||||
}
|
||||
self.clear_selection();
|
||||
self.trigger_sync();
|
||||
self.load_tasks();
|
||||
}
|
||||
|
||||
fn bulk_move_to_existing_list(&mut self, target_list_id: &str) {
|
||||
let indices: Vec<usize> = self.selected_tasks.iter().copied().collect();
|
||||
for &i in &indices {
|
||||
if i >= self.tasks.len() { continue; }
|
||||
let original = &self.tasks[i];
|
||||
let new_task = Task {
|
||||
id: uuid_v4(),
|
||||
list_id: target_list_id.to_string(),
|
||||
title: original.title.clone(),
|
||||
notes: original.notes.clone(),
|
||||
status: original.status.clone(),
|
||||
due: original.due,
|
||||
position: 0,
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
};
|
||||
self.db.insert_task(&new_task).ok();
|
||||
self.db.push_sync(
|
||||
SyncAction::Create,
|
||||
&new_task.id,
|
||||
target_list_id,
|
||||
&serde_json::to_string(&new_task).unwrap_or_default(),
|
||||
).ok();
|
||||
|
||||
self.db.delete_task(&original.id).ok();
|
||||
self.db.push_sync(
|
||||
SyncAction::Delete,
|
||||
&original.id,
|
||||
&original.list_id,
|
||||
"",
|
||||
).ok();
|
||||
}
|
||||
self.clear_selection();
|
||||
self.trigger_sync();
|
||||
if let Some(pos) = self.lists.iter().position(|l| l.id == target_list_id) {
|
||||
self.selected_list = pos;
|
||||
}
|
||||
self.load_tasks();
|
||||
}
|
||||
|
||||
fn execute_bulk_action(&mut self, action_idx: usize) {
|
||||
match action_idx {
|
||||
0 => self.bulk_mark_completed(),
|
||||
1 => self.bulk_mark_uncomplete(),
|
||||
2 => self.bulk_set_due_today(),
|
||||
3 => self.bulk_set_due_tomorrow(),
|
||||
4 => self.bulk_set_due_next_week(),
|
||||
5 => {
|
||||
self.popup_input.clear();
|
||||
self.popup_cursor = 0;
|
||||
self.pending_bulk_move = true;
|
||||
self.show_popup = Some(Popup::Input);
|
||||
}
|
||||
6 => {
|
||||
self.popup_list_indices = self.lists.iter()
|
||||
.map(|l| (l.title.clone(), l.id.clone()))
|
||||
.collect();
|
||||
self.popup_list_selected = 0;
|
||||
self.show_popup = Some(Popup::PickList);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn reorder_task(&mut self, direction: i64) {
|
||||
if self.tasks.is_empty() {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user