diff --git a/client/src/components/admin/add_table.rs b/client/src/components/admin/add_table.rs index 2dfd1be..e0c21ad 100644 --- a/client/src/components/admin/add_table.rs +++ b/client/src/components/admin/add_table.rs @@ -4,27 +4,28 @@ use crate::state::app::highlight::HighlightState; use crate::state::app::state::AppState; use crate::state::pages::add_table::{AddTableFocus, AddTableState}; use crate::state::pages::canvas_state::CanvasState; +use crate::state::pages::add_table::{ColumnDefinition, LinkDefinition}; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Modifier, Style, Stylize}, + style::{Modifier, Style}, text::{Line, Span}, - widgets::{Block, BorderType, Borders, Paragraph, Cell, Row, Table}, + widgets::{Block, BorderType, Borders, Cell, Paragraph, Row, Table}, Frame, }; -// Assuming render_canvas exists and works like in register.rs use crate::components::handlers::canvas::render_canvas; -/// Renders the Add New Table page layout. +/// Renders the Add New Table page layout, structuring the display of table information, +/// input fields, and action buttons. pub fn render_add_table( f: &mut Frame, area: Rect, theme: &Theme, - app_state: &AppState, + _app_state: &AppState, // Currently unused, might be needed later add_table_state: &mut AddTableState, - is_edit_mode: bool, - highlight_state: &HighlightState, + is_edit_mode: bool, // Determines if canvas inputs are in edit mode + highlight_state: &HighlightState, // For text highlighting in canvas ) { - // Determine if focus is on canvas inputs vs other elements based on AddTableState + // Determine if focus is on canvas inputs vs other elements let focus_on_canvas_inputs = matches!( add_table_state.current_focus, AddTableFocus::InputTableName @@ -32,9 +33,10 @@ pub fn render_add_table( | AddTableFocus::InputColumnType ); - // Main block for the whole page + // --- Main Page Block --- let main_block = Block::default() .title(" Add New Table ") + .title_alignment(Alignment::Center) .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(theme.border)) @@ -42,51 +44,82 @@ pub fn render_add_table( let inner_area = main_block.inner(area); f.render_widget(main_block, area); - // Split the inner area horizontally: Left info pane | Right input/action pane - let horizontal_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage(50), // Left Pane - Constraint::Percentage(50), // Right Pane - ].as_ref()) - .split(inner_area); - - let left_pane = horizontal_chunks[0]; - let right_pane = horizontal_chunks[1]; - - // --- Left Pane --- - let left_vertical_chunks = Layout::default() + // --- Main Vertical Layout --- + // Splits the page into: Top Info, Middle Area, Bottom Buttons + let main_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(3), // Profile & Table Name header - Constraint::Min(5), // Columns section (expandable) - Constraint::Length(1), // Separator (placeholder for now) - Constraint::Min(3), // Indexes section (expandable) - Constraint::Length(1), // Separator (placeholder for now) - Constraint::Min(3), // Links section (expandable) - ].as_ref()) - .split(left_pane); + Constraint::Length(3), // Top: Profile & Committed Table Name + Constraint::Min(10), // Middle: Columns | Inputs/Indexes/Links + Constraint::Length(3), // Bottom: Save/Cancel Buttons + ]) + .split(inner_area); - // --- Left Pane Rendering --- - // Profile & Table Name section (Displays current state) + let top_info_area = main_chunks[0]; + let middle_area = main_chunks[1]; + let bottom_buttons_area = main_chunks[2]; + + // --- Top Info Rendering --- let profile_text = Paragraph::new(vec![ Line::from(Span::styled( - format!("profile: {}", add_table_state.profile_name), // Use actual profile - theme.fg, + format!("profile: {}", add_table_state.profile_name), + theme.fg, )), + // Display the *committed* table name from the state Line::from(Span::styled( - format!("table name: {}", add_table_state.table_name), // Use actual table name (from input) - theme.fg, - )) + format!("table name: {}", add_table_state.table_name), + theme.fg, + )), ]) - .block( - Block::default() + .block( + Block::default() .borders(Borders::BOTTOM) .border_style(Style::default().fg(theme.secondary)), - ); - f.render_widget(profile_text, left_vertical_chunks[0]); + ); + f.render_widget(profile_text, top_info_area); - // --- Columns Table --- + // --- Middle Area Layout --- + // Splits into: Left (Columns Table) | Right (Inputs & Other Tables) + let middle_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage(60), // Left Pane: Columns Table + Constraint::Percentage(40), // Right Pane: Inputs etc. + ]) + .split(middle_area); + + let columns_area = middle_chunks[0]; + let right_pane_area = middle_chunks[1]; + + // --- Right Pane Layout --- + // Splits vertically: Canvas, Add Button, Indexes/Links Area + let right_pane_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(5), // Input Canvas Area + Constraint::Length(3), // Add Button Area + Constraint::Min(5), // Indexes & Links Area + ]) + .split(right_pane_area); + + let canvas_area = right_pane_chunks[0]; + let add_button_area = right_pane_chunks[1]; + let indexes_links_area = right_pane_chunks[2]; + + // --- Indexes & Links Layout --- + // Splits horizontally within their allocated area + let indexes_links_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage(50), // Indexes Table + Constraint::Percentage(50), // Links Table + ]) + .split(indexes_links_area); + + let indexes_area = indexes_links_chunks[0]; + let links_area = indexes_links_chunks[1]; + + // --- Columns Table Rendering --- let columns_focused = add_table_state.current_focus == AddTableFocus::ColumnsTable; let columns_border_style = if columns_focused { @@ -94,8 +127,7 @@ pub fn render_add_table( } else { Style::default().fg(theme.secondary) }; - // --- Create Table Rows from State --- - let column_rows: Vec = add_table_state + let column_rows: Vec> = add_table_state .columns .iter() .map(|col_def| { @@ -106,114 +138,55 @@ pub fn render_add_table( .style(Style::default().fg(theme.fg)) }) .collect(); - - // --- Define Table Header --- let header_cells = ["Name", "Type"] .iter() .map(|h| Cell::from(*h).style(Style::default().fg(theme.accent))); let header = Row::new(header_cells).height(1).bottom_margin(1); - - // --- Create the Table Widget --- - let columns_table = Table::new(column_rows, [Constraint::Percentage(50), Constraint::Percentage(50)]) - .header(header) - .block( + let columns_table = Table::new( + column_rows, + [Constraint::Percentage(60), Constraint::Percentage(40)], + ) + .header(header) + .block( Block::default() .title(Span::styled(" Columns ", theme.fg)) - .borders(Borders::TOP) // Separator from Profile/Name + .title_alignment(Alignment::Center) + .borders(Borders::TOP | Borders::RIGHT) // Add right border .border_style(columns_border_style), - ) - .highlight_style(Style::default().add_modifier(Modifier::REVERSED).fg(theme.highlight)) // Style for selected row - .highlight_symbol(" > "); // Symbol for selected row - - // --- Render the Table --- + ) + .row_highlight_style( + Style::default() + .add_modifier(Modifier::REVERSED) + .fg(theme.highlight), + ) + .highlight_symbol(" > "); f.render_stateful_widget( columns_table, - left_vertical_chunks[1], + columns_area, &mut add_table_state.column_table_state, ); - // --- Indexes Table --- - let indexes_focused = - add_table_state.current_focus == AddTableFocus::IndexesTable; - let indexes_border_style = if indexes_focused { - Style::default().fg(theme.highlight) - } else { - Style::default().fg(theme.secondary) - }; - // TODO: Replace this Paragraph with a Table/List widget for add_table_state.indexes - let indexes_content = Paragraph::new(vec![ - Line::from(Span::styled("Column name", theme.accent)), // Header - Line::from("... Index list placeholder ..."), - ]) - .block( - Block::default() - .title(Span::styled(" Indexes ", theme.fg)) - .borders(Borders::TOP) // Separator from Columns - .border_style(indexes_border_style), // Indicate focus - ); - f.render_widget(indexes_content, left_vertical_chunks[3]); - - // --- Links Table --- - let links_focused = add_table_state.current_focus == AddTableFocus::LinksTable; - let links_border_style = if links_focused { - Style::default().fg(theme.highlight) - } else { - Style::default().fg(theme.secondary) - }; - // TODO: Replace this Paragraph with a Table widget for add_table_state.links - let links_content = Paragraph::new(vec![ - Line::from(Span::styled("Linked table Required", theme.accent)), // Header - Line::from("... Link list placeholder ..."), - ]) - .block( - Block::default() - .title(Span::styled(" Links ", theme.fg)) - .borders(Borders::TOP) // Separator from Indexes - .border_style(links_border_style), // Indicate focus - ); - f.render_widget(links_content, left_vertical_chunks[5]); - - // --- Right Pane --- - let right_vertical_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(5), // Area for render_canvas (3 fields + 2 border) - Constraint::Length(3), // Add Button Area - Constraint::Min(1), // Spacer - Constraint::Length(3), // Save/Cancel buttons area - ].as_ref()) - .split(right_pane); - - let canvas_area = right_vertical_chunks[0]; - let add_button_area = right_vertical_chunks[1]; - let bottom_buttons_area = right_vertical_chunks[3]; - - // --- Use render_canvas for Inputs --- - // Pass is_edit_mode determined by the main loop based on AppMode - // Only show edit styling if AppMode is Edit AND focus is on one of the canvas inputs + // --- Canvas Rendering --- let _active_field_rect = render_canvas( f, canvas_area, add_table_state, // Implements CanvasState - &add_table_state.fields(), // Get field names from state + &add_table_state.fields(), &add_table_state.current_field(), - &add_table_state.inputs(), // Get inputs from state + &add_table_state.inputs(), theme, - is_edit_mode && focus_on_canvas_inputs, // Only truly edit mode if focus is on canvas + is_edit_mode && focus_on_canvas_inputs, highlight_state, ); - // --- Buttons --- - // Helper to get style based on focus - let get_button_style = |button_focus: AddTableFocus| { - let is_focused = add_table_state.current_focus == button_focus; - // Base style: secondary color, unless focused then highlight color + // --- Button Style Helpers --- + let get_button_style = |button_focus: AddTableFocus, current_focus| { + let is_focused = current_focus == button_focus; let mut style = Style::default().fg(if is_focused { theme.highlight } else { theme.secondary }); - // Apply bold and reverse if focused if is_focused { style = style .add_modifier(Modifier::BOLD) @@ -221,18 +194,20 @@ pub fn render_add_table( } style }; - // Helper to get border style based on focus - let get_button_border_style = |button_focus: AddTableFocus| { - if add_table_state.current_focus == button_focus { - Style::default().fg(theme.highlight) // Highlight border when focused + let get_button_border_style = |button_focus: AddTableFocus, current_focus| { + if current_focus == button_focus { + Style::default().fg(theme.highlight) } else { - Style::default().fg(theme.secondary) // Dim border otherwise + Style::default().fg(theme.secondary) } }; - // Add Button + // --- Add Button Rendering --- let add_button = Paragraph::new(" Add ") - .style(get_button_style(AddTableFocus::AddColumnButton)) + .style(get_button_style( + AddTableFocus::AddColumnButton, + add_table_state.current_focus, + )) .alignment(Alignment::Center) .block( Block::default() @@ -240,34 +215,129 @@ pub fn render_add_table( .border_type(BorderType::Rounded) .border_style(get_button_border_style( AddTableFocus::AddColumnButton, + add_table_state.current_focus, )), ); f.render_widget(add_button, add_button_area); - // Bottom Buttons Area (Save, Cancel) + // --- Indexes Table Rendering --- + let indexes_focused = + add_table_state.current_focus == AddTableFocus::IndexesTable; + let indexes_border_style = if indexes_focused { + Style::default().fg(theme.highlight) + } else { + Style::default().fg(theme.secondary) + }; + let index_rows: Vec> = add_table_state + .indexes + .iter() + .map(|index_name| { + Row::new(vec![Cell::from(index_name.clone())]) + .style(Style::default().fg(theme.fg)) + }) + .collect(); + let index_header_cells = ["Column Name"] + .iter() + .map(|h| Cell::from(*h).style(Style::default().fg(theme.accent))); + let index_header = Row::new(index_header_cells).height(1).bottom_margin(1); + let indexes_table = + Table::new(index_rows, [Constraint::Percentage(100)]) + .header(index_header) + .block( + Block::default() + .title(Span::styled(" Indexes ", theme.fg)) + .title_alignment(Alignment::Center) + .borders(Borders::TOP | Borders::RIGHT) // Add right border + .border_style(indexes_border_style), + ) + .row_highlight_style( + Style::default() + .add_modifier(Modifier::REVERSED) + .fg(theme.highlight), + ) + .highlight_symbol(" > "); + f.render_stateful_widget( + indexes_table, + indexes_area, + &mut add_table_state.index_table_state, + ); + + // --- Links Table Rendering --- + let links_focused = add_table_state.current_focus == AddTableFocus::LinksTable; + let links_border_style = if links_focused { + Style::default().fg(theme.highlight) + } else { + Style::default().fg(theme.secondary) + }; + let link_rows: Vec> = add_table_state + .links + .iter() + .map(|link_def| { + Row::new(vec![ + Cell::from(link_def.linked_table_name.clone()), + // Display checkbox style for boolean + Cell::from(if link_def.is_required { "[X]" } else { "[ ]" }), + ]) + .style(Style::default().fg(theme.fg)) + }) + .collect(); + let link_header_cells = ["Linked Table", "Req"] // Shortened header + .iter() + .map(|h| Cell::from(*h).style(Style::default().fg(theme.accent))); + let link_header = Row::new(link_header_cells).height(1).bottom_margin(1); + let links_table = + Table::new(link_rows, [Constraint::Percentage(80), Constraint::Min(5)]) // Adjust constraints + .header(link_header) + .block( + Block::default() + .title(Span::styled(" Links ", theme.fg)) + .title_alignment(Alignment::Center) + .borders(Borders::TOP) // No left border needed here + .border_style(links_border_style), + ) + .row_highlight_style( + Style::default() + .add_modifier(Modifier::REVERSED) + .fg(theme.highlight), + ) + .highlight_symbol(" > "); + f.render_stateful_widget( + links_table, + links_area, + &mut add_table_state.link_table_state, + ); + + // --- Save/Cancel Buttons Rendering --- let bottom_button_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(50), // Save Button Constraint::Percentage(50), // Cancel Button - ].as_ref()) + ]) .split(bottom_buttons_area); - // Save Button let save_button = Paragraph::new(" Save table ") - .style(get_button_style(AddTableFocus::SaveButton)) + .style(get_button_style( + AddTableFocus::SaveButton, + add_table_state.current_focus, + )) .alignment(Alignment::Center) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) - .border_style(get_button_border_style(AddTableFocus::SaveButton)), + .border_style(get_button_border_style( + AddTableFocus::SaveButton, + add_table_state.current_focus, + )), ); f.render_widget(save_button, bottom_button_chunks[0]); - // Cancel Button let cancel_button = Paragraph::new(" Cancel ") - .style(get_button_style(AddTableFocus::CancelButton)) + .style(get_button_style( + AddTableFocus::CancelButton, + add_table_state.current_focus, + )) .alignment(Alignment::Center) .block( Block::default() @@ -275,6 +345,7 @@ pub fn render_add_table( .border_type(BorderType::Rounded) .border_style(get_button_border_style( AddTableFocus::CancelButton, + add_table_state.current_focus, )), ); f.render_widget(cancel_button, bottom_button_chunks[1]);