diff --git a/client/src/components/admin.rs b/client/src/components/admin.rs index 78c9c6f..8d35d7b 100644 --- a/client/src/components/admin.rs +++ b/client/src/components/admin.rs @@ -1,4 +1,6 @@ // src/components/admin.rs pub mod admin_panel; +pub mod admin_panel_admin; pub use admin_panel::*; +pub use admin_panel_admin::*; diff --git a/client/src/components/admin/admin_panel.rs b/client/src/components/admin/admin_panel.rs index 7074f21..c99d198 100644 --- a/client/src/components/admin/admin_panel.rs +++ b/client/src/components/admin/admin_panel.rs @@ -98,8 +98,8 @@ fn render_admin_panel_non_admin( .block(Block::default().title("Profiles")) .highlight_style(Style::default().bg(theme.highlight).fg(theme.bg)); - let mut list_state_clone = admin_state.list_state.clone(); - f.render_stateful_widget(list, content_chunks[0], &mut list_state_clone); + let mut profile_list_state_clone = admin_state.profile_list_state.clone(); + f.render_stateful_widget(list, content_chunks[0], &mut profile_list_state_clone); // Profile details - Use selection info from admin_state if let Some(profile) = admin_state diff --git a/client/src/components/admin/admin_panel_admin.rs b/client/src/components/admin/admin_panel_admin.rs new file mode 100644 index 0000000..8dd7c01 --- /dev/null +++ b/client/src/components/admin/admin_panel_admin.rs @@ -0,0 +1,127 @@ +// src/components/admin/admin_panel_admin.rs + +// Add necessary imports that were previously in admin_panel.rs +// and are used by this function +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}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, List, ListItem, Paragraph}, + Frame, +}; + +/// Renders the view specific to admin users. +/// Implements the two-pane layout for profile and table browsing. +// Make it public (`pub`) if called from outside this module, +// or keep it private (no `pub`) if only called from admin_panel.rs +// For now, let's assume it's only called from admin_panel.rs +pub(super) fn render_admin_panel_admin( // Use pub(super) or pub based on need + f: &mut Frame, + admin_state: &mut AdminState, + app_state: &AppState, + content_chunks: &[Rect], + theme: &Theme, +) { + let profile_tree = &app_state.profile_tree; + let selected_profile_index = admin_state.get_selected_profile_index(); + + // --- Profile List (Left Pane) --- + let profile_items: Vec = profile_tree + .profiles + .iter() + .enumerate() + .map(|(i, p)| { + let is_selected = selected_profile_index == Some(i); + let style = if is_selected && admin_state.current_focus == AdminFocus::Profiles { + Style::default().fg(theme.bg).bg(theme.highlight) + } else if is_selected { + Style::default().fg(theme.fg).bg(theme.inactive_highlight_bg) + } else { + Style::default().fg(theme.fg) + }; + ListItem::new(Line::from(Span::styled(&p.name, style))) + }) + .collect(); + + let profile_list_block = Block::default() + .title(" Profiles ") + .borders(Borders::ALL) + .border_type(BorderType::Plain) + .border_style(if admin_state.current_focus == AdminFocus::Profiles { + Style::default().fg(theme.accent) + } else { + Style::default().fg(theme.border) + }); + + let profile_list = List::new(profile_items).block(profile_list_block); + + f.render_stateful_widget( + profile_list, + content_chunks[0], + &mut admin_state.profile_list_state, + ); + + // --- Table List & Details (Right Pane) --- + let table_list_block = Block::default() + .title(" Tables & Dependencies ") + .borders(Borders::ALL) + .border_type(BorderType::Plain) + .border_style(if admin_state.current_focus == AdminFocus::Tables { + Style::default().fg(theme.accent) + } else { + Style::default().fg(theme.border) + }); + + if let Some(profile_idx) = selected_profile_index { + if let Some(profile) = profile_tree.profiles.get(profile_idx) { + let selected_table_index = admin_state.get_selected_table_index(); + let table_items: Vec = profile + .tables + .iter() + .enumerate() + .map(|(i, table)| { + let is_selected = selected_table_index == Some(i); + let base_style = if is_selected && admin_state.current_focus == AdminFocus::Tables { + Style::default().fg(theme.bg).bg(theme.highlight) + } else if is_selected { + Style::default().fg(theme.fg).bg(theme.inactive_highlight_bg) + } else { + Style::default().fg(theme.fg) + }; + + let mut line_spans = vec![Span::styled(&table.name, base_style)]; + if !table.depends_on.is_empty() { + line_spans.push(Span::styled(" -> ", base_style.fg(theme.secondary))); + line_spans.push(Span::styled( + format!("[{}]", table.depends_on.join(", ")), + base_style.fg(theme.secondary), + )); + } + ListItem::new(Line::from(line_spans)) + }) + .collect(); + + let table_list = List::new(table_items); + + f.render_stateful_widget( + table_list.block(table_list_block), + content_chunks[1], + &mut admin_state.table_list_state, + ); + return; + } + } + + let placeholder = Paragraph::new("Select a profile to see tables.") + .style(Style::default().fg(theme.secondary)) + .alignment(Alignment::Center); + + f.render_widget( + placeholder.block(table_list_block), + content_chunks[1], + ); +} + diff --git a/client/src/components/common/autocomplete.rs b/client/src/components/common/autocomplete.rs index b4c22a6..856c54a 100644 --- a/client/src/components/common/autocomplete.rs +++ b/client/src/components/common/autocomplete.rs @@ -81,10 +81,10 @@ pub fn render_autocomplete_dropdown( let list = List::new(items); // State for managing selection highlight (still needed for logic) - let mut list_state = ListState::default(); - list_state.select(selected_index); + let mut profile_list_state = ListState::default(); + profile_list_state.select(selected_index); // Render the list statefully *over* the background block - f.render_stateful_widget(list, dropdown_area, &mut list_state); + f.render_stateful_widget(list, dropdown_area, &mut profile_list_state); } diff --git a/client/src/config/colors/themes.rs b/client/src/config/colors/themes.rs index e0c07dd..c73e541 100644 --- a/client/src/config/colors/themes.rs +++ b/client/src/config/colors/themes.rs @@ -11,6 +11,7 @@ pub struct Theme { pub warning: Color, pub border: Color, pub highlight_bg: Color, + pub inactive_highlight_bg: Color,// admin panel no idea what it really is } impl Theme { @@ -33,6 +34,7 @@ impl Theme { warning: Color::Rgb(255, 182, 193), // Pastel pink border: Color::Rgb(220, 220, 220), // Light gray border highlight_bg: Color::Rgb(70, 70, 70), // Darker grey for highlight background + inactive_highlight_bg: Color::Rgb(50, 50, 50), } } @@ -47,6 +49,7 @@ impl Theme { warning: Color::Rgb(255, 99, 71), // Bright red border: Color::Rgb(100, 100, 100), // Medium gray border highlight_bg: Color::Rgb(180, 180, 180), // Lighter grey for highlight background + inactive_highlight_bg: Color::Rgb(50, 50, 50), } } @@ -61,6 +64,7 @@ impl Theme { warning: Color::Rgb(255, 0, 0), // Red border: Color::Rgb(0, 0, 0), // Black border highlight_bg: Color::Rgb(180, 180, 180), // Lighter grey for highlight background + inactive_highlight_bg: Color::Rgb(50, 50, 50), } } } diff --git a/client/src/state/pages/admin.rs b/client/src/state/pages/admin.rs index 1a12c1b..5883d94 100644 --- a/client/src/state/pages/admin.rs +++ b/client/src/state/pages/admin.rs @@ -2,64 +2,178 @@ use ratatui::widgets::ListState; +// Define the focus states for the admin panel panes +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum AdminFocus { + #[default] // Default focus is on the profiles list + Profiles, + Tables, +} + #[derive(Default, Clone, Debug)] pub struct AdminState { pub profiles: Vec, - pub list_state: ListState, + pub profile_list_state: ListState, // State for the profiles list + pub table_list_state: ListState, // State for the tables list + pub current_focus: AdminFocus, // Tracks which pane is focused } impl AdminState { /// Gets the index of the currently selected item. pub fn get_selected_index(&self) -> Option { - self.list_state.selected() + self.profile_list_state.selected() } /// Gets the name of the currently selected profile. pub fn get_selected_profile_name(&self) -> Option<&String> { - self.list_state.selected().and_then(|i| self.profiles.get(i)) + self.profile_list_state.selected().and_then(|i| self.profiles.get(i)) } /// Populates the profile list and updates/resets the selection. pub fn set_profiles(&mut self, new_profiles: Vec) { - let current_selection_index = self.list_state.selected(); + let current_selection_index = self.profile_list_state.selected(); self.profiles = new_profiles; if self.profiles.is_empty() { - self.list_state.select(None); + self.profile_list_state.select(None); } else { let new_selection = match current_selection_index { Some(index) => Some(index.min(self.profiles.len() - 1)), None => Some(0), }; - self.list_state.select(new_selection); + self.profile_list_state.select(new_selection); } } /// Selects the next profile in the list, wrapping around. pub fn next(&mut self) { if self.profiles.is_empty() { - self.list_state.select(None); + self.profile_list_state.select(None); return; } - let i = match self.list_state.selected() { + let i = match self.profile_list_state.selected() { Some(i) => if i >= self.profiles.len() - 1 { 0 } else { i + 1 }, None => 0, }; - self.list_state.select(Some(i)); + self.profile_list_state.select(Some(i)); } /// Selects the previous profile in the list, wrapping around. pub fn previous(&mut self) { if self.profiles.is_empty() { - self.list_state.select(None); + self.profile_list_state.select(None); return; } - let i = match self.list_state.selected() { + let i = match self.profile_list_state.selected() { Some(i) => if i == 0 { self.profiles.len() - 1 } else { i - 1 }, None => self.profiles.len() - 1, }; - self.list_state.select(Some(i)); + self.profile_list_state.select(Some(i)); } + /// Gets the index of the currently selected profile. + pub fn get_selected_profile_index(&self) -> Option { + self.profile_list_state.selected() + } + + /// Gets the index of the currently selected table. + pub fn get_selected_table_index(&self) -> Option { + self.table_list_state.selected() + } + + /// Selects a profile by index and resets table selection. + pub fn select_profile(&mut self, index: Option) { + self.profile_list_state.select(index); + self.table_list_state.select(None); // Reset table selection + } + + /// Selects a table by index. + pub fn select_table(&mut self, index: Option) { + self.table_list_state.select(index); + } + + /// Selects the next profile, wrapping around. + /// `profile_count` should be the total number of profiles available. + pub fn next_profile(&mut self, profile_count: usize) { + if profile_count == 0 { + return; + } + let i = match self.get_selected_profile_index() { + Some(i) => { + if i >= profile_count - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.select_profile(Some(i)); // Use the helper method + } + + /// Selects the previous profile, wrapping around. + /// `profile_count` should be the total number of profiles available. + pub fn previous_profile(&mut self, profile_count: usize) { + if profile_count == 0 { + return; + } + let i = match self.get_selected_profile_index() { + Some(i) => { + if i == 0 { + profile_count - 1 + } else { + i - 1 + } + } + None => 0, // Or profile_count - 1 if you prefer wrapping from None + }; + self.select_profile(Some(i)); // Use the helper method + } + + /// Selects the next table, wrapping around. + /// `table_count` should be the number of tables in the *currently selected* profile. + pub fn next_table(&mut self, table_count: usize) { + if table_count == 0 { + return; + } + let i = match self.get_selected_table_index() { + Some(i) => { + if i >= table_count - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.select_table(Some(i)); + } + + /// Selects the previous table, wrapping around. + /// `table_count` should be the number of tables in the *currently selected* profile. + pub fn previous_table(&mut self, table_count: usize) { + if table_count == 0 { + return; + } + let i = match self.get_selected_table_index() { + Some(i) => { + if i == 0 { + table_count - 1 + } else { + i - 1 + } + } + None => 0, // Or table_count - 1 + }; + self.select_table(Some(i)); + } + + /// Toggles focus between the profile list and the table list. + pub fn toggle_focus(&mut self) { + self.current_focus = match self.current_focus { + AdminFocus::Profiles => AdminFocus::Tables, + AdminFocus::Tables => AdminFocus::Profiles, + }; + } }