feat: adiciona novas dependências, implementa reimportação de arquivos e melhorias na interface
This commit is contained in:
Generated
+20
@@ -857,6 +857,7 @@ dependencies = [
|
||||
"egui",
|
||||
"encoding_rs",
|
||||
"genpdf",
|
||||
"image",
|
||||
"regex",
|
||||
"rfd",
|
||||
"rusqlite",
|
||||
@@ -865,6 +866,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"winres",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3922,6 +3924,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
@@ -5007,6 +5018,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
|
||||
@@ -20,3 +20,7 @@ dirs = "5"
|
||||
thiserror = "2"
|
||||
regex = "1"
|
||||
rfd = "0.15"
|
||||
image = { version = "0.25", default-features = false, features = ["ico"] }
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Verificar se já existe no codigo, pois na tela de configuração do Layout ele
|
||||
|
||||
---
|
||||
|
||||
### F-05 — Recarregar Arquivo Sem Reconfigurar (Sim bem necessario)
|
||||
### F-05 — Recarregar Arquivo Sem Reconfigurar (Implementado)
|
||||
|
||||
**Problema:** Quando o usuário corrige o arquivo fonte e quer re-verificar, precisa navegar todo o fluxo novamente (selecionar arquivo → configurar colunas → analisar).
|
||||
|
||||
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
# Sugestões de Melhoria — Fluxo da Interface
|
||||
|
||||
**Data:** 02/03/2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Indicador de progresso das etapas
|
||||
|
||||
O fluxo possui 3–4 passos bem definidos (`Importando → ConfigurandoColunas → ExibindoResultado`), mas não há nenhum indicador visual de onde o usuário está.
|
||||
|
||||
**Sugestão:** Adicionar um breadcrumb simples no topo de todas as telas:
|
||||
|
||||
```
|
||||
① Arquivo ② Colunas ③ Resultado
|
||||
```
|
||||
|
||||
O passo atual ficaria destacado. Isso orienta o usuário sobre o que falta sem exigir nenhuma regra de negócio adicional.
|
||||
|
||||
---
|
||||
|
||||
## 2. Unificar importação e configuração de colunas em um único painel
|
||||
|
||||
Atualmente o caminho é:
|
||||
1. Selecionar arquivo → clicar **"▶ Configurar Colunas"**
|
||||
2. Configurar colunas → clicar **"▶ Importar e Analisar"**
|
||||
|
||||
São 3 ações separadas para chegar à análise.
|
||||
|
||||
**Sugestão:** Mover as configurações CSV/XLSX para a mesma tela de importação como uma seção expansível ("Configurações avançadas"), deixando o botão principal como **"▶ Importar e Analisar"** direto.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pré-visualização das primeiras linhas do arquivo
|
||||
|
||||
Após selecionar o arquivo, o usuário precisa alternar entre a aplicação e a planilha para descobrir quais índices correspondem a cada campo.
|
||||
|
||||
**Sugestão:** Exibir as primeiras 3–5 linhas do arquivo em uma tabela simples logo após a seleção, para que o usuário identifique visualmente o índice de cada coluna sem sair do app.
|
||||
|
||||
---
|
||||
|
||||
## 4. Confirmação ao clicar em "Nova Análise"
|
||||
|
||||
O botão **"< Nova Análise"** em `resultado.rs` executa `app.notas_importadas.clear()` imediatamente, sem nenhum modal de confirmação. Um clique acidental descarta o resultado atual sem aviso.
|
||||
|
||||
**Sugestão:** Exibir modal de confirmação com a mensagem:
|
||||
> "Deseja iniciar uma nova análise? O resultado atual será descartado."
|
||||
|
||||
Botões: **Confirmar** | **Cancelar**
|
||||
|
||||
---
|
||||
|
||||
## 5. Reorganizar seções do resultado
|
||||
|
||||
A ordem atual das seções em `resultado.rs` é:
|
||||
|
||||
```
|
||||
Totais → Faltantes → Duplicatas
|
||||
```
|
||||
|
||||
O objetivo principal do software é detectar faltantes e duplicatas; os totais são informação complementar.
|
||||
|
||||
**Sugestão:** Inverter para:
|
||||
|
||||
```
|
||||
Faltantes → Duplicatas → Totais
|
||||
```
|
||||
|
||||
Isso coloca a informação mais relevante no topo da tela.
|
||||
|
||||
---
|
||||
|
||||
## 6. Seletor de layout também na tela de configuração de colunas
|
||||
|
||||
O dropdown de layouts está disponível apenas em `import.rs`. O usuário frequentemente percebe que precisa de um layout diferente **depois** de visitar a tela de configuração e ver os campos.
|
||||
|
||||
**Sugestão:** Duplicar o seletor de layout no topo de `configuracao_colunas.rs`, evitando que o usuário volte à tela anterior só para trocar o layout.
|
||||
|
||||
---
|
||||
|
||||
## 7. Botão "Salvar como layout..." na tela de configuração
|
||||
|
||||
Para salvar um layout atualmente o usuário precisa navegar para `GerenciandoLayouts`. Esse desvio quebra o fluxo principal.
|
||||
|
||||
**Sugestão:** Adicionar um botão **"💾 Salvar como layout..."** diretamente em `configuracao_colunas.rs` que abre um modal simples pedindo apenas o nome do layout. Internamente, chama o mesmo use case `salvar_layout`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Feedback visual durante a análise
|
||||
|
||||
A transição `ConfigurandoColunas → ExibindoResultado` pode demorar com arquivos grandes (até 100k registros, conforme RNF03). Atualmente o app não exibe nenhum sinal enquanto processa, parecendo travado.
|
||||
|
||||
**Sugestão:** Adicionar o estado `Analisando` no `EstadoApp` (já previsto no IMPLEMENTACAO.md mas não implementado) e exibir uma mensagem simples tipo:
|
||||
|
||||
```
|
||||
⏳ Analisando... aguarde.
|
||||
```
|
||||
|
||||
Mesmo sem progresso percentual, já elimina a percepção de travamento.
|
||||
|
||||
---
|
||||
|
||||
## Resumo de Impacto
|
||||
|
||||
| # | Sugestão | Impacto UX | Esforço estimado |
|
||||
|---|----------|------------|-----------------|
|
||||
| 1 | Breadcrumb de etapas | Médio | Baixo |
|
||||
| 2 | Unificar importação + configuração | Alto | Médio |
|
||||
| 3 | Pré-visualização do arquivo | Alto | Médio |
|
||||
| 4 | Confirmação em "Nova Análise" | Baixo | Baixo |
|
||||
| 5 | Reordenar seções do resultado | Médio | Baixo |
|
||||
| 6 | Seletor de layout em configuração | Médio | Baixo |
|
||||
| 7 | Salvar layout na tela de configuração | Médio | Baixo |
|
||||
| 8 | Feedback durante análise | Alto | Baixo |
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("icon.ico");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use crate::domain::{
|
||||
},
|
||||
services::{
|
||||
detector_duplicidade::duplicidades_por_serie,
|
||||
detector_sequencia::{LIMITE_FALTANTES, calcular_intervalo, detectar_faltantes},
|
||||
detector_sequencia::{calcular_intervalo, detectar_faltantes, LIMITE_FALTANTES},
|
||||
},
|
||||
};
|
||||
use rust_decimal::Decimal;
|
||||
@@ -107,7 +107,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn nota_com_tipo(numero: u64, serie: &str, tipo: &str) -> Nota {
|
||||
Nota::new(numero, serie.to_string(), Some(tipo.to_string()), None, None)
|
||||
Nota::new(
|
||||
numero,
|
||||
serie.to_string(),
|
||||
Some(tipo.to_string()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod chave_serie;
|
||||
pub mod layout;
|
||||
pub mod nota;
|
||||
pub mod resultado_analise;
|
||||
pub mod serie;
|
||||
pub mod chave_serie;
|
||||
|
||||
@@ -50,7 +50,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn nota_com_tipo(numero: u64, serie: &str, tipo: &str) -> Nota {
|
||||
Nota::new(numero, serie.to_string(), Some(tipo.to_string()), None, None)
|
||||
Nota::new(
|
||||
numero,
|
||||
serie.to_string(),
|
||||
Some(tipo.to_string()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -84,16 +90,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mesmo_numero_serie_tipos_diferentes_nao_e_duplicata() {
|
||||
let notas = vec![nota_com_tipo(1, "001", "NFE"), nota_com_tipo(1, "001", "NFCE")];
|
||||
let notas = vec![
|
||||
nota_com_tipo(1, "001", "NFE"),
|
||||
nota_com_tipo(1, "001", "NFCE"),
|
||||
];
|
||||
let dup = detectar_duplicidades(¬as);
|
||||
assert!(dup.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mesmo_numero_serie_tipo_igual_e_duplicata() {
|
||||
let notas = vec![nota_com_tipo(1, "001", "NFE"), nota_com_tipo(1, "001", "NFE")];
|
||||
let notas = vec![
|
||||
nota_com_tipo(1, "001", "NFE"),
|
||||
nota_com_tipo(1, "001", "NFE"),
|
||||
];
|
||||
let dup = detectar_duplicidades(¬as);
|
||||
assert_eq!(dup.get(&(1, "001".to_string(), Some("NFE".to_string()))), Some(&2));
|
||||
assert_eq!(
|
||||
dup.get(&(1, "001".to_string(), Some("NFE".to_string()))),
|
||||
Some(&2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -26,15 +26,14 @@ pub fn ler_csv(
|
||||
linha_cabecalho: usize,
|
||||
) -> Result<ResultadoCsv, ErroArquivo> {
|
||||
// Verificar tamanho
|
||||
let metadata = std::fs::metadata(caminho)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
let metadata =
|
||||
std::fs::metadata(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
if metadata.len() > LIMITE_BYTES {
|
||||
return Err(ErroArquivo::TamanhoExcedido(metadata.len()));
|
||||
}
|
||||
|
||||
// Ler conteúdo bruto
|
||||
let bytes = std::fs::read(caminho)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
let bytes = std::fs::read(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
|
||||
// Decodificar encoding
|
||||
let conteudo = match encoding.to_lowercase().as_str() {
|
||||
@@ -42,10 +41,8 @@ pub fn ler_csv(
|
||||
let (decoded, _, _) = WINDOWS_1252.decode(&bytes);
|
||||
decoded.into_owned()
|
||||
}
|
||||
_ => {
|
||||
String::from_utf8(bytes)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))?
|
||||
}
|
||||
_ => String::from_utf8(bytes)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(format!("Encoding inválido: {}", e)))?,
|
||||
};
|
||||
|
||||
let mut avisos = ResumoAvisos::default();
|
||||
@@ -97,8 +94,7 @@ pub fn preview_csv(
|
||||
encoding: &str,
|
||||
n: usize,
|
||||
) -> Result<Vec<Vec<String>>, ErroArquivo> {
|
||||
let bytes = std::fs::read(caminho)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
let bytes = std::fs::read(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
|
||||
let conteudo = match encoding.to_lowercase().as_str() {
|
||||
"windows-1252" | "latin-1" | "iso-8859-1" => {
|
||||
|
||||
@@ -11,10 +11,8 @@ use std::path::Path;
|
||||
// Fontes embutidas no binário em tempo de compilação.
|
||||
// Liberation Sans (~402 KB cada) substitui Arial do sistema (~993 KB cada),
|
||||
// eliminando dependência de fonte externa e reduzindo o tamanho dos PDFs gerados.
|
||||
const FONT_REGULAR: &[u8] =
|
||||
include_bytes!("../../assets/fonts/LiberationSans-Regular.ttf");
|
||||
const FONT_BOLD: &[u8] =
|
||||
include_bytes!("../../assets/fonts/LiberationSans-Bold.ttf");
|
||||
const FONT_REGULAR: &[u8] = include_bytes!("../../assets/fonts/LiberationSans-Regular.ttf");
|
||||
const FONT_BOLD: &[u8] = include_bytes!("../../assets/fonts/LiberationSans-Bold.ttf");
|
||||
|
||||
/// Metadados do relatório.
|
||||
pub struct MetadadosRelatorio {
|
||||
@@ -56,12 +54,10 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
doc.set_page_decorator(decorator);
|
||||
|
||||
// Título
|
||||
doc.push(
|
||||
Paragraph::new("").styled_string(
|
||||
"Relatório de Análise de Notas Fiscais",
|
||||
style::Style::new().bold().with_font_size(16),
|
||||
),
|
||||
);
|
||||
doc.push(Paragraph::new("").styled_string(
|
||||
"Relatório de Análise de Notas Fiscais",
|
||||
style::Style::new().bold().with_font_size(16),
|
||||
));
|
||||
doc.push(Break::new(1));
|
||||
|
||||
// Metadados
|
||||
@@ -81,7 +77,8 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
|
||||
// Totais
|
||||
doc.push(
|
||||
Paragraph::new("").styled_string("Totais", style::Style::new().bold().with_font_size(14)),
|
||||
Paragraph::new("")
|
||||
.styled_string("Totais", style::Style::new().bold().with_font_size(14)),
|
||||
);
|
||||
doc.push(Paragraph::new(format!(
|
||||
"Total Geral: R$ {}",
|
||||
@@ -102,9 +99,10 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
doc.push(Break::new(1));
|
||||
|
||||
// Notas Faltantes
|
||||
doc.push(
|
||||
Paragraph::new("").styled_string("Notas Faltantes por Série", style::Style::new().bold().with_font_size(14)),
|
||||
);
|
||||
doc.push(Paragraph::new("").styled_string(
|
||||
"Notas Faltantes por Série",
|
||||
style::Style::new().bold().with_font_size(14),
|
||||
));
|
||||
|
||||
// Use faltantes keys for this section (may differ from soma keys if no values)
|
||||
let mut chaves_faltantes: Vec<&ChaveSerie> = resultado.faltantes_por_serie.keys().collect();
|
||||
@@ -114,7 +112,10 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
let faltantes = match resultado.faltantes_por_serie.get(*chave) {
|
||||
Some(f) if !f.is_empty() => f,
|
||||
_ => {
|
||||
doc.push(Paragraph::new(format!(" Série {}: nenhuma faltante", chave.label())));
|
||||
doc.push(Paragraph::new(format!(
|
||||
" Série {}: nenhuma faltante",
|
||||
chave.label()
|
||||
)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -131,9 +132,10 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
doc.push(Break::new(1));
|
||||
|
||||
// Duplicatas
|
||||
doc.push(
|
||||
Paragraph::new("").styled_string("Duplicatas por Série", style::Style::new().bold().with_font_size(14)),
|
||||
);
|
||||
doc.push(Paragraph::new("").styled_string(
|
||||
"Duplicatas por Série",
|
||||
style::Style::new().bold().with_font_size(14),
|
||||
));
|
||||
|
||||
let mut chaves_dup: Vec<&ChaveSerie> = resultado.duplicadas_por_serie.keys().collect();
|
||||
chaves_dup.sort();
|
||||
@@ -142,7 +144,10 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
let duplicatas = match resultado.duplicadas_por_serie.get(*chave) {
|
||||
Some(d) if !d.is_empty() => d,
|
||||
_ => {
|
||||
doc.push(Paragraph::new(format!(" Série {}: nenhuma duplicata", chave.label())));
|
||||
doc.push(Paragraph::new(format!(
|
||||
" Série {}: nenhuma duplicata",
|
||||
chave.label()
|
||||
)));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -155,7 +160,9 @@ impl PdfGenerator for GenpdfGenerator {
|
||||
for (numero, count) in duplicatas {
|
||||
doc.push(Paragraph::new(format!(
|
||||
" NF {} / Série {} — {} ocorrências",
|
||||
numero, chave.label(), count
|
||||
numero,
|
||||
chave.label(),
|
||||
count
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,15 +125,9 @@ pub fn listar(conn: &Connection) -> Result<Vec<Layout>> {
|
||||
linha_cabecalho: row.get::<_, i64>(5)? as usize,
|
||||
indice_numero: row.get::<_, i64>(6)? as usize,
|
||||
indice_serie: row.get::<_, i64>(7)? as usize,
|
||||
indice_valor: row
|
||||
.get::<_, Option<i64>>(8)?
|
||||
.map(|v| v as usize),
|
||||
indice_data: row
|
||||
.get::<_, Option<i64>>(9)?
|
||||
.map(|v| v as usize),
|
||||
indice_documento_tipo: row
|
||||
.get::<_, Option<i64>>(15)?
|
||||
.map(|v| v as usize),
|
||||
indice_valor: row.get::<_, Option<i64>>(8)?.map(|v| v as usize),
|
||||
indice_data: row.get::<_, Option<i64>>(9)?.map(|v| v as usize),
|
||||
indice_documento_tipo: row.get::<_, Option<i64>>(15)?.map(|v| v as usize),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -14,11 +14,9 @@ pub fn aplicar_migrations(conn: &Connection) -> Result<()> {
|
||||
)?;
|
||||
|
||||
let versao_atual: i64 = conn
|
||||
.query_row(
|
||||
"SELECT versao FROM schema_version LIMIT 1;",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.query_row("SELECT versao FROM schema_version LIMIT 1;", [], |row| {
|
||||
row.get(0)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
if versao_atual < 1 {
|
||||
@@ -32,9 +30,15 @@ pub fn aplicar_migrations(conn: &Connection) -> Result<()> {
|
||||
}
|
||||
|
||||
if versao_atual == 0 {
|
||||
conn.execute("INSERT INTO schema_version (versao) VALUES (?1);", [VERSAO_SCHEMA_ATUAL])?;
|
||||
conn.execute(
|
||||
"INSERT INTO schema_version (versao) VALUES (?1);",
|
||||
[VERSAO_SCHEMA_ATUAL],
|
||||
)?;
|
||||
} else if versao_atual < VERSAO_SCHEMA_ATUAL {
|
||||
conn.execute("UPDATE schema_version SET versao = ?1;", [VERSAO_SCHEMA_ATUAL])?;
|
||||
conn.execute(
|
||||
"UPDATE schema_version SET versao = ?1;",
|
||||
[VERSAO_SCHEMA_ATUAL],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -24,8 +24,8 @@ pub struct ResultadoXlsx {
|
||||
pub fn listar_abas(caminho: &Path) -> Result<Vec<String>, ErroArquivo> {
|
||||
verificar_tamanho(caminho)?;
|
||||
|
||||
let workbook = open_workbook_auto(caminho)
|
||||
.map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
let workbook =
|
||||
open_workbook_auto(caminho).map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
|
||||
Ok(workbook.sheet_names().to_vec())
|
||||
}
|
||||
@@ -45,8 +45,8 @@ pub fn ler_xlsx(
|
||||
) -> Result<ResultadoXlsx, ErroArquivo> {
|
||||
verificar_tamanho(caminho)?;
|
||||
|
||||
let mut workbook = open_workbook_auto(caminho)
|
||||
.map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
let mut workbook =
|
||||
open_workbook_auto(caminho).map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
|
||||
let range: calamine::Range<calamine::Data> = workbook
|
||||
.worksheet_range(nome_aba)
|
||||
@@ -99,14 +99,11 @@ pub fn ler_xlsx(
|
||||
|
||||
/// Retorna as primeiras 5 linhas de uma aba XLSX, a partir da linha 1.
|
||||
/// Usado exclusivamente para pré-visualização na UI.
|
||||
pub fn preview_xlsx(
|
||||
caminho: &Path,
|
||||
nome_aba: &str,
|
||||
) -> Result<Vec<Vec<String>>, ErroArquivo> {
|
||||
pub fn preview_xlsx(caminho: &Path, nome_aba: &str) -> Result<Vec<Vec<String>>, ErroArquivo> {
|
||||
verificar_tamanho(caminho)?;
|
||||
|
||||
let mut workbook = open_workbook_auto(caminho)
|
||||
.map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
let mut workbook =
|
||||
open_workbook_auto(caminho).map_err(|e| ErroArquivo::Corrompido(e.to_string()))?;
|
||||
|
||||
let range: calamine::Range<calamine::Data> = workbook
|
||||
.worksheet_range(nome_aba)
|
||||
@@ -182,8 +179,8 @@ pub fn parsear_letra_linha(s: &str) -> Option<Coordenada> {
|
||||
}
|
||||
|
||||
fn verificar_tamanho(caminho: &Path) -> Result<(), ErroArquivo> {
|
||||
let metadata = std::fs::metadata(caminho)
|
||||
.map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
let metadata =
|
||||
std::fs::metadata(caminho).map_err(|e| ErroArquivo::ErroLeitura(e.to_string()))?;
|
||||
if metadata.len() > LIMITE_BYTES {
|
||||
return Err(ErroArquivo::TamanhoExcedido(metadata.len()));
|
||||
}
|
||||
|
||||
+24
-5
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod application;
|
||||
mod domain;
|
||||
mod infrastructure;
|
||||
@@ -5,15 +7,32 @@ mod ui;
|
||||
|
||||
use ui::app::App;
|
||||
|
||||
fn load_icon() -> Option<egui::viewport::IconData> {
|
||||
let bytes = include_bytes!("../icon.ico");
|
||||
let img = image::load_from_memory(bytes).ok()?.into_rgba8();
|
||||
let (width, height) = img.dimensions();
|
||||
Some(egui::viewport::IconData {
|
||||
rgba: img.into_raw(),
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let mut viewport = egui::ViewportBuilder::default()
|
||||
.with_title("Comparador de Notas")
|
||||
.with_inner_size([1024.0, 768.0])
|
||||
.with_min_inner_size([800.0, 600.0]);
|
||||
|
||||
if let Some(icon) = load_icon() {
|
||||
viewport = viewport.with_icon(std::sync::Arc::new(icon));
|
||||
}
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_title("Comparador de Notas")
|
||||
.with_inner_size([1024.0, 768.0])
|
||||
.with_min_inner_size([800.0, 600.0]),
|
||||
viewport,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
eframe::run_native(
|
||||
"Comparador de Notas",
|
||||
native_options,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::application::usecases::importar_arquivo::{importar_csv, importar_xlsx};
|
||||
use crate::application::usecases::executar_analise::{
|
||||
expandir_analise, pre_analisar, series_com_intervalo_excessivo,
|
||||
};
|
||||
use crate::application::usecases::importar_arquivo::{importar_csv, importar_xlsx};
|
||||
use crate::domain::entities::layout::{Layout, TipoArquivo};
|
||||
use crate::ui::app::{App, EstadoApp, ResultadoPendente};
|
||||
use egui::{Context, Ui};
|
||||
@@ -169,7 +169,8 @@ fn renderizar_csv(ui: &mut Ui, app: &mut App) {
|
||||
app.layout_csv_atual.delimitador as u8,
|
||||
&app.layout_csv_atual.encoding.clone(),
|
||||
5,
|
||||
).ok();
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,10 +239,7 @@ fn importar_json(app: &mut App, conteudo: &str) {
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
app.exibir_erro(format!(
|
||||
"Conflito de nome: layout '{}' já existe.",
|
||||
nome
|
||||
));
|
||||
app.exibir_erro(format!("Conflito de nome: layout '{}' já existe.", nome));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,9 @@ pub fn renderizar_tabela_preview(ui: &mut egui::Ui, linhas: &[Vec<String>]) {
|
||||
}
|
||||
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"Pré-visualização ({} linha(s))",
|
||||
linhas.len()
|
||||
))
|
||||
.small()
|
||||
.weak(),
|
||||
egui::RichText::new(format!("Pré-visualização ({} linha(s))", linhas.len()))
|
||||
.small()
|
||||
.weak(),
|
||||
);
|
||||
ui.add_space(2.0);
|
||||
|
||||
@@ -69,4 +66,4 @@ pub fn renderizar_tabela_preview(ui: &mut egui::Ui, linhas: &[Vec<String>]) {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user