diff --git a/client/src/components/admin/admin_panel.rs b/client/src/components/admin/admin_panel.rs index 5de7236..1de30b1 100644 --- a/client/src/components/admin/admin_panel.rs +++ b/client/src/components/admin/admin_panel.rs @@ -1,118 +1,106 @@ // src/components/admin/admin_panel.rs +use crate::config::colors::themes::Theme; +use crate::state::pages::admin::AdminState; // Import the persistent state +use common::proto::multieko2::table_definition::ProfileTreeResponse; use ratatui::{ - widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph}, - style::Style, - text::{Line, Span, Text}, layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Style, Modifier}, // Added Modifier + text::{Line, Span, Text}, + widgets::{Block, BorderType, Borders, List, ListItem, Paragraph}, // Removed ListState import here Frame, }; -use common::proto::multieko2::table_definition::ProfileTreeResponse; -use crate::config::colors::themes::Theme; -pub struct AdminPanelState { - pub list_state: ListState, - pub profiles: Vec, -} +// Renamed from AdminPanelState::render and made a standalone function +pub fn render_admin_panel( + f: &mut Frame, + admin_state: &AdminState, // Accept the persistent state (immutable borrow is enough for rendering) + area: Rect, + theme: &Theme, + profile_tree: &ProfileTreeResponse, + selected_profile: &Option, // The globally selected profile for the checkmark +) { + let block = Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(theme.accent)) + .style(Style::default().bg(theme.bg)); -impl AdminPanelState { - pub fn new(profiles: Vec) -> Self { - let mut list_state = ListState::default(); - if !profiles.is_empty() { - list_state.select(Some(0)); - } - Self { list_state, profiles } - } + let inner_area = block.inner(area); + f.render_widget(block, area); - pub fn next(&mut self) { - let i = self.list_state.selected().map_or(0, |i| - if i >= self.profiles.len() - 1 { 0 } else { i + 1 }); - self.list_state.select(Some(i)); - } + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(3), Constraint::Min(1)]) + .split(inner_area); - pub fn previous(&mut self) { - let i = self.list_state.selected().map_or(0, |i| - if i == 0 { self.profiles.len() - 1 } else { i - 1 }); - self.list_state.select(Some(i)); - } + // Title + let title = Line::from(Span::styled("Admin Panel", Style::default().fg(theme.highlight))); + let title_widget = Paragraph::new(title).alignment(Alignment::Center); + f.render_widget(title_widget, chunks[0]); - pub fn render( - &mut self, - f: &mut Frame, - area: Rect, - theme: &Theme, - profile_tree: &ProfileTreeResponse, - selected_profile: &Option, - ) { - let block = Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Rounded) - .border_style(Style::default().fg(theme.accent)) - .style(Style::default().bg(theme.bg)); + // Content + let content_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) + .split(chunks[1]); - let inner_area = block.inner(area); - f.render_widget(block, area); - - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Length(3), Constraint::Min(1)]) - .split(inner_area); - - // Title - let title = Line::from(Span::styled("Admin Panel", Style::default().fg(theme.highlight))); - let title_widget = Paragraph::new(title).alignment(Alignment::Center); - f.render_widget(title_widget, chunks[0]); - - // Content - let content_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) - .split(chunks[1]); - - // Profile list - let items: Vec = self.profiles.iter() - .map(|p| ListItem::new(Line::from(vec![ + // Profile list - Use data from admin_state + let items: Vec = admin_state + .profiles // Use profiles from the persistent state + .iter() + .map(|p| { + ListItem::new(Line::from(vec![ Span::styled( + // Check against the globally selected profile for the checkmark if Some(p) == selected_profile.as_ref() { "✓ " } else { " " }, - Style::default().fg(theme.accent) + Style::default().fg(theme.accent), ), Span::styled(p, Style::default().fg(theme.fg)), - ]))) - .collect(); + ])) + }) + .collect(); - let list = List::new(items) - .block(Block::default().title("Profiles")) - .highlight_style(Style::default().bg(theme.highlight).fg(theme.bg)); - - f.render_stateful_widget(list, content_chunks[0], &mut self.list_state); + let list = List::new(items) + .block(Block::default().title("Profiles")) + .highlight_style(Style::default().bg(theme.highlight).fg(theme.bg)); - // Profile details - if let Some(profile) = self.list_state.selected() - .and_then(|i| profile_tree.profiles.get(i)) - { - let mut text = Text::default(); - text.lines.push(Line::from(vec![ - Span::styled("Profile: ", Style::default().fg(theme.accent)), - Span::styled(&profile.name, Style::default().fg(theme.highlight)), - ])); + // Render statefully using a CLONE of the list_state from persistent AdminState + // Cloning is necessary because render_stateful_widget needs `&mut ListState` + // but we only have `&AdminState`. This is a common pattern in Ratatui. + let mut list_state_clone = admin_state.list_state.clone(); + f.render_stateful_widget(list, content_chunks[0], &mut list_state_clone); - text.lines.push(Line::from("")); - text.lines.push(Line::from(Span::styled("Tables:", Style::default().fg(theme.accent)))); - - for table in &profile.tables { - let mut line = vec![Span::styled(format!("├─ {}", table.name), theme.fg)]; - if !table.depends_on.is_empty() { - line.push(Span::styled( - format!(" → {}", table.depends_on.join(", ")), - Style::default().fg(theme.secondary) - )); - } - text.lines.push(Line::from(line)); + // Profile details - Use selection info from admin_state + if let Some(profile) = admin_state + .get_selected_index() // Use the method from persistent state + .and_then(|i| profile_tree.profiles.get(i)) + { + let mut text = Text::default(); + text.lines.push(Line::from(vec![ + Span::styled("Profile: ", Style::default().fg(theme.accent)), + Span::styled(&profile.name, Style::default().fg(theme.highlight)), + ])); + + text.lines.push(Line::from("")); + text.lines.push(Line::from(Span::styled( + "Tables:", + Style::default().fg(theme.accent), + ))); + + for table in &profile.tables { + let mut line = vec![Span::styled(format!("├─ {}", table.name), theme.fg)]; + if !table.depends_on.is_empty() { + line.push(Span::styled( + format!(" → {}", table.depends_on.join(", ")), + Style::default().fg(theme.secondary), + )); } - - let details_widget = Paragraph::new(text) - .block(Block::default().title("Details")); - f.render_widget(details_widget, content_chunks[1]); + text.lines.push(Line::from(line)); } + + let details_widget = Paragraph::new(text).block(Block::default().title("Details")); + f.render_widget(details_widget, content_chunks[1]); } } + diff --git a/client/src/functions/modes/navigation.rs b/client/src/functions/modes/navigation.rs index 7b6121e..b2fa295 100644 --- a/client/src/functions/modes/navigation.rs +++ b/client/src/functions/modes/navigation.rs @@ -1,3 +1,3 @@ // src/functions/modes/navigation.rs -pub mod admin_nav; +// pub mod admin_nav; diff --git a/client/src/functions/modes/navigation/admin_nav.rs b/client/src/functions/modes/navigation/admin_nav.rs index adaa29d..0d30d14 100644 --- a/client/src/functions/modes/navigation/admin_nav.rs +++ b/client/src/functions/modes/navigation/admin_nav.rs @@ -1,36 +1 @@ // src/functions/modes/navigation/admin_nav.rs - -use crate::state::app::state::AppState; -use crate::state::pages::admin::AdminState; - -/// Handles moving the selection up in the admin profile list. -pub fn move_admin_list_up(app_state: &AppState, admin_state: &mut AdminState) { - // Read profile count directly from app_state where the source data lives - let profile_count = app_state.profile_tree.profiles.len(); - if profile_count == 0 { - admin_state.list_state.select(None); // Ensure nothing selected if empty - return; - } - - let current_index = admin_state.get_selected_index().unwrap_or(0); - let new_index = if current_index == 0 { - profile_count - 1 // Wrap to end - } else { - current_index.saturating_sub(1) // Move up - }; - admin_state.list_state.select(Some(new_index)); -} - -/// Handles moving the selection down in the admin profile list. -pub fn move_admin_list_down(app_state: &AppState, admin_state: &mut AdminState) { - // Read profile count directly from app_state - let profile_count = app_state.profile_tree.profiles.len(); - if profile_count == 0 { - admin_state.list_state.select(None); // Ensure nothing selected if empty - return; - } - - let current_index = admin_state.get_selected_index().unwrap_or(0); - let new_index = (current_index + 1) % profile_count; // Wrap around - admin_state.list_state.select(Some(new_index)); -} diff --git a/client/src/modes/general/navigation.rs b/client/src/modes/general/navigation.rs index d134241..7833c50 100644 --- a/client/src/modes/general/navigation.rs +++ b/client/src/modes/general/navigation.rs @@ -10,7 +10,6 @@ use crate::state::pages::admin::AdminState; use crate::state::pages::canvas_state::CanvasState; use crate::ui::handlers::context::UiContext; use crate::modes::handlers::event::EventOutcome; -use crate::functions::modes::navigation::admin_nav; pub async fn handle_navigation_event( key: KeyEvent, @@ -100,7 +99,7 @@ pub fn move_up(app_state: &mut AppState, login_state: &mut LoginState, register_ } else if app_state.ui.show_intro { app_state.ui.intro_state.previous_option(); } else if app_state.ui.show_admin { - admin_nav::move_admin_list_up(app_state, admin_state); + admin_state.previous(); } } @@ -113,13 +112,7 @@ pub fn move_down(app_state: &mut AppState, admin_state: &mut AdminState) { } else if app_state.ui.show_intro { app_state.ui.intro_state.next_option(); } else if app_state.ui.show_admin { - // Assuming profile_tree.profiles is the list we're navigating - let profile_count = app_state.profile_tree.profiles.len(); - if profile_count == 0 { - return; - } - - admin_nav::move_admin_list_down(app_state, admin_state); + admin_state.next(); } } diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 81be572..87aea2e 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -119,7 +119,13 @@ impl EventHandler { let mut message = String::from("Selected"); // Default message match context { UiContext::Intro => { - intro::handle_intro_selection(app_state, index); // Pass index + intro::handle_intro_selection(app_state, index); + if app_state.ui.show_admin { + let profile_names = app_state.profile_tree.profiles.iter() + .map(|p| p.name.clone()) + .collect(); + admin_state.set_profiles(profile_names); + } message = format!("Intro Option {} selected", index); } UiContext::Login => { diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 9ed99c5..91c455b 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -6,7 +6,6 @@ use crate::components::{ render_status_line, handlers::sidebar::{self, calculate_sidebar_layout}, form::form::render_form, - admin::{admin_panel::AdminPanelState}, auth::{login::render_login, register::render_register}, }; use crate::config::colors::themes::Theme; @@ -25,6 +24,7 @@ pub fn render_ui( auth_state: &mut AuthState, login_state: &LoginState, register_state: &RegisterState, + admin_state: &mut AdminState, theme: &Theme, is_edit_mode: bool, total_count: u64, @@ -68,24 +68,9 @@ pub fn render_ui( login_state.current_field < 2 ); } else if app_state.ui.show_admin { - // Create temporary AdminPanelState for rendering - let mut admin_state = AdminPanelState::new( - app_state.profile_tree.profiles - .iter() - .map(|p| p.name.clone()) - .collect() - ); - - // Set the selected item - FIXED - if !admin_state.profiles.is_empty() { - let selected_index = admin_state.get_selected_index() - .unwrap_or(0) - .min(admin_state.profiles.len() - 1); - admin_state.list_state.select(Some(selected_index)); - } - - admin_state.render( + crate::components::admin::admin_panel::render_admin_panel( f, + admin_state, main_content_area, theme, &app_state.profile_tree, diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 57faeb9..5ebe964 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -61,6 +61,7 @@ pub async fn run_ui() -> Result<(), Box> { &mut auth_state, &login_state, ®ister_state, + &mut admin_state, &theme, is_edit_mode, app_state.total_count,