// src/pages/admin_panel/add_table/state.rs use canvas::{DataProvider, AppMode}; use canvas::FormEditor; 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: canvas::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: canvas::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 { 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)); } // 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: 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 column inputs and set focus 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", column_name_in)) } /// Helper method to delete selected items pub fn delete_selected_items(&mut self) -> Option { let mut deleted_items: Vec = Vec::new(); // Remove selected columns 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(); 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; self.column_table_state.select(None); self.index_table_state.select(None); Some(format!("Deleted: {}", deleted_items.join(", "))) } } } impl DataProvider for AddTableState { fn field_count(&self) -> usize { 3 // Table name, Column name, Column type } fn field_name(&self, index: usize) -> &str { match index { 0 => "Table name", 1 => "Name", 2 => "Type", _ => "", } } fn field_value(&self, index: usize) -> &str { match index { 0 => &self.table_name_input, 1 => &self.column_name_input, 2 => &self.column_type_input, _ => "", } } fn set_field_value(&mut self, index: usize, value: String) { match index { 0 => self.table_name_input = value, 1 => self.column_name_input = value, 2 => self.column_type_input = value, _ => {} } self.has_unsaved_changes = true; } fn supports_suggestions(&self, _field_index: usize) -> bool { false // AddTableState doesn’t use suggestions } } pub struct AddTableFormState { pub state: AddTableState, pub editor: FormEditor, pub focus_outside_canvas: bool, } impl std::fmt::Debug for AddTableFormState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AddTableFormState") .field("state", &self.state) .field("focus_outside_canvas", &self.focus_outside_canvas) .finish() } } impl AddTableFormState { pub fn new(profile_name: String) -> Self { let mut state = AddTableState::default(); state.profile_name = profile_name; let editor = FormEditor::new(state.clone()); Self { state, editor, focus_outside_canvas: false, } } pub fn from_state(state: AddTableState) -> Self { let editor = FormEditor::new(state.clone()); Self { state, editor, focus_outside_canvas: false, } } /// Sync state from editor’s snapshot pub fn sync_from_editor(&mut self) { self.state = self.editor.data_provider().clone(); } // === Delegates to AddTableState fields === pub fn current_focus(&self) -> AddTableFocus { self.state.current_focus } pub fn set_current_focus(&mut self, focus: AddTableFocus) { self.state.current_focus = focus; } pub fn profile_name(&self) -> &str { &self.state.profile_name } pub fn table_name(&self) -> &str { &self.state.table_name } pub fn columns(&self) -> &Vec { &self.state.columns } pub fn indexes(&self) -> &Vec { &self.state.indexes } pub fn links(&self) -> &Vec { &self.state.links } pub fn column_table_state(&mut self) -> &mut TableState { &mut self.state.column_table_state } pub fn index_table_state(&mut self) -> &mut TableState { &mut self.state.index_table_state } pub fn link_table_state(&mut self) -> &mut TableState { &mut self.state.link_table_state } }