use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Row, Table}, }; use crate::app::AppState; pub fn render(f: &mut Frame, state: &AppState, area: Rect) { let Some(ref stats) = state.library_stats else { let msg = Paragraph::new("Loading statistics... (press X to refresh)") .block(Block::default().borders(Borders::ALL).title(" Statistics ")); f.render_widget(msg, area); return; }; let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(8), // Overview Constraint::Length(10), // Media by type Constraint::Min(6), // Top tags & collections ]) .split(area); // Overview section let overview_lines = vec![ Line::from(vec![ Span::styled(" Total Media: ", Style::default().fg(Color::Gray)), Span::styled( stats.total_media.to_string(), Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), ), Span::raw(" "), Span::styled("Total Size: ", Style::default().fg(Color::Gray)), Span::styled( super::format_size(stats.total_size_bytes), Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), ), ]), Line::from(vec![ Span::styled(" Avg Size: ", Style::default().fg(Color::Gray)), Span::styled( super::format_size(stats.avg_file_size_bytes), Style::default().fg(Color::White), ), ]), Line::from(vec![ Span::styled(" Tags: ", Style::default().fg(Color::Gray)), Span::styled( stats.total_tags.to_string(), Style::default().fg(Color::Green), ), Span::raw(" "), Span::styled("Collections: ", Style::default().fg(Color::Gray)), Span::styled( stats.total_collections.to_string(), Style::default().fg(Color::Green), ), Span::raw(" "), Span::styled("Duplicates: ", Style::default().fg(Color::Gray)), Span::styled( stats.total_duplicates.to_string(), Style::default().fg(Color::Yellow), ), ]), Line::from(vec![ Span::styled(" Newest: ", Style::default().fg(Color::Gray)), Span::styled( stats.newest_item.as_deref().map_or("-", super::format_date), Style::default().fg(Color::White), ), Span::raw(" "), Span::styled("Oldest: ", Style::default().fg(Color::Gray)), Span::styled( stats.oldest_item.as_deref().map_or("-", super::format_date), Style::default().fg(Color::White), ), ]), ]; let overview = Paragraph::new(overview_lines) .block(Block::default().borders(Borders::ALL).title(" Overview ")); f.render_widget(overview, chunks[0]); // Media by Type table let type_rows: Vec = stats .media_by_type .iter() .map(|tc| { let color = super::media_type_color(&tc.name); Row::new(vec![ Span::styled(tc.name.clone(), Style::default().fg(color)), Span::styled(tc.count.to_string(), Style::default().fg(Color::White)), ]) }) .collect(); let storage_rows: Vec = stats .storage_by_type .iter() .map(|tc| { Row::new(vec![ Span::styled(tc.name.clone(), Style::default().fg(Color::Gray)), Span::styled( super::format_size(tc.count), Style::default().fg(Color::White), ), ]) }) .collect(); let type_cols = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(chunks[1]); let type_table = Table::new(type_rows, [Constraint::Min(20), Constraint::Length(10)]).block( Block::default() .borders(Borders::ALL) .title(" Media by Type "), ); f.render_widget(type_table, type_cols[0]); let storage_table = Table::new(storage_rows, [Constraint::Min(20), Constraint::Length(12)]) .block( Block::default() .borders(Borders::ALL) .title(" Storage by Type "), ); f.render_widget(storage_table, type_cols[1]); // Top tags and collections let bottom_cols = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(chunks[2]); let tag_rows: Vec = stats .top_tags .iter() .map(|tc| { Row::new(vec![ Span::styled(tc.name.clone(), Style::default().fg(Color::Green)), Span::styled(tc.count.to_string(), Style::default().fg(Color::White)), ]) }) .collect(); let tags_table = Table::new(tag_rows, [Constraint::Min(20), Constraint::Length(10)]) .block(Block::default().borders(Borders::ALL).title(" Top Tags ")); f.render_widget(tags_table, bottom_cols[0]); let col_rows: Vec = stats .top_collections .iter() .map(|tc| { Row::new(vec![ Span::styled(tc.name.clone(), Style::default().fg(Color::Magenta)), Span::styled(tc.count.to_string(), Style::default().fg(Color::White)), ]) }) .collect(); let cols_table = Table::new(col_rows, [Constraint::Min(20), Constraint::Length(10)]).block( Block::default() .borders(Borders::ALL) .title(" Top Collections "), ); f.render_widget(cols_table, bottom_cols[1]); }