use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Row, Table, Tabs}, }; use crate::app::{AppState, BooksSubView}; pub fn render(f: &mut Frame, state: &AppState, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), // Sub-tab headers Constraint::Min(0), // Content area ]) .split(area); render_sub_tabs(f, state, chunks[0]); match state.books_sub_view { BooksSubView::List => render_book_list(f, state, chunks[1]), BooksSubView::Series => render_series(f, state, chunks[1]), BooksSubView::Authors => render_authors(f, state, chunks[1]), } } fn render_sub_tabs(f: &mut Frame, state: &AppState, area: Rect) { let titles: Vec = vec!["List", "Series", "Authors"] .into_iter() .map(|t| Line::from(Span::styled(t, Style::default().fg(Color::White)))) .collect(); let selected = match state.books_sub_view { BooksSubView::List => 0, BooksSubView::Series => 1, BooksSubView::Authors => 2, }; let tabs = Tabs::new(titles) .block(Block::default().borders(Borders::ALL).title(" Books ")) .select(selected) .style(Style::default().fg(Color::Gray)) .highlight_style( Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), ); f.render_widget(tabs, area); } fn render_book_list(f: &mut Frame, state: &AppState, area: Rect) { let header = Row::new(vec!["Title", "Author", "Format", "Pages"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .books_list .iter() .enumerate() .map(|(i, media)| { let style = if Some(i) == state.books_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { Style::default() }; let title = media .title .as_deref() .unwrap_or(&media.file_name) .to_string(); let author = media.artist.as_deref().unwrap_or("-").to_string(); // Extract format from media_type or file extension let format = media .file_name .rsplit('.') .next() .map_or_else(|| media.media_type.clone(), str::to_uppercase); // Page count from custom fields if available let pages = media .custom_fields .get("page_count") .map_or_else(|| "-".to_string(), |f| f.value.clone()); Row::new(vec![title, author, format, pages]).style(style) }) .collect(); let title = format!(" Book List ({}) ", state.books_list.len()); let table = Table::new(rows, [ Constraint::Percentage(40), Constraint::Percentage(30), Constraint::Percentage(15), Constraint::Percentage(15), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, area); } fn render_series(f: &mut Frame, state: &AppState, area: Rect) { let header = Row::new(vec!["Series Name", "Books"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .books_series .iter() .enumerate() .map(|(i, series)| { let style = if Some(i) == state.books_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { Style::default() }; Row::new(vec![series.name.clone(), series.count.to_string()]).style(style) }) .collect(); let title = format!(" Series ({}) ", state.books_series.len()); let table = Table::new(rows, [ Constraint::Percentage(70), Constraint::Percentage(30), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, area); } fn render_authors(f: &mut Frame, state: &AppState, area: Rect) { let header = Row::new(vec!["Author Name", "Books"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .books_authors .iter() .enumerate() .map(|(i, author)| { let style = if Some(i) == state.books_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { Style::default() }; Row::new(vec![author.name.clone(), author.count.to_string()]).style(style) }) .collect(); let title = format!(" Authors ({}) ", state.books_authors.len()); let table = Table::new(rows, [ Constraint::Percentage(70), Constraint::Percentage(30), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, area); }