diff --git a/client/src/components/form/form.rs b/client/src/components/form/form.rs index bfe630e..12a3ec7 100644 --- a/client/src/components/form/form.rs +++ b/client/src/components/form/form.rs @@ -17,28 +17,29 @@ pub fn render_form( fields: &[&str], current_field_idx: &usize, inputs: &[&String], + table_name: &str, // This parameter receives the correct table name theme: &Theme, is_edit_mode: bool, highlight_state: &HighlightState, total_count: u64, current_position: u64, ) { - // Create Adresar card + // Use the dynamic `table_name` parameter for the title instead of a hardcoded string. + let card_title = format!(" {} ", table_name); + let adresar_card = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(theme.border)) - .title(" Adresar ") + .title(card_title) // Use the dynamic title .style(Style::default().bg(theme.bg).fg(theme.fg)); f.render_widget(adresar_card, area); - // Define inner area let inner_area = area.inner(Margin { horizontal: 1, vertical: 1, }); - // Create main layout let main_layout = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -47,12 +48,11 @@ pub fn render_form( ]) .split(inner_area); - // Render count/position let count_position_text = if total_count == 0 && current_position == 1 { "Total: 0 | New Entry".to_string() } else if current_position > total_count && total_count > 0 { format!("Total: {} | New Entry ({})", total_count, current_position) - } else if total_count == 0 && current_position > 1 { // Should not happen if logic is correct + } else if total_count == 0 && current_position > 1 { format!("Total: 0 | New Entry ({})", current_position) } else { @@ -63,7 +63,6 @@ pub fn render_form( .alignment(Alignment::Left); f.render_widget(count_para, main_layout[0]); - // Delegate input handling to canvas render_canvas( f, main_layout[1], diff --git a/client/src/modes/canvas/read_only.rs b/client/src/modes/canvas/read_only.rs index 125b602..c8705de 100644 --- a/client/src/modes/canvas/read_only.rs +++ b/client/src/modes/canvas/read_only.rs @@ -23,8 +23,6 @@ pub async fn handle_read_only_event( add_table_state: &mut AddTableState, add_logic_state: &mut AddLogicState, key_sequence_tracker: &mut KeySequenceTracker, - current_position: &mut u64, - total_count: u64, grpc_client: &mut GrpcClient, command_message: &mut String, edit_mode_cooldown: &mut bool, @@ -74,12 +72,10 @@ pub async fn handle_read_only_event( action, form_state, grpc_client, - current_position, - total_count, ideal_cursor_column, ) .await? - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions + } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { crate::tui::functions::login::handle_action(action).await? } else if app_state.ui.show_add_table { add_table_ro::execute_action( @@ -143,12 +139,10 @@ pub async fn handle_read_only_event( action, form_state, grpc_client, - current_position, - total_count, ideal_cursor_column, ) .await? - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions + } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { crate::tui::functions::login::handle_action(action).await? } else if app_state.ui.show_add_table { add_table_ro::execute_action( @@ -177,7 +171,7 @@ pub async fn handle_read_only_event( key_sequence_tracker, command_message, ).await? - } else if app_state.ui.show_login { // Handle login general actions + } else if app_state.ui.show_login { auth_ro::execute_action( action, app_state, @@ -211,8 +205,6 @@ pub async fn handle_read_only_event( action, form_state, grpc_client, - current_position, - total_count, ideal_cursor_column, ) .await? @@ -245,7 +237,7 @@ pub async fn handle_read_only_event( key_sequence_tracker, command_message, ).await? - } else if app_state.ui.show_login { // Handle login general actions + } else if app_state.ui.show_login { auth_ro::execute_action( action, app_state, diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 8a88219..15712ef 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -338,15 +338,23 @@ impl EventHandler { } } - let mut current_position = form_state.current_position; - let total_count = form_state.total_count; let (_should_exit, message) = read_only::handle_read_only_event( - app_state, key_event, config, form_state, login_state, register_state, - &mut admin_state.add_table_state, &mut admin_state.add_logic_state, - &mut self.key_sequence_tracker, &mut current_position, total_count, - grpc_client, &mut self.command_message, &mut self.edit_mode_cooldown, + app_state, + key_event, + config, + form_state, + login_state, + register_state, + &mut admin_state.add_table_state, + &mut admin_state.add_logic_state, + &mut self.key_sequence_tracker, + // No more current_position or total_count arguments + grpc_client, + &mut self.command_message, + &mut self.edit_mode_cooldown, &mut self.ideal_cursor_column, - ).await?; + ) + .await?; return Ok(EventOutcome::Ok(message)); } @@ -365,15 +373,22 @@ impl EventHandler { return Ok(EventOutcome::Ok("".to_string())); } - let mut current_position = form_state.current_position; - let total_count = form_state.total_count; let (_should_exit, message) = read_only::handle_read_only_event( - app_state, key_event, config, form_state, login_state, register_state, - &mut admin_state.add_table_state, &mut admin_state.add_logic_state, - &mut self.key_sequence_tracker, &mut current_position, total_count, - grpc_client, &mut self.command_message, &mut self.edit_mode_cooldown, + app_state, + key_event, + config, + form_state, + login_state, + register_state, + &mut admin_state.add_table_state, + &mut admin_state.add_logic_state, + &mut self.key_sequence_tracker, + grpc_client, + &mut self.command_message, + &mut self.edit_mode_cooldown, &mut self.ideal_cursor_column, - ).await?; + ) + .await?; return Ok(EventOutcome::Ok(message)); } diff --git a/client/src/modes/highlight/highlight.rs b/client/src/modes/highlight/highlight.rs index 023f2f4..4e07c82 100644 --- a/client/src/modes/highlight/highlight.rs +++ b/client/src/modes/highlight/highlight.rs @@ -44,8 +44,6 @@ pub async fn handle_highlight_event( &mut admin_state.add_table_state, &mut admin_state.add_logic_state, key_sequence_tracker, - current_position, - total_count, grpc_client, command_message, // Pass the message buffer edit_mode_cooldown, diff --git a/client/src/services/ui_service.rs b/client/src/services/ui_service.rs index 346e5e4..fe236f8 100644 --- a/client/src/services/ui_service.rs +++ b/client/src/services/ui_service.rs @@ -161,7 +161,7 @@ impl UiService { form_state.total_count = total_count; if total_count > 0 { - form_state.current_position = 1; + form_state.current_position = total_count; } else { form_state.current_position = 1; } diff --git a/client/src/state/pages/form.rs b/client/src/state/pages/form.rs index f32a6a0..cdf66aa 100644 --- a/client/src/state/pages/form.rs +++ b/client/src/state/pages/form.rs @@ -51,7 +51,6 @@ impl FormState { theme: &Theme, is_edit_mode: bool, highlight_state: &HighlightState, - // total_count and current_position are now part of self ) { let fields_str_slice: Vec<&str> = self.fields.iter().map(|s| s.as_str()).collect(); @@ -64,6 +63,7 @@ impl FormState { &fields_str_slice, &self.current_field, &values_str_slice, + &self.table_name, theme, is_edit_mode, highlight_state, @@ -107,33 +107,35 @@ impl FormState { &mut self, response_data: &HashMap, ) { - self.values = self.fields.iter() - .map(|field| response_data.get(field).cloned().unwrap_or_default()) - .collect(); + // Create a new vector for the values, ensuring they are in the correct order. + self.values = self.fields.iter().map(|field_from_schema| { + // For each field from our schema, find the corresponding key in the + // response data by doing a case-insensitive comparison. + response_data + .iter() + .find(|(key_from_data, _)| key_from_data.eq_ignore_ascii_case(field_from_schema)) + .map(|(_, value)| value.clone()) // If found, clone its value. + .unwrap_or_default() // If not found, use an empty string. + }).collect(); - if let Some(id_str) = response_data.get("id") { - match id_str.parse::() { - Ok(parsed_id) => self.id = parsed_id, - Err(e) => { - tracing::error!( - "Failed to parse 'id' field '{}' for table {}.{}: {}", - id_str, - self.profile_name, - self.table_name, - e - ); - self.id = 0; // Default to 0 if parsing fails - } + // Now, do the same case-insensitive lookup for the 'id' field. + let id_str_opt = response_data + .iter() + .find(|(k, _)| k.eq_ignore_ascii_case("id")) + .map(|(_, v)| v); + + if let Some(id_str) = id_str_opt { + if let Ok(parsed_id) = id_str.parse::() { + self.id = parsed_id; + } else { + tracing::error!( "Failed to parse 'id' field '{}' for table {}.{}", id_str, self.profile_name, self.table_name); + self.id = 0; } } else { - // If no ID is present, it might be a new record structure or an error - // For now, assume it means the record doesn't have an ID from the server yet self.id = 0; } + self.has_unsaved_changes = false; - // current_field and current_cursor_pos might need resetting or adjusting - // depending on the desired behavior after loading data. - // For now, let's reset current_field to 0. self.current_field = 0; self.current_cursor_pos = 0; } diff --git a/client/src/tui/functions/form.rs b/client/src/tui/functions/form.rs index a1f38de..986c45e 100644 --- a/client/src/tui/functions/form.rs +++ b/client/src/tui/functions/form.rs @@ -1,19 +1,16 @@ // src/tui/functions/form.rs +use crate::state::pages::canvas_state::CanvasState; // Import the trait use crate::state::pages::form::FormState; use crate::services::grpc_client::GrpcClient; -use crate::state::pages::canvas_state::CanvasState; -use crate::services::ui_service::UiService; use anyhow::{anyhow, Result}; pub async fn handle_action( action: &str, form_state: &mut FormState, - grpc_client: &mut GrpcClient, - current_position: &mut u64, - total_count: u64, + _grpc_client: &mut GrpcClient, ideal_cursor_column: &mut usize, ) -> Result { - // Check for unsaved changes in both cases + // FIX: Call has_unsaved_changes() via the CanvasState trait. if form_state.has_unsaved_changes() { return Ok( "Unsaved changes. Save (Ctrl+S) or Revert (Ctrl+R) before navigating." @@ -21,56 +18,31 @@ pub async fn handle_action( ); } + let total_count = form_state.total_count; + match action { "previous_entry" => { - let new_position = form_state.current_position.saturating_sub(1); - if new_position >= 1 { - form_state.current_position = new_position; - *current_position = new_position; - - if new_position <= form_state.total_count { - let load_message = UiService::load_table_data_by_position(grpc_client, form_state).await?; - - let current_input = form_state.get_current_input(); - let max_cursor_pos = if !current_input.is_empty() { - current_input.len() - 1 - } else { 0 }; - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); - - Ok(load_message) + if total_count > 0 { + if form_state.current_position > 1 { + form_state.current_position -= 1; } else { - Ok(format!("Moved to position {}", new_position)) + form_state.current_position = total_count; } - } else { - Ok("Already at first position".into()) + *ideal_cursor_column = 0; } } "next_entry" => { - if form_state.current_position <= form_state.total_count { - form_state.current_position += 1; - *current_position = form_state.current_position; - - if form_state.current_position <= form_state.total_count { - let load_message = UiService::load_table_data_by_position(grpc_client, form_state).await?; - - let current_input = form_state.get_current_input(); - let max_cursor_pos = if !current_input.is_empty() { - current_input.len() - 1 - } else { 0 }; - form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos); - - Ok(load_message) + if total_count > 0 { + if form_state.current_position < total_count { + form_state.current_position += 1; } else { - form_state.reset_to_empty(); - form_state.current_field = 0; - form_state.current_cursor_pos = 0; - *ideal_cursor_column = 0; - Ok("New form entry mode".into()) + form_state.current_position = 1; } - } else { - Ok("Already at last entry".into()) + *ideal_cursor_column = 0; } } - _ => Err(anyhow!("Unknown form action: {}", action)) + _ => return Err(anyhow!("Unknown form action: {}", action)), } + + Ok(String::new()) } diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 2d9fe54..0a67ed3 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -137,6 +137,7 @@ pub fn render_ui( f, app_state, auth_state, admin_state, main_content_area, theme, &app_state.profile_tree, &app_state.selected_profile, ); + } else if app_state.ui.show_form { let (sidebar_area, form_actual_area) = calculate_sidebar_layout( app_state.ui.show_sidebar, main_content_area @@ -158,12 +159,24 @@ pub fn render_ui( }; let fields_vec: Vec<&str> = form_state.fields.iter().map(AsRef::as_ref).collect(); let values_vec: Vec<&String> = form_state.values.iter().collect(); + + // --- START FIX --- + // Add the missing `&form_state.table_name` argument to this function call. render_form( - f, form_render_area, form_state, &fields_vec, &form_state.current_field, - &values_vec, theme, is_event_handler_edit_mode, highlight_state, + f, + form_render_area, + form_state, + &fields_vec, + &form_state.current_field, + &values_vec, + &form_state.table_name, // <-- THIS ARGUMENT WAS MISSING + theme, + is_event_handler_edit_mode, + highlight_state, form_state.total_count, form_state.current_position, ); + // --- END FIX --- } if let Some(area) = buffer_list_area { diff --git a/server/src/tables_data/handlers/get_table_data.rs b/server/src/tables_data/handlers/get_table_data.rs index acb2857..dfb8128 100644 --- a/server/src/tables_data/handlers/get_table_data.rs +++ b/server/src/tables_data/handlers/get_table_data.rs @@ -56,7 +56,6 @@ pub async fn get_table_data( let system_columns = vec![ ("id".to_string(), "BIGINT".to_string()), ("deleted".to_string(), "BOOLEAN".to_string()), - ("firma".to_string(), "TEXT".to_string()), ]; let all_columns: Vec<(String, String)> = system_columns .into_iter() diff --git a/server/src/tables_data/handlers/post_table_data.rs b/server/src/tables_data/handlers/post_table_data.rs index 285be71..6559dbb 100644 --- a/server/src/tables_data/handlers/post_table_data.rs +++ b/server/src/tables_data/handlers/post_table_data.rs @@ -19,19 +19,11 @@ pub async fn post_table_data( let table_name = request.table_name; let mut data = HashMap::new(); - // Process and validate all data values + // CORRECTED: Process and trim all incoming data values. + // We remove the hardcoded validation. We will let the database's + // NOT NULL constraints or Steel validation scripts handle required fields. for (key, value) in request.data { - let trimmed = value.trim().to_string(); - - // Handle specially - it cannot be empty - if trimmed.is_empty() { - return Err(Status::invalid_argument("Firma cannot be empty")); - } - - // Add trimmed non-empty values to data map - if !trimmed.is_empty() { - data.insert(key, trimmed); - } + data.insert(key, value.trim().to_string()); } // Lookup profile