rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
use crossterm::event::KeyCode;
use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::{Color, Modifier, Style},
    widgets::{Block, Borders, Cell, Paragraph, Row, Table, TableState},
    Frame,
};

use crate::history::HistoryEntry;

pub struct HistoryPanel {
    pub entries: Vec<HistoryEntry>,
    pub state: TableState,
}

impl HistoryPanel {
    pub fn new(entries: Vec<HistoryEntry>) -> Self {
        let mut state = TableState::default();
        if !entries.is_empty() {
            state.select(Some(0));
        }
        Self { entries, state }
    }

    pub fn handle_key(&mut self, code: KeyCode) {
        match code {
            KeyCode::Char('j') | KeyCode::Down => self.next(1),
            KeyCode::Char('k') | KeyCode::Up => self.prev(1),
            KeyCode::PageDown => self.next(10),
            KeyCode::PageUp => self.prev(10),
            _ => {}
        }
    }

    fn next(&mut self, step: usize) {
        let len = self.entries.len();
        if len == 0 { return; }
        let i = self.state.selected().map_or(0, |i| (i + step).min(len - 1));
        self.state.select(Some(i));
    }

    fn prev(&mut self, step: usize) {
        let i = self.state.selected().map_or(0, |i| i.saturating_sub(step));
        self.state.select(Some(i));
    }

    pub fn render(&self, f: &mut Frame, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Min(0), Constraint::Length(3)])
            .split(area);

        let rows: Vec<Row> = self
            .entries
            .iter()
            .map(|e| {
                let timestamp = e.timestamp.format("%Y-%m-%d %H:%M").to_string();
                let pkg_summary: String = e
                    .packages
                    .iter()
                    .take(3)
                    .map(|p| {
                        if let Some(ref nv) = p.new_version {
                            format!("{}{}", p.name, nv)
                        } else {
                            p.name.clone()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(", ");

                let summary = if e.packages.len() > 3 {
                    format!("{} (+{})", pkg_summary, e.packages.len() - 3)
                } else {
                    pkg_summary
                };

                Row::new(vec![
                    Cell::from(format!("#{}", e.id)),
                    Cell::from(timestamp),
                    Cell::from(e.operation.clone()),
                    Cell::from(summary),
                ])
            })
            .collect();

        let widths = [
            Constraint::Length(5),
            Constraint::Length(18),
            Constraint::Length(12),
            Constraint::Min(30),
        ];

        let table = Table::new(rows, widths)
            .header(
                Row::new(vec!["ID", "Time", "Operation", "Packages"])
                    .style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
            )
            .block(Block::default().borders(Borders::ALL).title(" Transaction History "))
            .row_highlight_style(Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD))
            .highlight_symbol("");

        let mut state = self.state.clone();
        f.render_stateful_widget(table, chunks[0], &mut state);

        let actions = Paragraph::new("  Enter: expand details  u: undo transaction  q: back  [?] Help")
            .block(Block::default().borders(Borders::ALL).title(" Actions "));
        f.render_widget(actions, chunks[1]);
    }
}