Multi-select tasks with Shift+Arrows and bulk actions
- Shift+Up/Down extends selection in task list - Enter opens BulkAction popup with 3 options: 1. Mark as completed 2. Set due date to Today 3. Move to new list (creates list, copies tasks, deletes originals) - Plain Up/Down clears selection, Escape clears it - Selected items highlighted in Yellow
This commit is contained in:
+66
-19
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use chrono::Datelike;
|
||||
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
@@ -76,6 +78,7 @@ pub fn render_task_list(
|
||||
selected: usize,
|
||||
focused: bool,
|
||||
_scroll: u16,
|
||||
selected_tasks: &BTreeSet<usize>,
|
||||
) {
|
||||
let total = tasks.len();
|
||||
let done = tasks.iter().filter(|t| t.status == TaskStatus::Completed).count();
|
||||
@@ -85,7 +88,9 @@ pub fn render_task_list(
|
||||
|
||||
let items: Vec<ListItem> = tasks
|
||||
.iter()
|
||||
.map(|task| {
|
||||
.enumerate()
|
||||
.map(|(idx, task)| {
|
||||
let is_selected = selected_tasks.contains(&idx);
|
||||
let checkbox = match task.status {
|
||||
TaskStatus::Completed => "[\u{2713}]",
|
||||
TaskStatus::NeedsAction => "[ ]",
|
||||
@@ -121,25 +126,29 @@ pub fn render_task_list(
|
||||
content_width.saturating_sub(used)
|
||||
};
|
||||
|
||||
let mut spans = vec![
|
||||
Span::styled(
|
||||
checkbox_str,
|
||||
Style::default().fg(if task.status == TaskStatus::Completed {
|
||||
Color::Green
|
||||
let checkbox_style = if is_selected {
|
||||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
} else if task.status == TaskStatus::Completed {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
};
|
||||
|
||||
let title_style = if is_selected {
|
||||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(DETAIL_COLOR).add_modifier(
|
||||
if task.status == TaskStatus::Completed {
|
||||
Modifier::CROSSED_OUT
|
||||
} else {
|
||||
Color::DarkGray
|
||||
}),
|
||||
),
|
||||
Span::styled(
|
||||
display_title,
|
||||
Style::default().fg(DETAIL_COLOR).add_modifier(
|
||||
if task.status == TaskStatus::Completed {
|
||||
Modifier::CROSSED_OUT
|
||||
} else {
|
||||
Modifier::empty()
|
||||
},
|
||||
),
|
||||
),
|
||||
Modifier::empty()
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
let mut spans = vec![
|
||||
Span::styled(checkbox_str, checkbox_style),
|
||||
Span::styled(display_title, title_style),
|
||||
];
|
||||
|
||||
if !due_text.is_empty() {
|
||||
@@ -498,6 +507,44 @@ pub fn render_confirm_popup(frame: &mut Frame, area: Rect) {
|
||||
frame.render_widget(paragraph, popup_area);
|
||||
}
|
||||
|
||||
pub fn render_bulk_action_popup(frame: &mut Frame, area: Rect, count: usize) {
|
||||
let popup_area = centered_rect(55, 9, area);
|
||||
frame.render_widget(Clear, popup_area);
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().bg(POPUP_BG))
|
||||
.border_style(Style::default().fg(POPUP_BORDER))
|
||||
.title(format!(" Bulk Actions ({} selected) ", count))
|
||||
.title_alignment(Alignment::Left);
|
||||
|
||||
let text = Text::from(vec![
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
" 1. Mark as completed",
|
||||
Style::default().fg(Color::Cyan),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
" 2. Set due date to Today",
|
||||
Style::default().fg(Color::Cyan),
|
||||
)),
|
||||
Line::from(Span::styled(
|
||||
" 3. Move to new list...",
|
||||
Style::default().fg(Color::Cyan),
|
||||
)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
" Press 1-3 or Esc to cancel",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)),
|
||||
]);
|
||||
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(block)
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
frame.render_widget(paragraph, popup_area);
|
||||
}
|
||||
|
||||
pub fn render_device_auth_popup(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
|
||||
Reference in New Issue
Block a user