use crate::application::usecases::importar_arquivo::{importar_csv, importar_xlsx}; use crate::domain::entities::layout::{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(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() { TipoArquivo::Csv => renderizar_csv(ui, app), TipoArquivo::Xlsx => renderizar_xlsx(ui, app), } // Pré-visualização do arquivo if let Some(preview) = &app.preview_arquivo.clone() { ui.add_space(8.0); ui.separator(); ui.add_space(4.0); crate::ui::screens::renderizar_tabela_preview(ui, preview); } 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); } }); if ui.button("💾 Salvar como layout...").clicked() { app.exibir_modal_salvar_layout(); } }); } 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", }; let mut delim_mudou = false; 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 = ','; delim_mudou = true; } if ui .selectable_label( app.layout_csv_atual.delimitador == ';', "Ponto e vírgula (;)", ) .clicked() { app.layout_csv_atual.delimitador = ';'; delim_mudou = true; } if ui .selectable_label( app.layout_csv_atual.delimitador == '\t', "Tabulação (Tab)", ) .clicked() { app.layout_csv_atual.delimitador = '\t'; delim_mudou = true; } }); if delim_mudou { if let Some(caminho) = &app.caminho_arquivo.clone() { app.preview_arquivo = crate::infrastructure::csv_reader::preview_csv( caminho, app.layout_csv_atual.delimitador as u8, &app.layout_csv_atual.encoding.clone(), 5, ).ok(); } } }); // 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) { 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) { 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) { 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) { 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)); } } }