admin page is now featured
This commit is contained in:
351
client/src/pages/admin/main/logic.rs
Normal file
351
client/src/pages/admin/main/logic.rs
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
// src/pages/admin/main/logic.rs
|
||||||
|
use crate::pages::admin::{AdminFocus, AdminState};
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::config::binds::config::Config;
|
||||||
|
use crate::buffer::state::{BufferState, AppView};
|
||||||
|
use crate::state::pages::add_table::{AddTableState, LinkDefinition};
|
||||||
|
use ratatui::widgets::ListState;
|
||||||
|
use crate::state::pages::add_logic::{AddLogicState, AddLogicFocus}; // Added AddLogicFocus import
|
||||||
|
|
||||||
|
// Helper functions list_select_next and list_select_previous remain the same
|
||||||
|
fn list_select_next(list_state: &mut ListState, item_count: usize) {
|
||||||
|
if item_count == 0 {
|
||||||
|
list_state.select(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match list_state.selected() {
|
||||||
|
Some(i) => if i >= item_count - 1 { 0 } else { i + 1 },
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_select_previous(list_state: &mut ListState, item_count: usize) {
|
||||||
|
if item_count == 0 {
|
||||||
|
list_state.select(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match list_state.selected() {
|
||||||
|
Some(i) => if i == 0 { item_count - 1 } else { i - 1 },
|
||||||
|
None => if item_count > 0 { item_count - 1 } else { 0 },
|
||||||
|
};
|
||||||
|
list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_admin_navigation(
|
||||||
|
key: crossterm::event::KeyEvent,
|
||||||
|
config: &Config,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
admin_state: &mut AdminState,
|
||||||
|
buffer_state: &mut BufferState,
|
||||||
|
command_message: &mut String,
|
||||||
|
) -> bool {
|
||||||
|
let action = config.get_general_action(key.code, key.modifiers).map(String::from);
|
||||||
|
let current_focus = admin_state.current_focus;
|
||||||
|
let profile_count = app_state.profile_tree.profiles.len();
|
||||||
|
let mut handled = false;
|
||||||
|
|
||||||
|
match current_focus {
|
||||||
|
AdminFocus::ProfilesPane => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.current_focus = AdminFocus::InsideProfilesList;
|
||||||
|
if !app_state.profile_tree.profiles.is_empty() {
|
||||||
|
if admin_state.profile_list_state.selected().is_none() {
|
||||||
|
admin_state.profile_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*command_message = "Navigating profiles. Use Up/Down. Esc to exit.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
// No wrap-around: Stay on ProfilesPane if trying to go "before" it
|
||||||
|
*command_message = "At first focusable pane.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::InsideProfilesList => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("move_up") => {
|
||||||
|
if profile_count > 0 {
|
||||||
|
list_select_previous(&mut admin_state.profile_list_state, profile_count);
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("move_down") => {
|
||||||
|
if profile_count > 0 {
|
||||||
|
list_select_next(&mut admin_state.profile_list_state, profile_count);
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.selected_profile_index = admin_state.profile_list_state.selected();
|
||||||
|
admin_state.selected_table_index = None; // Deselect table when profile changes
|
||||||
|
if let Some(profile_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
admin_state.table_list_state.select(Some(0)); // Auto-select first table for nav
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
*command_message = format!(
|
||||||
|
"Profile '{}' set as active.",
|
||||||
|
admin_state.get_selected_profile_name().unwrap_or(&"N/A".to_string())
|
||||||
|
);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("exit_table_scroll") => {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
*command_message = "Focus: Profiles Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Tables => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.current_focus = AdminFocus::InsideTablesList;
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(profile_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
if admin_state.table_list_state.selected().is_none() {
|
||||||
|
admin_state.table_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
*command_message = "Select a profile first to view its tables.".to_string();
|
||||||
|
}
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideTablesList && !admin_state.table_list_state.selected().is_none() {
|
||||||
|
*command_message = "Navigating tables. Use Up/Down. Esc to exit.".to_string();
|
||||||
|
} else if admin_state.table_list_state.selected().is_none() {
|
||||||
|
if current_profile_idx.is_none() {
|
||||||
|
*command_message = "No profile selected to view tables.".to_string();
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables in selected profile.".to_string();
|
||||||
|
}
|
||||||
|
admin_state.current_focus = AdminFocus::Tables; // Stay in Tables pane if no tables to enter
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
*command_message = "Focus: Profiles Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button1;
|
||||||
|
*command_message = "Focus: Add Logic Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::InsideTablesList => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("move_up") => {
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(p_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
list_select_previous(&mut admin_state.table_list_state, profile.tables.len());
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables to navigate.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "No active profile for tables.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("move_down") => {
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(p_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
list_select_next(&mut admin_state.table_list_state, profile.tables.len());
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables to navigate.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "No active profile for tables.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("select") => { // This is for persistently selecting a table with [*]
|
||||||
|
admin_state.selected_table_index = admin_state.table_list_state.selected();
|
||||||
|
let table_name = admin_state.selected_profile_index
|
||||||
|
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
|
||||||
|
.and_then(|p| admin_state.selected_table_index.and_then(|t_idx| p.tables.get(t_idx)))
|
||||||
|
.map_or("N/A", |t| t.name.as_str());
|
||||||
|
*command_message = format!("Table '{}' set as active.", table_name);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("exit_table_scroll") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button1 => { // Add Logic Button
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => { // Typically "Enter" key
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if let Some(t_idx) = admin_state.selected_table_index {
|
||||||
|
if let Some(table) = profile.tables.get(t_idx) {
|
||||||
|
// Both profile and table are selected, proceed
|
||||||
|
admin_state.add_logic_state = AddLogicState {
|
||||||
|
profile_name: profile.name.clone(),
|
||||||
|
selected_table_name: Some(table.name.clone()),
|
||||||
|
selected_table_id: Some(table.id), // If you have table IDs
|
||||||
|
editor_keybinding_mode: config.editor.keybinding_mode.clone(),
|
||||||
|
current_focus: AddLogicFocus::default(),
|
||||||
|
..AddLogicState::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store table info for later fetching
|
||||||
|
app_state.pending_table_structure_fetch = Some((
|
||||||
|
profile.name.clone(),
|
||||||
|
table.name.clone()
|
||||||
|
));
|
||||||
|
|
||||||
|
buffer_state.update_history(AppView::AddLogic);
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
*command_message = format!(
|
||||||
|
"Opening Add Logic for table '{}' in profile '{}'...",
|
||||||
|
table.name, profile.name
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
*command_message = "Error: Selected table data not found.".to_string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "Select a table first!".to_string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "Error: Selected profile data not found.".to_string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "Select a profile first!".to_string();
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button2;
|
||||||
|
*command_message = "Focus: Add Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button2 => { // Add Table Button
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
let selected_profile_name = profile.name.clone();
|
||||||
|
// Prepare links from the selected profile's existing tables
|
||||||
|
let available_links: Vec<LinkDefinition> = profile.tables.iter()
|
||||||
|
.map(|table| LinkDefinition {
|
||||||
|
linked_table_name: table.name.clone(),
|
||||||
|
is_required: false, // Default, can be changed in AddTable screen
|
||||||
|
selected: false,
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
admin_state.add_table_state = AddTableState {
|
||||||
|
profile_name: selected_profile_name,
|
||||||
|
links: available_links,
|
||||||
|
..AddTableState::default() // Reset other fields
|
||||||
|
};
|
||||||
|
buffer_state.update_history(AppView::AddTable);
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
*command_message = format!("Opening Add Table for profile '{}'...", admin_state.add_table_state.profile_name);
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "Error: Selected profile index out of bounds.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "Please select a profile ([*]) first to add a table.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button1;
|
||||||
|
*command_message = "Focus: Add Logic Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button3;
|
||||||
|
*command_message = "Focus: Change Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button3 => { // Change Table Button
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
// Future: Logic to load selected table into AddTableState for editing
|
||||||
|
*command_message = "Action: Change Table (Not Implemented)".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button2;
|
||||||
|
*command_message = "Focus: Add Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
// No wrap-around: Stay on Button3 if trying to go "after" it
|
||||||
|
*command_message = "At last focusable button.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handled
|
||||||
|
}
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
// src/pages/admin/main/mod.rs
|
// src/pages/admin/main/mod.rs
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
pub mod ui;
|
||||||
|
pub mod ui_admin;
|
||||||
|
pub mod logic;
|
||||||
|
pub mod tui;
|
||||||
|
|||||||
12
client/src/pages/admin/main/tui.rs
Normal file
12
client/src/pages/admin/main/tui.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// src/pages/admin/main/tui.rs
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::pages::admin::AdminState;
|
||||||
|
|
||||||
|
pub fn handle_admin_selection(app_state: &mut AppState, admin_state: &AdminState) {
|
||||||
|
let profiles = &app_state.profile_tree.profiles;
|
||||||
|
if let Some(selected_index) = admin_state.get_selected_index() {
|
||||||
|
if let Some(profile) = profiles.get(selected_index) {
|
||||||
|
app_state.selected_profile = Some(profile.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
client/src/pages/admin/main/ui.rs
Normal file
131
client/src/pages/admin/main/ui.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// src/pages/admin/main/ui.rs
|
||||||
|
|
||||||
|
use crate::config::colors::themes::Theme;
|
||||||
|
use crate::state::pages::auth::AuthState;
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::pages::admin::AdminState;
|
||||||
|
use common::proto::komp_ac::table_definition::ProfileTreeResponse;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::Style,
|
||||||
|
text::{Line, Span, Text},
|
||||||
|
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph, Wrap},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
use crate::pages::admin::main::ui_admin::render_admin_panel_admin;
|
||||||
|
|
||||||
|
pub fn render_admin_panel(
|
||||||
|
f: &mut Frame,
|
||||||
|
app_state: &AppState,
|
||||||
|
auth_state: &AuthState,
|
||||||
|
admin_state: &mut AdminState,
|
||||||
|
area: Rect,
|
||||||
|
theme: &Theme,
|
||||||
|
profile_tree: &ProfileTreeResponse,
|
||||||
|
selected_profile: &Option<String>,
|
||||||
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(theme.accent))
|
||||||
|
.style(Style::default().bg(theme.bg));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Content
|
||||||
|
let content_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
|
||||||
|
.split(chunks[1]);
|
||||||
|
|
||||||
|
if auth_state.role.as_deref() != Some("admin") {
|
||||||
|
render_admin_panel_non_admin(
|
||||||
|
f,
|
||||||
|
admin_state,
|
||||||
|
&content_chunks,
|
||||||
|
theme,
|
||||||
|
profile_tree,
|
||||||
|
selected_profile,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
render_admin_panel_admin(
|
||||||
|
f,
|
||||||
|
chunks[1],
|
||||||
|
app_state,
|
||||||
|
admin_state,
|
||||||
|
theme,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the view for non-admin users (profile list and details).
|
||||||
|
fn render_admin_panel_non_admin(
|
||||||
|
f: &mut Frame,
|
||||||
|
admin_state: &AdminState,
|
||||||
|
content_chunks: &[Rect],
|
||||||
|
theme: &Theme,
|
||||||
|
profile_tree: &ProfileTreeResponse,
|
||||||
|
selected_profile: &Option<String>,
|
||||||
|
) {
|
||||||
|
// Profile list - Use data from admin_state
|
||||||
|
let items: Vec<ListItem> = admin_state
|
||||||
|
.profiles
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
ListItem::new(Line::from(vec![
|
||||||
|
Span::styled(
|
||||||
|
if Some(p) == selected_profile.as_ref() { "✓ " } else { " " },
|
||||||
|
Style::default().fg(theme.accent),
|
||||||
|
),
|
||||||
|
Span::styled(p, Style::default().fg(theme.fg)),
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let list = List::new(items)
|
||||||
|
.block(Block::default().title("Profiles"))
|
||||||
|
.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 profile_list_state_clone);
|
||||||
|
|
||||||
|
// Profile details - Use selection info from admin_state
|
||||||
|
if let Some(profile) = admin_state
|
||||||
|
.get_selected_index()
|
||||||
|
.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),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
text.lines.push(Line::from(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
let details_widget = Paragraph::new(text)
|
||||||
|
.block(Block::default().title("Details"))
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
f.render_widget(details_widget, content_chunks[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
180
client/src/pages/admin/main/ui_admin.rs
Normal file
180
client/src/pages/admin/main/ui_admin.rs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
// src/pages/admin/main/ui_admin.rs
|
||||||
|
|
||||||
|
use crate::config::colors::themes::Theme;
|
||||||
|
use crate::pages::admin::{AdminFocus, AdminState};
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
|
style::Style,
|
||||||
|
text::{Line, Span, Text},
|
||||||
|
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn render_admin_panel_admin(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
app_state: &AppState,
|
||||||
|
admin_state: &mut AdminState,
|
||||||
|
theme: &Theme,
|
||||||
|
) {
|
||||||
|
let main_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(0), Constraint::Length(1)].as_ref())
|
||||||
|
.split(area);
|
||||||
|
let panes_area = main_chunks[0];
|
||||||
|
let buttons_area = main_chunks[1];
|
||||||
|
|
||||||
|
let pane_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(25), // Profiles
|
||||||
|
Constraint::Percentage(40), // Tables
|
||||||
|
Constraint::Percentage(35), // Dependencies
|
||||||
|
].as_ref())
|
||||||
|
.split(panes_area);
|
||||||
|
|
||||||
|
let profiles_pane = pane_chunks[0];
|
||||||
|
let tables_pane = pane_chunks[1];
|
||||||
|
let deps_pane = pane_chunks[2];
|
||||||
|
|
||||||
|
// --- Profiles Pane (Left) ---
|
||||||
|
let profile_pane_has_focus = matches!(admin_state.current_focus, AdminFocus::ProfilesPane | AdminFocus::InsideProfilesList);
|
||||||
|
let profile_border_style = if profile_pane_has_focus {
|
||||||
|
Style::default().fg(theme.highlight)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(theme.border)
|
||||||
|
};
|
||||||
|
let profiles_block = Block::default().title(" Profiles ").borders(Borders::ALL).border_type(BorderType::Rounded).border_style(profile_border_style);
|
||||||
|
let profiles_inner_area = profiles_block.inner(profiles_pane);
|
||||||
|
f.render_widget(profiles_block, profiles_pane);
|
||||||
|
let profile_list_items: Vec<ListItem> = app_state.profile_tree.profiles.iter().enumerate().map(|(idx, profile)| {
|
||||||
|
let is_persistently_selected = admin_state.selected_profile_index == Some(idx);
|
||||||
|
let is_nav_highlighted = admin_state.profile_list_state.selected() == Some(idx) && admin_state.current_focus == AdminFocus::InsideProfilesList;
|
||||||
|
let prefix = if is_persistently_selected { "[*] " } else { "[ ] " };
|
||||||
|
let item_style = if is_nav_highlighted { Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) }
|
||||||
|
else if is_persistently_selected { Style::default().fg(theme.accent) }
|
||||||
|
else { Style::default().fg(theme.fg) };
|
||||||
|
ListItem::new(Line::from(vec![Span::styled(prefix, item_style), Span::styled(&profile.name, item_style)]))
|
||||||
|
}).collect();
|
||||||
|
let profile_list = List::new(profile_list_items)
|
||||||
|
.highlight_style(if admin_state.current_focus == AdminFocus::InsideProfilesList { Style::default().add_modifier(ratatui::style::Modifier::REVERSED) } else { Style::default() })
|
||||||
|
.highlight_symbol(if admin_state.current_focus == AdminFocus::InsideProfilesList { "> " } else { " " });
|
||||||
|
f.render_stateful_widget(profile_list, profiles_inner_area, &mut admin_state.profile_list_state);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Tables Pane (Middle) ---
|
||||||
|
let table_pane_has_focus = matches!(admin_state.current_focus, AdminFocus::Tables | AdminFocus::InsideTablesList);
|
||||||
|
let table_border_style = if table_pane_has_focus { Style::default().fg(theme.highlight) } else { Style::default().fg(theme.border) };
|
||||||
|
|
||||||
|
let profile_to_display_tables_for_idx: Option<usize>;
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideProfilesList {
|
||||||
|
profile_to_display_tables_for_idx = admin_state.profile_list_state.selected();
|
||||||
|
} else {
|
||||||
|
profile_to_display_tables_for_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
}
|
||||||
|
let tables_pane_title_profile_name = profile_to_display_tables_for_idx
|
||||||
|
.and_then(|idx| app_state.profile_tree.profiles.get(idx))
|
||||||
|
.map_or("None Selected", |p| p.name.as_str());
|
||||||
|
let tables_block = Block::default().title(format!(" Tables (Profile: {}) ", tables_pane_title_profile_name)).borders(Borders::ALL).border_type(BorderType::Rounded).border_style(table_border_style);
|
||||||
|
let tables_inner_area = tables_block.inner(tables_pane);
|
||||||
|
f.render_widget(tables_block, tables_pane);
|
||||||
|
|
||||||
|
let table_list_items_for_display: Vec<ListItem> =
|
||||||
|
if let Some(profile_data_for_tables) = profile_to_display_tables_for_idx
|
||||||
|
.and_then(|idx| app_state.profile_tree.profiles.get(idx)) {
|
||||||
|
profile_data_for_tables.tables.iter().enumerate().map(|(idx, table)| {
|
||||||
|
let is_table_persistently_selected = admin_state.selected_table_index == Some(idx) &&
|
||||||
|
profile_to_display_tables_for_idx == admin_state.selected_profile_index;
|
||||||
|
let is_table_nav_highlighted = admin_state.table_list_state.selected() == Some(idx) &&
|
||||||
|
admin_state.current_focus == AdminFocus::InsideTablesList;
|
||||||
|
let prefix = if is_table_persistently_selected { "[*] " } else { "[ ] " };
|
||||||
|
let style = if is_table_nav_highlighted { Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) }
|
||||||
|
else if is_table_persistently_selected { Style::default().fg(theme.accent) }
|
||||||
|
else { Style::default().fg(theme.fg) };
|
||||||
|
ListItem::new(Line::from(vec![Span::styled(prefix, style), Span::styled(&table.name, style)]))
|
||||||
|
}).collect()
|
||||||
|
} else {
|
||||||
|
vec![ListItem::new("Select a profile to see tables")]
|
||||||
|
};
|
||||||
|
let table_list = List::new(table_list_items_for_display)
|
||||||
|
.highlight_style(if admin_state.current_focus == AdminFocus::InsideTablesList { Style::default().add_modifier(ratatui::style::Modifier::REVERSED) } else { Style::default() })
|
||||||
|
.highlight_symbol(if admin_state.current_focus == AdminFocus::InsideTablesList { "> " } else { " " });
|
||||||
|
f.render_stateful_widget(table_list, tables_inner_area, &mut admin_state.table_list_state);
|
||||||
|
|
||||||
|
|
||||||
|
// --- Dependencies Pane (Right) ---
|
||||||
|
let mut deps_pane_title_table_name = "N/A".to_string();
|
||||||
|
let dependencies_to_display: Vec<String>;
|
||||||
|
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideTablesList {
|
||||||
|
// If navigating tables, show dependencies for the '>' highlighted table.
|
||||||
|
// The profile context is `profile_to_display_tables_for_idx` (from Tables pane logic).
|
||||||
|
if let Some(p_idx_for_current_tables) = profile_to_display_tables_for_idx {
|
||||||
|
if let Some(current_profile_showing_tables) = app_state.profile_tree.profiles.get(p_idx_for_current_tables) {
|
||||||
|
if let Some(table_nav_idx) = admin_state.table_list_state.selected() { // The '>' highlighted table
|
||||||
|
if let Some(navigated_table) = current_profile_showing_tables.tables.get(table_nav_idx) {
|
||||||
|
deps_pane_title_table_name = navigated_table.name.clone();
|
||||||
|
dependencies_to_display = navigated_table.depends_on.clone();
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // Navigated table index out of bounds
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // No table navigated with '>'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // Profile for tables out of bounds
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // No profile active for table display
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, show dependencies for the '[*]' persistently selected table & profile.
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index { // Must be a persistently selected profile
|
||||||
|
if let Some(selected_profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if let Some(t_idx) = admin_state.selected_table_index { // Must be a persistently selected table
|
||||||
|
if let Some(selected_table) = selected_profile.tables.get(t_idx) {
|
||||||
|
deps_pane_title_table_name = selected_table.name.clone();
|
||||||
|
dependencies_to_display = selected_table.depends_on.clone();
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
let deps_block = Block::default()
|
||||||
|
.title(format!(" Dependencies (Table: {}) ", deps_pane_title_table_name))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(theme.border));
|
||||||
|
let deps_inner_area = deps_block.inner(deps_pane);
|
||||||
|
f.render_widget(deps_block, deps_pane);
|
||||||
|
|
||||||
|
let mut deps_content = Text::default();
|
||||||
|
deps_content.lines.push(Line::from(Span::styled(
|
||||||
|
"Depends On:",
|
||||||
|
Style::default().fg(theme.accent),
|
||||||
|
)));
|
||||||
|
|
||||||
|
if !dependencies_to_display.is_empty() {
|
||||||
|
for dep in dependencies_to_display {
|
||||||
|
deps_content.lines.push(Line::from(Span::styled(format!("- {}", dep), theme.fg)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deps_content.lines.push(Line::from(Span::styled(" None", theme.secondary)));
|
||||||
|
}
|
||||||
|
let deps_paragraph = Paragraph::new(deps_content);
|
||||||
|
f.render_widget(deps_paragraph, deps_inner_area);
|
||||||
|
|
||||||
|
// --- Buttons Row ---
|
||||||
|
let button_chunks = Layout::default().direction(Direction::Horizontal).constraints([Constraint::Percentage(33), Constraint::Percentage(34), Constraint::Percentage(33)].as_ref()).split(buttons_area);
|
||||||
|
let btn_base_style = Style::default().fg(theme.secondary);
|
||||||
|
let get_btn_style = |button_focus: AdminFocus| { if admin_state.current_focus == button_focus { btn_base_style.add_modifier(ratatui::style::Modifier::REVERSED) } else { btn_base_style } };
|
||||||
|
let btn1 = Paragraph::new("Add Logic").style(get_btn_style(AdminFocus::Button1)).alignment(Alignment::Center);
|
||||||
|
let btn2 = Paragraph::new("Add Table").style(get_btn_style(AdminFocus::Button2)).alignment(Alignment::Center);
|
||||||
|
let btn3 = Paragraph::new("Change Table").style(get_btn_style(AdminFocus::Button3)).alignment(Alignment::Center);
|
||||||
|
f.render_widget(btn1, button_chunks[0]);
|
||||||
|
f.render_widget(btn2, button_chunks[1]);
|
||||||
|
f.render_widget(btn3, button_chunks[2]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user