From b2b91cda55b48bb7fe76cc68f4d5072e6a9e9df6 Mon Sep 17 00:00:00 2001 From: FelipeCN <57776624+felipecaninnovaes@users.noreply.github.com> Date: Tue, 3 Mar 2026 08:48:19 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20adiciona=20nova=20a=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20confirma=C3=A7=C3=A3o=20para=20iniciar=20nova=20an=C3=A1lise?= =?UTF-8?q?=20e=20implementa=20breadcrumb=20na=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/app.rs | 41 ++++++++++++++++++++++ src/ui/screens/configuracao_colunas.rs | 47 +++++++++++++++++++++++++- src/ui/screens/resultado.rs | 22 ++++++------ 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/ui/app.rs b/src/ui/app.rs index e1ee63a..2d3f454 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -66,6 +66,7 @@ pub enum AcaoModal { ConfirmarExpansaoFaltantes, ConfirmarExclusaoLayout(i64), SobrescreverLayout, + ConfirmarNovaAnalise, } /// Struct principal da aplicação egui. @@ -274,6 +275,10 @@ impl App { } } AcaoModal::SobrescreverLayout => {} // Handled inline in layouts screen + AcaoModal::ConfirmarNovaAnalise => { + self.notas_importadas.clear(); + self.estado = EstadoApp::Importando; + } } } @@ -316,11 +321,47 @@ impl App { } } +impl App { + /// Renderiza o breadcrumb de etapas no topo (exceto na tela de layouts). + pub fn renderizar_breadcrumb(&self, ctx: &Context) { + let passo_ativo: usize = match self.tela_atual() { + 0 | 1 => 1, + 2 | 4 => 2, + 3 => 3, + _ => return, // GerenciandoLayouts: sem breadcrumb + }; + + egui::TopBottomPanel::top("breadcrumb").show(ctx, |ui| { + ui.add_space(4.0); + ui.horizontal(|ui| { + for (i, label) in ["① Arquivo", "② Colunas", "③ Resultado"].iter().enumerate() { + let n = i + 1; + let texto = egui::RichText::new(*label); + if n == passo_ativo { + ui.label(texto.strong()); + } else if n < passo_ativo { + ui.label(texto); + } else { + ui.label(texto.weak()); + } + if n < 3 { + ui.label(egui::RichText::new(" ›").weak()); + } + } + }); + ui.add_space(4.0); + }); + } +} + impl eframe::App for App { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { // Sempre renderizar modal por cima de tudo self.renderizar_modal(ctx); + // Breadcrumb de etapas no topo + self.renderizar_breadcrumb(ctx); + // Determinar qual tela exibir sem borrar self.estado let tela_id = self.tela_atual(); diff --git a/src/ui/screens/configuracao_colunas.rs b/src/ui/screens/configuracao_colunas.rs index a9176c2..bac9ae7 100644 --- a/src/ui/screens/configuracao_colunas.rs +++ b/src/ui/screens/configuracao_colunas.rs @@ -1,5 +1,5 @@ use crate::application::usecases::importar_arquivo::{importar_csv, importar_xlsx}; -use crate::domain::entities::layout::TipoArquivo; +use crate::domain::entities::layout::{Layout, TipoArquivo}; use crate::ui::app::{App, EstadoApp}; use egui::{Context, Ui}; @@ -12,6 +12,51 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) { ui.label(format!("Arquivo: {}", caminho.display())); } + ui.add_space(8.0); + + // Seletor de layout + let tipo_atual = app.tipo_arquivo_atual.clone(); + let opcoes_layout: Vec<(i64, String)> = app + .layouts_salvos + .iter() + .filter(|l| l.tipo() == tipo_atual) + .filter_map(|l| l.id().map(|id| (id, l.nome().to_string()))) + .collect(); + let nome_layout_atual = app.nome_layout_atual.clone(); + + ui.horizontal(|ui| { + ui.label("Layout:"); + egui::ComboBox::from_id_salt("combo_layouts_config") + .selected_text(if nome_layout_atual.is_empty() { + "— Selecionar layout —" + } else { + &nome_layout_atual + }) + .show_ui(ui, |ui| { + for (id, nome) in &opcoes_layout { + if ui + .selectable_label(nome_layout_atual == *nome, nome.as_str()) + .clicked() + { + app.nome_layout_atual = nome.clone(); + if let Some(layout) = + app.layouts_salvos.iter().find(|l| l.id() == Some(*id)) + { + let layout = layout.clone(); + match &layout { + Layout::Csv { config, .. } => { + app.layout_csv_atual = config.clone(); + } + Layout::Xlsx { config, .. } => { + app.layout_xlsx_atual = config.clone(); + } + } + } + } + } + }); + }); + ui.add_space(12.0); match app.tipo_arquivo_atual.clone() { diff --git a/src/ui/screens/resultado.rs b/src/ui/screens/resultado.rs index db92bfe..652ec3d 100644 --- a/src/ui/screens/resultado.rs +++ b/src/ui/screens/resultado.rs @@ -4,7 +4,7 @@ use crate::domain::{ services::parser_monetario::formatar_valor_br, }; use crate::infrastructure::pdf_generator::GenpdfGenerator; -use crate::ui::app::{App, EstadoApp}; +use crate::ui::app::{AcaoModal, App, EstadoApp}; use egui::{Context, Ui}; const OPCOES_PAGINA: &[usize] = &[50, 100, 200, 1000]; @@ -22,9 +22,11 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) { ui.horizontal(|ui| { if ui.button("< Nova Análise").clicked() { - app.estado = EstadoApp::Importando; - app.notas_importadas.clear(); - return; + app.exibir_confirmacao( + "Nova Análise", + "Deseja iniciar uma nova análise? O resultado atual será descartado.", + AcaoModal::ConfirmarNovaAnalise, + ); } if ui.button("⚙ Reconfigurar Colunas").clicked() { @@ -58,12 +60,6 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) { ui.separator(); egui::ScrollArea::vertical().show(ui, |ui| { - // Totais - renderizar_totais(ui, &resultado); - - ui.add_space(12.0); - ui.separator(); - // Faltantes renderizar_faltantes(ui, app, &resultado); @@ -72,6 +68,12 @@ pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) { // Duplicatas renderizar_duplicatas(ui, app, &resultado); + + ui.add_space(12.0); + ui.separator(); + + // Totais + renderizar_totais(ui, &resultado); }); }