suggestions in the dropdown menu now works amazingly well
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
/tantivy_indexes
|
/tantivy_indexes
|
||||||
|
server/tantivy_indexes
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
// src/components/common/autocomplete.rs
|
// src/components/common/autocomplete.rs
|
||||||
|
|
||||||
|
use common::proto::multieko2::search::search_response::Hit;
|
||||||
|
use serde::Deserialize;
|
||||||
|
// Keep all existing imports
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
@@ -9,6 +12,11 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
// Helper struct for parsing the JSON inside a Hit
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SuggestionContent {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
/// Renders an opaque dropdown list for autocomplete suggestions.
|
/// Renders an opaque dropdown list for autocomplete suggestions.
|
||||||
pub fn render_autocomplete_dropdown(
|
pub fn render_autocomplete_dropdown(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
@@ -88,3 +96,84 @@ pub fn render_autocomplete_dropdown(
|
|||||||
f.render_stateful_widget(list, dropdown_area, &mut profile_list_state);
|
f.render_stateful_widget(list, dropdown_area, &mut profile_list_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- NEW FUNCTION FOR RICH SUGGESTIONS ---
|
||||||
|
/// Renders an opaque dropdown list for rich `Hit`-based suggestions.
|
||||||
|
pub fn render_rich_autocomplete_dropdown(
|
||||||
|
f: &mut Frame,
|
||||||
|
input_rect: Rect,
|
||||||
|
frame_area: Rect,
|
||||||
|
theme: &Theme,
|
||||||
|
suggestions: &[Hit], // <-- Accepts &[Hit]
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
) {
|
||||||
|
if suggestions.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Get display names from Hits, with a fallback for parsing errors ---
|
||||||
|
let display_names: Vec<String> = suggestions
|
||||||
|
.iter()
|
||||||
|
.map(|hit| {
|
||||||
|
serde_json::from_str::<SuggestionContent>(&hit.content_json)
|
||||||
|
.map(|content| content.name)
|
||||||
|
.unwrap_or_else(|_| format!("ID: {}", hit.id)) // Fallback display
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// --- Calculate Dropdown Size & Position ---
|
||||||
|
let max_suggestion_width =
|
||||||
|
display_names.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
|
||||||
|
let horizontal_padding: u16 = 2;
|
||||||
|
let dropdown_width = (max_suggestion_width + horizontal_padding).max(10);
|
||||||
|
let dropdown_height = (suggestions.len() as u16).min(5);
|
||||||
|
|
||||||
|
let mut dropdown_area = Rect {
|
||||||
|
x: input_rect.x,
|
||||||
|
y: input_rect.y + 1,
|
||||||
|
width: dropdown_width,
|
||||||
|
height: dropdown_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Clamping Logic (prevent rendering off-screen) ---
|
||||||
|
if dropdown_area.bottom() > frame_area.height {
|
||||||
|
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
|
||||||
|
}
|
||||||
|
if dropdown_area.right() > frame_area.width {
|
||||||
|
dropdown_area.x = frame_area.width.saturating_sub(dropdown_width);
|
||||||
|
}
|
||||||
|
dropdown_area.x = dropdown_area.x.max(0);
|
||||||
|
dropdown_area.y = dropdown_area.y.max(0);
|
||||||
|
|
||||||
|
// --- Rendering Logic ---
|
||||||
|
let background_block =
|
||||||
|
Block::default().style(Style::default().bg(Color::DarkGray));
|
||||||
|
f.render_widget(background_block, dropdown_area);
|
||||||
|
|
||||||
|
let items: Vec<ListItem> = display_names
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| {
|
||||||
|
let is_selected = selected_index == Some(i);
|
||||||
|
let s_width = s.width() as u16;
|
||||||
|
let padding_needed = dropdown_width.saturating_sub(s_width);
|
||||||
|
let padded_s =
|
||||||
|
format!("{}{}", s, " ".repeat(padding_needed as usize));
|
||||||
|
|
||||||
|
ListItem::new(padded_s).style(if is_selected {
|
||||||
|
Style::default()
|
||||||
|
.fg(theme.bg)
|
||||||
|
.bg(theme.highlight)
|
||||||
|
.add_modifier(Modifier::BOLD)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(theme.fg).bg(Color::DarkGray)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let list = List::new(items);
|
||||||
|
let mut list_state = ListState::default();
|
||||||
|
list_state.select(selected_index);
|
||||||
|
|
||||||
|
f.render_stateful_widget(list, dropdown_area, &mut list_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,16 +78,34 @@ pub fn render_form(
|
|||||||
|
|
||||||
// --- NEW: RENDER AUTOCOMPLETE ---
|
// --- NEW: RENDER AUTOCOMPLETE ---
|
||||||
if form_state.autocomplete_active {
|
if form_state.autocomplete_active {
|
||||||
if let Some(suggestions) = form_state.get_suggestions() {
|
// Use the Rect of the active field that render_canvas found for us.
|
||||||
if let Some(active_rect) = active_field_rect {
|
if let Some(active_rect) = active_field_rect {
|
||||||
if !suggestions.is_empty() {
|
let selected_index = form_state.get_selected_suggestion_index();
|
||||||
|
|
||||||
|
// THE DECIDER LOGIC:
|
||||||
|
// 1. Check for rich suggestions first.
|
||||||
|
if let Some(rich_suggestions) = form_state.get_rich_suggestions() {
|
||||||
|
if !rich_suggestions.is_empty() {
|
||||||
|
autocomplete::render_rich_autocomplete_dropdown(
|
||||||
|
f,
|
||||||
|
active_rect,
|
||||||
|
f.area(), // Use f.area() for clamping, not f.size()
|
||||||
|
theme,
|
||||||
|
rich_suggestions,
|
||||||
|
selected_index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Fallback to simple suggestions if rich ones aren't available.
|
||||||
|
else if let Some(simple_suggestions) = form_state.get_suggestions() {
|
||||||
|
if !simple_suggestions.is_empty() {
|
||||||
autocomplete::render_autocomplete_dropdown(
|
autocomplete::render_autocomplete_dropdown(
|
||||||
f,
|
f,
|
||||||
active_rect,
|
active_rect,
|
||||||
f.area(),
|
f.area(),
|
||||||
theme,
|
theme,
|
||||||
suggestions,
|
simple_suggestions,
|
||||||
form_state.get_selected_suggestion_index(),
|
selected_index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
// src/modes/canvas/edit.rs
|
// src/modes/canvas/edit.rs
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
|
use crate::functions::modes::edit::{
|
||||||
|
add_logic_e, add_table_e, auth_e, form_e,
|
||||||
|
};
|
||||||
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::state::pages::admin::AdminState;
|
||||||
use crate::state::pages::{
|
use crate::state::pages::{
|
||||||
auth::{LoginState, RegisterState},
|
auth::{LoginState, RegisterState},
|
||||||
canvas_state::CanvasState,
|
canvas_state::CanvasState,
|
||||||
|
form::FormState,
|
||||||
};
|
};
|
||||||
use crate::state::pages::form::FormState;
|
|
||||||
use crate::state::pages::admin::AdminState;
|
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
|
||||||
use crate::functions::modes::edit::{add_logic_e, auth_e, form_e, add_table_e};
|
|
||||||
use crate::state::app::state::AppState;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use common::proto::multieko2::search::search_response::Hit;
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
@@ -33,20 +36,25 @@ pub async fn handle_edit_event(
|
|||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
) -> Result<EditEventOutcome> {
|
) -> Result<EditEventOutcome> {
|
||||||
if app_state.ui.show_form && form_state.autocomplete_active {
|
if app_state.ui.show_form && form_state.autocomplete_active {
|
||||||
if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) {
|
if let Some(action) =
|
||||||
|
config.get_edit_action_for_key(key.code, key.modifiers)
|
||||||
|
{
|
||||||
match action {
|
match action {
|
||||||
"suggestion_down" => {
|
"suggestion_down" => {
|
||||||
if !form_state.autocomplete_suggestions.is_empty() {
|
if !form_state.autocomplete_suggestions.is_empty() {
|
||||||
let current = form_state.selected_suggestion_index.unwrap_or(0);
|
let current =
|
||||||
let next = (current + 1) % form_state.autocomplete_suggestions.len();
|
form_state.selected_suggestion_index.unwrap_or(0);
|
||||||
|
let next = (current + 1)
|
||||||
|
% form_state.autocomplete_suggestions.len();
|
||||||
form_state.selected_suggestion_index = Some(next);
|
form_state.selected_suggestion_index = Some(next);
|
||||||
}
|
}
|
||||||
return Ok(EditEventOutcome::Message(String::new()));
|
return Ok(EditEventOutcome::Message(String::new()));
|
||||||
}
|
}
|
||||||
"suggestion_up" => {
|
"suggestion_up" => {
|
||||||
if !form_state.autocomplete_suggestions.is_empty() {
|
if !form_state.autocomplete_suggestions.is_empty() {
|
||||||
let current = form_state.selected_suggestion_index.unwrap_or(0);
|
let current =
|
||||||
|
form_state.selected_suggestion_index.unwrap_or(0);
|
||||||
let prev = if current == 0 {
|
let prev = if current == 0 {
|
||||||
form_state.autocomplete_suggestions.len() - 1
|
form_state.autocomplete_suggestions.len() - 1
|
||||||
} else {
|
} else {
|
||||||
@@ -58,19 +66,31 @@ if app_state.ui.show_form && form_state.autocomplete_active {
|
|||||||
}
|
}
|
||||||
"exit" => {
|
"exit" => {
|
||||||
form_state.deactivate_autocomplete();
|
form_state.deactivate_autocomplete();
|
||||||
return Ok(EditEventOutcome::Message("Autocomplete cancelled".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Autocomplete cancelled".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
"enter_decider" => {
|
"enter_decider" => {
|
||||||
if let Some(selected_idx) = form_state.selected_suggestion_index {
|
if let Some(selected_idx) =
|
||||||
if let Some(selection) = form_state.autocomplete_suggestions.get(selected_idx).cloned() {
|
form_state.selected_suggestion_index
|
||||||
let current_input = form_state.get_current_input_mut();
|
{
|
||||||
*current_input = selection;
|
if let Some(selection) = form_state
|
||||||
|
.autocomplete_suggestions
|
||||||
|
.get(selected_idx)
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
|
let current_input =
|
||||||
|
form_state.get_current_input_mut();
|
||||||
|
// Use the ID from the Hit struct for the field value
|
||||||
|
*current_input = selection.id.to_string();
|
||||||
let new_cursor_pos = current_input.len();
|
let new_cursor_pos = current_input.len();
|
||||||
form_state.set_current_cursor_pos(new_cursor_pos);
|
form_state.set_current_cursor_pos(new_cursor_pos);
|
||||||
*ideal_cursor_column = new_cursor_pos;
|
*ideal_cursor_column = new_cursor_pos;
|
||||||
form_state.deactivate_autocomplete();
|
form_state.deactivate_autocomplete();
|
||||||
form_state.set_has_unsaved_changes(true);
|
form_state.set_has_unsaved_changes(true);
|
||||||
return Ok(EditEventOutcome::Message("Selection made".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Selection made".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If no selection, fall through to default behavior
|
// If no selection, fall through to default behavior
|
||||||
@@ -80,7 +100,7 @@ if app_state.ui.show_form && form_state.autocomplete_active {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
|
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
|
||||||
&config.keybindings.global,
|
&config.keybindings.global,
|
||||||
key.code,
|
key.code,
|
||||||
@@ -91,171 +111,410 @@ if app_state.ui.show_form && form_state.autocomplete_active {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(action) = config.get_action_for_key_in_mode(
|
if let Some(action) = config
|
||||||
&config.keybindings.common,
|
.get_action_for_key_in_mode(
|
||||||
key.code,
|
&config.keybindings.common,
|
||||||
key.modifiers,
|
key.code,
|
||||||
).as_deref() {
|
key.modifiers,
|
||||||
|
)
|
||||||
|
.as_deref()
|
||||||
|
{
|
||||||
if matches!(action, "save" | "revert") {
|
if matches!(action, "save" | "revert") {
|
||||||
let message_string: String = if app_state.ui.show_login {
|
let message_string: String = if app_state.ui.show_login {
|
||||||
auth_e::execute_common_action(action, login_state, grpc_client, current_position, total_count).await?
|
auth_e::execute_common_action(
|
||||||
|
action,
|
||||||
|
login_state,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_common_action(action, register_state, grpc_client, current_position, total_count).await?
|
auth_e::execute_common_action(
|
||||||
|
action,
|
||||||
|
register_state,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
format!("Action '{}' not implemented for Add Table in edit mode.", action)
|
format!(
|
||||||
|
"Action '{}' not implemented for Add Table in edit mode.",
|
||||||
|
action
|
||||||
|
)
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
format!("Action '{}' not implemented for Add Logic in edit mode.", action)
|
format!(
|
||||||
|
"Action '{}' not implemented for Add Logic in edit mode.",
|
||||||
|
action
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let outcome = form_e::execute_common_action(action, form_state, grpc_client).await?;
|
let outcome =
|
||||||
|
form_e::execute_common_action(action, form_state, grpc_client)
|
||||||
|
.await?;
|
||||||
match outcome {
|
match outcome {
|
||||||
EventOutcome::Ok(msg) | EventOutcome::DataSaved(_, msg) => msg,
|
EventOutcome::Ok(msg) | EventOutcome::DataSaved(_, msg) => msg,
|
||||||
_ => format!("Unexpected outcome from common action: {:?}", outcome),
|
_ => format!(
|
||||||
|
"Unexpected outcome from common action: {:?}",
|
||||||
|
outcome
|
||||||
|
),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(message_string));
|
return Ok(EditEventOutcome::Message(message_string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers).as_deref() {
|
if let Some(action_str) =
|
||||||
tracing::info!("[Handler] `handle_edit_event` received action: '{}'", action_str);
|
config.get_edit_action_for_key(key.code, key.modifiers).as_deref()
|
||||||
|
{
|
||||||
|
tracing::info!(
|
||||||
|
"[Handler] `handle_edit_event` received action: '{}'",
|
||||||
|
action_str
|
||||||
|
);
|
||||||
|
|
||||||
// --- MANUAL AUTOCOMPLETE TRIGGER ---
|
// --- MANUAL AUTOCOMPLETE TRIGGER ---
|
||||||
if action_str == "trigger_autocomplete" {
|
if action_str == "trigger_autocomplete" {
|
||||||
tracing::info!("[Handler] Action is 'trigger_autocomplete'. Checking conditions..."); // <-- ADD THIS
|
tracing::info!("[Handler] Action is 'trigger_autocomplete'. Checking conditions...");
|
||||||
if app_state.ui.show_form {
|
if app_state.ui.show_form {
|
||||||
tracing::info!("[Handler] In form view. Checking field..."); // <-- ADD THIS
|
|
||||||
if let Some(field_def) = form_state.fields.get(form_state.current_field) {
|
if let Some(field_def) = form_state.fields.get(form_state.current_field) {
|
||||||
if field_def.is_link {
|
if field_def.is_link {
|
||||||
tracing::info!("[Handler] Field '{}' is a link. Activating autocomplete.", field_def.display_name); // <-- ADD THIS
|
// Use our new field to get the table to search
|
||||||
form_state.autocomplete_active = true;
|
if let Some(target_table) = &field_def.link_target_table {
|
||||||
form_state.selected_suggestion_index = Some(0);
|
tracing::info!(
|
||||||
form_state.autocomplete_suggestions = vec![
|
"[Handler] Field '{}' is a link to table '{}'. Triggering search.",
|
||||||
"Hardcoded Supplier A".to_string(),
|
field_def.display_name,
|
||||||
"Hardcoded Supplier B".to_string(),
|
target_table
|
||||||
"Hardcoded Company C".to_string(),
|
);
|
||||||
];
|
|
||||||
return Ok(EditEventOutcome::Message("Autocomplete triggered".to_string()));
|
// Set loading state and activate autocomplete UI
|
||||||
|
form_state.autocomplete_loading = true;
|
||||||
|
form_state.autocomplete_active = true;
|
||||||
|
form_state.autocomplete_suggestions.clear();
|
||||||
|
form_state.selected_suggestion_index = None;
|
||||||
|
|
||||||
|
let query = form_state.get_current_input().to_string();
|
||||||
|
let table_to_search = target_table.clone();
|
||||||
|
|
||||||
|
// Perform the gRPC call asynchronously
|
||||||
|
match grpc_client.search_table(table_to_search, query).await {
|
||||||
|
Ok(response) => {
|
||||||
|
form_state.autocomplete_suggestions = response.hits;
|
||||||
|
if !form_state.autocomplete_suggestions.is_empty() {
|
||||||
|
form_state.selected_suggestion_index = Some(0);
|
||||||
|
}
|
||||||
|
form_state.autocomplete_loading = false; // Turn off loading
|
||||||
|
return Ok(EditEventOutcome::Message(format!(
|
||||||
|
"Found {} suggestions.",
|
||||||
|
form_state.autocomplete_suggestions.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Handle errors gracefully
|
||||||
|
form_state.autocomplete_loading = false;
|
||||||
|
form_state.deactivate_autocomplete(); // Close UI on error
|
||||||
|
let error_msg = format!("Search failed: {}", e);
|
||||||
|
tracing::error!("[Handler] {}", error_msg);
|
||||||
|
return Ok(EditEventOutcome::Message(error_msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let msg = "Field is a link, but target table is not defined.".to_string();
|
||||||
|
tracing::error!("[Handler] {}", msg);
|
||||||
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tracing::error!("[Handler] Field '{}' is NOT a link.", field_def.display_name); // <-- ADD THIS
|
let msg = format!("Field '{}' is not a linkable field.", field_def.display_name);
|
||||||
|
tracing::error!("[Handler] {}", msg);
|
||||||
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tracing::error!("[Handler] Not in form view. Cannot trigger autocomplete."); // <-- ADD THIS
|
|
||||||
}
|
}
|
||||||
return Ok(EditEventOutcome::Message("Not a linkable field".to_string()));
|
// Fallback message if not in a form or something went wrong
|
||||||
|
return Ok(EditEventOutcome::Message("Autocomplete not available here.".to_string()));
|
||||||
}
|
}
|
||||||
// --- END OF NEW LOGIC ---
|
// --- END OF NEW LOGIC ---
|
||||||
|
|
||||||
if action_str == "enter_decider" {
|
if action_str == "enter_decider" {
|
||||||
let effective_action = if app_state.ui.show_register
|
let effective_action = if app_state.ui.show_register
|
||||||
&& register_state.in_suggestion_mode
|
&& register_state.in_suggestion_mode
|
||||||
&& register_state.current_field() == 4 {
|
&& register_state.current_field() == 4
|
||||||
|
{
|
||||||
"select_suggestion"
|
"select_suggestion"
|
||||||
} else if app_state.ui.show_add_logic
|
} else if app_state.ui.show_add_logic
|
||||||
&& admin_state.add_logic_state.in_target_column_suggestion_mode
|
&& admin_state
|
||||||
&& admin_state.add_logic_state.current_field() == 1 {
|
.add_logic_state
|
||||||
|
.in_target_column_suggestion_mode
|
||||||
|
&& admin_state.add_logic_state.current_field() == 1
|
||||||
|
{
|
||||||
"select_suggestion"
|
"select_suggestion"
|
||||||
} else {
|
} else {
|
||||||
"next_field"
|
"next_field"
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg = if app_state.ui.show_login {
|
let msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(effective_action, key, login_state, ideal_cursor_column).await?
|
auth_e::execute_edit_action(
|
||||||
|
effective_action,
|
||||||
|
key,
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action(effective_action, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
add_table_e::execute_edit_action(
|
||||||
|
effective_action,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
add_logic_e::execute_edit_action(effective_action, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
add_logic_e::execute_edit_action(
|
||||||
|
effective_action,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_edit_action(effective_action, key, register_state, ideal_cursor_column).await?
|
auth_e::execute_edit_action(
|
||||||
|
effective_action,
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
form_e::execute_edit_action(effective_action, key, form_state, ideal_cursor_column).await?
|
form_e::execute_edit_action(
|
||||||
|
effective_action,
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if action_str == "exit" {
|
if action_str == "exit" {
|
||||||
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
if app_state.ui.show_register && register_state.in_suggestion_mode {
|
||||||
let msg = auth_e::execute_edit_action("exit_suggestion_mode", key, register_state, ideal_cursor_column).await?;
|
let msg = auth_e::execute_edit_action(
|
||||||
|
"exit_suggestion_mode",
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
} else if app_state.ui.show_add_logic && admin_state.add_logic_state.in_target_column_suggestion_mode {
|
} else if app_state.ui.show_add_logic
|
||||||
admin_state.add_logic_state.in_target_column_suggestion_mode = false;
|
&& admin_state
|
||||||
admin_state.add_logic_state.show_target_column_suggestions = false;
|
.add_logic_state
|
||||||
admin_state.add_logic_state.selected_target_column_suggestion_index = None;
|
.in_target_column_suggestion_mode
|
||||||
return Ok(EditEventOutcome::Message("Exited column suggestions".to_string()));
|
{
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.in_target_column_suggestion_mode = false;
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.show_target_column_suggestions = false;
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.selected_target_column_suggestion_index = None;
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Exited column suggestions".to_string(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
return Ok(EditEventOutcome::ExitEditMode);
|
return Ok(EditEventOutcome::ExitEditMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.current_field() == 1 {
|
if app_state.ui.show_add_logic
|
||||||
|
&& admin_state.add_logic_state.current_field() == 1
|
||||||
|
{
|
||||||
if action_str == "suggestion_down" {
|
if action_str == "suggestion_down" {
|
||||||
if !admin_state.add_logic_state.in_target_column_suggestion_mode {
|
if !admin_state
|
||||||
if let Some(profile_name) = admin_state.add_logic_state.profile_name.clone().into() {
|
.add_logic_state
|
||||||
if let Some(table_name) = admin_state.add_logic_state.selected_table_name.clone() {
|
.in_target_column_suggestion_mode
|
||||||
|
{
|
||||||
|
if let Some(profile_name) =
|
||||||
|
admin_state.add_logic_state.profile_name.clone().into()
|
||||||
|
{
|
||||||
|
if let Some(table_name) = admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.selected_table_name
|
||||||
|
.clone()
|
||||||
|
{
|
||||||
debug!("Fetching table structure for autocomplete: Profile='{}', Table='{}'", profile_name, table_name);
|
debug!("Fetching table structure for autocomplete: Profile='{}', Table='{}'", profile_name, table_name);
|
||||||
match grpc_client.get_table_structure(profile_name, table_name).await {
|
match grpc_client
|
||||||
|
.get_table_structure(profile_name, table_name)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(ts_response) => {
|
Ok(ts_response) => {
|
||||||
admin_state.add_logic_state.table_columns_for_suggestions =
|
admin_state
|
||||||
ts_response.columns.into_iter().map(|c| c.name).collect();
|
.add_logic_state
|
||||||
admin_state.add_logic_state.update_target_column_suggestions();
|
.table_columns_for_suggestions =
|
||||||
if !admin_state.add_logic_state.target_column_suggestions.is_empty() {
|
ts_response
|
||||||
admin_state.add_logic_state.in_target_column_suggestion_mode = true;
|
.columns
|
||||||
return Ok(EditEventOutcome::Message("Column suggestions shown".to_string()));
|
.into_iter()
|
||||||
|
.map(|c| c.name)
|
||||||
|
.collect();
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.update_target_column_suggestions();
|
||||||
|
if !admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.target_column_suggestions
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.in_target_column_suggestion_mode =
|
||||||
|
true;
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Column suggestions shown"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
return Ok(EditEventOutcome::Message("No column suggestions for current input".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"No column suggestions for current input"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Error fetching table structure: {}", e);
|
debug!(
|
||||||
admin_state.add_logic_state.table_columns_for_suggestions.clear();
|
"Error fetching table structure: {}",
|
||||||
admin_state.add_logic_state.update_target_column_suggestions();
|
e
|
||||||
return Ok(EditEventOutcome::Message(format!("Error fetching columns: {}", e)));
|
);
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.table_columns_for_suggestions
|
||||||
|
.clear();
|
||||||
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.update_target_column_suggestions();
|
||||||
|
return Ok(EditEventOutcome::Message(
|
||||||
|
format!("Error fetching columns: {}", e),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(EditEventOutcome::Message("No table selected for column suggestions".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"No table selected for column suggestions"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(EditEventOutcome::Message("Profile name missing for column suggestions".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Profile name missing for column suggestions"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let msg = add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?;
|
let msg = add_logic_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
} else if admin_state.add_logic_state.in_target_column_suggestion_mode && action_str == "suggestion_up" {
|
} else if admin_state
|
||||||
let msg = add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?;
|
.add_logic_state
|
||||||
|
.in_target_column_suggestion_mode
|
||||||
|
&& action_str == "suggestion_up"
|
||||||
|
{
|
||||||
|
let msg = add_logic_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
if !register_state.in_suggestion_mode && action_str == "suggestion_down" {
|
if !register_state.in_suggestion_mode
|
||||||
|
&& action_str == "suggestion_down"
|
||||||
|
{
|
||||||
register_state.update_role_suggestions();
|
register_state.update_role_suggestions();
|
||||||
if !register_state.role_suggestions.is_empty() {
|
if !register_state.role_suggestions.is_empty() {
|
||||||
register_state.in_suggestion_mode = true;
|
register_state.in_suggestion_mode = true;
|
||||||
return Ok(EditEventOutcome::Message("Role suggestions shown".to_string()));
|
return Ok(EditEventOutcome::Message(
|
||||||
|
"Role suggestions shown".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if register_state.in_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up") {
|
if register_state.in_suggestion_mode
|
||||||
let msg = auth_e::execute_edit_action(action_str, key, register_state, ideal_cursor_column).await?;
|
&& matches!(action_str, "suggestion_down" | "suggestion_up")
|
||||||
|
{
|
||||||
|
let msg = auth_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = if app_state.ui.show_login {
|
let msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(action_str, key, login_state, ideal_cursor_column).await?
|
auth_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action(action_str, key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
add_table_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
if !(admin_state.add_logic_state.in_target_column_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
if !(admin_state
|
||||||
add_logic_e::execute_edit_action(action_str, key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
.add_logic_state
|
||||||
} else { String::new() }
|
.in_target_column_suggestion_mode
|
||||||
|
&& matches!(action_str, "suggestion_down" | "suggestion_up"))
|
||||||
|
{
|
||||||
|
add_logic_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
if !(register_state.in_suggestion_mode && matches!(action_str, "suggestion_down" | "suggestion_up")) {
|
if !(register_state.in_suggestion_mode
|
||||||
auth_e::execute_edit_action(action_str, key, register_state, ideal_cursor_column).await?
|
&& matches!(action_str, "suggestion_down" | "suggestion_up"))
|
||||||
} else { String::new() }
|
{
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
form_e::execute_edit_action(action_str, key, form_state, ideal_cursor_column).await?
|
form_e::execute_edit_action(
|
||||||
|
action_str,
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
};
|
};
|
||||||
return Ok(EditEventOutcome::Message(msg));
|
return Ok(EditEventOutcome::Message(msg));
|
||||||
}
|
}
|
||||||
@@ -267,29 +526,65 @@ if app_state.ui.show_form && form_state.autocomplete_active {
|
|||||||
register_state.selected_suggestion_index = None;
|
register_state.selected_suggestion_index = None;
|
||||||
exited_suggestion_mode_for_typing = true;
|
exited_suggestion_mode_for_typing = true;
|
||||||
}
|
}
|
||||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.in_target_column_suggestion_mode {
|
if app_state.ui.show_add_logic
|
||||||
|
&& admin_state.add_logic_state.in_target_column_suggestion_mode
|
||||||
|
{
|
||||||
admin_state.add_logic_state.in_target_column_suggestion_mode = false;
|
admin_state.add_logic_state.in_target_column_suggestion_mode = false;
|
||||||
admin_state.add_logic_state.show_target_column_suggestions = false;
|
admin_state.add_logic_state.show_target_column_suggestions = false;
|
||||||
admin_state.add_logic_state.selected_target_column_suggestion_index = None;
|
admin_state
|
||||||
|
.add_logic_state
|
||||||
|
.selected_target_column_suggestion_index = None;
|
||||||
exited_suggestion_mode_for_typing = true;
|
exited_suggestion_mode_for_typing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut char_insert_msg = if app_state.ui.show_login {
|
let mut char_insert_msg = if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action("insert_char", key, login_state, ideal_cursor_column).await?
|
auth_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
login_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
add_table_e::execute_edit_action("insert_char", key, &mut admin_state.add_table_state, ideal_cursor_column).await?
|
add_table_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_table_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
add_logic_e::execute_edit_action("insert_char", key, &mut admin_state.add_logic_state, ideal_cursor_column).await?
|
add_logic_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
auth_e::execute_edit_action("insert_char", key, register_state, ideal_cursor_column).await?
|
auth_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
} else {
|
} else {
|
||||||
form_e::execute_edit_action("insert_char", key, form_state, ideal_cursor_column).await?
|
form_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
form_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
if app_state.ui.show_register && register_state.current_field() == 4 {
|
if app_state.ui.show_register && register_state.current_field() == 4 {
|
||||||
register_state.update_role_suggestions();
|
register_state.update_role_suggestions();
|
||||||
}
|
}
|
||||||
if app_state.ui.show_add_logic && admin_state.add_logic_state.current_field() == 1 {
|
if app_state.ui.show_add_logic
|
||||||
|
&& admin_state.add_logic_state.current_field() == 1
|
||||||
|
{
|
||||||
admin_state.add_logic_state.update_target_column_suggestions();
|
admin_state.add_logic_state.update_target_column_suggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// src/state/canvas_state.rs
|
// src/state/pages/canvas_state.rs
|
||||||
|
|
||||||
|
use common::proto::multieko2::search::search_response::Hit;
|
||||||
|
|
||||||
pub trait CanvasState {
|
pub trait CanvasState {
|
||||||
fn current_field(&self) -> usize;
|
fn current_field(&self) -> usize;
|
||||||
@@ -16,4 +18,7 @@ pub trait CanvasState {
|
|||||||
// --- Autocomplete Support ---
|
// --- Autocomplete Support ---
|
||||||
fn get_suggestions(&self) -> Option<&[String]>;
|
fn get_suggestions(&self) -> Option<&[String]>;
|
||||||
fn get_selected_suggestion_index(&self) -> Option<usize>;
|
fn get_selected_suggestion_index(&self) -> Option<usize>;
|
||||||
|
fn get_rich_suggestions(&self) -> Option<&[Hit]> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use crate::state::app::highlight::HighlightState;
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
|
use common::proto::multieko2::search::search_response::Hit; // Import Hit
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -12,7 +13,8 @@ use std::collections::HashMap;
|
|||||||
pub struct FieldDefinition {
|
pub struct FieldDefinition {
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub data_key: String,
|
pub data_key: String,
|
||||||
pub is_link: bool, // --- NEW --- To identify FK fields
|
pub is_link: bool,
|
||||||
|
pub link_target_table: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -28,10 +30,11 @@ pub struct FormState {
|
|||||||
pub has_unsaved_changes: bool,
|
pub has_unsaved_changes: bool,
|
||||||
pub current_cursor_pos: usize,
|
pub current_cursor_pos: usize,
|
||||||
|
|
||||||
// --- NEW AUTOCOMPLETE STATE ---
|
// --- MODIFIED AUTOCOMPLETE STATE ---
|
||||||
pub autocomplete_active: bool,
|
pub autocomplete_active: bool,
|
||||||
pub autocomplete_suggestions: Vec<String>,
|
pub autocomplete_suggestions: Vec<Hit>, // Changed to use the Hit struct
|
||||||
pub selected_suggestion_index: Option<usize>,
|
pub selected_suggestion_index: Option<usize>,
|
||||||
|
pub autocomplete_loading: bool, // To show a loading indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormState {
|
impl FormState {
|
||||||
@@ -56,6 +59,7 @@ impl FormState {
|
|||||||
autocomplete_active: false,
|
autocomplete_active: false,
|
||||||
autocomplete_suggestions: Vec::new(),
|
autocomplete_suggestions: Vec::new(),
|
||||||
selected_suggestion_index: None,
|
selected_suggestion_index: None,
|
||||||
|
autocomplete_loading: false, // Initialize loading state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +168,7 @@ impl FormState {
|
|||||||
self.autocomplete_active = false;
|
self.autocomplete_active = false;
|
||||||
self.autocomplete_suggestions.clear();
|
self.autocomplete_suggestions.clear();
|
||||||
self.selected_suggestion_index = None;
|
self.selected_suggestion_index = None;
|
||||||
|
self.autocomplete_loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +221,14 @@ impl CanvasState for FormState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- MODIFIED: Implement autocomplete trait methods ---
|
// --- MODIFIED: Implement autocomplete trait methods ---
|
||||||
|
|
||||||
|
/// Returns None because this state uses rich suggestions.
|
||||||
fn get_suggestions(&self) -> Option<&[String]> {
|
fn get_suggestions(&self) -> Option<&[String]> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns rich suggestions.
|
||||||
|
fn get_rich_suggestions(&self) -> Option<&[Hit]> {
|
||||||
if self.autocomplete_active {
|
if self.autocomplete_active {
|
||||||
Some(&self.autocomplete_suggestions)
|
Some(&self.autocomplete_suggestions)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
display_name: col_name.clone(),
|
display_name: col_name.clone(),
|
||||||
data_key: col_name,
|
data_key: col_name,
|
||||||
is_link: false,
|
is_link: false,
|
||||||
|
link_target_table: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -347,19 +348,18 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
.map(|c| c.name.clone())
|
.map(|c| c.name.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 1. Process regular columns first, filtering out FKs
|
|
||||||
let mut field_definitions: Vec<FieldDefinition> =
|
let mut field_definitions: Vec<FieldDefinition> =
|
||||||
filter_user_columns(all_columns)
|
filter_user_columns(all_columns)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|col_name| !col_name.ends_with("_id")) // Exclude FKs
|
.filter(|col_name| !col_name.ends_with("_id"))
|
||||||
.map(|col_name| FieldDefinition {
|
.map(|col_name| FieldDefinition {
|
||||||
display_name: col_name.clone(),
|
display_name: col_name.clone(),
|
||||||
data_key: col_name,
|
data_key: col_name,
|
||||||
is_link: false, // Regular fields are not links
|
is_link: false,
|
||||||
|
link_target_table: None, // Regular fields have no target
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 2. Process linked tables to create the correct labels
|
|
||||||
let linked_tables: Vec<String> = app_state
|
let linked_tables: Vec<String> = app_state
|
||||||
.profile_tree
|
.profile_tree
|
||||||
.profiles
|
.profiles
|
||||||
@@ -375,19 +375,22 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
.split_once('_')
|
.split_once('_')
|
||||||
.map_or(linked_table_name.as_str(), |(_, rest)| rest);
|
.map_or(linked_table_name.as_str(), |(_, rest)| rest);
|
||||||
let data_key = format!("{}_id", base_name);
|
let data_key = format!("{}_id", base_name);
|
||||||
let display_name = linked_table_name;
|
let display_name = linked_table_name.clone(); // Clone for use below
|
||||||
|
|
||||||
field_definitions.push(FieldDefinition {
|
field_definitions.push(FieldDefinition {
|
||||||
display_name,
|
display_name,
|
||||||
data_key,
|
data_key,
|
||||||
is_link: true, // These fields ARE links
|
is_link: true,
|
||||||
|
// --- POPULATE THE NEW FIELD ---
|
||||||
|
link_target_table: Some(linked_table_name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// --- END OF MODIFIED LOGIC ---
|
// --- END OF MODIFIED LOGIC ---
|
||||||
|
|
||||||
form_state = FormState::new(
|
form_state = FormState::new(
|
||||||
prof_name.clone(),
|
prof_name.clone(),
|
||||||
tbl_name.clone(),
|
tbl_name.clone(),
|
||||||
field_definitions,
|
field_definitions, // This now contains the complete definitions
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = UiService::fetch_and_set_table_count(
|
if let Err(e) = UiService::fetch_and_set_table_count(
|
||||||
|
|||||||
Reference in New Issue
Block a user