From 738d58b5f135221ba3ce48e527acadc7eb0ab15c Mon Sep 17 00:00:00 2001 From: Priec Date: Tue, 2 Sep 2025 11:46:35 +0200 Subject: [PATCH] moving add_table to add_logic modern architecture3 --- .../src/pages/admin_panel/add_table/event.rs | 16 +- .../src/pages/admin_panel/add_table/loader.rs | 78 +++++++ .../src/pages/admin_panel/add_table/logic.rs | 201 ++---------------- client/src/pages/admin_panel/add_table/mod.rs | 1 + .../src/pages/admin_panel/add_table/state.rs | 73 +++++-- 5 files changed, 157 insertions(+), 212 deletions(-) create mode 100644 client/src/pages/admin_panel/add_table/loader.rs diff --git a/client/src/pages/admin_panel/add_table/event.rs b/client/src/pages/admin_panel/add_table/event.rs index 3d17873..6ca3848 100644 --- a/client/src/pages/admin_panel/add_table/event.rs +++ b/client/src/pages/admin_panel/add_table/event.rs @@ -4,8 +4,9 @@ use anyhow::Result; use crate::config::binds::config::Config; use crate::movement::{move_focus, MovementAction}; use crate::pages::admin_panel::add_table::logic::{ - handle_add_column_action, handle_delete_selected_columns, handle_save_table_action, + handle_add_column_action, handle_delete_selected_columns, }; +use crate::pages::admin_panel::add_table::loader::handle_save_table_action; use crate::pages::admin_panel::add_table::nav::SaveTableResultSender; use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableFormState}; use crate::services::grpc_client::GrpcClient; @@ -245,11 +246,9 @@ pub fn handle_add_table_event( match ma { MovementAction::Select => match page.current_focus() { AddTableFocus::AddColumnButton => { - if let Some(focus_after_add) = - handle_add_column_action(&mut page.state, &mut String::new()) - { - page.set_current_focus(focus_after_add); - return Ok(EventOutcome::Ok("Column added".into())); + if let Some(msg) = page.state.add_column_from_inputs() { + // Focus is set by the state method; just bubble message + return Ok(EventOutcome::Ok(msg)); } } AddTableFocus::SaveButton => { @@ -269,7 +268,10 @@ pub fn handle_add_table_event( return Ok(EventOutcome::Ok("Saving table...".into())); } AddTableFocus::DeleteSelectedButton => { - let msg = handle_delete_selected_columns(&mut page.state); + let msg = page + .state + .delete_selected_items() + .unwrap_or_else(|| "No items selected for deletion".to_string()); return Ok(EventOutcome::Ok(msg)); } AddTableFocus::CancelButton => { diff --git a/client/src/pages/admin_panel/add_table/loader.rs b/client/src/pages/admin_panel/add_table/loader.rs new file mode 100644 index 0000000..692edc6 --- /dev/null +++ b/client/src/pages/admin_panel/add_table/loader.rs @@ -0,0 +1,78 @@ +// src/pages/admin_panel/add_table/loader.rs + +use anyhow::{anyhow, Result}; +use tracing::debug; + +use crate::pages::admin_panel::add_table::state::AddTableState; +use crate::services::grpc_client::GrpcClient; +use common::proto::komp_ac::table_definition::{ + ColumnDefinition as ProtoColumnDefinition, PostTableDefinitionRequest, TableLink as ProtoTableLink, +}; + +/// Prepares and sends the request to save the new table definition via gRPC. +pub async fn handle_save_table_action( + grpc_client: &mut GrpcClient, + add_table_state: &AddTableState, + ) -> Result { + if add_table_state.table_name.is_empty() { + return Err(anyhow!("Table name cannot be empty.")); + } + if add_table_state.columns.is_empty() { + return Err(anyhow!("Table must have at least one column.")); + } + + let proto_columns: Vec = add_table_state + .columns + .iter() + .map(|col| ProtoColumnDefinition { + name: col.name.clone(), + field_type: col.data_type.clone(), + }) + .collect(); + + let proto_indexes: Vec = add_table_state + .indexes + .iter() + .filter(|idx| idx.selected) + .map(|idx| idx.name.clone()) + .collect(); + + let proto_links: Vec = add_table_state + .links + .iter() + .filter(|link| link.selected) + .map(|link| ProtoTableLink { + linked_table_name: link.linked_table_name.clone(), + required: false, + }) + .collect(); + + let request = PostTableDefinitionRequest { + table_name: add_table_state.table_name.clone(), + columns: proto_columns, + indexes: proto_indexes, + links: proto_links, + profile_name: add_table_state.profile_name.clone(), + }; + + debug!("Sending PostTableDefinitionRequest: {:?}", request); + + match grpc_client.post_table_definition(request).await { + Ok(response) => { + if response.success { + Ok(format!( + "Table '{}' saved successfully.", + add_table_state.table_name + )) + } else { + let error_message = if !response.sql.is_empty() { + format!("Server failed to save table: {}", response.sql) + } else { + "Server failed to save table (unknown reason).".to_string() + }; + Err(anyhow!(error_message)) + } + } + Err(e) => Err(anyhow!("gRPC call failed: {}", e)), + } +} diff --git a/client/src/pages/admin_panel/add_table/logic.rs b/client/src/pages/admin_panel/add_table/logic.rs index 8c1db4f..0708f10 100644 --- a/client/src/pages/admin_panel/add_table/logic.rs +++ b/client/src/pages/admin_panel/add_table/logic.rs @@ -1,197 +1,24 @@ // src/pages/admin_panel/add_table/logic.rs -use crate::pages::admin_panel::add_table::state; -use crate::pages::admin_panel::add_table::state::{AddTableState, AddTableFocus, IndexDefinition, ColumnDefinition}; -use crate::services::GrpcClient; -use anyhow::{anyhow, Result}; -use common::proto::komp_ac::table_definition::{ - PostTableDefinitionRequest, - ColumnDefinition as ProtoColumnDefinition, - TableLink as ProtoTableLink, -}; -use tracing::debug; -/// Handles the logic for adding a column when the "Add" button is activated. -/// -/// Takes the mutable state and command message string. -/// Returns `Some(AddTableFocus)` indicating the desired focus state after a successful add, -/// or `None` if the action failed (e.g., validation error). +use crate::pages::admin_panel::add_table::state::{AddTableState, AddTableFocus}; + +/// Thin wrapper around AddTableState::add_column_from_inputs +/// Returns Some(AddTableFocus) for compatibility with old call sites. pub fn handle_add_column_action( add_table_state: &mut AddTableState, command_message: &mut String, ) -> Option { - - // Trim and create owned Strings from inputs - let table_name_in = add_table_state.table_name_input.trim(); - let column_name_in = add_table_state.column_name_input.trim(); - let column_type_in = add_table_state.column_type_input.trim(); - - // Validate all inputs needed for this combined action - let has_table_name = !table_name_in.is_empty(); - let has_column_name = !column_name_in.is_empty(); - let has_column_type = !column_type_in.is_empty(); - - match (has_table_name, has_column_name, has_column_type) { - // Case 1: Both column fields have input (Table name is optional here) - (_, true, true) => { - let mut msg = String::new(); - // Optionally update table name if provided - if has_table_name { - add_table_state.table_name = table_name_in.to_string(); - msg.push_str(&format!("Table name set to '{}'. ", add_table_state.table_name)); - } - // Add the column - let new_column = ColumnDefinition { - name: column_name_in.to_string(), - data_type: column_type_in.to_string(), - selected: false, - }; - add_table_state.columns.push(new_column.clone()); // Clone for msg - msg.push_str(&format!("Column '{}' added.", new_column.name)); - - // Add corresponding index definition (initially unselected) - let new_index = IndexDefinition { - name: column_name_in.to_string(), - selected: false, - }; - add_table_state.indexes.push(new_index); - *command_message = msg; - - // Clear all inputs and reset cursors - add_table_state.table_name_input.clear(); - add_table_state.column_name_input.clear(); - add_table_state.column_type_input.clear(); - add_table_state.table_name_cursor_pos = 0; - add_table_state.column_name_cursor_pos = 0; - add_table_state.column_type_cursor_pos = 0; - add_table_state.has_unsaved_changes = true; - Some(AddTableFocus::InputColumnName) // Focus for next column - } - // Case 2: Only one column field has input (Error) - (_, true, false) | (_, false, true) => { - *command_message = "Both Column Name and Type are required to add a column.".to_string(); - None // Indicate validation failure - } - // Case 3: Only Table name has input (No column input) - (true, false, false) => { - add_table_state.table_name = table_name_in.to_string(); - *command_message = format!("Table name set to '{}'.", add_table_state.table_name); - // Clear only table name input - add_table_state.table_name_input.clear(); - add_table_state.table_name_cursor_pos = 0; - add_table_state.has_unsaved_changes = true; - Some(AddTableFocus::InputTableName) // Keep focus here - } - // Case 4: All fields are empty - (false, false, false) => { - *command_message = "No input provided.".to_string(); - None - } + if let Some(msg) = add_table_state.add_column_from_inputs() { + *command_message = msg; + // State sets focus internally; return it explicitly for old call sites + return Some(add_table_state.current_focus); } + None } -/// Handles deleting columns marked as selected in the AddTableState. -pub fn handle_delete_selected_columns( - add_table_state: &mut AddTableState, -) -> String { - let initial_count = add_table_state.columns.len(); - // Keep only the columns that are NOT selected - let initial_selected_indices: std::collections::HashSet = add_table_state - .columns - .iter() - .filter(|col| col.selected) - .map(|col| col.name.clone()) - .collect(); - add_table_state.columns.retain(|col| !col.selected); - let deleted_count = initial_count - add_table_state.columns.len(); - - if deleted_count > 0 { - add_table_state.indexes.retain(|index| !initial_selected_indices.contains(&index.name)); - add_table_state.has_unsaved_changes = true; - // Reset selection highlight as indices have changed - add_table_state.column_table_state.select(None); - // Optionally, select the first item if the list is not empty - // if !add_table_state.columns.is_empty() { - // add_table_state.column_table_state.select(Some(0)); - // } - add_table_state.index_table_state.select(None); - format!("Deleted {} selected column(s).", deleted_count) - } else { - "No columns marked for deletion.".to_string() - } -} - -/// Prepares and sends the request to save the new table definition via gRPC. -pub async fn handle_save_table_action( - grpc_client: &mut GrpcClient, - add_table_state: &AddTableState, -) -> Result { - // --- Basic Validation --- - if add_table_state.table_name.is_empty() { - return Err(anyhow!("Table name cannot be empty.")); - } - if add_table_state.columns.is_empty() { - return Err(anyhow!("Table must have at least one column.")); - } - - // --- Prepare Proto Data --- - let proto_columns: Vec = add_table_state - .columns - .iter() - .map(|col| ProtoColumnDefinition { - name: col.name.clone(), - field_type: col.data_type.clone(), // Assuming data_type maps directly - }) - .collect(); - - let proto_indexes: Vec = add_table_state - .indexes - .iter() - .filter(|idx| idx.selected) // Only include selected indexes - .map(|idx| idx.name.clone()) - .collect(); - - let proto_links: Vec = add_table_state - .links - .iter() - .filter(|link| link.selected) // Only include selected links - .map(|link| ProtoTableLink { - linked_table_name: link.linked_table_name.clone(), - // Assuming 'required' maps directly, adjust if needed - // For now, the proto only seems to use linked_table_name based on example - // If your proto evolves, map link.is_required here. - required: false, // Set based on your proto definition/needs - }) - .collect(); - - // --- Create Request --- - let request = PostTableDefinitionRequest { - table_name: add_table_state.table_name.clone(), - columns: proto_columns, - indexes: proto_indexes, - links: proto_links, - profile_name: add_table_state.profile_name.clone(), - }; - - debug!("Sending PostTableDefinitionRequest: {:?}", request); - - // --- Call gRPC Service --- - match grpc_client.post_table_definition(request).await { - Ok(response) => { - if response.success { - Ok(format!( - "Table '{}' saved successfully.", - add_table_state.table_name - )) - } else { - // Use the SQL message from the response if available, otherwise generic error - let error_message = if !response.sql.is_empty() { - format!("Server failed to save table: {}", response.sql) - } else { - "Server failed to save table (unknown reason).".to_string() - }; - Err(anyhow!(error_message)) - } - } - Err(e) => Err(anyhow!("gRPC call failed: {}", e)), - } +/// Thin wrapper around AddTableState::delete_selected_items +pub fn handle_delete_selected_columns(add_table_state: &mut AddTableState) -> String { + add_table_state + .delete_selected_items() + .unwrap_or_else(|| "No items selected for deletion".to_string()) } diff --git a/client/src/pages/admin_panel/add_table/mod.rs b/client/src/pages/admin_panel/add_table/mod.rs index d432d8a..7e9f921 100644 --- a/client/src/pages/admin_panel/add_table/mod.rs +++ b/client/src/pages/admin_panel/add_table/mod.rs @@ -5,3 +5,4 @@ pub mod nav; pub mod state; pub mod logic; pub mod event; +pub mod loader; diff --git a/client/src/pages/admin_panel/add_table/state.rs b/client/src/pages/admin_panel/add_table/state.rs index 1135d92..b98a80d 100644 --- a/client/src/pages/admin_panel/add_table/state.rs +++ b/client/src/pages/admin_panel/add_table/state.rs @@ -98,23 +98,48 @@ impl AddTableState { /// Helper method to add a column from current inputs pub fn add_column_from_inputs(&mut self) -> Option { - if self.column_name_input.trim().is_empty() || self.column_type_input.trim().is_empty() { - return Some("Both column name and type are required".to_string()); + let table_name_in = self.table_name_input.trim().to_string(); + let column_name_in = self.column_name_input.trim().to_string(); + let column_type_in = self.column_type_input.trim().to_string(); + + // Case: "only table name" provided → set it and stay on TableName + if !table_name_in.is_empty() && column_name_in.is_empty() && column_type_in.is_empty() { + self.table_name = table_name_in; + self.table_name_input.clear(); + self.table_name_cursor_pos = 0; + self.current_focus = AddTableFocus::InputTableName; + self.has_unsaved_changes = true; + return Some(format!("Table name set to '{}'.", self.table_name)); } - // Check for duplicate column names - if self.columns.iter().any(|col| col.name == self.column_name_input.trim()) { + // Column validation + if column_name_in.is_empty() || column_type_in.is_empty() { + return Some("Both column name and type are required".to_string()); + } + if self.columns.iter().any(|col| col.name == column_name_in) { return Some("Column name already exists".to_string()); } + // If table_name input present while adding first column, apply it too + if !table_name_in.is_empty() { + self.table_name = table_name_in; + self.table_name_input.clear(); + self.table_name_cursor_pos = 0; + } + // Add the column self.columns.push(ColumnDefinition { - name: self.column_name_input.trim().to_string(), - data_type: self.column_type_input.trim().to_string(), + name: column_name_in.clone(), + data_type: column_type_in.clone(), + selected: false, + }); + // Add a corresponding (unselected) index with the same name + self.indexes.push(IndexDefinition { + name: column_name_in.clone(), selected: false, }); - // Clear inputs and reset focus to column name for next entry + // Clear column inputs and set focus for next entry self.column_name_input.clear(); self.column_type_input.clear(); self.column_name_cursor_pos = 0; @@ -123,23 +148,33 @@ impl AddTableState { self.last_canvas_field = 1; self.has_unsaved_changes = true; - Some(format!("Column '{}' added successfully", self.columns.last().unwrap().name)) + Some(format!("Column '{}' added successfully", column_name_in)) } /// Helper method to delete selected items pub fn delete_selected_items(&mut self) -> Option { - let mut deleted_items = Vec::new(); + let mut deleted_items: Vec = Vec::new(); // Remove selected columns - let initial_column_count = self.columns.len(); - self.columns.retain(|col| { - if col.selected { - deleted_items.push(format!("column '{}'", col.name)); - false - } else { - true - } - }); + let selected_col_names: std::collections::HashSet = self + .columns + .iter() + .filter(|c| c.selected) + .map(|c| c.name.clone()) + .collect(); + if !selected_col_names.is_empty() { + self.columns.retain(|col| { + if selected_col_names.contains(&col.name) { + deleted_items.push(format!("column '{}'", col.name)); + false + } else { + true + } + }); + // Also purge indexes for deleted columns + self.indexes + .retain(|idx| !selected_col_names.contains(&idx.name)); + } // Remove selected indexes let initial_index_count = self.indexes.len(); @@ -167,6 +202,8 @@ impl AddTableState { Some("No items selected for deletion".to_string()) } else { self.has_unsaved_changes = true; + self.column_table_state.select(None); + self.index_table_state.select(None); Some(format!("Deleted: {}", deleted_items.join(", "))) } }