feat: implement main application structure with UI and layout management

- 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`.
This commit is contained in:
2026-03-02 22:14:02 -03:00
parent 31cd94907a
commit e64988a139
36 changed files with 9166 additions and 2 deletions
+139
View File
@@ -0,0 +1,139 @@
use crate::domain::entities::nota::Nota;
use crate::domain::entities::resultado_analise::IntervaloSerie;
/// Limite de faltantes por série antes de solicitar confirmação do usuário (RF04).
pub const LIMITE_FALTANTES: u64 = 10_000;
/// Calcula o intervalo de faltantes de uma lista de notas de uma mesma série,
/// sem materializar a lista completa.
///
/// Retorna `None` se não há notas.
pub fn calcular_intervalo(notas: &[&Nota]) -> Option<IntervaloSerie> {
if notas.is_empty() {
return None;
}
let mut numeros: Vec<u64> = notas.iter().map(|n| n.numero).collect();
numeros.sort_unstable();
numeros.dedup(); // Ignorar duplicatas no cálculo de sequência
let minimo = *numeros.first().unwrap();
let maximo = *numeros.last().unwrap();
// Contar faltantes de forma incremental
let mut faltantes: u64 = 0;
for w in numeros.windows(2) {
let a = w[0];
let b = w[1];
if b > a + 1 {
faltantes += b - a - 1;
}
}
Some(IntervaloSerie {
minimo,
maximo,
contagem_faltantes: faltantes,
})
}
/// Materializa a lista completa de números faltantes para uma série.
/// Deve ser chamado apenas após confirmação do usuário quando o intervalo
/// excede `LIMITE_FALTANTES`.
///
/// A lista é retornada em ordem crescente.
pub fn detectar_faltantes(notas: &[&Nota]) -> Vec<u64> {
if notas.is_empty() {
return Vec::new();
}
let mut numeros: Vec<u64> = notas.iter().map(|n| n.numero).collect();
numeros.sort_unstable();
numeros.dedup();
if numeros.len() <= 1 {
return Vec::new();
}
let mut faltantes = Vec::new();
for w in numeros.windows(2) {
let a = w[0];
let b = w[1];
for faltante in (a + 1)..b {
faltantes.push(faltante);
}
}
faltantes
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::entities::nota::Nota;
use rust_decimal::Decimal;
fn nota(numero: u64) -> Nota {
Nota::new(numero, "001".to_string(), None, None)
}
#[test]
fn sem_faltantes() {
let notas = vec![nota(1), nota(2), nota(3)];
let refs: Vec<&Nota> = notas.iter().collect();
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
}
#[test]
fn com_faltante_simples() {
let notas = vec![nota(1), nota(2), nota(3), nota(5)];
let refs: Vec<&Nota> = notas.iter().collect();
assert_eq!(detectar_faltantes(&refs), vec![4u64]);
}
#[test]
fn com_multiplos_faltantes() {
let notas = vec![nota(1), nota(5)];
let refs: Vec<&Nota> = notas.iter().collect();
assert_eq!(detectar_faltantes(&refs), vec![2u64, 3, 4]);
}
#[test]
fn serie_com_um_registro() {
let notas = vec![nota(7)];
let refs: Vec<&Nota> = notas.iter().collect();
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
}
#[test]
fn vazio() {
let refs: Vec<&Nota> = vec![];
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
}
#[test]
fn intervalo_correto() {
let notas = vec![nota(1), nota(2), nota(5)];
let refs: Vec<&Nota> = notas.iter().collect();
let intervalo = calcular_intervalo(&refs).unwrap();
assert_eq!(intervalo.minimo, 1);
assert_eq!(intervalo.maximo, 5);
assert_eq!(intervalo.contagem_faltantes, 2);
}
#[test]
fn intervalo_excede_limite() {
let notas = vec![nota(1), nota(20_000)];
let refs: Vec<&Nota> = notas.iter().collect();
let intervalo = calcular_intervalo(&refs).unwrap();
assert!(intervalo.excede_limite(LIMITE_FALTANTES));
}
#[test]
fn duplicatas_ignoradas_no_calculo() {
// Duplicados não devem gerar faltantes falsos
let notas = vec![nota(1), nota(1), nota(2), nota(3)];
let refs: Vec<&Nota> = notas.iter().collect();
assert_eq!(detectar_faltantes(&refs), Vec::<u64>::new());
}
}