Corrected: Responsive design for low-resolution monitors.
This commit is contained in:
@@ -0,0 +1,286 @@
|
|||||||
|
# AGENTS.md — Guia de desenvolvimento da UI
|
||||||
|
|
||||||
|
Este documento descreve como a interface do **Comparador de Notas** é estruturada,
|
||||||
|
quais padrões devem ser seguidos e o que não fazer. Leia antes de criar ou modificar
|
||||||
|
qualquer arquivo em `src/ui/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Arquitetura da UI
|
||||||
|
|
||||||
|
O projeto usa **iced 0.13** com arquitetura Elm (Model / Update / View).
|
||||||
|
|
||||||
|
```
|
||||||
|
src/ui/
|
||||||
|
├── app.rs Estado global (App), update, view raiz, breadcrumb
|
||||||
|
├── message.rs Enum Message — todos os eventos da UI
|
||||||
|
├── mod.rs Re-exporta submódulos
|
||||||
|
├── theme.rs Paleta de cores, estilos de widgets (sem lógica)
|
||||||
|
├── components/
|
||||||
|
│ ├── modal.rs Overlay de modal bloqueante
|
||||||
|
│ ├── paginacao.rs Controles de paginação (◀ / ▶)
|
||||||
|
│ └── tabela_preview.rs Tabela de pré-visualização do arquivo importado
|
||||||
|
└── screens/
|
||||||
|
├── import.rs Tela 1 — seleção de arquivo e layout
|
||||||
|
├── selecionar_aba.rs Tela 1.5 — seleção de aba XLSX
|
||||||
|
├── configuracao_colunas.rs Tela 2 — mapeamento de colunas
|
||||||
|
├── resultado.rs Tela 3 — resultado da análise
|
||||||
|
└── layouts.rs Tela lateral — gerenciamento de layouts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsabilidades por camada
|
||||||
|
|
||||||
|
| Camada | Responsabilidade |
|
||||||
|
|--------|-----------------|
|
||||||
|
| `app.rs` | Estado global, `update()`, `view()` raiz, roteamento entre telas, tarefas assíncronas |
|
||||||
|
| `screens/` | Renderização de cada tela: coleta inputs, monta widgets, emite `Message` |
|
||||||
|
| `components/` | Widgets reutilizáveis sem estado próprio (recebem dados por parâmetro) |
|
||||||
|
| `theme.rs` | Apenas estilos visuais. Sem lógica de negócio. Sem `Message`. |
|
||||||
|
| `message.rs` | Todos os eventos possíveis da UI. Nenhuma lógica aqui. |
|
||||||
|
|
||||||
|
**Regra:** nenhuma lógica de domínio (parsing, validação de sequência, cálculos) pode
|
||||||
|
estar em `src/ui/`. A UI apenas chama use cases de `src/application/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Tema e paleta de cores
|
||||||
|
|
||||||
|
Todas as cores estão em `theme.rs` como constantes `Color`. **Nunca use valores RGB
|
||||||
|
literais fora de `theme.rs`.**
|
||||||
|
|
||||||
|
### Paleta
|
||||||
|
|
||||||
|
| Constante | Hex | Uso |
|
||||||
|
|-----------|-----|-----|
|
||||||
|
| `BG` | `#0F172A` | Fundo geral da janela |
|
||||||
|
| `SURFACE` | `#1E293B` | Cards primários |
|
||||||
|
| `SURFACE_2` | `#334155` | Cards secundários, cabeçalho de tabela |
|
||||||
|
| `BORDER` | `#334155` | Bordas de inputs e cards |
|
||||||
|
| `TEXT` | `#F1F5F9` | Texto principal |
|
||||||
|
| `TEXT_SECONDARY` | `#94A3B8` | Labels, placeholders |
|
||||||
|
| `TEXT_MUTED` | `#64748B` | Texto desabilitado |
|
||||||
|
| `PRIMARY` | `#3B82F6` | Botões primários, step ativo |
|
||||||
|
| `PRIMARY_HOVER` | `#2563EB` | Hover em botões primários |
|
||||||
|
| `SUCCESS` | `#22C55E` | Badge OK, barra ≥ 90% |
|
||||||
|
| `WARNING` | `#F59E0B` | Badge faltante, barra 60–89% |
|
||||||
|
| `DANGER` | `#EF4444` | Badge duplicada, barra < 60% |
|
||||||
|
| `TRACK_BG` | `#111827` | Trilha da progress bar |
|
||||||
|
|
||||||
|
### Helpers de estilo disponíveis em `theme.rs`
|
||||||
|
|
||||||
|
**Containers:**
|
||||||
|
- `t::fundo` — fundo geral da janela
|
||||||
|
- `t::card` — card principal (SURFACE + borda + radius 8)
|
||||||
|
- `t::card_secondary` — card secundário (SURFACE_2 + borda + radius 6)
|
||||||
|
- `t::cabecalho_tabela` — cabeçalho de tabela sem borda
|
||||||
|
- `t::badge_sucesso` / `t::badge_aviso` / `t::badge_perigo` — badges coloridos
|
||||||
|
- `t::area_erro` — área de validação com fundo vermelho sutil
|
||||||
|
- `t::stat_card` — card de estatística (igual a `t::card`)
|
||||||
|
- `t::separador` — linha divisória fina
|
||||||
|
- `t::breadcrumb_bg` — fundo da barra de breadcrumb
|
||||||
|
|
||||||
|
**Botões:**
|
||||||
|
- `t::btn_primary` — azul sólido, ação principal
|
||||||
|
- `t::btn_secondary` — SURFACE_2, ação secundária
|
||||||
|
- `t::btn_ghost` — transparente com borda, ação terciária
|
||||||
|
- `t::btn_danger` — vermelho semitransparente, exclusão
|
||||||
|
- `t::btn_aba_ativa` / `t::btn_aba_inativa` — seleção de aba XLSX
|
||||||
|
- `t::btn_pagina_ativo` / `t::btn_pagina_inativo` — paginação
|
||||||
|
|
||||||
|
**Inputs:**
|
||||||
|
- `t::input_dark` — text_input com fundo BG, borda BORDER, focus PRIMARY
|
||||||
|
|
||||||
|
**Progress bar:**
|
||||||
|
- `t::progress_bar_por_percentual(f32)` — retorna closure com cor por threshold:
|
||||||
|
- ≥ 0.90 → SUCCESS, 0.60–0.89 → WARNING, < 0.60 → DANGER
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Regras de layout e responsividade
|
||||||
|
|
||||||
|
### Princípio geral
|
||||||
|
|
||||||
|
A janela tem tamanho mínimo de **800×600**. Todo layout deve funcionar bem nessa
|
||||||
|
dimensão e escalar corretamente ao aumentar.
|
||||||
|
|
||||||
|
### O que usar
|
||||||
|
|
||||||
|
| Situação | Valor correto |
|
||||||
|
|----------|--------------|
|
||||||
|
| Widget que deve preencher o espaço disponível | `Length::Fill` |
|
||||||
|
| Label ao lado de input em linha | `Length::FillPortion(3)` (label) + input com tamanho fixo pequeno ou `Fill` |
|
||||||
|
| Input numérico curto (índice, posição) | `Length::Fixed(90.0)` ou `Length::Fixed(110.0)` |
|
||||||
|
| Input de texto longo (nome, aba) | `Length::Fill` |
|
||||||
|
| Pick list de opções | `Length::Fill` |
|
||||||
|
| Modal/card centralizado com largura máxima | `.max_width(N)` + `Length::Fill` |
|
||||||
|
| Botões em linha que podem quebrar | `.wrap()` no `row![]` |
|
||||||
|
| Elemento que deve ter tamanho mínimo sem crescer | `Length::Shrink` |
|
||||||
|
| Células de tabela com scroll horizontal | `Length::Fixed(100.0)` mínimo |
|
||||||
|
|
||||||
|
### O que **não** fazer
|
||||||
|
|
||||||
|
- **Não use `Length::Fixed` em labels de formulário.** Labels devem usar
|
||||||
|
`FillPortion` para se adaptar ao espaço disponível.
|
||||||
|
- **Não use `Length::Fixed` em pick lists ou text inputs de texto livre.**
|
||||||
|
Use `Fill` para que se adaptem à largura do container pai.
|
||||||
|
- **Não coloque valores maiores que `max_width` em modais.** Use `.max_width(N)`
|
||||||
|
em vez de `Fixed(N)` para que o modal encolha em janelas menores.
|
||||||
|
- **Não deixe telas sem `scrollable`.** Toda tela com conteúdo vertical deve ser
|
||||||
|
envolvida em `scrollable()` para evitar clipping em janelas pequenas.
|
||||||
|
- **Não use `row![]` com muitos itens sem `.wrap()`.** Botões de ação e grupos
|
||||||
|
de controles devem usar `.wrap()` para quebrar linha quando não couberem.
|
||||||
|
|
||||||
|
### Padrão de campo de formulário
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Linha de campo: label proporcional + input
|
||||||
|
fn campo_row<'a>(label: &'a str, input: Element<'a, Message>) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
text(label)
|
||||||
|
.size(13)
|
||||||
|
.color(t::TEXT_SECONDARY)
|
||||||
|
.width(Length::FillPortion(3)), // proporcional, não fixo
|
||||||
|
input, // input define seu próprio tamanho
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Padrão de campo opcional com checkbox
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Quando ativo: checkbox (FillPortion) + input
|
||||||
|
// Quando inativo: só o checkbox
|
||||||
|
if ativo {
|
||||||
|
row![
|
||||||
|
cb.width(Length::FillPortion(3)),
|
||||||
|
text_input("...", &val)
|
||||||
|
.width(Length::Fixed(90.0)), // input numérico curto
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_y(Alignment::Center)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
row![cb].into()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Estrutura de uma tela (screen)
|
||||||
|
|
||||||
|
Toda tela segue o mesmo padrão de função pública `view`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn view(app: &App) -> Element<'_, Message> {
|
||||||
|
// 1. Montar seções/componentes individuais
|
||||||
|
let secao_x = ...;
|
||||||
|
let secao_y = ...;
|
||||||
|
|
||||||
|
// 2. Combinar em coluna principal
|
||||||
|
let conteudo = column![secao_x, secao_y]
|
||||||
|
.spacing(14)
|
||||||
|
.padding([20, 24])
|
||||||
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
// 3. Envolver em scrollable + container de fundo
|
||||||
|
container(scrollable(conteudo))
|
||||||
|
.style(t::fundo)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Toda tela deve ter `scrollable` e `container` com `t::fundo` na raiz.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Componentes reutilizáveis
|
||||||
|
|
||||||
|
### `modal::view_com_modal(conteudo, modal)`
|
||||||
|
|
||||||
|
Envolve qualquer `Element` com um overlay de modal bloqueante.
|
||||||
|
Chamado em `app.rs` quando `self.modal.is_some()`.
|
||||||
|
|
||||||
|
- Modal tem `max_width(420)` + `Length::Fill` para ser responsivo.
|
||||||
|
- Tipos disponíveis: `Informacao`, `Aviso`, `Erro`, `Confirmacao`, `InputTexto`.
|
||||||
|
- Disparar modal: usar os helpers em `app.rs` (`exibir_erro`, `exibir_aviso`, `exibir_confirmacao`).
|
||||||
|
|
||||||
|
### `tabela_preview::tabela_preview(linhas)`
|
||||||
|
|
||||||
|
Renderiza as primeiras N linhas do arquivo com cabeçalho estilo Excel (A, B, C...).
|
||||||
|
|
||||||
|
- Células com `Fixed(100.0)` — tamanho fixo mínimo com scroll horizontal.
|
||||||
|
- A altura da área de scroll está fixada em `Fixed(160.0)` — intencional.
|
||||||
|
- Scroll horizontal via `scrollable::Direction::Horizontal`.
|
||||||
|
|
||||||
|
### `paginacao::controles_paginacao(pagina, total, msg_anterior, msg_proxima)`
|
||||||
|
|
||||||
|
Row de botões ◀ / "Página X / Y" / ▶.
|
||||||
|
Emite as mensagens passadas como parâmetro.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Roteamento entre telas
|
||||||
|
|
||||||
|
O roteamento é feito pelo enum `EstadoApp` em `app.rs`:
|
||||||
|
|
||||||
|
| Estado | Tela renderizada |
|
||||||
|
|--------|-----------------|
|
||||||
|
| `Importando` | `screens/import.rs` |
|
||||||
|
| `SelecionandoAba` | `screens/selecionar_aba.rs` |
|
||||||
|
| `ConfigurandoColunas` | `screens/configuracao_colunas.rs` |
|
||||||
|
| `ConfirmandoIntervalo` | `screens/configuracao_colunas.rs` (mesmo view) |
|
||||||
|
| `ExibindoResultado(r)` | `screens/resultado.rs` |
|
||||||
|
| `GerenciandoLayouts` | `screens/layouts.rs` |
|
||||||
|
| `Analisando` | Spinner inline em `app.rs` |
|
||||||
|
|
||||||
|
Transições são sempre via `Message` → `update()`. **Nunca altere `self.estado`
|
||||||
|
diretamente de dentro de uma tela.**
|
||||||
|
|
||||||
|
O breadcrumb é renderizado automaticamente por `app.rs` para todos os estados
|
||||||
|
exceto `GerenciandoLayouts` e `Analisando`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Adicionando uma nova tela
|
||||||
|
|
||||||
|
1. Crie `src/ui/screens/minha_tela.rs` com função `pub fn view(app: &App) -> Element<'_, Message>`.
|
||||||
|
2. Adicione `pub mod minha_tela;` em `src/ui/screens/mod.rs`.
|
||||||
|
3. Adicione a variante correspondente em `EstadoApp` (`app.rs`).
|
||||||
|
4. Adicione o arm no `match &self.estado` em `app.view()` (`app.rs`).
|
||||||
|
5. Adicione as mensagens necessárias em `message.rs`.
|
||||||
|
6. Trate as mensagens no `update()` de `app.rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Adicionando um novo componente
|
||||||
|
|
||||||
|
1. Crie `src/ui/components/meu_componente.rs`.
|
||||||
|
2. Adicione `pub mod meu_componente;` em `src/ui/components/mod.rs`.
|
||||||
|
3. O componente deve ser uma função pura: recebe dados por parâmetro, retorna `Element<'_, Message>`.
|
||||||
|
4. Sem estado interno, sem `self`, sem acesso ao banco.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Adicionando novos estilos ao tema
|
||||||
|
|
||||||
|
- Sempre adicione em `theme.rs`.
|
||||||
|
- Siga o padrão dos helpers existentes: função que recebe `&Theme` e retorna o `Style` do widget.
|
||||||
|
- Para cores com alpha: use `Color { a: 0.N, ..CONSTANTE }` em vez de valores RGB manuais.
|
||||||
|
- Nomeie helpers de container como `nome_do_contexto`, botões como `btn_nome`, inputs como `input_nome`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Mensagens e estado assíncrono
|
||||||
|
|
||||||
|
- Operações bloqueantes (I/O, análise) são sempre executadas em `Task::perform` com
|
||||||
|
`tokio::task::spawn_blocking`.
|
||||||
|
- O resultado retorna para `update()` via `Message`.
|
||||||
|
- O estado `EstadoApp::Analisando` é usado enquanto a operação está em background.
|
||||||
|
- `ResultadoPendente` é o tipo intermediário entre a thread de análise e a UI.
|
||||||
|
|
||||||
|
**Não bloquear a thread principal da UI.** Qualquer operação lenta deve usar `Task`.
|
||||||
@@ -138,7 +138,8 @@ fn caixa_modal<'a>(
|
|||||||
col = col.push(botoes);
|
col = col.push(botoes);
|
||||||
|
|
||||||
container(col.align_x(Alignment::Start))
|
container(col.align_x(Alignment::Start))
|
||||||
.width(Length::Fixed(420.0))
|
.max_width(420)
|
||||||
|
.width(Length::Fill)
|
||||||
.padding(24)
|
.padding(24)
|
||||||
.style(t::card)
|
.style(t::card)
|
||||||
.into()
|
.into()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub fn tabela_preview(linhas: &[Vec<String>]) -> Element<'_, Message> {
|
|||||||
.size(12)
|
.size(12)
|
||||||
.color(t::TEXT_SECONDARY),
|
.color(t::TEXT_SECONDARY),
|
||||||
)
|
)
|
||||||
.width(Length::Fixed(120.0))
|
.width(Length::Fixed(100.0))
|
||||||
.padding([4, 6])
|
.padding([4, 6])
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
@@ -63,7 +63,7 @@ pub fn tabela_preview(linhas: &[Vec<String>]) -> Element<'_, Message> {
|
|||||||
celula.to_string()
|
celula.to_string()
|
||||||
};
|
};
|
||||||
container(text(truncado).font(Font::MONOSPACE).size(11).color(t::TEXT))
|
container(text(truncado).font(Font::MONOSPACE).size(11).color(t::TEXT))
|
||||||
.width(Length::Fixed(120.0))
|
.width(Length::Fixed(100.0))
|
||||||
.padding([3, 6])
|
.padding([3, 6])
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
|||||||
text("Layout:")
|
text("Layout:")
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(t::TEXT_SECONDARY)
|
.color(t::TEXT_SECONDARY)
|
||||||
.width(Length::Fixed(80.0)),
|
.width(Length::Shrink),
|
||||||
pick_list(opcoes_layout, nome_layout_sel, {
|
pick_list(opcoes_layout, nome_layout_sel, {
|
||||||
let layouts = app.layouts_salvos.clone();
|
let layouts = app.layouts_salvos.clone();
|
||||||
move |nome_selecionado: String| {
|
move |nome_selecionado: String| {
|
||||||
@@ -76,7 +76,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
|||||||
})
|
})
|
||||||
.placeholder("— Selecionar layout —")
|
.placeholder("— Selecionar layout —")
|
||||||
.style(input_style_pick)
|
.style(input_style_pick)
|
||||||
.width(Length::Fixed(260.0)),
|
.width(Length::Fill),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(Alignment::Center),
|
.align_y(Alignment::Center),
|
||||||
@@ -251,7 +251,7 @@ fn view_csv(app: &App) -> Element<'_, Message> {
|
|||||||
Message::DelimitadorAlterado(c)
|
Message::DelimitadorAlterado(c)
|
||||||
})
|
})
|
||||||
.style(input_style_pick)
|
.style(input_style_pick)
|
||||||
.width(Length::Fixed(200.0))
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
campo_row(
|
campo_row(
|
||||||
@@ -262,7 +262,7 @@ fn view_csv(app: &App) -> Element<'_, Message> {
|
|||||||
Message::EncodingAlterado
|
Message::EncodingAlterado
|
||||||
)
|
)
|
||||||
.style(input_style_pick)
|
.style(input_style_pick)
|
||||||
.width(Length::Fixed(200.0))
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
campo_row(
|
campo_row(
|
||||||
@@ -274,7 +274,7 @@ fn view_csv(app: &App) -> Element<'_, Message> {
|
|||||||
.unwrap_or(Message::Noop)
|
.unwrap_or(Message::Noop)
|
||||||
})
|
})
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(80.0))
|
.width(Length::Fixed(90.0))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
secao_subtitulo("Mapeamento de colunas (índice base 0)"),
|
secao_subtitulo("Mapeamento de colunas (índice base 0)"),
|
||||||
@@ -287,7 +287,7 @@ fn view_csv(app: &App) -> Element<'_, Message> {
|
|||||||
.unwrap_or(Message::Noop)
|
.unwrap_or(Message::Noop)
|
||||||
})
|
})
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(80.0))
|
.width(Length::Fixed(90.0))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
campo_row(
|
campo_row(
|
||||||
@@ -299,7 +299,7 @@ fn view_csv(app: &App) -> Element<'_, Message> {
|
|||||||
.unwrap_or(Message::Noop)
|
.unwrap_or(Message::Noop)
|
||||||
})
|
})
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(80.0))
|
.width(Length::Fixed(90.0))
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
campo_indice_opcional_csv(
|
campo_indice_opcional_csv(
|
||||||
@@ -341,7 +341,7 @@ fn view_xlsx(app: &App) -> Element<'_, Message> {
|
|||||||
text_input("Nome da aba", &c.aba)
|
text_input("Nome da aba", &c.aba)
|
||||||
.on_input(Message::AbaXlsxAlterada)
|
.on_input(Message::AbaXlsxAlterada)
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(200.0))
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -354,7 +354,7 @@ fn view_xlsx(app: &App) -> Element<'_, Message> {
|
|||||||
"Aba",
|
"Aba",
|
||||||
pick_list(app.abas_xlsx.clone(), aba_sel, Message::AbaXlsxAlterada)
|
pick_list(app.abas_xlsx.clone(), aba_sel, Message::AbaXlsxAlterada)
|
||||||
.style(input_style_pick)
|
.style(input_style_pick)
|
||||||
.width(Length::Fixed(200.0))
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -416,7 +416,7 @@ fn campo_row<'a>(label: &'a str, input: Element<'a, Message>) -> Element<'a, Mes
|
|||||||
text(label)
|
text(label)
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(t::TEXT_SECONDARY)
|
.color(t::TEXT_SECONDARY)
|
||||||
.width(Length::Fixed(220.0)),
|
.width(Length::FillPortion(3)),
|
||||||
input,
|
input,
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
@@ -437,11 +437,11 @@ fn campo_indice_opcional_csv(
|
|||||||
|
|
||||||
if ativo {
|
if ativo {
|
||||||
row![
|
row![
|
||||||
cb.width(Length::Fixed(220.0)),
|
cb.width(Length::FillPortion(3)),
|
||||||
text_input("0", &val_str)
|
text_input("0", &val_str)
|
||||||
.on_input(move |s| { s.parse::<usize>().map(&msg_valor).unwrap_or(Message::Noop) })
|
.on_input(move |s| { s.parse::<usize>().map(&msg_valor).unwrap_or(Message::Noop) })
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(80.0)),
|
.width(Length::Fixed(90.0)),
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
@@ -461,7 +461,7 @@ fn campo_letra_linha<'a>(
|
|||||||
text_input("ex: B3", valor)
|
text_input("ex: B3", valor)
|
||||||
.on_input(msg)
|
.on_input(msg)
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(100.0))
|
.width(Length::Fixed(110.0))
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -479,11 +479,11 @@ fn campo_letra_linha_opcional<'a>(
|
|||||||
|
|
||||||
if ativo {
|
if ativo {
|
||||||
row![
|
row![
|
||||||
cb.width(Length::Fixed(220.0)),
|
cb.width(Length::FillPortion(3)),
|
||||||
text_input("ex: B3", &val_str)
|
text_input("ex: B3", &val_str)
|
||||||
.on_input(msg_valor)
|
.on_input(msg_valor)
|
||||||
.style(t::input_dark)
|
.style(t::input_dark)
|
||||||
.width(Length::Fixed(100.0)),
|
.width(Length::Fixed(110.0)),
|
||||||
]
|
]
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.align_y(Alignment::Center)
|
.align_y(Alignment::Center)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::ui::app::App;
|
use crate::ui::app::App;
|
||||||
use crate::ui::message::Message;
|
use crate::ui::message::Message;
|
||||||
use crate::ui::theme as t;
|
use crate::ui::theme as t;
|
||||||
use iced::widget::{button, column, container, pick_list, row, text};
|
use iced::widget::{button, column, container, pick_list, row, scrollable, text};
|
||||||
use iced::{Alignment, Element, Length};
|
use iced::{Alignment, Element, Length};
|
||||||
|
|
||||||
/// Tela de importação de arquivos.
|
/// Tela de importação de arquivos.
|
||||||
@@ -110,7 +110,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
|||||||
text("Layout salvo:")
|
text("Layout salvo:")
|
||||||
.size(13)
|
.size(13)
|
||||||
.color(t::TEXT_SECONDARY)
|
.color(t::TEXT_SECONDARY)
|
||||||
.width(Length::Fixed(110.0)),
|
.width(Length::Shrink),
|
||||||
pick_list(opcoes_layout, nome_layout_sel, {
|
pick_list(opcoes_layout, nome_layout_sel, {
|
||||||
let layouts = app.layouts_salvos.clone();
|
let layouts = app.layouts_salvos.clone();
|
||||||
move |nome_selecionado: String| {
|
move |nome_selecionado: String| {
|
||||||
@@ -173,7 +173,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
|||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
// ── Layout geral ──────────────────────────────────────────────────────────
|
// ── Layout geral ──────────────────────────────────────────────────────────
|
||||||
container(
|
container(scrollable(
|
||||||
column![
|
column![
|
||||||
text("Importar Arquivo").size(20).color(t::TEXT),
|
text("Importar Arquivo").size(20).color(t::TEXT),
|
||||||
text("Selecione e configure sua planilha para análise")
|
text("Selecione e configure sua planilha para análise")
|
||||||
@@ -185,7 +185,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
|||||||
.align_x(Alignment::Center)
|
.align_x(Alignment::Center)
|
||||||
.padding([32, 20])
|
.padding([32, 20])
|
||||||
.width(Length::Fill),
|
.width(Length::Fill),
|
||||||
)
|
))
|
||||||
.style(t::fundo)
|
.style(t::fundo)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|||||||
@@ -31,22 +31,27 @@ pub fn view<'a>(app: &'a App, resultado: &'a ResultadoAnalise) -> Element<'a, Me
|
|||||||
let botoes_topo = row![
|
let botoes_topo = row![
|
||||||
button("< Nova Análise")
|
button("< Nova Análise")
|
||||||
.on_press(Message::NovaAnalise)
|
.on_press(Message::NovaAnalise)
|
||||||
.style(t::btn_secondary),
|
.style(t::btn_secondary)
|
||||||
button("Reconfigurar Colunas")
|
.width(Length::Shrink),
|
||||||
|
button("Reconfigurar")
|
||||||
.on_press(Message::IrParaConfiguracaoColunas)
|
.on_press(Message::IrParaConfiguracaoColunas)
|
||||||
.style(t::btn_ghost),
|
.style(t::btn_ghost)
|
||||||
button("Reanalisar Arquivo")
|
.width(Length::Shrink),
|
||||||
|
button("Reanalisar")
|
||||||
.on_press_maybe(
|
.on_press_maybe(
|
||||||
app.caminho_arquivo
|
app.caminho_arquivo
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|_| Message::ReanalisarArquivo)
|
.map(|_| Message::ReanalisarArquivo)
|
||||||
)
|
)
|
||||||
.style(t::btn_ghost),
|
.style(t::btn_ghost)
|
||||||
|
.width(Length::Shrink),
|
||||||
button("Exportar PDF")
|
button("Exportar PDF")
|
||||||
.on_press(Message::ExportarPdf)
|
.on_press(Message::ExportarPdf)
|
||||||
.style(t::btn_primary),
|
.style(t::btn_primary)
|
||||||
|
.width(Length::Shrink),
|
||||||
]
|
]
|
||||||
.spacing(8);
|
.spacing(8)
|
||||||
|
.wrap();
|
||||||
|
|
||||||
// ── Controle de itens por página ──────────────────────────────────────────
|
// ── Controle de itens por página ──────────────────────────────────────────
|
||||||
let opcoes_por_pagina = row(OPCOES_PAGINA
|
let opcoes_por_pagina = row(OPCOES_PAGINA
|
||||||
|
|||||||
Reference in New Issue
Block a user