199 lines
7.9 KiB
Rust
199 lines
7.9 KiB
Rust
// src/tui/functions/common/add_table.rs
|
|
use crate::state::pages::add_table::{
|
|
AddTableFocus, AddTableState, ColumnDefinition, IndexDefinition,
|
|
};
|
|
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).
|
|
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);
|
|
*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
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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)),
|
|
}
|
|
}
|