Refactor UI components for layout management and results display
- Replaced the `renderizar` function in `layouts.rs` with a new `view` function using Iced for a more modern UI approach. - Introduced a new `view_secao_layouts` function to handle the display of saved layouts. - Updated the `resultado.rs` file to use Iced for rendering the results screen, including buttons for actions and pagination controls. - Created a new `selecionar_aba.rs` file for the selection of XLSX sheet tabs, implementing a preview feature. - Removed old rendering functions and replaced them with Iced components for better performance and maintainability. - Added design tokens in `design_tokens.json` for consistent styling across the application. - Created a mockup HTML file to visualize the UI design.
This commit is contained in:
+51
-249
@@ -1,264 +1,66 @@
|
||||
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};
|
||||
use crate::domain::entities::layout::{Layout, TipoArquivo};
|
||||
use crate::ui::app::App;
|
||||
use crate::ui::message::Message;
|
||||
use iced::widget::{button, column, container, horizontal_space, row, scrollable, text};
|
||||
use iced::{Alignment, Element, Length};
|
||||
|
||||
/// 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);
|
||||
/// Tela de gerenciamento de layouts.
|
||||
pub fn view(app: &App) -> Element<'_, Message> {
|
||||
let titulo = text("Gerenciar Layouts").size(22);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("< Voltar").clicked() {
|
||||
app.estado = EstadoApp::Importando;
|
||||
}
|
||||
});
|
||||
let botao_voltar = row![button("< Voltar").on_press(Message::IrParaImportacao)].spacing(8);
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
let secao_csv = view_secao_layouts("Layouts CSV", &app.layouts_salvos, TipoArquivo::Csv);
|
||||
let secao_xlsx = view_secao_layouts("Layouts XLSX", &app.layouts_salvos, TipoArquivo::Xlsx);
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Importar de JSON
|
||||
let importar_row = row![
|
||||
text("Importar layout de arquivo JSON:").size(14),
|
||||
button("📥 Importar JSON").on_press(Message::ImportarLayoutJson),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(8.0);
|
||||
let content = column![titulo, botao_voltar, secao_csv, secao_xlsx, importar_row,]
|
||||
.spacing(16)
|
||||
.padding(16)
|
||||
.width(Length::Fill);
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
container(scrollable(content))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
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);
|
||||
fn view_secao_layouts<'a>(
|
||||
titulo: &'a str,
|
||||
layouts: &'a [Layout],
|
||||
tipo: TipoArquivo,
|
||||
) -> Element<'a, Message> {
|
||||
let mut col = column![text(titulo).size(16)].spacing(4);
|
||||
|
||||
if layouts.is_empty() {
|
||||
ui.label("(nenhum layout salvo)");
|
||||
return;
|
||||
let filtrados: Vec<&Layout> = layouts.iter().filter(|l| l.tipo() == tipo).collect();
|
||||
|
||||
if filtrados.is_empty() {
|
||||
col = col.push(text("(nenhum layout salvo)").size(13));
|
||||
return col.into();
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
for layout in filtrados {
|
||||
if let Some(id) = layout.id() {
|
||||
let linha = row![
|
||||
text(layout.nome()).size(14).width(Length::Fill),
|
||||
horizontal_space(),
|
||||
button("📂 Carregar").on_press(Message::LayoutSelecionado(id)),
|
||||
button("📤 Exportar JSON").on_press(Message::ExportarLayoutJson(id)),
|
||||
button("🗑 Excluir").on_press(Message::ExcluirLayout(id)),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
// 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;
|
||||
col = col.push(linha);
|
||||
}
|
||||
}
|
||||
|
||||
col.into()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user