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
@@ -0,0 +1,211 @@
use crate::domain::entities::layout::{Layout, LayoutCsv, LayoutXlsx};
use rusqlite::{params, Connection, Result};
/// Salva um layout no banco. Retorna o id gerado.
pub fn salvar(conn: &Connection, layout: &Layout) -> Result<i64> {
match layout {
Layout::Csv { nome, config, .. } => {
conn.execute(
"INSERT INTO layouts
(nome, tipo, delimitador, encoding, linha_cabecalho,
indice_numero, indice_serie, indice_valor, indice_data)
VALUES (?1, 'csv', ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
params![
nome,
config.delimitador.to_string(),
config.encoding,
config.linha_cabecalho as i64,
config.indice_numero as i64,
config.indice_serie as i64,
config.indice_valor.map(|v| v as i64),
config.indice_data.map(|v| v as i64),
],
)?;
Ok(conn.last_insert_rowid())
}
Layout::Xlsx { nome, config, .. } => {
conn.execute(
"INSERT INTO layouts
(nome, tipo, aba, pos_numero, pos_serie, pos_valor, pos_data)
VALUES (?1, 'xlsx', ?2, ?3, ?4, ?5, ?6)",
params![
nome,
config.aba,
config.pos_numero,
config.pos_serie,
config.pos_valor,
config.pos_data,
],
)?;
Ok(conn.last_insert_rowid())
}
}
}
/// Atualiza um layout existente no banco.
pub fn atualizar(conn: &Connection, layout: &Layout) -> Result<()> {
let id = layout
.id()
.ok_or_else(|| rusqlite::Error::InvalidParameterName("id ausente".to_string()))?;
match layout {
Layout::Csv { nome, config, .. } => {
conn.execute(
"UPDATE layouts SET
nome = ?1, delimitador = ?2, encoding = ?3,
linha_cabecalho = ?4, indice_numero = ?5, indice_serie = ?6,
indice_valor = ?7, indice_data = ?8
WHERE id = ?9",
params![
nome,
config.delimitador.to_string(),
config.encoding,
config.linha_cabecalho as i64,
config.indice_numero as i64,
config.indice_serie as i64,
config.indice_valor.map(|v| v as i64),
config.indice_data.map(|v| v as i64),
id,
],
)?;
}
Layout::Xlsx { nome, config, .. } => {
conn.execute(
"UPDATE layouts SET
nome = ?1, aba = ?2, pos_numero = ?3, pos_serie = ?4,
pos_valor = ?5, pos_data = ?6
WHERE id = ?7",
params![
nome,
config.aba,
config.pos_numero,
config.pos_serie,
config.pos_valor,
config.pos_data,
id,
],
)?;
}
}
Ok(())
}
/// Lista todos os layouts salvos.
pub fn listar(conn: &Connection) -> Result<Vec<Layout>> {
let mut stmt = conn.prepare(
"SELECT id, nome, tipo,
delimitador, encoding, linha_cabecalho,
indice_numero, indice_serie, indice_valor, indice_data,
aba, pos_numero, pos_serie, pos_valor, pos_data
FROM layouts ORDER BY nome ASC",
)?;
let layouts: Result<Vec<Layout>> = stmt
.query_map([], |row| {
let id: i64 = row.get(0)?;
let nome: String = row.get(1)?;
let tipo: String = row.get(2)?;
if tipo == "csv" {
let delim_str: String = row.get(3)?;
let delimitador = delim_str.chars().next().unwrap_or(';');
Ok(Layout::Csv {
id: Some(id),
nome,
config: LayoutCsv {
delimitador,
encoding: row.get(4)?,
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),
},
})
} else {
Ok(Layout::Xlsx {
id: Some(id),
nome,
config: LayoutXlsx {
aba: row.get(10)?,
pos_numero: row.get(11)?,
pos_serie: row.get(12)?,
pos_valor: row.get(13)?,
pos_data: row.get(14)?,
},
})
}
})?
.collect();
layouts
}
/// Busca um layout pelo id.
pub fn buscar_por_id(conn: &Connection, id: i64) -> Result<Option<Layout>> {
let mut stmt = conn.prepare(
"SELECT id, nome, tipo,
delimitador, encoding, linha_cabecalho,
indice_numero, indice_serie, indice_valor, indice_data,
aba, pos_numero, pos_serie, pos_valor, pos_data
FROM layouts WHERE id = ?1",
)?;
let mut results = stmt.query_map([id], |row| {
let id: i64 = row.get(0)?;
let nome: String = row.get(1)?;
let tipo: String = row.get(2)?;
if tipo == "csv" {
let delim_str: String = row.get(3)?;
let delimitador = delim_str.chars().next().unwrap_or(';');
Ok(Layout::Csv {
id: Some(id),
nome,
config: LayoutCsv {
delimitador,
encoding: row.get(4)?,
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),
},
})
} else {
Ok(Layout::Xlsx {
id: Some(id),
nome,
config: LayoutXlsx {
aba: row.get(10)?,
pos_numero: row.get(11)?,
pos_serie: row.get(12)?,
pos_valor: row.get(13)?,
pos_data: row.get(14)?,
},
})
}
})?;
results.next().transpose()
}
/// Remove um layout pelo id.
pub fn excluir(conn: &Connection, id: i64) -> Result<()> {
conn.execute("DELETE FROM layouts WHERE id = ?1", [id])?;
Ok(())
}
/// Verifica se já existe um layout com o nome fornecido.
pub fn existe_nome(conn: &Connection, nome: &str) -> Result<bool> {
let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM layouts WHERE nome = ?1",
[nome],
|row| row.get(0),
)?;
Ok(count > 0)
}