admin page
This commit is contained in:
@@ -9,7 +9,7 @@ use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
|
|||||||
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
||||||
use crate::functions::modes::navigation::add_table_nav;
|
use crate::functions::modes::navigation::add_table_nav;
|
||||||
use crate::pages::admin::main::logic::handle_admin_navigation;
|
use crate::pages::admin::main::logic::handle_admin_navigation;
|
||||||
use crate::pages::admin::main::tui::handle_admin_selection;
|
use crate::pages::admin::admin::tui::handle_admin_selection;
|
||||||
use crate::modes::general::command_navigation::{
|
use crate::modes::general::command_navigation::{
|
||||||
handle_command_navigation_event, NavigationState,
|
handle_command_navigation_event, NavigationState,
|
||||||
};
|
};
|
||||||
@@ -33,6 +33,7 @@ use crate::pages::intro;
|
|||||||
use crate::pages::login::logic::LoginResult;
|
use crate::pages::login::logic::LoginResult;
|
||||||
use crate::pages::register::RegisterResult;
|
use crate::pages::register::RegisterResult;
|
||||||
use crate::pages::routing::{Router, Page};
|
use crate::pages::routing::{Router, Page};
|
||||||
|
use crate::movement::MovementAction;
|
||||||
use crate::dialog;
|
use crate::dialog;
|
||||||
use crate::pages::forms::FormState;
|
use crate::pages::forms::FormState;
|
||||||
use crate::pages::forms::logic::{save, revert, SaveOutcome};
|
use crate::pages::forms::logic::{save, revert, SaveOutcome};
|
||||||
@@ -404,9 +405,35 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Page::Admin(state) => {
|
Page::Admin(state) => {
|
||||||
|
if auth_state.role.as_deref() == Some("admin") {
|
||||||
if state.handle_movement(app_state, ma) {
|
if state.handle_movement(app_state, ma) {
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Non-admin: simple profile navigation
|
||||||
|
match ma {
|
||||||
|
MovementAction::Up | MovementAction::Previous => {
|
||||||
|
state.previous();
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
MovementAction::Down | MovementAction::Next => {
|
||||||
|
state.next();
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
MovementAction::Select => {
|
||||||
|
if let Some(idx) = state.get_selected_index() {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(idx) {
|
||||||
|
app_state.selected_profile = Some(profile.name.clone());
|
||||||
|
return Ok(EventOutcome::Ok(format!(
|
||||||
|
"Profile '{}' selected",
|
||||||
|
profile.name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Page::Intro(state) => {
|
Page::Intro(state) => {
|
||||||
if state.handle_movement(ma) {
|
if state.handle_movement(ma) {
|
||||||
@@ -420,6 +447,7 @@ impl EventHandler {
|
|||||||
// Optional page-specific handlers (non-movement or rich actions)
|
// Optional page-specific handlers (non-movement or rich actions)
|
||||||
if let Page::Admin(admin_state) = &mut router.current {
|
if let Page::Admin(admin_state) = &mut router.current {
|
||||||
if auth_state.role.as_deref() == Some("admin") {
|
if auth_state.role.as_deref() == Some("admin") {
|
||||||
|
// Full admin navigation
|
||||||
if handle_admin_navigation(
|
if handle_admin_navigation(
|
||||||
key_event,
|
key_event,
|
||||||
config,
|
config,
|
||||||
@@ -428,9 +456,30 @@ impl EventHandler {
|
|||||||
buffer_state,
|
buffer_state,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
) {
|
) {
|
||||||
return Ok(EventOutcome::Ok(
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
self.command_message.clone(),
|
}
|
||||||
));
|
} else {
|
||||||
|
// Non-admin: allow simple profile navigation
|
||||||
|
if let Some(action) = config.get_general_action(key_event.code, key_event.modifiers) {
|
||||||
|
match action {
|
||||||
|
"move_up" => {
|
||||||
|
admin_state.previous();
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
"move_down" => {
|
||||||
|
admin_state.next();
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
"select" => {
|
||||||
|
if let Some(idx) = admin_state.get_selected_index() {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(idx) {
|
||||||
|
app_state.selected_profile = Some(profile.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(EventOutcome::Ok("Profile selected".to_string()));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,10 +520,8 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generic navigation for the rest (Intro/Login/Register/Form)
|
// Generic navigation for the rest (Intro/Login/Register/Form)
|
||||||
let nav_outcome = if matches!(
|
let nav_outcome = if matches!(&router.current, Page::AddTable(_) | Page::AddLogic(_)) {
|
||||||
&router.current,
|
// Skip generic navigation for AddTable/AddLogic (they have their own handlers)
|
||||||
Page::Admin(_) | Page::AddTable(_) | Page::AddLogic(_)
|
|
||||||
) {
|
|
||||||
Ok(EventOutcome::Ok(String::new()))
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
} else {
|
} else {
|
||||||
navigation::handle_navigation_event(
|
navigation::handle_navigation_event(
|
||||||
|
|||||||
7
client/src/pages/admin/admin/mod.rs
Normal file
7
client/src/pages/admin/admin/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// src/pages/admin/admin/mod.rs
|
||||||
|
|
||||||
|
pub mod state;
|
||||||
|
pub mod ui;
|
||||||
|
pub mod tui;
|
||||||
|
|
||||||
|
pub use state::{AdminState, AdminFocus};
|
||||||
195
client/src/pages/admin/admin/state.rs
Normal file
195
client/src/pages/admin/admin/state.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
// src/pages/admin/admin/state.rs
|
||||||
|
use ratatui::widgets::ListState;
|
||||||
|
use crate::state::pages::add_table::AddTableState;
|
||||||
|
use crate::state::pages::add_logic::AddLogicState;
|
||||||
|
use crate::movement::{move_focus, MovementAction};
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
|
||||||
|
/// Focus states for the admin panel
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum AdminFocus {
|
||||||
|
#[default]
|
||||||
|
ProfilesPane,
|
||||||
|
InsideProfilesList,
|
||||||
|
Tables,
|
||||||
|
InsideTablesList,
|
||||||
|
Button1,
|
||||||
|
Button2,
|
||||||
|
Button3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full admin panel state (for logged-in admins)
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
|
pub struct AdminState {
|
||||||
|
pub profiles: Vec<String>,
|
||||||
|
pub profile_list_state: ListState,
|
||||||
|
pub table_list_state: ListState,
|
||||||
|
pub selected_profile_index: Option<usize>,
|
||||||
|
pub selected_table_index: Option<usize>,
|
||||||
|
pub current_focus: AdminFocus,
|
||||||
|
pub add_table_state: AddTableState,
|
||||||
|
pub add_logic_state: AddLogicState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdminState {
|
||||||
|
pub fn get_selected_index(&self) -> Option<usize> {
|
||||||
|
self.profile_list_state.selected()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_profile_name(&self) -> Option<&String> {
|
||||||
|
self.profile_list_state.selected().and_then(|i| self.profiles.get(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_profiles(&mut self, new_profiles: Vec<String>) {
|
||||||
|
let current_selection_index = self.profile_list_state.selected();
|
||||||
|
self.profiles = new_profiles;
|
||||||
|
|
||||||
|
if self.profiles.is_empty() {
|
||||||
|
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.profile_list_state.select(new_selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if self.profiles.is_empty() {
|
||||||
|
self.profile_list_state.select(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match self.profile_list_state.selected() {
|
||||||
|
Some(i) => if i >= self.profiles.len() - 1 { 0 } else { i + 1 },
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.profile_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.profiles.is_empty() {
|
||||||
|
self.profile_list_state.select(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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.profile_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_movement(
|
||||||
|
&mut self,
|
||||||
|
app: &AppState,
|
||||||
|
action: MovementAction,
|
||||||
|
) -> bool {
|
||||||
|
use AdminFocus::*;
|
||||||
|
|
||||||
|
const ORDER: [AdminFocus; 5] = [
|
||||||
|
ProfilesPane,
|
||||||
|
Tables,
|
||||||
|
Button1,
|
||||||
|
Button2,
|
||||||
|
Button3,
|
||||||
|
];
|
||||||
|
|
||||||
|
match (self.current_focus, action) {
|
||||||
|
(ProfilesPane, MovementAction::Select) => {
|
||||||
|
if !app.profile_tree.profiles.is_empty()
|
||||||
|
&& self.profile_list_state.selected().is_none()
|
||||||
|
{
|
||||||
|
self.profile_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
self.current_focus = InsideProfilesList;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(Tables, MovementAction::Select) => {
|
||||||
|
let p_idx = self
|
||||||
|
.selected_profile_index
|
||||||
|
.or_else(|| self.profile_list_state.selected());
|
||||||
|
if let Some(pi) = p_idx {
|
||||||
|
let len = app
|
||||||
|
.profile_tree
|
||||||
|
.profiles
|
||||||
|
.get(pi)
|
||||||
|
.map(|p| p.tables.len())
|
||||||
|
.unwrap_or(0);
|
||||||
|
if len > 0 && self.table_list_state.selected().is_none() {
|
||||||
|
self.table_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.current_focus = InsideTablesList;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.current_focus {
|
||||||
|
InsideProfilesList => match action {
|
||||||
|
MovementAction::Up => {
|
||||||
|
if !app.profile_tree.profiles.is_empty() {
|
||||||
|
let curr = self.profile_list_state.selected().unwrap_or(0);
|
||||||
|
let next = curr.saturating_sub(1);
|
||||||
|
self.profile_list_state.select(Some(next));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Down => {
|
||||||
|
let len = app.profile_tree.profiles.len();
|
||||||
|
if len > 0 {
|
||||||
|
let curr = self.profile_list_state.selected().unwrap_or(0);
|
||||||
|
let next = if curr + 1 < len { curr + 1 } else { curr };
|
||||||
|
self.profile_list_state.select(Some(next));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Esc => {
|
||||||
|
self.current_focus = ProfilesPane;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Next | MovementAction::Previous => true,
|
||||||
|
MovementAction::Select => false,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
InsideTablesList => {
|
||||||
|
let tables_len = {
|
||||||
|
let p_idx = self
|
||||||
|
.selected_profile_index
|
||||||
|
.or_else(|| self.profile_list_state.selected());
|
||||||
|
p_idx.and_then(|pi| app.profile_tree.profiles.get(pi))
|
||||||
|
.map(|p| p.tables.len())
|
||||||
|
.unwrap_or(0)
|
||||||
|
};
|
||||||
|
match action {
|
||||||
|
MovementAction::Up => {
|
||||||
|
if tables_len > 0 {
|
||||||
|
let curr = self.table_list_state.selected().unwrap_or(0);
|
||||||
|
let next = curr.saturating_sub(1);
|
||||||
|
self.table_list_state.select(Some(next));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Down => {
|
||||||
|
if tables_len > 0 {
|
||||||
|
let curr = self.table_list_state.selected().unwrap_or(0);
|
||||||
|
let next = if curr + 1 < tables_len { curr + 1 } else { curr };
|
||||||
|
self.table_list_state.select(Some(next));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Esc => {
|
||||||
|
self.current_focus = Tables;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
MovementAction::Next | MovementAction::Previous => true,
|
||||||
|
MovementAction::Select => false,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
move_focus(&ORDER, &mut self.current_focus, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// src/pages/admin/main/tui.rs
|
// src/pages/admin/admin/tui.rs
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::pages::admin::AdminState;
|
use crate::pages::admin::AdminState;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// src/pages/admin/main/ui_admin.rs
|
// src/pages/admin/admin/ui.rs
|
||||||
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::pages::admin::{AdminFocus, AdminState};
|
use crate::pages::admin::{AdminFocus, AdminState};
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod ui_admin;
|
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod tui;
|
|
||||||
|
pub use state::NonAdminState;
|
||||||
|
|||||||
@@ -1,48 +1,19 @@
|
|||||||
// src/pages/admin/main/state.rs
|
// src/pages/admin/main/state.rs
|
||||||
|
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
use crate::state::pages::add_table::AddTableState;
|
|
||||||
use crate::state::pages::add_logic::AddLogicState;
|
|
||||||
use crate::movement::{move_focus, MovementAction};
|
|
||||||
use crate::state::app::state::AppState;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
ProfilesPane,
|
|
||||||
InsideProfilesList,
|
|
||||||
Tables,
|
|
||||||
InsideTablesList,
|
|
||||||
Button1,
|
|
||||||
Button2,
|
|
||||||
Button3,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// State for non-admin users (simple profile browser)
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct AdminState {
|
pub struct NonAdminState {
|
||||||
pub profiles: Vec<String>, // Holds profile names (used by non-admin view)
|
pub profiles: Vec<String>, // profile names
|
||||||
pub profile_list_state: ListState, // Tracks navigation highlight (>) in profiles
|
pub profile_list_state: ListState, // highlight state
|
||||||
pub table_list_state: ListState, // Tracks navigation highlight (>) in tables
|
pub selected_profile_index: Option<usize>, // persistent selection
|
||||||
pub selected_profile_index: Option<usize>, // Index with [*] in profiles (persistent)
|
|
||||||
pub selected_table_index: Option<usize>, // Index with [*] in tables (persistent)
|
|
||||||
pub current_focus: AdminFocus, // Tracks which pane is focused
|
|
||||||
pub add_table_state: AddTableState,
|
|
||||||
pub add_logic_state: AddLogicState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdminState {
|
impl NonAdminState {
|
||||||
/// Gets the index of the currently selected item.
|
|
||||||
pub fn get_selected_index(&self) -> Option<usize> {
|
pub fn get_selected_index(&self) -> Option<usize> {
|
||||||
self.profile_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.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<String>) {
|
pub fn set_profiles(&mut self, new_profiles: Vec<String>) {
|
||||||
let current_selection_index = self.profile_list_state.selected();
|
let current_selection_index = self.profile_list_state.selected();
|
||||||
self.profiles = new_profiles;
|
self.profiles = new_profiles;
|
||||||
@@ -58,7 +29,6 @@ impl AdminState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects the next profile in the list, wrapping around.
|
|
||||||
pub fn next(&mut self) {
|
pub fn next(&mut self) {
|
||||||
if self.profiles.is_empty() {
|
if self.profiles.is_empty() {
|
||||||
self.profile_list_state.select(None);
|
self.profile_list_state.select(None);
|
||||||
@@ -71,7 +41,6 @@ impl AdminState {
|
|||||||
self.profile_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) {
|
pub fn previous(&mut self) {
|
||||||
if self.profiles.is_empty() {
|
if self.profiles.is_empty() {
|
||||||
self.profile_list_state.select(None);
|
self.profile_list_state.select(None);
|
||||||
@@ -83,220 +52,4 @@ impl AdminState {
|
|||||||
};
|
};
|
||||||
self.profile_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<usize> {
|
|
||||||
self.profile_list_state.selected()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the index of the currently selected table.
|
|
||||||
pub fn get_selected_table_index(&self) -> Option<usize> {
|
|
||||||
self.table_list_state.selected()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Selects a profile by index and resets table selection.
|
|
||||||
pub fn select_profile(&mut self, index: Option<usize>) {
|
|
||||||
self.profile_list_state.select(index);
|
|
||||||
self.table_list_state.select(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Selects a table by index.
|
|
||||||
pub fn select_table(&mut self, index: Option<usize>) {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AdminState {
|
|
||||||
pub fn handle_movement(
|
|
||||||
&mut self,
|
|
||||||
app: &AppState,
|
|
||||||
action: MovementAction,
|
|
||||||
) -> bool {
|
|
||||||
use AdminFocus::*;
|
|
||||||
|
|
||||||
const ORDER: [AdminFocus; 5] = [
|
|
||||||
ProfilesPane,
|
|
||||||
Tables,
|
|
||||||
Button1,
|
|
||||||
Button2,
|
|
||||||
Button3,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Enter "inside" on Select
|
|
||||||
match (self.current_focus, action) {
|
|
||||||
(ProfilesPane, MovementAction::Select) => {
|
|
||||||
if !app.profile_tree.profiles.is_empty()
|
|
||||||
&& self.profile_list_state.selected().is_none()
|
|
||||||
{
|
|
||||||
self.profile_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
self.current_focus = InsideProfilesList;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
(Tables, MovementAction::Select) => {
|
|
||||||
let p_idx = self
|
|
||||||
.selected_profile_index
|
|
||||||
.or_else(|| self.profile_list_state.selected());
|
|
||||||
if let Some(pi) = p_idx {
|
|
||||||
let len = app
|
|
||||||
.profile_tree
|
|
||||||
.profiles
|
|
||||||
.get(pi)
|
|
||||||
.map(|p| p.tables.len())
|
|
||||||
.unwrap_or(0);
|
|
||||||
if len > 0 && self.table_list_state.selected().is_none() {
|
|
||||||
self.table_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.current_focus = InsideTablesList;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inside navigation + Esc to exit; don't consume Select (admin_nav handles it)
|
|
||||||
match self.current_focus {
|
|
||||||
InsideProfilesList => match action {
|
|
||||||
MovementAction::Up => {
|
|
||||||
if !app.profile_tree.profiles.is_empty() {
|
|
||||||
let curr = self.profile_list_state.selected().unwrap_or(0);
|
|
||||||
let next = curr.saturating_sub(1);
|
|
||||||
self.profile_list_state.select(Some(next));
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Down => {
|
|
||||||
let len = app.profile_tree.profiles.len();
|
|
||||||
if len > 0 {
|
|
||||||
let curr = self.profile_list_state.selected().unwrap_or(0);
|
|
||||||
let next = if curr + 1 < len { curr + 1 } else { curr };
|
|
||||||
self.profile_list_state.select(Some(next));
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Esc => {
|
|
||||||
self.current_focus = ProfilesPane;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Next | MovementAction::Previous => true, // block outer moves
|
|
||||||
MovementAction::Select => false,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
InsideTablesList => {
|
|
||||||
let tables_len = {
|
|
||||||
let p_idx = self
|
|
||||||
.selected_profile_index
|
|
||||||
.or_else(|| self.profile_list_state.selected());
|
|
||||||
p_idx.and_then(|pi| app.profile_tree.profiles.get(pi))
|
|
||||||
.map(|p| p.tables.len())
|
|
||||||
.unwrap_or(0)
|
|
||||||
};
|
|
||||||
match action {
|
|
||||||
MovementAction::Up => {
|
|
||||||
if tables_len > 0 {
|
|
||||||
let curr = self.table_list_state.selected().unwrap_or(0);
|
|
||||||
let next = curr.saturating_sub(1);
|
|
||||||
self.table_list_state.select(Some(next));
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Down => {
|
|
||||||
if tables_len > 0 {
|
|
||||||
let curr = self.table_list_state.selected().unwrap_or(0);
|
|
||||||
let next = if curr + 1 < tables_len { curr + 1 } else { curr };
|
|
||||||
self.table_list_state.select(Some(next));
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Esc => {
|
|
||||||
self.current_focus = Tables;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MovementAction::Next | MovementAction::Previous => true, // block outer moves
|
|
||||||
MovementAction::Select => false,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Default: outer navigation via helper
|
|
||||||
return move_focus(&ORDER, &mut self.current_focus, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use ratatui::{
|
|||||||
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Wrap},
|
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Wrap},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use crate::pages::admin::main::ui_admin::render_admin_panel_admin;
|
use crate::pages::admin::admin::ui::render_admin_panel_admin;
|
||||||
|
|
||||||
pub fn render_admin_panel(
|
pub fn render_admin_panel(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
@@ -92,8 +92,7 @@ fn render_admin_panel_non_admin(
|
|||||||
.block(Block::default().title("Profiles"))
|
.block(Block::default().title("Profiles"))
|
||||||
.highlight_style(Style::default().bg(theme.highlight).fg(theme.bg));
|
.highlight_style(Style::default().bg(theme.highlight).fg(theme.bg));
|
||||||
|
|
||||||
let mut profile_list_state_clone = admin_state.profile_list_state.clone();
|
f.render_stateful_widget(list, content_chunks[0], &mut 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
|
// Profile details - Use selection info from admin_state
|
||||||
if let Some(profile) = admin_state
|
if let Some(profile) = admin_state
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
// src/pages/admin/mod.rs
|
// src/pages/admin/mod.rs
|
||||||
pub mod main;
|
|
||||||
|
|
||||||
pub use main::state::{AdminState, AdminFocus};
|
pub mod main; // non-admin
|
||||||
|
pub mod admin; // full admin panel
|
||||||
|
|
||||||
|
pub use main::NonAdminState;
|
||||||
|
pub use admin::{AdminState, AdminFocus};
|
||||||
|
|||||||
Reference in New Issue
Block a user