From cc994fb9406a3294ba6e4c970842d707fcd2e9f1 Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 16 Apr 2025 13:21:59 +0200 Subject: [PATCH] admin panel for admin --- client/src/components/admin/admin_panel.rs | 12 +- .../src/components/admin/admin_panel_admin.rs | 169 ++++++++++++++++-- client/src/ui/handlers/render.rs | 1 + 3 files changed, 170 insertions(+), 12 deletions(-) diff --git a/client/src/components/admin/admin_panel.rs b/client/src/components/admin/admin_panel.rs index ad7b0c9..387971f 100644 --- a/client/src/components/admin/admin_panel.rs +++ b/client/src/components/admin/admin_panel.rs @@ -2,6 +2,7 @@ use crate::config::colors::themes::Theme; use crate::state::pages::auth::AuthState; +use crate::state::app::state::AppState; use crate::state::pages::admin::AdminState; use common::proto::multieko2::table_definition::ProfileTreeResponse; use ratatui::{ @@ -15,8 +16,9 @@ use super::admin_panel_admin::render_admin_panel_admin; pub fn render_admin_panel( f: &mut Frame, + app_state: &AppState, auth_state: &AuthState, - admin_state: &AdminState, + admin_state: &mut AdminState, area: Rect, theme: &Theme, profile_tree: &ProfileTreeResponse, @@ -57,7 +59,13 @@ pub fn render_admin_panel( selected_profile, ); } else { - render_admin_panel_admin(f, &content_chunks, theme); + render_admin_panel_admin( + f, + content_chunks[0], + app_state, + admin_state, + theme, + ); } } diff --git a/client/src/components/admin/admin_panel_admin.rs b/client/src/components/admin/admin_panel_admin.rs index 0ecec3a..473cb68 100644 --- a/client/src/components/admin/admin_panel_admin.rs +++ b/client/src/components/admin/admin_panel_admin.rs @@ -4,19 +4,168 @@ use crate::config::colors::themes::Theme; use crate::state::pages::admin::{AdminFocus, AdminState}; use crate::state::app::state::AppState; use ratatui::{ - layout::{Alignment, Rect}, - style::{Style, Stylize}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::Style, text::{Line, Span}, - widgets::{Block, BorderType, Borders, List, ListItem, Paragraph}, + widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph}, Frame, }; /// Renders the view specific to admin users. -pub fn render_admin_panel_admin(f: &mut Frame, content_chunks: &[Rect], theme: &Theme) { - // Admin-specific view placeholder - let admin_message = Paragraph::new("Admin-specific view. Profile selection not applicable.") - .style(Style::default().fg(theme.fg)) - .alignment(Alignment::Center); - // Render only in the right pane for now, leaving left empty - f.render_widget(admin_message, content_chunks[1]); +pub fn render_admin_panel_admin( + f: &mut Frame, + area: Rect, + app_state: &AppState, + admin_state: &mut AdminState, + theme: &Theme, +) { + let main_block = Block::default() + .title(" Admin Panel ") + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(theme.border)); + f.render_widget(main_block, area); + + let inner_area = area.inner(ratatui::layout::Margin { vertical: 1, horizontal: 1 }); + + // Split the inner area into two panes + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref()) + .split(inner_area); + + let left_pane = chunks[0]; + let right_pane = chunks[1]; + + // --- Profiles Pane (Left) --- + let profile_focus = admin_state.current_focus == AdminFocus::Profiles; + let profile_border_style = if profile_focus { + Style::default().fg(theme.highlight) + } else { + Style::default().fg(theme.border) + }; + + let profile_list_items: Vec = app_state + .profile_tree + .profiles + .iter() + .enumerate() + .map(|(i, profile)| { + let is_selected = admin_state.profile_list_state.selected() == Some(i); + let prefix = if is_selected { "[*] " } else { "[ ] " }; + let style = if is_selected { + Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) + } else { + Style::default().fg(theme.fg) + }; + ListItem::new(Line::from(vec![Span::styled(prefix, style), Span::styled(&profile.name, style)])) + }) + .collect(); + + let profile_list = List::new(profile_list_items) + .block( + Block::default() + .title(" Profiles ") + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(profile_border_style), + ) + .highlight_style(Style::default().add_modifier(ratatui::style::Modifier::REVERSED)) + .highlight_symbol(if profile_focus { "> " } else { " " }); + + f.render_stateful_widget(profile_list, left_pane, &mut admin_state.profile_list_state); + + // --- Tables & Dependencies Pane (Right) --- + let table_focus = admin_state.current_focus == AdminFocus::Tables; + let table_border_style = if table_focus { + Style::default().fg(theme.highlight) + } else { + Style::default().fg(theme.border) + }; + + let right_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(0), Constraint::Length(3)]) + .split(right_pane); + + let tables_area = right_chunks[0]; + let deps_area = right_chunks[1]; + + let selected_profile_idx = admin_state.profile_list_state.selected(); + let selected_profile_name = app_state + .profile_tree + .profiles + .get(selected_profile_idx.unwrap_or(usize::MAX)) + .map_or("None", |p| &p.name); + + let (table_list_items, selected_table_deps): (Vec, Vec) = if let Some( + profile, + ) = selected_profile_idx.and_then(|idx| app_state.profile_tree.profiles.get(idx)) { + let items: Vec = profile + .tables + .iter() + .enumerate() + .map(|(i, table)| { + let is_selected = admin_state.table_list_state.selected() == Some(i); + let prefix = if is_selected { "[*] " } else { "[ ] " }; + let deps_str = if !table.depends_on.is_empty() { + format!(" -> [{}]", table.depends_on.join(", ")) + } else { + String::new() + }; + let style = if is_selected { + Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) + } else { + Style::default().fg(theme.fg) + }; + ListItem::new(Line::from(vec![ + Span::styled(prefix, style), + Span::styled(&table.name, style), + Span::styled(deps_str, style.fg(theme.secondary)), + ])) + }) + .collect(); + + let deps = admin_state.table_list_state.selected() + .and_then(|idx| profile.tables.get(idx)) + .map_or(vec![], |t| t.depends_on.clone()); + + (items, deps) + } else { + (vec![ListItem::new("No profile selected or profile has no tables")], vec![]) + }; + + let table_list = List::new(table_list_items) + .block( + Block::default() + .title(format!(" Tables & Dependencies (Profile: {}) ", selected_profile_name)) + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(table_border_style), + ) + .highlight_style(Style::default().add_modifier(ratatui::style::Modifier::REVERSED)) + .highlight_symbol(if table_focus { "> " } else { " " }); + + f.render_stateful_widget(table_list, tables_area, &mut admin_state.table_list_state); + + // --- Dependencies Display --- + let selected_table_name = selected_profile_idx + .and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx)) + .and_then(|p| admin_state.table_list_state.selected().and_then(|t_idx| p.tables.get(t_idx))) + .map_or("N/A", |t| &t.name); + + let deps_text = if !selected_table_deps.is_empty() { + format!("Dependencies for {}: {}", selected_table_name, selected_table_deps.join(", ")) + } else { + format!("Dependencies for {}: None", selected_table_name) + }; + + let deps_paragraph = Paragraph::new(deps_text) + .style(Style::default().fg(theme.secondary)) + .block( + Block::default() + .borders(Borders::TOP) + .border_style(Style::default().fg(theme.border)), + ); + f.render_widget(deps_paragraph, deps_area); } diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 8909d6e..874588e 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -109,6 +109,7 @@ pub fn render_ui( } else if app_state.ui.show_admin { crate::components::admin::admin_panel::render_admin_panel( f, + app_state, auth_state, admin_state, main_content_area,