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::config::binds::config::Config;
|
||||||
use crate::movement::{move_focus, MovementAction};
|
use crate::movement::{move_focus, MovementAction};
|
||||||
use crate::pages::admin_panel::add_table::logic::{
|
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::nav::SaveTableResultSender;
|
||||||
use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableFormState};
|
use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableFormState};
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
@@ -245,11 +246,9 @@ pub fn handle_add_table_event(
|
|||||||
match ma {
|
match ma {
|
||||||
MovementAction::Select => match page.current_focus() {
|
MovementAction::Select => match page.current_focus() {
|
||||||
AddTableFocus::AddColumnButton => {
|
AddTableFocus::AddColumnButton => {
|
||||||
if let Some(focus_after_add) =
|
if let Some(msg) = page.state.add_column_from_inputs() {
|
||||||
handle_add_column_action(&mut page.state, &mut String::new())
|
// Focus is set by the state method; just bubble message
|
||||||
{
|
return Ok(EventOutcome::Ok(msg));
|
||||||
page.set_current_focus(focus_after_add);
|
|
||||||
return Ok(EventOutcome::Ok("Column added".into()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddTableFocus::SaveButton => {
|
AddTableFocus::SaveButton => {
|
||||||
@@ -269,7 +268,10 @@ pub fn handle_add_table_event(
|
|||||||
return Ok(EventOutcome::Ok("Saving table...".into()));
|
return Ok(EventOutcome::Ok("Saving table...".into()));
|
||||||
}
|
}
|
||||||
AddTableFocus::DeleteSelectedButton => {
|
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));
|
return Ok(EventOutcome::Ok(msg));
|
||||||
}
|
}
|
||||||
AddTableFocus::CancelButton => {
|
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
|
// 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.
|
use crate::pages::admin_panel::add_table::state::{AddTableState, AddTableFocus};
|
||||||
///
|
|
||||||
/// Takes the mutable state and command message string.
|
/// Thin wrapper around AddTableState::add_column_from_inputs
|
||||||
/// Returns `Some(AddTableFocus)` indicating the desired focus state after a successful add,
|
/// Returns Some(AddTableFocus) for compatibility with old call sites.
|
||||||
/// or `None` if the action failed (e.g., validation error).
|
|
||||||
pub fn handle_add_column_action(
|
pub fn handle_add_column_action(
|
||||||
add_table_state: &mut AddTableState,
|
add_table_state: &mut AddTableState,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
) -> Option<AddTableFocus> {
|
) -> Option<AddTableFocus> {
|
||||||
|
if let Some(msg) = add_table_state.add_column_from_inputs() {
|
||||||
// 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;
|
*command_message = msg;
|
||||||
|
// State sets focus internally; return it explicitly for old call sites
|
||||||
// Clear all inputs and reset cursors
|
return Some(add_table_state.current_focus);
|
||||||
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
|
None
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles deleting columns marked as selected in the AddTableState.
|
/// Thin wrapper around AddTableState::delete_selected_items
|
||||||
pub fn handle_delete_selected_columns(
|
pub fn handle_delete_selected_columns(add_table_state: &mut AddTableState) -> String {
|
||||||
add_table_state: &mut AddTableState,
|
add_table_state
|
||||||
) -> String {
|
.delete_selected_items()
|
||||||
let initial_count = add_table_state.columns.len();
|
.unwrap_or_else(|| "No items selected for deletion".to_string())
|
||||||
// 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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ pub mod nav;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod loader;
|
||||||
|
|||||||
@@ -98,23 +98,48 @@ impl AddTableState {
|
|||||||
|
|
||||||
/// Helper method to add a column from current inputs
|
/// Helper method to add a column from current inputs
|
||||||
pub fn add_column_from_inputs(&mut self) -> Option<String> {
|
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() {
|
let table_name_in = self.table_name_input.trim().to_string();
|
||||||
return Some("Both column name and type are required".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
|
// Column validation
|
||||||
if self.columns.iter().any(|col| col.name == self.column_name_input.trim()) {
|
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());
|
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
|
// Add the column
|
||||||
self.columns.push(ColumnDefinition {
|
self.columns.push(ColumnDefinition {
|
||||||
name: self.column_name_input.trim().to_string(),
|
name: column_name_in.clone(),
|
||||||
data_type: self.column_type_input.trim().to_string(),
|
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,
|
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_name_input.clear();
|
||||||
self.column_type_input.clear();
|
self.column_type_input.clear();
|
||||||
self.column_name_cursor_pos = 0;
|
self.column_name_cursor_pos = 0;
|
||||||
@@ -123,23 +148,33 @@ impl AddTableState {
|
|||||||
self.last_canvas_field = 1;
|
self.last_canvas_field = 1;
|
||||||
self.has_unsaved_changes = true;
|
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
|
/// Helper method to delete selected items
|
||||||
pub fn delete_selected_items(&mut self) -> Option<String> {
|
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
|
// 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| {
|
self.columns.retain(|col| {
|
||||||
if col.selected {
|
if selected_col_names.contains(&col.name) {
|
||||||
deleted_items.push(format!("column '{}'", col.name));
|
deleted_items.push(format!("column '{}'", col.name));
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Also purge indexes for deleted columns
|
||||||
|
self.indexes
|
||||||
|
.retain(|idx| !selected_col_names.contains(&idx.name));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove selected indexes
|
// Remove selected indexes
|
||||||
let initial_index_count = self.indexes.len();
|
let initial_index_count = self.indexes.len();
|
||||||
@@ -167,6 +202,8 @@ impl AddTableState {
|
|||||||
Some("No items selected for deletion".to_string())
|
Some("No items selected for deletion".to_string())
|
||||||
} else {
|
} else {
|
||||||
self.has_unsaved_changes = true;
|
self.has_unsaved_changes = true;
|
||||||
|
self.column_table_state.select(None);
|
||||||
|
self.index_table_state.select(None);
|
||||||
Some(format!("Deleted: {}", deleted_items.join(", ")))
|
Some(format!("Deleted: {}", deleted_items.join(", ")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user