- 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`.
296 lines
10 KiB
Rust
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));
|
|
}
|
|
}
|
|
}
|