kanban-tui 0.9.0

Terminal user interface for the kanban project management tool
Documentation
use crate::app::{App, DialogMode, Focus};
use crossterm::event::KeyCode;
use kanban_domain::CardFilters;
use kanban_view::filters::{FilterDialogSection, FilterDialogState};

impl App {
    pub fn handle_open_filter_dialog(&mut self) {
        if self.focus.active != Focus::Cards || self.selection.active_board_id.is_none() {
            return;
        }

        let filters = CardFilters {
            show_unassigned_sprints: self.filter.hide_assigned_cards,
            selected_sprint_ids: self.filter.active_sprint_filters.clone(),
            date_from: None,
            date_to: None,
            selected_tags: Default::default(),
        };

        self.filter.dialog_state = Some(FilterDialogState::new(filters));
        self.open_dialog(DialogMode::FilterOptions);
    }

    pub fn handle_filter_options_popup(&mut self, key_code: KeyCode) {
        use crossterm::event::KeyCode;

        if let Some(ref mut dialog_state) = self.filter.dialog_state {
            match key_code {
                KeyCode::Esc => {
                    self.filter.dialog_state = None;
                    self.pop_mode();
                }
                KeyCode::Char('j') | KeyCode::Down => match dialog_state.current_section {
                    FilterDialogSection::Sprints => {
                        if let Some(board_id) = self.selection.active_board_id.and_then(|id| {
                            self.model
                                .board_by_id_state(id)
                                .loaded()
                                .copied()
                                .map(|b| b.id)
                        }) {
                            {
                                let sprint_count = self
                                    .model
                                    .sprints()
                                    .iter()
                                    .filter(|s| s.board_id == board_id)
                                    .count();
                                let total_items = 1 + sprint_count;
                                if dialog_state.item_selection < total_items.saturating_sub(1) {
                                    dialog_state.item_selection += 1;
                                } else {
                                    dialog_state.next_section();
                                }
                            }
                        }
                    }
                    _ => {
                        dialog_state.next_section();
                    }
                },
                KeyCode::Char('k') | KeyCode::Up => match dialog_state.current_section {
                    FilterDialogSection::Sprints if dialog_state.item_selection > 0 => {
                        dialog_state.item_selection -= 1;
                    }
                    _ => {
                        dialog_state.prev_section();
                    }
                },
                KeyCode::Char(' ') => {
                    if dialog_state.current_section == FilterDialogSection::Sprints {
                        if dialog_state.item_selection == 0 {
                            dialog_state.filters.show_unassigned_sprints =
                                !dialog_state.filters.show_unassigned_sprints;
                            tracing::info!(
                                "Toggled unassigned sprints filter: {}",
                                dialog_state.filters.show_unassigned_sprints
                            );
                            self.apply_filters();
                        } else if let Some(board) = self
                            .selection
                            .active_board_id
                            .and_then(|id| self.model.board_by_id_state(id).loaded().copied())
                        {
                            {
                                let sprints = self.model.sprints();
                                let board_sprints: Vec<_> =
                                    sprints.iter().filter(|s| s.board_id == board.id).collect();

                                let sprint_idx = dialog_state.item_selection - 1;
                                if let Some(sprint) = board_sprints.get(sprint_idx) {
                                    if dialog_state
                                        .filters
                                        .selected_sprint_ids
                                        .contains(&sprint.id)
                                    {
                                        dialog_state.filters.selected_sprint_ids.remove(&sprint.id);
                                    } else {
                                        dialog_state.filters.selected_sprint_ids.insert(sprint.id);
                                    }
                                    tracing::info!(
                                        "Toggled sprint: {}",
                                        sprint.formatted_name(board, None)
                                    );
                                    self.apply_filters();
                                }
                            }
                        }
                    }
                }
                KeyCode::Enter => {
                    self.apply_filters();
                    self.filter.dialog_state = None;
                    self.pop_mode();
                }
                _ => {}
            }
        }
    }

    fn apply_filters(&mut self) {
        if let Some(dialog_state) = &self.filter.dialog_state {
            self.filter.hide_assigned_cards = dialog_state.filters.show_unassigned_sprints;
            self.filter.active_sprint_filters = dialog_state.filters.selected_sprint_ids.clone();
            tracing::info!(
                "Applied filters: unassigned={}, sprints={}",
                self.filter.hide_assigned_cards,
                self.filter.active_sprint_filters.len()
            );
        }
    }
}