moving add_table to add_logic modern architecture3
This commit is contained in:
@@ -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 => {
|
||||
|
||||
78
client/src/pages/admin_panel/add_table/loader.rs
Normal file
78
client/src/pages/admin_panel/add_table/loader.rs
Normal file
@@ -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<String> {
|
||||
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<ProtoColumnDefinition> = add_table_state
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| ProtoColumnDefinition {
|
||||
name: col.name.clone(),
|
||||
field_type: col.data_type.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let proto_indexes: Vec<String> = add_table_state
|
||||
.indexes
|
||||
.iter()
|
||||
.filter(|idx| idx.selected)
|
||||
.map(|idx| idx.name.clone())
|
||||
.collect();
|
||||
|
||||
let proto_links: Vec<ProtoTableLink> = 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)),
|
||||
}
|
||||
}
|
||||
@@ -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<AddTableFocus> {
|
||||
|
||||
// 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);
|
||||
if let Some(msg) = add_table_state.add_column_from_inputs() {
|
||||
*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
|
||||
// State sets focus internally; return it explicitly for old call sites
|
||||
return Some(add_table_state.current_focus);
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<String> = 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<String> {
|
||||
// --- 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<ProtoColumnDefinition> = 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<String> = add_table_state
|
||||
.indexes
|
||||
.iter()
|
||||
.filter(|idx| idx.selected) // Only include selected indexes
|
||||
.map(|idx| idx.name.clone())
|
||||
.collect();
|
||||
|
||||
let proto_links: Vec<ProtoTableLink> = 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())
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ pub mod nav;
|
||||
pub mod state;
|
||||
pub mod logic;
|
||||
pub mod event;
|
||||
pub mod loader;
|
||||
|
||||
@@ -98,23 +98,48 @@ impl AddTableState {
|
||||
|
||||
/// Helper method to add a column from current inputs
|
||||
pub fn add_column_from_inputs(&mut self) -> Option<String> {
|
||||
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<String> {
|
||||
let mut deleted_items = Vec::new();
|
||||
let mut deleted_items: Vec<String> = Vec::new();
|
||||
|
||||
// Remove selected columns
|
||||
let initial_column_count = self.columns.len();
|
||||
let selected_col_names: std::collections::HashSet<String> = self
|
||||
.columns
|
||||
.iter()
|
||||
.filter(|c| c.selected)
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
if !selected_col_names.is_empty() {
|
||||
self.columns.retain(|col| {
|
||||
if col.selected {
|
||||
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(", ")))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user