Files
comparador-notas/src/ui/screens/configuracao_colunas.rs
T
Felipe e64988a139 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`.
2026-03-02 22:14:02 -03:00

296 lines
10 KiB
Rust

use crate::application::usecases::importar_arquivo::{importar_csv, importar_xlsx};
use crate::domain::entities::layout::TipoArquivo;
use crate::ui::app::{App, EstadoApp};
use egui::{Context, Ui};
/// Renderiza a tela de configuração de colunas.
pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
ui.heading("Configuração de Colunas");
ui.add_space(8.0);
if let Some(caminho) = &app.caminho_arquivo.clone() {
ui.label(format!("Arquivo: {}", caminho.display()));
}
ui.add_space(12.0);
match app.tipo_arquivo_atual.clone() {
TipoArquivo::Csv => renderizar_csv(ui, app),
TipoArquivo::Xlsx => renderizar_xlsx(ui, app),
}
ui.add_space(16.0);
ui.separator();
ui.add_space(8.0);
// Validação e botões de ação
let (valido, erros) = validar_config(app);
if !erros.is_empty() {
for erro in &erros {
ui.colored_label(egui::Color32::RED, format!("⚠ {}", erro));
}
ui.add_space(8.0);
}
ui.horizontal(|ui| {
if ui.button("← Voltar").clicked() {
app.estado = EstadoApp::Importando;
}
ui.add_enabled_ui(valido && app.caminho_arquivo.is_some(), |ui| {
if ui.button("▶ Importar e Analisar").clicked() {
executar_importacao(app);
}
});
});
}
fn renderizar_csv(ui: &mut Ui, app: &mut App) {
ui.group(|ui| {
ui.label("Configurações CSV");
ui.add_space(4.0);
// Delimitador
ui.horizontal(|ui| {
ui.label("Delimitador:");
let delim_str = match app.layout_csv_atual.delimitador {
',' => "Vírgula (,)",
';' => "Ponto e vírgula (;)",
'\t' => "Tabulação (Tab)",
_ => "Outro",
};
egui::ComboBox::from_id_salt("combo_delimitador")
.selected_text(delim_str)
.show_ui(ui, |ui| {
if ui.selectable_label(app.layout_csv_atual.delimitador == ',', "Vírgula (,)").clicked() {
app.layout_csv_atual.delimitador = ',';
}
if ui.selectable_label(app.layout_csv_atual.delimitador == ';', "Ponto e vírgula (;)").clicked() {
app.layout_csv_atual.delimitador = ';';
}
if ui.selectable_label(app.layout_csv_atual.delimitador == '\t', "Tabulação (Tab)").clicked() {
app.layout_csv_atual.delimitador = '\t';
}
});
});
// Encoding
ui.horizontal(|ui| {
ui.label("Encoding:");
egui::ComboBox::from_id_salt("combo_encoding")
.selected_text(&app.layout_csv_atual.encoding)
.show_ui(ui, |ui| {
if ui.selectable_label(app.layout_csv_atual.encoding == "utf-8", "UTF-8").clicked() {
app.layout_csv_atual.encoding = "utf-8".to_string();
}
if ui.selectable_label(app.layout_csv_atual.encoding == "windows-1252", "Windows-1252 (Latin-1)").clicked() {
app.layout_csv_atual.encoding = "windows-1252".to_string();
}
});
});
// Linha cabeçalho
ui.horizontal(|ui| {
ui.label("Linha do cabeçalho (0 = sem cabeçalho):");
let mut val = app.layout_csv_atual.linha_cabecalho;
ui.add(egui::DragValue::new(&mut val).range(0..=100));
app.layout_csv_atual.linha_cabecalho = val;
});
});
ui.add_space(8.0);
ui.group(|ui| {
ui.label("Mapeamento de Colunas (índice base 0)");
ui.add_space(4.0);
campo_indice(ui, "Número (obrigatório):", &mut app.layout_csv_atual.indice_numero);
campo_indice(ui, "Série (obrigatório):", &mut app.layout_csv_atual.indice_serie);
campo_indice_opcional(ui, "Valor (opcional):", &mut app.layout_csv_atual.indice_valor);
campo_indice_opcional(ui, "Data (opcional):", &mut app.layout_csv_atual.indice_data);
});
}
fn renderizar_xlsx(ui: &mut Ui, app: &mut App) {
ui.group(|ui| {
ui.label("Configurações XLSX");
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.label("Aba:");
if app.abas_xlsx.is_empty() {
ui.text_edit_singleline(&mut app.layout_xlsx_atual.aba);
} else {
let aba_atual = app.layout_xlsx_atual.aba.clone();
egui::ComboBox::from_id_salt("combo_aba")
.selected_text(&aba_atual)
.show_ui(ui, |ui| {
for aba in &app.abas_xlsx.clone() {
if ui.selectable_label(aba_atual == *aba, aba).clicked() {
app.layout_xlsx_atual.aba = aba.clone();
}
}
});
}
});
});
ui.add_space(8.0);
ui.group(|ui| {
ui.label("Mapeamento de Colunas (formato LetraLinha, ex: B3)");
ui.add_space(4.0);
campo_letra_linha(ui, "Número (obrigatório):", &mut app.layout_xlsx_atual.pos_numero);
campo_letra_linha(ui, "Série (obrigatório):", &mut app.layout_xlsx_atual.pos_serie);
campo_letra_linha_opcional(ui, "Valor (opcional):", &mut app.layout_xlsx_atual.pos_valor);
campo_letra_linha_opcional(ui, "Data (opcional):", &mut app.layout_xlsx_atual.pos_data);
});
}
fn campo_indice(ui: &mut Ui, label: &str, valor: &mut usize) {
ui.horizontal(|ui| {
ui.label(label);
ui.add(egui::DragValue::new(valor).range(0..=999usize));
});
}
fn campo_indice_opcional(ui: &mut Ui, label: &str, valor: &mut Option<usize>) {
ui.horizontal(|ui| {
let mut ativo = valor.is_some();
if ui.checkbox(&mut ativo, label).changed() {
*valor = if ativo { Some(0) } else { None };
}
if let Some(v) = valor {
ui.add(egui::DragValue::new(v).range(0..=999usize));
}
});
}
fn campo_letra_linha(ui: &mut Ui, label: &str, valor: &mut String) {
ui.horizontal(|ui| {
ui.label(label);
ui.text_edit_singleline(valor);
});
}
fn campo_letra_linha_opcional(ui: &mut Ui, label: &str, valor: &mut Option<String>) {
ui.horizontal(|ui| {
let mut ativo = valor.is_some();
if ui.checkbox(&mut ativo, label).changed() {
*valor = if ativo { Some(String::new()) } else { None };
}
if let Some(v) = valor {
ui.text_edit_singleline(v);
}
});
}
/// Valida a configuração atual. Retorna (é_válido, lista_de_erros).
fn validar_config(app: &App) -> (bool, Vec<String>) {
let mut erros = Vec::new();
match &app.tipo_arquivo_atual {
TipoArquivo::Csv => {
let c = &app.layout_csv_atual;
// Verificar índices duplicados
let mut indices: Vec<(String, usize)> = vec![
("Numero".to_string(), c.indice_numero),
("Serie".to_string(), c.indice_serie),
];
if let Some(v) = c.indice_valor {
indices.push(("Valor".to_string(), v));
}
if let Some(d) = c.indice_data {
indices.push(("Data".to_string(), d));
}
verificar_duplicados(&indices, &mut erros);
}
TipoArquivo::Xlsx => {
let c = &app.layout_xlsx_atual;
if c.aba.trim().is_empty() {
erros.push("Selecione uma aba".to_string());
}
if c.pos_numero.trim().is_empty() {
erros.push("Posição do campo Numero é obrigatória".to_string());
} else if crate::infrastructure::xlsx_reader::parsear_letra_linha(&c.pos_numero)
.is_none()
{
erros.push(format!("Posição Numero inválida: '{}'", c.pos_numero));
}
if c.pos_serie.trim().is_empty() {
erros.push("Posição do campo Serie é obrigatória".to_string());
} else if crate::infrastructure::xlsx_reader::parsear_letra_linha(&c.pos_serie)
.is_none()
{
erros.push(format!("Posição Serie inválida: '{}'", c.pos_serie));
}
}
}
let valido = erros.is_empty();
(valido, erros)
}
fn verificar_duplicados(indices: &[(String, usize)], erros: &mut Vec<String>) {
for i in 0..indices.len() {
for j in (i + 1)..indices.len() {
if indices[i].1 == indices[j].1 {
erros.push(format!(
"Campos '{}' e '{}' mapeados para o mesmo índice {}",
indices[i].0, indices[j].0, indices[i].1
));
}
}
}
}
fn executar_importacao(app: &mut App) {
let caminho = match &app.caminho_arquivo {
Some(p) => p.clone(),
None => return,
};
let resultado = match app.tipo_arquivo_atual.clone() {
TipoArquivo::Csv => {
importar_csv(&caminho, &app.layout_csv_atual)
.map_err(|e| e.to_string())
}
TipoArquivo::Xlsx => {
importar_xlsx(&caminho, &app.layout_xlsx_atual)
.map_err(|e| e.to_string())
}
};
match resultado {
Ok(res) => {
if res.notas.is_empty() {
app.exibir_aviso("Aviso", "Nenhuma nota válida encontrada no arquivo.");
return;
}
let avisos = res.avisos.clone();
app.notas_importadas = res.notas;
app.avisos_importacao = if avisos.tem_avisos() {
Some(avisos.clone())
} else {
None
};
// Executar análise
app.executar_analise();
// Exibir avisos consolidados após análise
if let Some(av) = &app.avisos_importacao {
if av.tem_avisos() {
let linhas = av.linhas_para_exibir().join("\n");
app.exibir_aviso("Avisos de Importação", linhas);
}
}
}
Err(e) => {
app.exibir_erro(format!("Erro ao importar arquivo: {}", e));
}
}
}