feat: implement main application structure with UI and layout management
- Added main application logic in `src/ui/app.rs` to handle state and modal dialogs. - Created module structure for UI components in `src/ui/mod.rs`. - Implemented column configuration screen in `src/ui/screens/configuracao_colunas.rs`. - Developed file import screen in `src/ui/screens/import.rs` for CSV and XLSX files. - Added layout management screen in `src/ui/screens/layouts.rs` for saving and importing layouts. - Created result display screen in `src/ui/screens/resultado.rs` to show analysis results. - Introduced modular organization for screens in `src/ui/screens/mod.rs`.
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
use crate::application::usecases::exportar_pdf::exportar_pdf;
|
||||
use crate::domain::{
|
||||
entities::resultado_analise::ResultadoAnalise,
|
||||
services::parser_monetario::formatar_valor_br,
|
||||
};
|
||||
use crate::infrastructure::pdf_generator::GenpdfGenerator;
|
||||
use crate::ui::app::{App, EstadoApp};
|
||||
use egui::{Context, Ui};
|
||||
|
||||
const OPCOES_PAGINA: &[usize] = &[50, 100, 200, 1000];
|
||||
|
||||
/// Renderiza a tela de resultados.
|
||||
pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
|
||||
// Extrair resultado do estado (sem mover)
|
||||
let resultado = match &app.estado {
|
||||
EstadoApp::ExibindoResultado(r) => r.clone(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
ui.heading("Resultado da Análise");
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("← Nova Análise").clicked() {
|
||||
app.estado = EstadoApp::Importando;
|
||||
app.notas_importadas.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if ui.button("⚙ Reconfigurar Colunas").clicked() {
|
||||
app.estado = EstadoApp::ConfigurandoColunas;
|
||||
return;
|
||||
}
|
||||
|
||||
if ui.button("📄 Exportar PDF").clicked() {
|
||||
exportar_para_pdf(app, &resultado);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Controle de itens por página
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Itens por página:");
|
||||
for &opcao in OPCOES_PAGINA {
|
||||
if ui
|
||||
.selectable_label(app.itens_por_pagina == opcao, opcao.to_string())
|
||||
.clicked()
|
||||
{
|
||||
app.itens_por_pagina = opcao;
|
||||
app.pagina_faltantes = 0;
|
||||
app.pagina_duplicatas = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
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);
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
|
||||
// Duplicatas
|
||||
renderizar_duplicatas(ui, app, &resultado);
|
||||
});
|
||||
}
|
||||
|
||||
fn renderizar_totais(ui: &mut Ui, resultado: &ResultadoAnalise) {
|
||||
ui.label(egui::RichText::new("Totais").heading().strong());
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.label(format!(
|
||||
"Total Geral: R$ {}",
|
||||
formatar_valor_br(&resultado.soma_total)
|
||||
));
|
||||
|
||||
let mut series: Vec<&String> = resultado.soma_por_serie.keys().collect();
|
||||
series.sort();
|
||||
|
||||
for serie in series {
|
||||
let soma = &resultado.soma_por_serie[serie];
|
||||
let total_notas = resultado.total_por_serie.get(serie).copied().unwrap_or(0);
|
||||
ui.label(format!(
|
||||
" Série {}: {} nota(s) — R$ {}",
|
||||
serie,
|
||||
total_notas,
|
||||
formatar_valor_br(soma)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn renderizar_faltantes(ui: &mut Ui, app: &mut App, resultado: &ResultadoAnalise) {
|
||||
let total_faltantes = resultado.total_faltantes();
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"Notas Faltantes ({} total)",
|
||||
total_faltantes
|
||||
))
|
||||
.heading()
|
||||
.strong(),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
|
||||
if total_faltantes == 0 {
|
||||
ui.label("✔ Nenhuma nota faltante.");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut series: Vec<&String> = resultado.faltantes_por_serie.keys().collect();
|
||||
series.sort();
|
||||
|
||||
for serie in series {
|
||||
let faltantes = &resultado.faltantes_por_serie[serie];
|
||||
if faltantes.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
ui.label(format!(
|
||||
"Série {} — {} faltante(s):",
|
||||
serie,
|
||||
faltantes.len()
|
||||
));
|
||||
|
||||
// Paginação
|
||||
let total_paginas = (faltantes.len() + app.itens_por_pagina - 1) / app.itens_por_pagina;
|
||||
if app.pagina_faltantes >= total_paginas {
|
||||
app.pagina_faltantes = 0;
|
||||
}
|
||||
|
||||
let inicio = app.pagina_faltantes * app.itens_por_pagina;
|
||||
let fim = (inicio + app.itens_por_pagina).min(faltantes.len());
|
||||
|
||||
for numero in &faltantes[inicio..fim] {
|
||||
ui.label(format!(" • {}", numero));
|
||||
}
|
||||
|
||||
if total_paginas > 1 {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("◀").clicked() && app.pagina_faltantes > 0 {
|
||||
app.pagina_faltantes -= 1;
|
||||
}
|
||||
ui.label(format!(
|
||||
"Página {} / {}",
|
||||
app.pagina_faltantes + 1,
|
||||
total_paginas
|
||||
));
|
||||
if ui.button("▶").clicked() && app.pagina_faltantes + 1 < total_paginas {
|
||||
app.pagina_faltantes += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn renderizar_duplicatas(ui: &mut Ui, app: &mut App, resultado: &ResultadoAnalise) {
|
||||
let total_dup = resultado.total_duplicatas();
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"Notas Duplicadas ({} grupo(s))",
|
||||
total_dup
|
||||
))
|
||||
.heading()
|
||||
.strong(),
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
|
||||
if total_dup == 0 {
|
||||
ui.label("✔ Nenhuma nota duplicada.");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut series: Vec<&String> = resultado.duplicadas_por_serie.keys().collect();
|
||||
series.sort();
|
||||
|
||||
for serie in series {
|
||||
let duplicatas = &resultado.duplicadas_por_serie[serie];
|
||||
if duplicatas.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
ui.label(format!(
|
||||
"Série {} — {} grupo(s) duplicado(s):",
|
||||
serie,
|
||||
duplicatas.len()
|
||||
));
|
||||
|
||||
let total_paginas =
|
||||
(duplicatas.len() + app.itens_por_pagina - 1) / app.itens_por_pagina;
|
||||
if app.pagina_duplicatas >= total_paginas {
|
||||
app.pagina_duplicatas = 0;
|
||||
}
|
||||
|
||||
let inicio = app.pagina_duplicatas * app.itens_por_pagina;
|
||||
let fim = (inicio + app.itens_por_pagina).min(duplicatas.len());
|
||||
|
||||
for (numero, count) in &duplicatas[inicio..fim] {
|
||||
ui.label(format!(
|
||||
" • NF {} / Série {} — {} ocorrências",
|
||||
numero, serie, count
|
||||
));
|
||||
}
|
||||
|
||||
if total_paginas > 1 {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("◀").clicked() && app.pagina_duplicatas > 0 {
|
||||
app.pagina_duplicatas -= 1;
|
||||
}
|
||||
ui.label(format!(
|
||||
"Página {} / {}",
|
||||
app.pagina_duplicatas + 1,
|
||||
total_paginas
|
||||
));
|
||||
if ui.button("▶").clicked() && app.pagina_duplicatas + 1 < total_paginas {
|
||||
app.pagina_duplicatas += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exportar_para_pdf(app: &mut App, resultado: &ResultadoAnalise) {
|
||||
if let Some(caminho) = rfd::FileDialog::new()
|
||||
.set_file_name("relatorio.pdf")
|
||||
.add_filter("PDF", &["pdf"])
|
||||
.save_file()
|
||||
{
|
||||
let gerador = GenpdfGenerator;
|
||||
let nome_layout = if app.nome_layout_atual.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(app.nome_layout_atual.as_str())
|
||||
};
|
||||
|
||||
match exportar_pdf(
|
||||
&gerador,
|
||||
resultado,
|
||||
&app.nome_arquivo,
|
||||
nome_layout,
|
||||
&caminho,
|
||||
) {
|
||||
Ok(_) => {
|
||||
app.exibir_aviso("Sucesso", format!("PDF exportado para: {}", caminho.display()));
|
||||
}
|
||||
Err(e) => {
|
||||
app.exibir_erro(format!("Erro ao exportar PDF: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user