use ratatui::{ Frame, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Row, Table, Tabs}, }; use super::format_date; use crate::app::AppState; pub fn render(f: &mut Frame, state: &AppState, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Min(0)]) .split(area); render_tab_bar(f, state, chunks[0]); match state.admin_tab { 0 => render_users(f, state, chunks[1]), 1 => render_devices(f, state, chunks[1]), _ => render_webhooks(f, state, chunks[1]), } } fn render_tab_bar(f: &mut Frame, state: &AppState, area: Rect) { let titles: Vec = vec!["Users", "Sync Devices", "Webhooks"] .into_iter() .map(|t| Line::from(Span::styled(t, Style::default().fg(Color::White)))) .collect(); let tabs = Tabs::new(titles) .block(Block::default().borders(Borders::ALL).title(" Admin ")) .select(state.admin_tab) .style(Style::default().fg(Color::Gray)) .highlight_style( Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), ); f.render_widget(tabs, area); } fn render_users(f: &mut Frame, state: &AppState, area: Rect) { let header = Row::new(vec!["Username", "Role", "Created"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .users_list .iter() .enumerate() .map(|(i, user)| { let style = if i == state.users_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { let role_color = match user.role.as_str() { "admin" => Color::Red, "editor" => Color::Yellow, _ => Color::White, }; Style::default().fg(role_color) }; Row::new(vec![ user.username.clone(), user.role.clone(), format_date(&user.created_at).to_string(), ]) .style(style) }) .collect(); let title = format!(" Users ({}) ", state.users_list.len()); let table = Table::new(rows, [ Constraint::Percentage(40), Constraint::Percentage(20), Constraint::Percentage(40), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, area); } fn render_devices(f: &mut Frame, state: &AppState, area: Rect) { let header = Row::new(vec!["Name", "Type", "Last Seen"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .sync_devices .iter() .enumerate() .map(|(i, dev)| { let style = if i == state.sync_devices_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { Style::default() }; Row::new(vec![ dev.name.clone(), dev.device_type.clone().unwrap_or_else(|| "-".into()), dev .last_seen .as_deref() .map_or("-", format_date) .to_string(), ]) .style(style) }) .collect(); let title = format!(" Sync Devices ({}) ", state.sync_devices.len()); let table = Table::new(rows, [ Constraint::Percentage(40), Constraint::Percentage(20), Constraint::Percentage(40), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, area); } fn render_webhooks(f: &mut Frame, state: &AppState, area: Rect) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0)]) .split(area); let header = Row::new(vec!["URL", "Events"]).style( Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ); let rows: Vec = state .webhooks .iter() .enumerate() .map(|(i, wh)| { let style = if i == state.webhooks_selected { Style::default().fg(Color::Black).bg(Color::Cyan) } else { Style::default() }; let events = if wh.events.is_empty() { "-".to_string() } else { wh.events.join(", ") }; Row::new(vec![wh.url.clone(), events]).style(style) }) .collect(); let title = format!(" Webhooks ({}) ", state.webhooks.len()); let table = Table::new(rows, [ Constraint::Percentage(50), Constraint::Percentage(50), ]) .header(header) .block(Block::default().borders(Borders::ALL).title(title)); f.render_widget(table, chunks[0]); }