we are suggesting properly table column names now

This commit is contained in:
filipriec
2025-05-26 19:42:23 +02:00
parent 913f6b6b64
commit f3cd921c76
5 changed files with 170 additions and 126 deletions

View File

@@ -23,16 +23,16 @@ use crate::tui::terminal::{EventReader, TerminalCore};
use crate::ui::handlers::render::render_ui;
use crate::tui::functions::common::login::LoginResult;
use crate::tui::functions::common::register::RegisterResult;
use crate::tui::functions::common::add_table::handle_save_table_action;
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
use crate::ui::handlers::context::{DialogPurpose, UiContext};
// Removed: use crate::tui::functions::common::add_table::handle_save_table_action;
// Removed: use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
use crate::ui::handlers::context::DialogPurpose; // UiContext removed if not used directly
use crate::tui::functions::common::login;
use crate::tui::functions::common::register;
use std::time::Instant;
use anyhow::{Context, Result};
use crossterm::cursor::SetCursorStyle;
use crossterm::event as crossterm_event;
use tracing::{error, info};
use tracing::{error, info, warn}; // Added warn
use tokio::sync::mpsc;
@@ -50,7 +50,7 @@ pub async fn run_ui() -> Result<()> {
mpsc::channel::<RegisterResult>(1);
let (save_table_result_sender, mut save_table_result_receiver) =
mpsc::channel::<Result<String>>(1);
let (save_logic_result_sender, mut save_logic_result_receiver) =
let (save_logic_result_sender, _save_logic_result_receiver) = // Prefixed and removed mut
mpsc::channel::<Result<String>>(1);
let mut event_handler = EventHandler::new(
@@ -73,8 +73,6 @@ pub async fn run_ui() -> Result<()> {
let mut auto_logged_in = false;
match load_auth_data() {
Ok(Some(stored_data)) => {
// TODO: Optionally validate token with server here
// For now, assume valid if successfully loaded
auth_state.auth_token = Some(stored_data.access_token);
auth_state.user_id = Some(stored_data.user_id);
auth_state.role = Some(stored_data.role);
@@ -91,27 +89,20 @@ pub async fn run_ui() -> Result<()> {
}
// --- END DATA ---
// Initialize app state with profile tree and table structure
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);
// Fetch the total count of Adresar entries
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?;
form_state.reset_to_empty();
// --- DATA2: Adjust initial view based on auth status ---
if auto_logged_in {
// User is auto-logged in, go to main app view
buffer_state.history = vec![AppView::Form];
buffer_state.active_index = 0;
info!("Initial view set to Form due to auto-login.");
}
// If not auto-logged in, BufferState default (Intro) will be used
// --- END DATA2 ---
// --- FPS Calculation State ---
let mut last_frame_time = Instant::now();
let mut current_fps = 0.0;
let mut needs_redraw = true;
@@ -119,7 +110,6 @@ pub async fn run_ui() -> Result<()> {
loop {
// --- Synchronize UI View from Active Buffer ---
if let Some(active_view) = buffer_state.get_active_view() {
// Reset all flags first
app_state.ui.show_intro = false;
app_state.ui.show_login = false;
app_state.ui.show_register = false;
@@ -142,21 +132,19 @@ pub async fn run_ui() -> Result<()> {
event_handler.command_message = format!("Error refreshing admin data: {}", e);
}
}
app_state.ui.show_admin = true; // <<< RESTORE THIS
let profile_names = app_state.profile_tree.profiles.iter() // <<< RESTORE THIS
.map(|p| p.name.clone()) // <<< RESTORE THIS
.collect(); // <<< RESTORE THIS
app_state.ui.show_admin = true;
let profile_names = app_state.profile_tree.profiles.iter()
.map(|p| p.name.clone())
.collect();
admin_state.set_profiles(profile_names);
// Only reset to ProfilesPane if not already in a specific admin sub-focus
if admin_state.current_focus == AdminFocus::default() ||
!matches!(admin_state.current_focus,
if admin_state.current_focus == AdminFocus::default() ||
!matches!(admin_state.current_focus,
AdminFocus::InsideProfilesList |
AdminFocus::Tables | AdminFocus::InsideTablesList |
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
admin_state.current_focus = AdminFocus::ProfilesPane;
}
// Pre-select first profile item for visual consistency, but '>' won't show until 'select'
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
admin_state.profile_list_state.select(Some(0));
}
@@ -164,16 +152,56 @@ pub async fn run_ui() -> Result<()> {
AppView::AddTable => app_state.ui.show_add_table = true,
AppView::AddLogic => app_state.ui.show_add_logic = true,
AppView::Form => app_state.ui.show_form = true,
AppView::Scratch => {} // Or show a scratchpad component
AppView::Scratch => {}
}
}
// --- End Synchronization ---
// --- Handle Pending Table Structure Fetches ---
if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
if app_state.ui.show_add_logic {
// Ensure admin_state.add_logic_state matches the pending fetch
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,
&mut admin_state.add_logic_state,
).await.unwrap_or_else(|e| {
error!("Error initializing add_logic_table_data: {}", e);
format!("Error fetching table structure: {}", e)
});
if !fetch_message.contains("Error") && !fetch_message.contains("Warning") {
info!("{}", fetch_message);
// Optionally update command message on success if desired
// event_handler.command_message = fetch_message;
} else {
event_handler.command_message = fetch_message; // Show error/warning to user
}
needs_redraw = true;
} else {
error!(
"Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}",
profile_name, table_name,
admin_state.add_logic_state.profile_name,
admin_state.add_logic_state.selected_table_name
);
// Cleared by .take(), no need to set to None explicitly unless re-queueing
}
} else {
warn!(
"Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.",
profile_name, table_name
);
// If you need to re-queue:
// app_state.pending_table_structure_fetch = Some((profile_name, table_name));
}
}
// --- 3. Draw UI ---
// Draw the current state *first*. This ensures the loading dialog
// set in the *previous* iteration gets rendered before the pending
// action check below.
if needs_redraw {
if needs_redraw {
terminal.draw(|f| {
render_ui(
f,
@@ -185,7 +213,7 @@ pub async fn run_ui() -> Result<()> {
&mut admin_state,
&buffer_state,
&theme,
event_handler.is_edit_mode, // Use event_handler's state
event_handler.is_edit_mode,
&event_handler.highlight_state,
app_state.total_count,
app_state.current_position,
@@ -201,7 +229,6 @@ pub async fn run_ui() -> Result<()> {
}
// --- Cursor Visibility Logic ---
// (Keep existing cursor logic here - depends on state drawn above)
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
match current_mode {
AppMode::Edit => { terminal.show_cursor()?; }
@@ -222,8 +249,6 @@ pub async fn run_ui() -> Result<()> {
let total_count = app_state.total_count;
let mut current_position = app_state.current_position;
let position_before_event = current_position;
// --- Determine if redraw is needed based on active login ---
// Always redraw if the loading dialog is currently showing.
if app_state.ui.dialog.is_loading {
needs_redraw = true;
}
@@ -231,10 +256,9 @@ pub async fn run_ui() -> Result<()> {
// --- 1. Handle Terminal Events ---
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
let mut event_processed = false;
// Poll for events *after* drawing and checking pending actions
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
let event = event_reader.read_event().context("Failed to read terminal event")?;
event_processed = true; // Mark that we received and will process an event
event_processed = true;
event_outcome_result = event_handler.handle_event(
event,
&config,
@@ -257,9 +281,6 @@ pub async fn run_ui() -> Result<()> {
if event_processed {
needs_redraw = true;
}
// Update position based on handler's modification
// This happens *after* the event is handled
app_state.current_position = current_position;
// --- Check for Login Results from Channel ---
@@ -272,7 +293,6 @@ pub async fn run_ui() -> Result<()> {
Err(mpsc::error::TryRecvError::Empty) => { /* No message waiting */ }
Err(mpsc::error::TryRecvError::Disconnected) => {
error!("Login result channel disconnected unexpectedly.");
// Optionally show an error dialog here
}
}
@@ -291,7 +311,7 @@ pub async fn run_ui() -> Result<()> {
// --- Check for Save Table Results ---
match save_table_result_receiver.try_recv() {
Ok(result) => {
app_state.hide_dialog(); // Hide loading indicator
app_state.hide_dialog();
match result {
Ok(ref success_message) => {
app_state.show_dialog(
@@ -304,12 +324,11 @@ pub async fn run_ui() -> Result<()> {
}
Err(e) => {
event_handler.command_message = format!("Save failed: {}", e);
// Optionally show an error dialog instead of just command message
}
}
needs_redraw = true;
}
Err(mpsc::error::TryRecvError::Empty) => {} // No message
Err(mpsc::error::TryRecvError::Empty) => {}
Err(mpsc::error::TryRecvError::Disconnected) => {
error!("Save table result channel disconnected unexpectedly.");
}
@@ -319,19 +338,15 @@ pub async fn run_ui() -> Result<()> {
let mut should_exit = false;
match event_outcome_result {
Ok(outcome) => match outcome {
EventOutcome::Ok(message) => {
if !message.is_empty() {
// Update command message only if event handling produced one
// Avoid overwriting messages potentially set by pending actions
// event_handler.command_message = message;
}
EventOutcome::Ok(_message) => {
// Message is often set directly in event_handler.command_message
}
EventOutcome::Exit(message) => {
event_handler.command_message = message;
should_exit = true;
}
EventOutcome::DataSaved(save_outcome, message) => {
event_handler.command_message = message; // Show save status
event_handler.command_message = message;
if let Err(e) = UiService::handle_save_outcome(
save_outcome,
&mut grpc_client,
@@ -345,119 +360,87 @@ pub async fn run_ui() -> Result<()> {
}
}
EventOutcome::ButtonSelected { context: _, index: _ } => {
// This case should ideally be fully handled within handle_event
// If initiate_login was called, it returned early.
// If not, the message was set and returned via Ok(message).
// Log if necessary, but likely no action needed here.
// log::warn!("ButtonSelected outcome reached main loop unexpectedly.");
// Handled within event_handler or specific navigation modules
}
},
Err(e) => {
event_handler.command_message = format!("Error: {}", e);
}
} // --- End Consequence Handling ---
}
// --- End Consequence Handling ---
// --- Position Change Handling (after outcome processing and pending actions) ---
// --- Position Change Handling ---
let position_changed = app_state.current_position != position_before_event;
let current_total_count = app_state.total_count;
let current_total_count = app_state.total_count; // Use current total_count
let mut position_logic_needs_redraw = false;
if app_state.ui.show_form {
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 // Limit to last character in readonly mode
} else {
0
};
form_state.current_cursor_pos =
event_handler.ideal_cursor_column.min(max_cursor_pos);
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);
position_logic_needs_redraw = true;
// Ensure position never exceeds total_count + 1
if app_state.current_position > current_total_count + 1 {
app_state.current_position = current_total_count + 1;
}
if app_state.current_position > current_total_count {
// New entry - reset form
form_state.reset_to_empty();
form_state.current_field = 0;
} else if app_state.current_position >= 1
&& app_state.current_position <= current_total_count
{
// Existing entry - load data
let current_position_to_load = app_state.current_position; // Use a copy
} 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, // Pass app_state mutably if needed by the service
&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 = form_state.get_current_input();
let max_cursor_pos = if !event_handler.is_edit_mode
&& !current_input.is_empty()
{
current_input.len() - 1 // In readonly mode, limit to last character
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.len()
current_input_after_load.len()
};
form_state.current_cursor_pos = event_handler
.ideal_cursor_column
.min(max_cursor_pos);
// Don't overwrite message from handle_event if load_message is simple success
if !load_message.starts_with("Loaded entry")
|| event_handler.command_message.is_empty()
{
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() {
event_handler.command_message = load_message;
}
} else {
// Invalid position (e.g., 0) - reset to first entry or new entry mode
app_state.current_position =
1.min(current_total_count + 1); // Go to 1 or new entry if empty
if app_state.current_position > total_count {
} else { // current_position is 0 or invalid
app_state.current_position = 1.min(current_total_count + 1);
if app_state.current_position > current_total_count { // Handles empty db case
form_state.reset_to_empty();
form_state.current_field = 0;
}
// If db is not empty, this will trigger load in next iteration if position changed to 1
}
} else if !position_changed && !event_handler.is_edit_mode {
// If position didn't change but we are in read-only, just adjust cursor
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);
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);
}
} else if app_state.ui.show_register {
if !event_handler.is_edit_mode {
let current_input = register_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
}
} else if app_state.ui.show_login {
if !event_handler.is_edit_mode {
let current_input = login_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
}
}
if position_logic_needs_redraw {
needs_redraw = true;
}
// --- End Position Change Handling ---
// Check exit condition *after* all processing for the iteration
if should_exit {
return Ok(());
}
@@ -466,9 +449,8 @@ pub async fn run_ui() -> Result<()> {
let now = Instant::now();
let frame_duration = now.duration_since(last_frame_time);
last_frame_time = now;
if frame_duration.as_secs_f64() > 1e-6 {
if frame_duration.as_secs_f64() > 1e-6 { // Avoid division by zero
current_fps = 1.0 / frame_duration.as_secs_f64();
}
} // End main loop
}