// src/state/pages/add_table.rs use canvas::canvas::{CanvasState, ActionContext, CanvasAction, AppMode}; use ratatui::widgets::TableState; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ColumnDefinition { pub name: String, pub data_type: String, pub selected: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct IndexDefinition { pub name: String, pub selected: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct LinkDefinition { pub linked_table_name: String, pub is_required: bool, pub selected: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AddTableFocus { #[default] InputTableName, // Field 0 for CanvasState InputColumnName, // Field 1 for CanvasState InputColumnType, // Field 2 for CanvasState AddColumnButton, // Result Tables ColumnsTable, IndexesTable, LinksTable, // Inside Tables (Scrolling Focus) InsideColumnsTable, InsideIndexesTable, InsideLinksTable, // Buttons SaveButton, DeleteSelectedButton, CancelButton, } #[derive(Debug, Clone)] pub struct AddTableState { pub profile_name: String, pub table_name: String, pub table_name_input: String, pub column_name_input: String, pub column_type_input: String, pub columns: Vec, pub indexes: Vec, pub links: Vec, pub current_focus: AddTableFocus, pub last_canvas_field: usize, pub column_table_state: TableState, pub index_table_state: TableState, pub link_table_state: TableState, pub table_name_cursor_pos: usize, pub column_name_cursor_pos: usize, pub column_type_cursor_pos: usize, pub has_unsaved_changes: bool, pub app_mode: AppMode, } impl Default for AddTableState { fn default() -> Self { AddTableState { profile_name: "default".to_string(), table_name: String::new(), table_name_input: String::new(), column_name_input: String::new(), column_type_input: String::new(), columns: Vec::new(), indexes: Vec::new(), links: Vec::new(), current_focus: AddTableFocus::InputTableName, last_canvas_field: 2, column_table_state: TableState::default(), index_table_state: TableState::default(), link_table_state: TableState::default(), table_name_cursor_pos: 0, column_name_cursor_pos: 0, column_type_cursor_pos: 0, has_unsaved_changes: false, app_mode: AppMode::Edit, } } } impl AddTableState { pub const INPUT_FIELD_COUNT: usize = 3; /// 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()); } // Check for duplicate column names if self.columns.iter().any(|col| col.name == self.column_name_input.trim()) { return Some("Column name already exists".to_string()); } // Add the column self.columns.push(ColumnDefinition { name: self.column_name_input.trim().to_string(), data_type: self.column_type_input.trim().to_string(), selected: false, }); // Clear inputs and reset focus to column name for next entry self.column_name_input.clear(); self.column_type_input.clear(); self.column_name_cursor_pos = 0; self.column_type_cursor_pos = 0; self.current_focus = AddTableFocus::InputColumnName; self.last_canvas_field = 1; self.has_unsaved_changes = true; Some(format!("Column '{}' added successfully", self.columns.last().unwrap().name)) } /// Helper method to delete selected items pub fn delete_selected_items(&mut self) -> Option { let mut deleted_items = 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 } }); // Remove selected indexes let initial_index_count = self.indexes.len(); self.indexes.retain(|idx| { if idx.selected { deleted_items.push(format!("index '{}'", idx.name)); false } else { true } }); // Remove selected links let initial_link_count = self.links.len(); self.links.retain(|link| { if link.selected { deleted_items.push(format!("link to '{}'", link.linked_table_name)); false } else { true } }); if deleted_items.is_empty() { Some("No items selected for deletion".to_string()) } else { self.has_unsaved_changes = true; Some(format!("Deleted: {}", deleted_items.join(", "))) } } } // Implement external library's CanvasState for AddTableState impl CanvasState for AddTableState { fn current_field(&self) -> usize { match self.current_focus { AddTableFocus::InputTableName => 0, AddTableFocus::InputColumnName => 1, AddTableFocus::InputColumnType => 2, // If focus is elsewhere, return the last canvas field used _ => self.last_canvas_field, } } fn current_cursor_pos(&self) -> usize { match self.current_focus { AddTableFocus::InputTableName => self.table_name_cursor_pos, AddTableFocus::InputColumnName => self.column_name_cursor_pos, AddTableFocus::InputColumnType => self.column_type_cursor_pos, _ => 0, // Default if focus is not on an input field } } fn set_current_field(&mut self, index: usize) { // Update both current focus and last canvas field self.current_focus = match index { 0 => { self.last_canvas_field = 0; AddTableFocus::InputTableName }, 1 => { self.last_canvas_field = 1; AddTableFocus::InputColumnName }, 2 => { self.last_canvas_field = 2; AddTableFocus::InputColumnType }, _ => self.current_focus, // Stay on current focus if index is out of bounds }; } fn set_current_cursor_pos(&mut self, pos: usize) { match self.current_focus { AddTableFocus::InputTableName => self.table_name_cursor_pos = pos, AddTableFocus::InputColumnName => self.column_name_cursor_pos = pos, AddTableFocus::InputColumnType => self.column_type_cursor_pos = pos, _ => {} // Do nothing if focus is not on an input field } } fn get_current_input(&self) -> &str { match self.current_focus { AddTableFocus::InputTableName => &self.table_name_input, AddTableFocus::InputColumnName => &self.column_name_input, AddTableFocus::InputColumnType => &self.column_type_input, _ => "", // Should not happen if called correctly } } fn get_current_input_mut(&mut self) -> &mut String { match self.current_focus { AddTableFocus::InputTableName => &mut self.table_name_input, AddTableFocus::InputColumnName => &mut self.column_name_input, AddTableFocus::InputColumnType => &mut self.column_type_input, _ => &mut self.table_name_input, // Fallback } } fn inputs(&self) -> Vec<&String> { vec![&self.table_name_input, &self.column_name_input, &self.column_type_input] } fn fields(&self) -> Vec<&str> { // These must match the order used in render_add_table vec!["Table name", "Name", "Type"] } fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { match action { // Handle adding column when user presses Enter on the Add button or uses specific action CanvasAction::Custom(action_str) if action_str == "add_column" => { self.add_column_from_inputs() } // Handle table saving CanvasAction::Custom(action_str) if action_str == "save_table" => { if self.table_name_input.trim().is_empty() { Some("Table name is required".to_string()) } else if self.columns.is_empty() { Some("At least one column is required".to_string()) } else { Some(format!("Saving table: {}", self.table_name_input)) } } // Handle deleting selected items CanvasAction::Custom(action_str) if action_str == "delete_selected" => { self.delete_selected_items() } // Handle canceling (clear form) CanvasAction::Custom(action_str) if action_str == "cancel" => { // Reset to defaults but keep profile_name let profile = self.profile_name.clone(); *self = Self::default(); self.profile_name = profile; Some("Form cleared".to_string()) } // Custom validation when moving between fields CanvasAction::NextField => { // When leaving table name field, update the table_name for display if self.current_field() == 0 && !self.table_name_input.trim().is_empty() { self.table_name = self.table_name_input.trim().to_string(); } None // Let canvas library handle the normal field movement } // Let canvas library handle everything else _ => None, } } fn current_mode(&self) -> AppMode { self.app_mode } }