hardcoded adresar to general form
This commit is contained in:
@@ -13,9 +13,9 @@ use crate::components::handlers::canvas::render_canvas;
|
||||
pub fn render_form(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
form_state: &impl CanvasState,
|
||||
form_state_param: &impl CanvasState,
|
||||
fields: &[&str],
|
||||
current_field: &usize,
|
||||
current_field_idx: &usize,
|
||||
inputs: &[&String],
|
||||
theme: &Theme,
|
||||
is_edit_mode: bool,
|
||||
@@ -48,7 +48,16 @@ pub fn render_form(
|
||||
.split(inner_area);
|
||||
|
||||
// Render count/position
|
||||
let count_position_text = format!("Total: {} | Position: {}", total_count, current_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
|
||||
format!("Total: 0 | New Entry ({})", current_position)
|
||||
}
|
||||
else {
|
||||
format!("Total: {} | Position: {}/{}", total_count, current_position, total_count)
|
||||
};
|
||||
let count_para = Paragraph::new(count_position_text)
|
||||
.style(Style::default().fg(theme.fg))
|
||||
.alignment(Alignment::Left);
|
||||
@@ -58,9 +67,9 @@ pub fn render_form(
|
||||
render_canvas(
|
||||
f,
|
||||
main_layout[1],
|
||||
form_state,
|
||||
form_state_param,
|
||||
fields,
|
||||
current_field,
|
||||
current_field_idx,
|
||||
inputs,
|
||||
theme,
|
||||
is_edit_mode,
|
||||
|
||||
@@ -14,8 +14,6 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
action: &str,
|
||||
state: &mut S,
|
||||
grpc_client: &mut GrpcClient,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<EventOutcome> {
|
||||
match action {
|
||||
"save" | "revert" => {
|
||||
@@ -30,8 +28,6 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
let save_result = save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
).await;
|
||||
|
||||
match save_result {
|
||||
@@ -50,8 +46,6 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
let revert_result = revert(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
).await;
|
||||
|
||||
match revert_result {
|
||||
|
||||
@@ -24,8 +24,6 @@ pub async fn handle_core_action(
|
||||
auth_client: &mut AuthClient,
|
||||
terminal: &mut TerminalCore,
|
||||
app_state: &mut AppState,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<EventOutcome> {
|
||||
match action {
|
||||
"save" => {
|
||||
@@ -36,8 +34,6 @@ pub async fn handle_core_action(
|
||||
let save_outcome = form_save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
).await.context("Register save action failed")?;
|
||||
let message = match save_outcome {
|
||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||
@@ -58,8 +54,6 @@ pub async fn handle_core_action(
|
||||
let save_outcome = form_save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
).await?;
|
||||
match save_outcome {
|
||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||
@@ -81,8 +75,6 @@ pub async fn handle_core_action(
|
||||
let message = form_revert(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
).await.context("Form revert x action failed")?;
|
||||
Ok(EventOutcome::Ok(message))
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub async fn handle_edit_event(
|
||||
// TODO: Implement common actions for AddLogic if needed
|
||||
format!("Action '{}' not implemented for Add Logic in edit mode.", action)
|
||||
} else { // Assuming Form view
|
||||
let outcome = form_e::execute_common_action(action, form_state, grpc_client, current_position, total_count).await?;
|
||||
let outcome = form_e::execute_common_action(action, form_state, grpc_client).await?;
|
||||
match outcome {
|
||||
EventOutcome::Ok(msg) | EventOutcome::DataSaved(_, msg) => msg,
|
||||
_ => format!("Unexpected outcome from common action: {:?}", outcome),
|
||||
|
||||
@@ -129,8 +129,6 @@ impl EventHandler {
|
||||
admin_state: &mut AdminState,
|
||||
buffer_state: &mut BufferState,
|
||||
app_state: &mut AppState,
|
||||
total_count: u64,
|
||||
current_position: &mut u64,
|
||||
) -> Result<EventOutcome> {
|
||||
let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state);
|
||||
|
||||
@@ -484,8 +482,6 @@ impl EventHandler {
|
||||
&mut self.auth_client,
|
||||
terminal,
|
||||
app_state,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -503,8 +499,8 @@ impl EventHandler {
|
||||
&mut admin_state.add_table_state,
|
||||
&mut admin_state.add_logic_state,
|
||||
&mut self.key_sequence_tracker,
|
||||
current_position,
|
||||
total_count,
|
||||
form_state.current_position,
|
||||
form_state.total_count,
|
||||
grpc_client,
|
||||
&mut self.command_message,
|
||||
&mut self.edit_mode_cooldown,
|
||||
@@ -545,8 +541,8 @@ impl EventHandler {
|
||||
&mut admin_state.add_table_state,
|
||||
&mut admin_state.add_logic_state,
|
||||
&mut self.key_sequence_tracker,
|
||||
current_position,
|
||||
total_count,
|
||||
form_state.current_position,
|
||||
form_state.total_count,
|
||||
grpc_client,
|
||||
&mut self.command_message,
|
||||
&mut self.edit_mode_cooldown,
|
||||
@@ -570,8 +566,6 @@ impl EventHandler {
|
||||
&mut self.auth_client,
|
||||
terminal,
|
||||
app_state,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -587,8 +581,6 @@ impl EventHandler {
|
||||
register_state,
|
||||
admin_state,
|
||||
&mut self.ideal_cursor_column,
|
||||
current_position,
|
||||
total_count,
|
||||
grpc_client,
|
||||
app_state,
|
||||
)
|
||||
@@ -677,8 +669,6 @@ impl EventHandler {
|
||||
grpc_client,
|
||||
command_handler,
|
||||
terminal,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await?;
|
||||
self.command_mode = false;
|
||||
|
||||
@@ -1,101 +1,200 @@
|
||||
// src/services/grpc_client.rs
|
||||
|
||||
use tonic::transport::Channel;
|
||||
use common::proto::multieko2::adresar::adresar_client::AdresarClient;
|
||||
use common::proto::multieko2::adresar::{AdresarResponse, PostAdresarRequest, PutAdresarRequest};
|
||||
use common::proto::multieko2::common::{CountResponse, PositionRequest, Empty};
|
||||
use common::proto::multieko2::common::{CountResponse, Empty};
|
||||
use common::proto::multieko2::table_structure::table_structure_service_client::TableStructureServiceClient;
|
||||
// Import the new request type for table structure
|
||||
use common::proto::multieko2::table_structure::{TableStructureResponse, GetTableStructureRequest};
|
||||
use common::proto::multieko2::table_structure::{GetTableStructureRequest, TableStructureResponse};
|
||||
use common::proto::multieko2::table_definition::{
|
||||
table_definition_client::TableDefinitionClient,
|
||||
ProfileTreeResponse, PostTableDefinitionRequest, TableDefinitionResponse,
|
||||
PostTableDefinitionRequest, ProfileTreeResponse, TableDefinitionResponse,
|
||||
};
|
||||
use common::proto::multieko2::table_script::{
|
||||
table_script_client::TableScriptClient,
|
||||
PostTableScriptRequest, TableScriptResponse,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use common::proto::multieko2::tables_data::{
|
||||
tables_data_client::TablesDataClient,
|
||||
GetTableDataByPositionRequest,
|
||||
GetTableDataResponse,
|
||||
GetTableDataCountRequest,
|
||||
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
|
||||
PutTableDataResponse,
|
||||
};
|
||||
use anyhow::{Context, Result}; // Added Context
|
||||
use std::collections::HashMap; // NEW
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GrpcClient {
|
||||
adresar_client: AdresarClient<Channel>,
|
||||
table_structure_client: TableStructureServiceClient<Channel>,
|
||||
table_definition_client: TableDefinitionClient<Channel>,
|
||||
table_script_client: TableScriptClient<Channel>,
|
||||
tables_data_client: TablesDataClient<Channel>, // NEW
|
||||
}
|
||||
|
||||
impl GrpcClient {
|
||||
pub async fn new() -> Result<Self> {
|
||||
let adresar_client = AdresarClient::connect("http://[::1]:50051").await?;
|
||||
let table_structure_client = TableStructureServiceClient::connect("http://[::1]:50051").await?;
|
||||
let table_definition_client = TableDefinitionClient::connect("http://[::1]:50051").await?;
|
||||
let table_script_client = TableScriptClient::connect("http://[::1]:50051").await?;
|
||||
let table_structure_client = TableStructureServiceClient::connect(
|
||||
"http://[::1]:50051",
|
||||
)
|
||||
.await
|
||||
.context("Failed to connect to TableStructureService")?;
|
||||
let table_definition_client = TableDefinitionClient::connect(
|
||||
"http://[::1]:50051",
|
||||
)
|
||||
.await
|
||||
.context("Failed to connect to TableDefinitionService")?;
|
||||
let table_script_client =
|
||||
TableScriptClient::connect("http://[::1]:50051")
|
||||
.await
|
||||
.context("Failed to connect to TableScriptService")?;
|
||||
let tables_data_client =
|
||||
TablesDataClient::connect("http://[::1]:50051")
|
||||
.await
|
||||
.context("Failed to connect to TablesDataService")?; // NEW
|
||||
|
||||
Ok(Self {
|
||||
adresar_client,
|
||||
// adresar_client, // REMOVE
|
||||
table_structure_client,
|
||||
table_definition_client,
|
||||
table_script_client,
|
||||
tables_data_client, // NEW
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_adresar_count(&mut self) -> Result<u64> {
|
||||
let request = tonic::Request::new(Empty::default());
|
||||
let response: CountResponse = self.adresar_client.get_adresar_count(request).await?.into_inner();
|
||||
Ok(response.count as u64)
|
||||
}
|
||||
|
||||
pub async fn get_adresar_by_position(&mut self, position: u64) -> Result<AdresarResponse> {
|
||||
let request = tonic::Request::new(PositionRequest { position: position as i64 });
|
||||
let response: AdresarResponse = self.adresar_client.get_adresar_by_position(request).await?.into_inner();
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn post_adresar(&mut self, request: PostAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
|
||||
let request = tonic::Request::new(request);
|
||||
let response = self.adresar_client.post_adresar(request).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn put_adresar(&mut self, request: PutAdresarRequest) -> Result<tonic::Response<AdresarResponse>> {
|
||||
let request = tonic::Request::new(request);
|
||||
let response = self.adresar_client.put_adresar(request).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// Updated get_table_structure method
|
||||
pub async fn get_table_structure(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
) -> Result<TableStructureResponse> {
|
||||
// Create the new request type
|
||||
let grpc_request = GetTableStructureRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
// Call the new gRPC method
|
||||
let response = self.table_structure_client.get_table_structure(request).await?;
|
||||
let response = self
|
||||
.table_structure_client
|
||||
.get_table_structure(request)
|
||||
.await
|
||||
.context("gRPC GetTableStructure call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_profile_tree(&mut self) -> Result<ProfileTreeResponse> {
|
||||
pub async fn get_profile_tree(
|
||||
&mut self,
|
||||
) -> Result<ProfileTreeResponse> {
|
||||
let request = tonic::Request::new(Empty::default());
|
||||
let response = self.table_definition_client.get_profile_tree(request).await?;
|
||||
let response = self
|
||||
.table_definition_client
|
||||
.get_profile_tree(request)
|
||||
.await
|
||||
.context("gRPC GetProfileTree call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn post_table_definition(&mut self, request: PostTableDefinitionRequest) -> Result<TableDefinitionResponse> {
|
||||
pub async fn post_table_definition(
|
||||
&mut self,
|
||||
request: PostTableDefinitionRequest,
|
||||
) -> Result<TableDefinitionResponse> {
|
||||
let tonic_request = tonic::Request::new(request);
|
||||
let response = self.table_definition_client.post_table_definition(tonic_request).await?;
|
||||
let response = self
|
||||
.table_definition_client
|
||||
.post_table_definition(tonic_request)
|
||||
.await
|
||||
.context("gRPC PostTableDefinition call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn post_table_script(&mut self, request: PostTableScriptRequest) -> Result<TableScriptResponse> {
|
||||
pub async fn post_table_script(
|
||||
&mut self,
|
||||
request: PostTableScriptRequest,
|
||||
) -> Result<TableScriptResponse> {
|
||||
let tonic_request = tonic::Request::new(request);
|
||||
let response = self.table_script_client.post_table_script(tonic_request).await?;
|
||||
let response = self
|
||||
.table_script_client
|
||||
.post_table_script(tonic_request)
|
||||
.await
|
||||
.context("gRPC PostTableScript call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
// NEW Methods for TablesData service
|
||||
pub async fn get_table_data_count(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
) -> Result<u64> {
|
||||
let grpc_request = GetTableDataCountRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.get_table_data_count(request)
|
||||
.await
|
||||
.context("gRPC GetTableDataCount call failed")?;
|
||||
Ok(response.into_inner().count as u64)
|
||||
}
|
||||
|
||||
pub async fn get_table_data_by_position(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
position: i32,
|
||||
) -> Result<GetTableDataResponse> {
|
||||
let grpc_request = GetTableDataByPositionRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
position,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.get_table_data_by_position(request)
|
||||
.await
|
||||
.context("gRPC GetTableDataByPosition call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn post_table_data(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
data: HashMap<String, String>,
|
||||
) -> Result<PostTableDataResponse> {
|
||||
let grpc_request = PostTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
data,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.post_table_data(request)
|
||||
.await
|
||||
.context("gRPC PostTableData call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn put_table_data(
|
||||
&mut self,
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
id: i64,
|
||||
data: HashMap<String, String>,
|
||||
) -> Result<PutTableDataResponse> {
|
||||
let grpc_request = PutTableDataRequest {
|
||||
profile_name,
|
||||
table_name,
|
||||
id,
|
||||
data,
|
||||
};
|
||||
let request = tonic::Request::new(grpc_request);
|
||||
let response = self
|
||||
.tables_data_client
|
||||
.put_table_data(request)
|
||||
.await
|
||||
.context("gRPC PutTableData call failed")?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,109 +91,172 @@ impl UiService {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize_app_state(
|
||||
// MODIFIED: To set initial view table in AppState and return initial column names
|
||||
pub async fn initialize_app_state_and_form(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
) -> Result<Vec<String>> {
|
||||
// Fetch profile tree
|
||||
let profile_tree = grpc_client.get_profile_tree().await.context("Failed to get profile tree")?;
|
||||
// Returns (initial_profile, initial_table, initial_columns)
|
||||
) -> Result<(String, String, Vec<String>)> {
|
||||
let profile_tree = grpc_client
|
||||
.get_profile_tree()
|
||||
.await
|
||||
.context("Failed to get profile tree")?;
|
||||
app_state.profile_tree = profile_tree;
|
||||
|
||||
// TODO for general tables and not hardcoded
|
||||
let default_profile_name = "default".to_string();
|
||||
let default_table_name = "2025_test_schema3".to_string();
|
||||
// Determine initial table to load (e.g., first table of first profile, or a default)
|
||||
// For now, let's hardcode a default for simplicity, but this should be more dynamic
|
||||
let initial_profile_name = app_state
|
||||
.profile_tree
|
||||
.profiles
|
||||
.first()
|
||||
.map(|p| p.name.clone())
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
|
||||
let initial_table_name = app_state
|
||||
.profile_tree
|
||||
.profiles
|
||||
.first()
|
||||
.and_then(|p| p.tables.first().map(|t| t.name.clone()))
|
||||
.unwrap_or_else(|| "2025_company_data1".to_string()); // Fallback if no tables
|
||||
|
||||
app_state.set_current_view_table(
|
||||
initial_profile_name.clone(),
|
||||
initial_table_name.clone(),
|
||||
);
|
||||
|
||||
// Fetch table structure for the default table
|
||||
let table_structure = grpc_client
|
||||
.get_table_structure(default_profile_name, default_table_name)
|
||||
.get_table_structure(
|
||||
initial_profile_name.clone(),
|
||||
initial_table_name.clone(),
|
||||
)
|
||||
.await
|
||||
.context("Failed to get initial table structure")?;
|
||||
.context(format!(
|
||||
"Failed to get initial table structure for {}.{}",
|
||||
initial_profile_name, initial_table_name
|
||||
))?;
|
||||
|
||||
// Extract the column names from the response
|
||||
let column_names: Vec<String> = table_structure
|
||||
.columns
|
||||
.iter()
|
||||
.map(|col| col.name.clone())
|
||||
.collect();
|
||||
|
||||
Ok(column_names)
|
||||
Ok((initial_profile_name, initial_table_name, column_names))
|
||||
}
|
||||
|
||||
pub async fn initialize_adresar_count(
|
||||
// NEW: Fetches and sets count for the current table in FormState
|
||||
pub async fn fetch_and_set_table_count(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
) -> Result<()> {
|
||||
let total_count = grpc_client.get_adresar_count().await.context("Failed to get adresar count")?;
|
||||
app_state.update_total_count(total_count);
|
||||
app_state.update_current_position(total_count.saturating_add(1)); // Start in new entry mode
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_adresar_count(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
) -> Result<()> {
|
||||
let total_count = grpc_client.get_adresar_count().await.context("Failed to get adresar by position")?;
|
||||
app_state.update_total_count(total_count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_adresar_by_position(
|
||||
grpc_client: &mut GrpcClient,
|
||||
_app_state: &mut AppState,
|
||||
form_state: &mut FormState,
|
||||
position: u64,
|
||||
) -> Result<()> {
|
||||
let total_count = grpc_client
|
||||
.get_table_data_count(
|
||||
form_state.profile_name.clone(),
|
||||
form_state.table_name.clone(),
|
||||
)
|
||||
.await
|
||||
.context(format!(
|
||||
"Failed to get count for table {}.{}",
|
||||
form_state.profile_name, form_state.table_name
|
||||
))?;
|
||||
form_state.total_count = total_count;
|
||||
|
||||
// Set initial position: if table has items, point to first, else point to new entry
|
||||
if total_count > 0 {
|
||||
form_state.current_position = 1;
|
||||
} else {
|
||||
form_state.current_position = 1; // For a new entry in an empty table
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// MODIFIED: Generic table data loading
|
||||
pub async fn load_table_data_by_position(
|
||||
grpc_client: &mut GrpcClient,
|
||||
form_state: &mut FormState, // Takes &mut FormState to update it
|
||||
// position is now read from form_state.current_position
|
||||
) -> Result<String> {
|
||||
match grpc_client.get_adresar_by_position(position).await {
|
||||
// Ensure current_position is valid before fetching
|
||||
if form_state.current_position == 0 || (form_state.total_count > 0 && form_state.current_position > form_state.total_count) {
|
||||
// This indicates a "new entry" state, no data to load from server.
|
||||
// The caller should handle this by calling form_state.reset_to_empty()
|
||||
// or ensuring this function isn't called for a new entry position.
|
||||
// For now, let's assume reset_to_empty was called if needed.
|
||||
form_state.reset_to_empty(); // Ensure fields are clear for new entry
|
||||
return Ok(format!(
|
||||
"New entry mode for table {}.{}",
|
||||
form_state.profile_name, form_state.table_name
|
||||
));
|
||||
}
|
||||
if form_state.total_count == 0 && form_state.current_position == 1 {
|
||||
// Table is empty, this is the position for a new entry
|
||||
form_state.reset_to_empty();
|
||||
return Ok(format!(
|
||||
"New entry mode for empty table {}.{}",
|
||||
form_state.profile_name, form_state.table_name
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
match grpc_client
|
||||
.get_table_data_by_position(
|
||||
form_state.profile_name.clone(),
|
||||
form_state.table_name.clone(),
|
||||
form_state.current_position as i32,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
// Set the ID properly
|
||||
form_state.id = response.id;
|
||||
|
||||
// Update form values dynamically
|
||||
form_state.values = vec![
|
||||
response.firma,
|
||||
response.kz,
|
||||
response.drc,
|
||||
response.ulica,
|
||||
response.psc,
|
||||
response.mesto,
|
||||
response.stat,
|
||||
response.banka,
|
||||
response.ucet,
|
||||
response.skladm,
|
||||
response.ico,
|
||||
response.kontakt,
|
||||
response.telefon,
|
||||
response.skladu,
|
||||
response.fax,
|
||||
];
|
||||
|
||||
form_state.has_unsaved_changes = false;
|
||||
Ok(format!("Loaded entry {}", position))
|
||||
form_state.update_from_response(&response.data);
|
||||
// ID, values, current_field, current_cursor_pos, has_unsaved_changes are set by update_from_response
|
||||
Ok(format!(
|
||||
"Loaded entry {}/{} for table {}.{}",
|
||||
form_state.current_position,
|
||||
form_state.total_count,
|
||||
form_state.profile_name,
|
||||
form_state.table_name
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
Ok(format!("Error loading entry: {}", e))
|
||||
// If loading fails (e.g., record deleted, network error), what should happen?
|
||||
// Maybe reset to a new entry state or show an error and keep current data.
|
||||
// For now, log error and return error message.
|
||||
tracing::error!(
|
||||
"Error loading entry {} for table {}.{}: {}",
|
||||
form_state.current_position,
|
||||
form_state.profile_name,
|
||||
form_state.table_name,
|
||||
e
|
||||
);
|
||||
// Potentially clear form or revert to a safe state
|
||||
// form_state.reset_to_empty();
|
||||
Err(anyhow::anyhow!(
|
||||
"Error loading entry {}: {}",
|
||||
form_state.current_position,
|
||||
e
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the consequences of a save operation, like updating counts.
|
||||
// MODIFIED: To work with FormState's count and position
|
||||
pub async fn handle_save_outcome(
|
||||
save_outcome: SaveOutcome,
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
_grpc_client: &mut GrpcClient, // May not be needed if count is fetched separately
|
||||
_app_state: &mut AppState, // May not be needed directly
|
||||
form_state: &mut FormState,
|
||||
) -> Result<()> {
|
||||
match save_outcome {
|
||||
SaveOutcome::CreatedNew(new_id) => {
|
||||
// A new record was created, update the count!
|
||||
UiService::update_adresar_count(grpc_client, app_state).await?;
|
||||
// Navigate to the new record (now that count is updated)
|
||||
app_state.update_current_position(app_state.total_count);
|
||||
form_state.id = new_id; // Ensure ID is set (might be redundant if save already did it)
|
||||
// form_state.total_count and form_state.current_position should have been updated
|
||||
// by the `save` function itself.
|
||||
// Ensure form_state.id is set.
|
||||
form_state.id = new_id;
|
||||
// Potentially, re-fetch count to be absolutely sure, but save should be authoritative.
|
||||
// UiService::fetch_and_set_table_count(grpc_client, form_state).await?;
|
||||
}
|
||||
SaveOutcome::UpdatedExisting | SaveOutcome::NoChange => {
|
||||
// No count update needed for these outcomes
|
||||
// No changes to total_count or current_position needed from here.
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -33,11 +33,12 @@ pub struct UiState {
|
||||
pub struct AppState {
|
||||
// Core editor state
|
||||
pub current_dir: String,
|
||||
pub total_count: u64,
|
||||
pub current_position: u64,
|
||||
pub profile_tree: ProfileTreeResponse,
|
||||
pub selected_profile: Option<String>,
|
||||
pub current_mode: AppMode,
|
||||
pub current_view_profile_name: Option<String>,
|
||||
pub current_view_table_name: Option<String>,
|
||||
|
||||
pub focused_button_index: usize,
|
||||
pub pending_table_structure_fetch: Option<(String, String)>,
|
||||
|
||||
@@ -52,10 +53,10 @@ impl AppState {
|
||||
.to_string();
|
||||
Ok(AppState {
|
||||
current_dir,
|
||||
total_count: 0,
|
||||
current_position: 0,
|
||||
profile_tree: ProfileTreeResponse::default(),
|
||||
selected_profile: None,
|
||||
current_view_profile_name: None,
|
||||
current_view_table_name: None,
|
||||
current_mode: AppMode::General,
|
||||
focused_button_index: 0,
|
||||
pending_table_structure_fetch: None,
|
||||
@@ -63,19 +64,15 @@ impl AppState {
|
||||
})
|
||||
}
|
||||
|
||||
// Existing methods remain unchanged
|
||||
pub fn update_total_count(&mut self, total_count: u64) {
|
||||
self.total_count = total_count;
|
||||
}
|
||||
|
||||
pub fn update_current_position(&mut self, current_position: u64) {
|
||||
self.current_position = current_position;
|
||||
}
|
||||
|
||||
pub fn update_mode(&mut self, mode: AppMode) {
|
||||
self.current_mode = mode;
|
||||
}
|
||||
|
||||
pub fn set_current_view_table(&mut self, profile_name: String, table_name: String) {
|
||||
self.current_view_profile_name = Some(profile_name);
|
||||
self.current_view_table_name = Some(table_name);
|
||||
}
|
||||
|
||||
// Add dialog helper methods
|
||||
/// Shows a dialog with the given title, message, and buttons.
|
||||
/// The first button (index 0) is active by default.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// src/state/pages/form.rs
|
||||
|
||||
use std::collections::HashMap; // NEW
|
||||
use crate::config::colors::themes::Theme;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::Frame;
|
||||
@@ -7,7 +9,13 @@ use crate::state::pages::canvas_state::CanvasState;
|
||||
|
||||
pub struct FormState {
|
||||
pub id: i64,
|
||||
pub fields: Vec<String>,
|
||||
// NEW fields for dynamic table context
|
||||
pub profile_name: String,
|
||||
pub table_name: String,
|
||||
pub total_count: u64,
|
||||
pub current_position: u64, // 1-based index, 0 or total_count + 1 for new entry
|
||||
|
||||
pub fields: Vec<String>, // Already dynamic, which is good
|
||||
pub values: Vec<String>,
|
||||
pub current_field: usize,
|
||||
pub has_unsaved_changes: bool,
|
||||
@@ -15,11 +23,19 @@ pub struct FormState {
|
||||
}
|
||||
|
||||
impl FormState {
|
||||
/// Create a new FormState with dynamic fields.
|
||||
pub fn new(fields: Vec<String>) -> Self {
|
||||
let values = vec![String::new(); fields.len()]; // Initialize values for each field
|
||||
// MODIFIED constructor
|
||||
pub fn new(
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
fields: Vec<String>,
|
||||
) -> Self {
|
||||
let values = vec![String::new(); fields.len()];
|
||||
FormState {
|
||||
id: 0,
|
||||
id: 0, // Default to 0, indicating a new or unloaded record
|
||||
profile_name,
|
||||
table_name,
|
||||
total_count: 0, // Will be fetched after initialization
|
||||
current_position: 0, // Will be set after count is fetched (e.g., 1 or total_count + 1)
|
||||
fields,
|
||||
values,
|
||||
current_field: 0,
|
||||
@@ -35,31 +51,42 @@ impl FormState {
|
||||
theme: &Theme,
|
||||
is_edit_mode: bool,
|
||||
highlight_state: &HighlightState,
|
||||
total_count: u64,
|
||||
current_position: u64,
|
||||
// total_count and current_position are now part of self
|
||||
) {
|
||||
let fields: Vec<&str> = self.fields.iter().map(|s| s.as_str()).collect();
|
||||
let values: Vec<&String> = self.values.iter().collect();
|
||||
let fields_str_slice: Vec<&str> =
|
||||
self.fields.iter().map(|s| s.as_str()).collect();
|
||||
let values_str_slice: Vec<&String> = self.values.iter().collect();
|
||||
|
||||
crate::components::form::form::render_form(
|
||||
f,
|
||||
area,
|
||||
self,
|
||||
&fields,
|
||||
self, // Pass self as CanvasState
|
||||
&fields_str_slice,
|
||||
&self.current_field,
|
||||
&values,
|
||||
&values_str_slice,
|
||||
theme,
|
||||
is_edit_mode,
|
||||
highlight_state,
|
||||
total_count,
|
||||
current_position,
|
||||
self.total_count, // MODIFIED: Use self.total_count
|
||||
self.current_position, // MODIFIED: Use self.current_position
|
||||
);
|
||||
}
|
||||
|
||||
// MODIFIED: Reset now also considers table context for counts
|
||||
pub fn reset_to_empty(&mut self) {
|
||||
self.id = 0; // Reset ID to 0 for new entries
|
||||
self.values.iter_mut().for_each(|v| v.clear()); // Clear all values
|
||||
self.id = 0;
|
||||
self.values.iter_mut().for_each(|v| v.clear());
|
||||
self.current_field = 0;
|
||||
self.current_cursor_pos = 0;
|
||||
self.has_unsaved_changes = false;
|
||||
// current_position should be set to total_count + 1 for a new entry
|
||||
// This might be better handled by the logic that calls reset_to_empty
|
||||
// For now, let's ensure it's consistent with a "new" state.
|
||||
if self.total_count > 0 {
|
||||
self.current_position = self.total_count + 1;
|
||||
} else {
|
||||
self.current_position = 1; // If table is empty, new record is at position 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_input(&self) -> &str {
|
||||
@@ -75,15 +102,43 @@ impl FormState {
|
||||
.expect("Invalid current_field index")
|
||||
}
|
||||
|
||||
pub fn update_from_response(&mut self, response: common::proto::multieko2::adresar::AdresarResponse) {
|
||||
self.id = response.id;
|
||||
self.values = vec![
|
||||
response.firma, response.kz, response.drc,
|
||||
response.ulica, response.psc, response.mesto,
|
||||
response.stat, response.banka, response.ucet,
|
||||
response.skladm, response.ico, response.kontakt,
|
||||
response.telefon, response.skladu, response.fax,
|
||||
];
|
||||
// MODIFIED: Update from a generic HashMap response
|
||||
pub fn update_from_response(
|
||||
&mut self,
|
||||
response_data: &HashMap<String, String>,
|
||||
) {
|
||||
self.values = self.fields
|
||||
.iter()
|
||||
.map(|field_name| {
|
||||
response_data.get(field_name).cloned().unwrap_or_default()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Some(id_str) = response_data.get("id") {
|
||||
match id_str.parse::<i64>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,31 +160,26 @@ impl CanvasState for FormState {
|
||||
}
|
||||
|
||||
fn get_current_input(&self) -> &str {
|
||||
self.values
|
||||
.get(self.current_field)
|
||||
.map(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
// Re-use the struct's own method
|
||||
FormState::get_current_input(self)
|
||||
}
|
||||
|
||||
fn get_current_input_mut(&mut self) -> &mut String {
|
||||
self.values
|
||||
.get_mut(self.current_field)
|
||||
.expect("Invalid current_field index")
|
||||
// Re-use the struct's own method
|
||||
FormState::get_current_input_mut(self)
|
||||
}
|
||||
|
||||
fn fields(&self) -> Vec<&str> {
|
||||
self.fields.iter().map(|s| s.as_str()).collect()
|
||||
}
|
||||
|
||||
// --- Implement the setter methods ---
|
||||
fn set_current_field(&mut self, index: usize) {
|
||||
if index < self.fields.len() { // Basic bounds check
|
||||
if index < self.fields.len() {
|
||||
self.current_field = index;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||
// Optional: Add validation based on current input length if needed
|
||||
self.current_cursor_pos = pos;
|
||||
}
|
||||
|
||||
@@ -137,12 +187,11 @@ impl CanvasState for FormState {
|
||||
self.has_unsaved_changes = changed;
|
||||
}
|
||||
|
||||
// --- Autocomplete Support (Not Used for FormState) ---
|
||||
fn get_suggestions(&self) -> Option<&[String]> {
|
||||
None // FormState doesn't provide suggestions
|
||||
None
|
||||
}
|
||||
|
||||
fn get_selected_suggestion_index(&self) -> Option<usize> {
|
||||
None // FormState doesn't have selected suggestions
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,114 +2,130 @@
|
||||
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::pages::form::FormState;
|
||||
use common::proto::multieko2::adresar::{PostAdresarRequest, PutAdresarRequest};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result}; // Added Context
|
||||
use std::collections::HashMap; // NEW
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SaveOutcome {
|
||||
NoChange, // Nothing needed saving
|
||||
UpdatedExisting, // An existing record was updated
|
||||
CreatedNew(i64), // A new record was created (include its new ID)
|
||||
NoChange,
|
||||
UpdatedExisting,
|
||||
CreatedNew(i64), // Keep the ID
|
||||
}
|
||||
|
||||
/// Shared logic for saving the current form state
|
||||
// MODIFIED save function
|
||||
pub async fn save(
|
||||
form_state: &mut FormState,
|
||||
grpc_client: &mut GrpcClient,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<SaveOutcome> { // <-- Return SaveOutcome
|
||||
) -> Result<SaveOutcome> {
|
||||
if !form_state.has_unsaved_changes {
|
||||
return Ok(SaveOutcome::NoChange); // Early exit if no changes
|
||||
return Ok(SaveOutcome::NoChange);
|
||||
}
|
||||
let is_new = *current_position == total_count + 1;
|
||||
|
||||
let outcome = if is_new {
|
||||
let post_request = PostAdresarRequest {
|
||||
firma: form_state.values[0].clone(),
|
||||
kz: form_state.values[1].clone(),
|
||||
drc: form_state.values[2].clone(),
|
||||
ulica: form_state.values[3].clone(),
|
||||
psc: form_state.values[4].clone(),
|
||||
mesto: form_state.values[5].clone(),
|
||||
stat: form_state.values[6].clone(),
|
||||
banka: form_state.values[7].clone(),
|
||||
ucet: form_state.values[8].clone(),
|
||||
skladm: form_state.values[9].clone(),
|
||||
ico: form_state.values[10].clone(),
|
||||
kontakt: form_state.values[11].clone(),
|
||||
telefon: form_state.values[12].clone(),
|
||||
skladu: form_state.values[13].clone(),
|
||||
fax: form_state.values[14].clone(),
|
||||
};
|
||||
let response = grpc_client.post_adresar(post_request).await?;
|
||||
let new_id = response.into_inner().id;
|
||||
form_state.id = new_id;
|
||||
SaveOutcome::CreatedNew(new_id) // <-- Return CreatedNew with ID
|
||||
let data_map: HashMap<String, String> = form_state
|
||||
.fields
|
||||
.iter()
|
||||
.zip(form_state.values.iter())
|
||||
.map(|(field, value)| (field.clone(), value.clone()))
|
||||
.collect();
|
||||
|
||||
let outcome: SaveOutcome;
|
||||
|
||||
let is_new_entry = form_state.id == 0 || (form_state.total_count > 0 && form_state.current_position > form_state.total_count) || (form_state.total_count == 0 && form_state.current_position == 1) ;
|
||||
|
||||
|
||||
if is_new_entry {
|
||||
let response = grpc_client
|
||||
.post_table_data(
|
||||
form_state.profile_name.clone(),
|
||||
form_state.table_name.clone(),
|
||||
data_map,
|
||||
)
|
||||
.await
|
||||
.context("Failed to post new table data")?;
|
||||
|
||||
if response.success {
|
||||
form_state.id = response.inserted_id;
|
||||
// After creating a new entry, total_count increases, and current_position becomes this new total_count
|
||||
form_state.total_count += 1;
|
||||
form_state.current_position = form_state.total_count;
|
||||
outcome = SaveOutcome::CreatedNew(response.inserted_id);
|
||||
} else {
|
||||
let put_request = PutAdresarRequest {
|
||||
id: form_state.id,
|
||||
firma: form_state.values[0].clone(),
|
||||
kz: form_state.values[1].clone(),
|
||||
drc: form_state.values[2].clone(),
|
||||
ulica: form_state.values[3].clone(),
|
||||
psc: form_state.values[4].clone(),
|
||||
mesto: form_state.values[5].clone(),
|
||||
stat: form_state.values[6].clone(),
|
||||
banka: form_state.values[7].clone(),
|
||||
ucet: form_state.values[8].clone(),
|
||||
skladm: form_state.values[9].clone(),
|
||||
ico: form_state.values[10].clone(),
|
||||
kontakt: form_state.values[11].clone(),
|
||||
telefon: form_state.values[12].clone(),
|
||||
skladu: form_state.values[13].clone(),
|
||||
fax: form_state.values[14].clone(),
|
||||
};
|
||||
let _ = grpc_client.put_adresar(put_request).await?;
|
||||
SaveOutcome::UpdatedExisting
|
||||
};
|
||||
return Err(anyhow::anyhow!(
|
||||
"Server failed to insert data: {}",
|
||||
response.message
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// This assumes form_state.id is valid for an existing record
|
||||
if form_state.id == 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Cannot update record: ID is 0, but not classified as new entry."
|
||||
));
|
||||
}
|
||||
let response = grpc_client
|
||||
.put_table_data(
|
||||
form_state.profile_name.clone(),
|
||||
form_state.table_name.clone(),
|
||||
form_state.id,
|
||||
data_map,
|
||||
)
|
||||
.await
|
||||
.context("Failed to put (update) table data")?;
|
||||
|
||||
if response.success {
|
||||
outcome = SaveOutcome::UpdatedExisting;
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Server failed to update data: {}",
|
||||
response.message
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
form_state.has_unsaved_changes = false;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// Discard changes since last save
|
||||
pub async fn revert(
|
||||
form_state: &mut FormState,
|
||||
form_state: &mut FormState, // Takes &mut FormState to update it
|
||||
grpc_client: &mut GrpcClient,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String> {
|
||||
let is_new = *current_position == total_count + 1;
|
||||
|
||||
if is_new {
|
||||
// Clear all fields for new entries
|
||||
form_state.values.iter_mut().for_each(|v| *v = String::new());
|
||||
form_state.has_unsaved_changes = false;
|
||||
if form_state.id == 0 || (form_state.total_count > 0 && form_state.current_position > form_state.total_count) || (form_state.total_count == 0 && form_state.current_position == 1) {
|
||||
let old_total_count = form_state.total_count; // Preserve for correct new position
|
||||
form_state.reset_to_empty(); // reset_to_empty will clear values and set id=0
|
||||
form_state.total_count = old_total_count; // Restore total_count
|
||||
if form_state.total_count > 0 { // Correctly set current_position for new
|
||||
form_state.current_position = form_state.total_count + 1;
|
||||
} else {
|
||||
form_state.current_position = 1;
|
||||
}
|
||||
return Ok("New entry cleared".to_string());
|
||||
}
|
||||
|
||||
let data = grpc_client.get_adresar_by_position(*current_position).await?;
|
||||
if form_state.current_position == 0 || form_state.current_position > form_state.total_count {
|
||||
if form_state.total_count > 0 {
|
||||
form_state.current_position = 1;
|
||||
} else {
|
||||
// No records to revert to, effectively a new entry state.
|
||||
form_state.reset_to_empty();
|
||||
return Ok("No saved data to revert to; form cleared.".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Update form fields with saved values
|
||||
form_state.values = vec![
|
||||
data.firma,
|
||||
data.kz,
|
||||
data.drc,
|
||||
data.ulica,
|
||||
data.psc,
|
||||
data.mesto,
|
||||
data.stat,
|
||||
data.banka,
|
||||
data.ucet,
|
||||
data.skladm,
|
||||
data.ico,
|
||||
data.kontakt,
|
||||
data.telefon,
|
||||
data.skladu,
|
||||
data.fax,
|
||||
];
|
||||
let response = grpc_client
|
||||
.get_table_data_by_position(
|
||||
form_state.profile_name.clone(),
|
||||
form_state.table_name.clone(),
|
||||
form_state.current_position as i32,
|
||||
)
|
||||
.await
|
||||
.context(format!(
|
||||
"Failed to get table data by position {} for table {}.{}",
|
||||
form_state.current_position,
|
||||
form_state.profile_name,
|
||||
form_state.table_name
|
||||
))?;
|
||||
|
||||
form_state.has_unsaved_changes = false;
|
||||
form_state.update_from_response(&response.data);
|
||||
Ok("Changes discarded, reloaded last saved version".to_string())
|
||||
}
|
||||
|
||||
@@ -47,8 +47,6 @@ pub fn render_ui(
|
||||
event_handler_command_mode_active: bool,
|
||||
event_handler_command_message: &str,
|
||||
navigation_state: &NavigationState,
|
||||
total_count: u64,
|
||||
current_position: u64,
|
||||
current_dir: &str,
|
||||
current_fps: f64,
|
||||
app_state: &AppState,
|
||||
@@ -163,7 +161,6 @@ pub fn render_ui(
|
||||
render_form(
|
||||
f, form_render_area, form_state, &fields_vec, &form_state.current_field,
|
||||
&values_vec, theme, is_event_handler_edit_mode, highlight_state,
|
||||
total_count, current_position,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// client/src/ui/handlers/ui.rs
|
||||
// src/ui/handlers/ui.rs
|
||||
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::config::colors::themes::Theme;
|
||||
@@ -27,36 +27,32 @@ use crate::ui::handlers::context::DialogPurpose;
|
||||
use crate::tui::functions::common::login;
|
||||
use crate::tui::functions::common::register;
|
||||
use std::time::Instant;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use crossterm::event as crossterm_event;
|
||||
use tracing::{error, info, warn};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
|
||||
pub async fn run_ui() -> Result<()> {
|
||||
let config = Config::load().context("Failed to load configuration")?;
|
||||
let theme = Theme::from_str(&config.colors.theme);
|
||||
let mut terminal = TerminalCore::new().context("Failed to initialize terminal")?;
|
||||
let mut grpc_client = GrpcClient::new().await?;
|
||||
let mut grpc_client = GrpcClient::new().await.context("Failed to create GrpcClient")?;
|
||||
let mut command_handler = CommandHandler::new();
|
||||
|
||||
|
||||
let (login_result_sender, mut login_result_receiver) =
|
||||
mpsc::channel::<LoginResult>(1);
|
||||
let (register_result_sender, mut register_result_receiver) =
|
||||
mpsc::channel::<RegisterResult>(1);
|
||||
let (save_table_result_sender, mut save_table_result_receiver) =
|
||||
mpsc::channel::<Result<String>>(1);
|
||||
let (save_logic_result_sender, _save_logic_result_receiver) =
|
||||
mpsc::channel::<Result<String>>(1);
|
||||
let (login_result_sender, mut login_result_receiver) = mpsc::channel::<LoginResult>(1);
|
||||
let (register_result_sender, mut register_result_receiver) = mpsc::channel::<RegisterResult>(1);
|
||||
let (save_table_result_sender, mut save_table_result_receiver) = mpsc::channel::<Result<String>>(1);
|
||||
let (save_logic_result_sender, _save_logic_result_receiver) = mpsc::channel::<Result<String>>(1);
|
||||
|
||||
let mut event_handler = EventHandler::new(
|
||||
login_result_sender.clone(),
|
||||
register_result_sender.clone(),
|
||||
save_table_result_sender.clone(),
|
||||
save_logic_result_sender.clone(),
|
||||
).await.context("Failed to create event handler")?;
|
||||
)
|
||||
.await
|
||||
.context("Failed to create event handler")?;
|
||||
let event_reader = EventReader::new();
|
||||
|
||||
let mut auth_state = AuthState::default();
|
||||
@@ -67,7 +63,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
let mut buffer_state = BufferState::default();
|
||||
let mut app_state = AppState::new().context("Failed to create initial app state")?;
|
||||
|
||||
|
||||
let mut auto_logged_in = false;
|
||||
match load_auth_data() {
|
||||
Ok(Some(stored_data)) => {
|
||||
@@ -86,14 +81,33 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize AppState and FormState with table data
|
||||
let (initial_profile, initial_table, initial_columns) =
|
||||
UiService::initialize_app_state_and_form(&mut grpc_client, &mut app_state)
|
||||
.await
|
||||
.context("Failed to initialize app state and form")?;
|
||||
|
||||
let column_names =
|
||||
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
||||
.await.context("Failed to initialize app state from UI service")?;
|
||||
let mut form_state = FormState::new(column_names);
|
||||
let mut form_state = FormState::new(
|
||||
initial_profile.clone(),
|
||||
initial_table.clone(),
|
||||
initial_columns,
|
||||
);
|
||||
|
||||
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?;
|
||||
UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state)
|
||||
.await
|
||||
.context(format!(
|
||||
"Failed to fetch initial count for table {}.{}",
|
||||
initial_profile, initial_table
|
||||
))?;
|
||||
|
||||
// Load initial data for the form
|
||||
if form_state.total_count > 0 {
|
||||
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
|
||||
event_handler.command_message = format!("Error loading initial data: {}", e);
|
||||
}
|
||||
} else {
|
||||
form_state.reset_to_empty();
|
||||
}
|
||||
|
||||
if auto_logged_in {
|
||||
buffer_state.history = vec![AppView::Form];
|
||||
@@ -104,9 +118,10 @@ pub async fn run_ui() -> Result<()> {
|
||||
let mut last_frame_time = Instant::now();
|
||||
let mut current_fps = 0.0;
|
||||
let mut needs_redraw = true;
|
||||
let mut prev_view_profile_name = app_state.current_view_profile_name.clone();
|
||||
let mut prev_view_table_name = app_state.current_view_table_name.clone();
|
||||
|
||||
loop {
|
||||
|
||||
if let Some(active_view) = buffer_state.get_active_view() {
|
||||
app_state.ui.show_intro = false;
|
||||
app_state.ui.show_login = false;
|
||||
@@ -154,14 +169,53 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle table change for FormView
|
||||
if app_state.ui.show_form {
|
||||
let current_view_profile = app_state.current_view_profile_name.clone();
|
||||
let current_view_table = app_state.current_view_table_name.clone();
|
||||
|
||||
if prev_view_profile_name != current_view_profile || prev_view_table_name != current_view_table {
|
||||
if let (Some(prof_name), Some(tbl_name)) = (current_view_profile.as_ref(), current_view_table.as_ref()) {
|
||||
app_state.show_loading_dialog("Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name));
|
||||
needs_redraw = true;
|
||||
|
||||
match grpc_client.get_table_structure(prof_name.clone(), tbl_name.clone()).await {
|
||||
Ok(structure_response) => {
|
||||
let new_columns: Vec<String> = structure_response.columns.iter().map(|c| c.name.clone()).collect();
|
||||
form_state = FormState::new(prof_name.clone(), tbl_name.clone(), new_columns);
|
||||
|
||||
if let Err(e) = UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state).await {
|
||||
app_state.update_dialog_content(&format!("Error fetching count: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||
} else {
|
||||
if form_state.total_count > 0 {
|
||||
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
|
||||
app_state.update_dialog_content(&format!("Error loading data: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||
} else {
|
||||
app_state.hide_dialog();
|
||||
}
|
||||
} else {
|
||||
form_state.reset_to_empty();
|
||||
app_state.hide_dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
app_state.update_dialog_content(&format!("Error fetching table structure: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||
app_state.current_view_profile_name = prev_view_profile_name.clone();
|
||||
app_state.current_view_table_name = prev_view_table_name.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
prev_view_profile_name = current_view_profile;
|
||||
prev_view_table_name = current_view_table;
|
||||
needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
|
||||
if app_state.ui.show_add_logic {
|
||||
|
||||
if admin_state.add_logic_state.profile_name == profile_name &&
|
||||
admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) {
|
||||
|
||||
info!("Fetching table structure for {}.{}", profile_name, table_name);
|
||||
let fetch_message = UiService::initialize_add_logic_table_data(
|
||||
&mut grpc_client,
|
||||
@@ -194,7 +248,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if needs_redraw {
|
||||
terminal.draw(|f| {
|
||||
render_ui(
|
||||
@@ -213,9 +266,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
event_handler.command_mode,
|
||||
&event_handler.command_message,
|
||||
&event_handler.navigation_state,
|
||||
|
||||
app_state.total_count,
|
||||
app_state.current_position,
|
||||
&app_state.current_dir,
|
||||
current_fps,
|
||||
&app_state,
|
||||
@@ -224,7 +274,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
needs_redraw = false;
|
||||
}
|
||||
|
||||
|
||||
if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() {
|
||||
if app_state.ui.show_add_logic {
|
||||
let profile_name = admin_state.add_logic_state.profile_name.clone();
|
||||
@@ -247,7 +296,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
||||
match current_mode {
|
||||
AppMode::Edit => { terminal.show_cursor()?; }
|
||||
@@ -264,15 +312,12 @@ pub async fn run_ui() -> Result<()> {
|
||||
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
|
||||
}
|
||||
|
||||
let position_before_event = form_state.current_position;
|
||||
|
||||
let total_count = app_state.total_count;
|
||||
let mut current_position = app_state.current_position;
|
||||
let position_before_event = current_position;
|
||||
if app_state.ui.dialog.is_loading {
|
||||
needs_redraw = true;
|
||||
}
|
||||
|
||||
|
||||
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
||||
let mut event_processed = false;
|
||||
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
|
||||
@@ -292,16 +337,12 @@ pub async fn run_ui() -> Result<()> {
|
||||
&mut admin_state,
|
||||
&mut buffer_state,
|
||||
&mut app_state,
|
||||
total_count,
|
||||
&mut current_position,
|
||||
).await;
|
||||
}
|
||||
|
||||
if event_processed {
|
||||
needs_redraw = true;
|
||||
}
|
||||
app_state.current_position = current_position;
|
||||
|
||||
|
||||
match login_result_receiver.try_recv() {
|
||||
Ok(result) => {
|
||||
@@ -315,7 +356,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
match register_result_receiver.try_recv() {
|
||||
Ok(result) => {
|
||||
if register::handle_registration_result(result, &mut app_state, &mut register_state) {
|
||||
@@ -353,7 +393,6 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut should_exit = false;
|
||||
match event_outcome_result {
|
||||
Ok(outcome) => match outcome {
|
||||
@@ -383,58 +422,76 @@ pub async fn run_ui() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let position_changed = app_state.current_position != position_before_event;
|
||||
let current_total_count = app_state.total_count;
|
||||
// --- MODIFIED: Position Change Handling (operates on form_state) ---
|
||||
let position_changed = form_state.current_position != position_before_event;
|
||||
let mut position_logic_needs_redraw = false;
|
||||
|
||||
if app_state.ui.show_form {
|
||||
if app_state.ui.show_form { // Only if the form is active
|
||||
if position_changed && !event_handler.is_edit_mode {
|
||||
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 = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
// This part is okay: update cursor for the current field BEFORE loading new data
|
||||
let current_input_before_load = form_state.get_current_input();
|
||||
let max_cursor_pos_before_load = if !current_input_before_load.is_empty() { current_input_before_load.chars().count() } else { 0 };
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_before_load);
|
||||
position_logic_needs_redraw = true;
|
||||
|
||||
if app_state.current_position > current_total_count + 1 {
|
||||
app_state.current_position = current_total_count + 1;
|
||||
// Validate new form_state.current_position
|
||||
if form_state.total_count > 0 && form_state.current_position > form_state.total_count + 1 {
|
||||
form_state.current_position = form_state.total_count + 1; // Cap at new entry
|
||||
} else if form_state.total_count == 0 && form_state.current_position > 1 {
|
||||
form_state.current_position = 1; // Cap at new entry for empty table
|
||||
}
|
||||
if form_state.current_position == 0 && form_state.total_count > 0 {
|
||||
form_state.current_position = 1; // Don't allow 0 if there are records
|
||||
}
|
||||
|
||||
if app_state.current_position > current_total_count {
|
||||
form_state.reset_to_empty();
|
||||
form_state.current_field = 0;
|
||||
} else if app_state.current_position >= 1 && app_state.current_position <= current_total_count {
|
||||
let current_position_to_load = app_state.current_position;
|
||||
let load_message = UiService::load_adresar_by_position(
|
||||
&mut grpc_client,
|
||||
&mut app_state,
|
||||
&mut form_state,
|
||||
current_position_to_load,
|
||||
)
|
||||
.await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?;
|
||||
|
||||
let current_input_after_load = form_state.get_current_input();
|
||||
let max_cursor_pos_after_load = if !event_handler.is_edit_mode && !current_input_after_load.is_empty() {
|
||||
current_input_after_load.len() - 1
|
||||
} else {
|
||||
current_input_after_load.len()
|
||||
};
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_after_load);
|
||||
|
||||
if !load_message.starts_with("Loaded entry") || event_handler.command_message.is_empty() {
|
||||
// Load data for the new position OR reset for new entry
|
||||
if (form_state.total_count > 0 && form_state.current_position <= form_state.total_count && form_state.current_position > 0)
|
||||
{
|
||||
// It's an existing record position
|
||||
match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
|
||||
Ok(load_message) => {
|
||||
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") {
|
||||
event_handler.command_message = load_message;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
event_handler.command_message = format!("Error loading data: {}", e);
|
||||
// Consider what to do with form_state here - maybe revert position or clear form
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app_state.current_position = 1.min(current_total_count + 1);
|
||||
if app_state.current_position > current_total_count {
|
||||
form_state.reset_to_empty();
|
||||
form_state.current_field = 0;
|
||||
// Position indicates a new entry (or table is empty and position is 1)
|
||||
form_state.reset_to_empty(); // This sets id=0, clears values, and sets current_position correctly
|
||||
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name);
|
||||
}
|
||||
|
||||
// NOW, after data is loaded or form is reset, get the current input string and its length
|
||||
let current_input_after_load_str = form_state.get_current_input();
|
||||
let current_input_len_after_load = current_input_after_load_str.chars().count();
|
||||
|
||||
let max_cursor_pos_for_readonly_after_load = if current_input_len_after_load > 0 {
|
||||
current_input_len_after_load.saturating_sub(1)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if event_handler.is_edit_mode {
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(current_input_len_after_load);
|
||||
} else {
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_for_readonly_after_load);
|
||||
// The check for empty string is implicitly handled by max_cursor_pos_for_readonly_after_load being 0
|
||||
}
|
||||
} else if !position_changed && !event_handler.is_edit_mode {
|
||||
let current_input = form_state.get_current_input();
|
||||
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||
|
||||
} else if !position_changed && !event_handler.is_edit_mode && app_state.ui.show_form {
|
||||
// Update cursor if not editing and position didn't change (e.g. arrow keys within field)
|
||||
let current_input_str = form_state.get_current_input();
|
||||
let current_input_len = current_input_str.chars().count();
|
||||
let max_cursor_pos = if current_input_len > 0 {
|
||||
current_input_len.saturating_sub(1)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
}
|
||||
} else if app_state.ui.show_register {
|
||||
@@ -455,12 +512,10 @@ pub async fn run_ui() -> Result<()> {
|
||||
needs_redraw = true;
|
||||
}
|
||||
|
||||
|
||||
if should_exit {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
let now = Instant::now();
|
||||
let frame_duration = now.duration_since(last_frame_time);
|
||||
last_frame_time = now;
|
||||
|
||||
Reference in New Issue
Block a user