Files
comparador-notas/src/ui/screens/layouts.rs
T
2026-03-03 13:51:26 -03:00

268 lines
9.2 KiB
Rust

use crate::application::usecases::layouts::{
exportar_layout_json, importar_layout_json, salvar_layout,
};
use crate::domain::entities::layout::{Layout, LayoutJson, TipoArquivo};
use crate::domain::errors::ErroLayout;
use crate::ui::app::{AcaoModal, App, EstadoApp};
use egui::{Context, Ui};
/// Renderiza a tela de gerenciamento de layouts.
pub fn renderizar(ui: &mut Ui, ctx: &Context, app: &mut App) {
ui.heading("Gerenciar Layouts");
ui.add_space(8.0);
ui.horizontal(|ui| {
if ui.button("< Voltar").clicked() {
app.estado = EstadoApp::Importando;
}
});
ui.add_space(12.0);
ui.separator();
// Salvar layout atual
ui.add_space(8.0);
ui.group(|ui| {
ui.label("Salvar Layout Atual");
ui.horizontal(|ui| {
ui.label("Nome:");
ui.text_edit_singleline(&mut app.nome_layout_atual);
if ui.button("💾 Salvar").clicked() {
salvar_layout_atual(app);
}
});
});
ui.add_space(12.0);
ui.separator();
ui.add_space(8.0);
// Layouts CSV
let layouts_csv: Vec<_> = app
.layouts_salvos
.iter()
.filter(|l| l.tipo() == TipoArquivo::Csv)
.cloned()
.collect();
let layouts_xlsx: Vec<_> = app
.layouts_salvos
.iter()
.filter(|l| l.tipo() == TipoArquivo::Xlsx)
.cloned()
.collect();
egui::ScrollArea::vertical().show(ui, |ui| {
renderizar_secao_layouts(ui, ctx, app, "Layouts CSV", &layouts_csv);
ui.add_space(12.0);
renderizar_secao_layouts(ui, ctx, app, "Layouts XLSX", &layouts_xlsx);
ui.add_space(16.0);
ui.separator();
ui.add_space(8.0);
// Importar de JSON
ui.horizontal(|ui| {
ui.label("Importar layout de arquivo JSON:");
if ui.button("📥 Importar JSON").clicked() {
if let Some(caminho) = rfd::FileDialog::new()
.add_filter("JSON", &["json"])
.pick_file()
{
match std::fs::read_to_string(&caminho) {
Ok(conteudo) => importar_json(app, &conteudo),
Err(e) => {
app.exibir_erro(format!("Erro ao ler arquivo JSON: {}", e));
}
}
}
}
});
});
}
fn renderizar_secao_layouts(
ui: &mut Ui,
_ctx: &Context,
app: &mut App,
titulo: &str,
layouts: &[Layout],
) {
ui.label(egui::RichText::new(titulo).strong());
ui.add_space(4.0);
if layouts.is_empty() {
ui.label("(nenhum layout salvo)");
return;
}
for layout in layouts {
ui.horizontal(|ui| {
ui.label(layout.nome());
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Excluir
if let Some(id) = layout.id() {
if ui.button("🗑 Excluir").clicked() {
app.exibir_confirmacao(
"Confirmar exclusão",
format!("Deseja excluir o layout '{}'?", layout.nome()),
AcaoModal::ConfirmarExclusaoLayout(id),
);
}
// Exportar
if ui.button("📤 Exportar JSON").clicked() {
match exportar_layout_json(layout) {
Ok((conteudo, nome_sugerido)) => {
if let Some(caminho) = rfd::FileDialog::new()
.set_file_name(&nome_sugerido)
.add_filter("JSON", &["json"])
.save_file()
{
if let Err(e) = std::fs::write(&caminho, &conteudo) {
app.exibir_erro(format!("Erro ao salvar JSON: {}", e));
}
}
}
Err(e) => {
app.exibir_erro(format!("Erro ao exportar layout: {}", e));
}
}
}
// Carregar
if ui.button("📂 Carregar").clicked() {
aplicar_layout(app, layout);
app.nome_layout_atual = layout.nome().to_string();
app.estado = EstadoApp::ConfigurandoColunas;
}
}
});
});
}
}
fn salvar_layout_atual(app: &mut App) {
if app.nome_layout_atual.trim().is_empty() {
app.exibir_aviso("Nome inválido", "Informe um nome para o layout.");
return;
}
let layout = match app.tipo_arquivo_atual.clone() {
TipoArquivo::Csv => Layout::Csv {
id: None,
nome: app.nome_layout_atual.trim().to_string(),
config: app.layout_csv_atual.clone(),
},
TipoArquivo::Xlsx => Layout::Xlsx {
id: None,
nome: app.nome_layout_atual.trim().to_string(),
config: app.layout_xlsx_atual.clone(),
},
};
if let Some(conn) = &app.conn {
match salvar_layout(conn, &layout) {
Ok(_) => {
app.recarregar_layouts();
app.exibir_aviso("Sucesso", "Layout salvo com sucesso.");
}
Err(ErroLayout::NomeConflitante(nome)) => {
let id_existente = app
.layouts_salvos
.iter()
.find(|l| l.nome() == nome)
.and_then(|l| l.id());
if let Some(id) = id_existente {
let layout_com_id = match app.tipo_arquivo_atual.clone() {
TipoArquivo::Csv => Layout::Csv {
id: Some(id),
nome: nome.clone(),
config: app.layout_csv_atual.clone(),
},
TipoArquivo::Xlsx => Layout::Xlsx {
id: Some(id),
nome: nome.clone(),
config: app.layout_xlsx_atual.clone(),
},
};
app.exibir_confirmacao(
"Conflito de nome",
format!(
"Já existe um layout com o nome '{}'. Deseja sobrescrever?",
nome
),
AcaoModal::SobrescreverLayout(layout_com_id),
);
}
}
Err(e) => {
app.exibir_erro(format!("Erro ao salvar layout: {}", e));
}
}
}
}
fn importar_json(app: &mut App, conteudo: &str) {
if let Some(conn) = &app.conn {
match importar_layout_json(conn, conteudo, false, None) {
Ok(_) => {
app.recarregar_layouts();
app.exibir_aviso("Sucesso", "Layout importado com sucesso.");
}
Err(ErroLayout::NomeConflitante(nome)) => {
// Recriar o layout parseado para passá-lo no modal de confirmação.
// O JSON já foi validado pela chamada acima, então o parse aqui não falha.
let parsed = serde_json::from_str::<LayoutJson>(conteudo)
.ok()
.and_then(|json_repr| Layout::try_from(json_repr).ok());
let id_existente = app
.layouts_salvos
.iter()
.find(|l| l.nome() == nome)
.and_then(|l| l.id());
match (parsed, id_existente) {
(Some(mut layout), Some(id)) => {
match &mut layout {
Layout::Csv { id: i, .. } => *i = Some(id),
Layout::Xlsx { id: i, .. } => *i = Some(id),
}
app.exibir_confirmacao(
"Conflito de nome",
format!(
"Já existe um layout com o nome '{}'. Deseja sobrescrever?",
nome
),
AcaoModal::SobrescreverLayout(layout),
);
}
_ => {
app.exibir_erro(format!(
"Conflito de nome: layout '{}' já existe.",
nome
));
}
}
}
Err(e) => {
app.exibir_erro(format!("Erro ao importar layout: {}", e));
}
}
}
}
fn aplicar_layout(app: &mut App, layout: &Layout) {
match layout {
Layout::Csv { config, .. } => {
app.layout_csv_atual = config.clone();
app.tipo_arquivo_atual = TipoArquivo::Csv;
}
Layout::Xlsx { config, .. } => {
app.layout_xlsx_atual = config.clone();
app.tipo_arquivo_atual = TipoArquivo::Xlsx;
}
}
}