# Dashboard Logística — Componentização adicional do padrão luz

**Data:** 2026-05-06
**Status:** Spec aprovada — implementação pendente
**Sinal afetado:** `componentes/Missoes/Logistica/sinais/Dashboard/`

---

## 1. Contexto

O sinal `Dashboard` da missão Logística introduziu o "padrão luz": componentes
PHP autocontidos em `html/luz/` que recebem um array nomeado e renderizam HTML.
Hoje existem três componentes:

- `luz_card_kpi.php` — card de KPI (título, valor, ícone, sparkline).
- `luz_alerta_item.php` — item de alerta no painel lateral.
- `luz_missao_card.php` — card de missão para grid kanban.

O `dashboard.php` (1304 linhas) e o `dashboard.js` (1785 linhas) repetem
exaustivamente outras estruturas que **já são chamadas de "padrão luz_card" nos
próprios comentários do arquivo** mas nunca foram extraídas. Esta spec cobre a
criação dessas extrações como componentes prontos, **sem alterar `dashboard.php`
nem `dashboard.js` agora** — a migração do markup existente é trabalho futuro
fora do escopo desta entrega.

## 2. Objetivos

- Disponibilizar 7 componentes novos + 1 helper de tokens, prontos para adoção
  futura, mantendo convenções do `luz_card_kpi` atual (array nomeado, sem
  dependência externa, estilos inline).
- Centralizar a paleta de tons (info/success/warning/danger/neutral) em um único
  arquivo PHP, eliminando a duplicação que existe hoje em `luz_card_kpi`,
  `luz_alerta_item` e `luz_missao_card`.
- Não introduzir novo mecanismo de carregamento de assets (sem `luz.css`, sem
  `<link>` extra, sem mudanças em `dashboard.php`).

## 3. Não-objetivos

- Migrar os 18 painéis hard-coded do `dashboard.php` para os novos componentes.
- Refatorar `dashboard.js`.
- Extrair o bloco `<style>` de ~400 linhas do `dashboard.php` para arquivo
  externo.
- Introduzir variáveis CSS (`--luz-tom-info-borda`, etc.).
- Versionamento ou release engineering — é um refator interno.

## 4. Decisões de arquitetura

### 4.1 API de slot — string HTML em array

Componentes que expõem corpo arbitrário (ex.: `luz_card_painel`, `luz_aba_section`)
recebem o conteúdo do corpo como **string HTML** num campo `corpo` do array,
seguindo a convenção do campo `extra` do `luz_card_kpi` atual.

Alternativas consideradas: wrapper open/close em dois arquivos; closure
PHP. Rejeitadas por quebrarem o padrão "1 arquivo por componente, array puro"
do luz existente.

Trade-off aceito: HTML denso vira string PHP sem highlight do editor. Mitigação:
quando o corpo for complexo (tabelas), o componente especializado
(`luz_tabela_paginada`) já encapsula o markup, então o `luz_card_painel`
raramente recebe HTML longo.

### 4.2 Tokens — `luz_tokens.php`, sem CSS externo

A paleta vira um único arquivo PHP `internos/luz_tokens.php` que retorna o array
de cores por tom. Componentes consomem via `require`.

Alternativas consideradas: `luz.css` com `:root { --luz-tom-info-borda: ... }` e
classes utilitárias; híbrido CSS+PHP. Rejeitadas por:

- O SVG do sparkline (`stroke="..."`) precisa da cor em PHP de qualquer jeito —
  CSS variável obrigaria duplicação.
- Carregar `luz.css` exigiria guard estático ou alteração no `dashboard.php`.
- Inconsistência: o `luz_card_kpi` atual usa estilos inline; introduzir uma
  segunda convenção piora a leitura do diretório.

Trade-off aceito: tokens não são consumíveis fora do PHP (ex.: outro CSS
querer `var(--luz-tom-info)`). Hoje ninguém fora dos componentes luz consome
esses tons — YAGNI.

### 4.3 Layout do diretório

```
html/luz/
├── luz_card_kpi.php         ← existente, NÃO mover
├── luz_alerta_item.php      ← existente, NÃO mover
├── luz_missao_card.php      ← existente, NÃO mover
│
├── cards/
│   ├── luz_card_painel.php
│   └── luz_destaque_pessoa.php
│
├── listas/
│   └── luz_tabela_paginada.php
│
├── layout/
│   ├── luz_aba_section.php
│   └── luz_grade_kpis.php
│
└── internos/
    ├── luz_tokens.php
    ├── luz_chip_titulo.php
    └── luz_skeleton.php
```

Os 3 componentes existentes ficam no root do `html/luz/` porque o `dashboard.php`
faz `require __DIR__ . '/luz/luz_card_kpi.php'` em múltiplos pontos — mover
quebraria o sinal. Quando a migração do `dashboard.php` for executada (escopo
futuro), avalia-se mover os existentes para `cards/` com aliases ou deixá-los
no root como entry points.

### 4.4 Convenção de uso

Cada componente segue o mesmo padrão do `luz_card_kpi`:

```php
$painel = [ /* parâmetros */ ];
require __DIR__ . '/luz/cards/luz_card_painel.php';
```

A variável tem o nome semântico do componente (`$painel`, `$tabela`, `$destaque`,
`$aba`, `$grade`, `$chip`, `$skeleton`) — espelha `$kpi`, `$alerta`, `$missao`
de hoje.

### 4.5 Composição

`luz_destaque_pessoa` e `luz_tabela_paginada` renderizam por dentro de
`luz_card_painel` (compõem em vez de duplicar). Isso significa que mudanças
no estilo do painel (sombra hover, gradient, borda) propagam para os
especializados sem retrabalho.

`luz_card_painel` usa `luz_chip_titulo` internamente. O `luz_card_kpi`
mantém seu cabeçalho inline — refatorá-lo para `luz_chip_titulo` exigiria
mudança visual (o KPI tem chip à direita / título à esquerda, oposto ao
`luz_chip_titulo`) e foi escopado para fora deste plano. O `luz_card_kpi`
apenas passa a consumir `luz_tokens.php`. Essa é a única alteração em
arquivo existente, e o HTML de saída é byte-idêntico (verificado via
golden diff durante a implementação).

## 5. Especificação dos componentes

### 5.1 `internos/luz_tokens.php`

Arquivo que retorna array PHP. Não emite HTML.

```php
return [
    'neutral' => ['valor' => '#e2e8f0', 'borda' => 'rgba(148,163,184,0.25)', 'chipFundo' => 'rgba(148,163,184,0.08)', 'chipIcone' => '#94a3b8', 'chipRing' => 'rgba(148,163,184,0.30)', 'spark' => '#94a3b8'],
    'success' => ['valor' => '#4ade80', 'borda' => 'rgba(34,197,94,0.40)',  'chipFundo' => 'rgba(34,197,94,0.10)',  'chipIcone' => '#4ade80', 'chipRing' => 'rgba(34,197,94,0.35)',  'spark' => '#4ade80'],
    'warning' => ['valor' => '#fbbf24', 'borda' => 'rgba(245,158,11,0.40)', 'chipFundo' => 'rgba(245,158,11,0.10)', 'chipIcone' => '#fbbf24', 'chipRing' => 'rgba(245,158,11,0.35)', 'spark' => '#fbbf24'],
    'danger'  => ['valor' => '#f87171', 'borda' => 'rgba(239,68,68,0.40)',  'chipFundo' => 'rgba(239,68,68,0.10)',  'chipIcone' => '#f87171', 'chipRing' => 'rgba(239,68,68,0.35)',  'spark' => '#f87171'],
    'info'    => ['valor' => '#60a5fa', 'borda' => 'rgba(59,130,246,0.40)', 'chipFundo' => 'rgba(59,130,246,0.10)', 'chipIcone' => '#60a5fa', 'chipRing' => 'rgba(59,130,246,0.35)', 'spark' => '#22d3ee'],
];
```

Valores extraídos 1:1 da `$paletaTons` em `luz_card_kpi.php:69-75`.

### 5.2 `internos/luz_chip_titulo.php`

```php
$chip = [
    'titulo'       => '',           // string, obrigatório
    'icone'        => 'bi-bar-chart', // classe bootstrap-icons
    'tom'          => 'neutral',    // info|success|warning|danger|neutral
    'tamanho'      => 'md',         // 'sm' (chip 2rem) | 'md' (chip 2.25rem)
    'tituloEscala' => 1.0,          // multiplicador no font-size do título
];
```

Renderiza: `<div flex>` com chip circular (ícone + cor do tom) à esquerda e
título uppercase tracking-wide ao lado.

### 5.3 `internos/luz_skeleton.php`

```php
$skeleton = [
    'altura'  => '80px',   // obrigatório, qualquer valor CSS válido
    'largura' => '100%',
    'classe'  => '',       // adicional, ex.: 'rounded-circle'
];
```

Renderiza: `<div class="dash-skeleton {classe}" style="height:...; width:...;">`.

A classe `dash-skeleton` continua sendo definida no `<style>` interno do
`dashboard.php` (linha 266) — o componente apenas encapsula o markup.

### 5.4 `cards/luz_card_painel.php`

```php
$painel = [
    'titulo'        => '',          // obrigatório
    'icone'         => 'bi-bar-chart',
    'tom'           => 'info',      // controla borda + chip
    'corpo'         => '',          // HTML cru — slot principal
    'headerExtra'   => '',          // HTML à direita do título (paginação, badge)
    'gridColumn'    => '',          // '', 'span 2', '1 / 7'...
    'gridRow'       => '',          // '', '2 / 3'...
    'paddingY'      => '0.875rem',  // alguns painéis usam '0.75rem'
    'gap'           => '0.6rem',
    'marginBottom'  => '',          // '' ou '1rem'
    'classe'        => '',          // classes extras no root
    'id'            => '',          // id no root
];
```

Renderiza:

```
<div class="g-card-kpi {classe}" data-tom="{tom}" id="{id}"
     style="border-radius:0.75rem; padding:{paddingY} 1rem;
            display:flex; flex-direction:column; gap:{gap};
            background: linear-gradient(145deg, rgba(15,12,41,0.95) 0%, rgba(20,18,50,0.9) 100%);
            border:1px solid {tons[tom].borda};
            box-shadow:0 4px 15px rgba(0,0,0,0.3);
            grid-column:{gridColumn}; grid-row:{gridRow};
            margin-bottom:{marginBottom};"
     onmouseover="..." onmouseout="...">
  <div style="display:flex; align-items:center; justify-content:space-between; gap:0.6rem;">
    {luz_chip_titulo}
    {headerExtra}     <!-- só renderiza wrapper se headerExtra != '' -->
  </div>
  {corpo}
</div>
```

### 5.5 `cards/luz_destaque_pessoa.php`

```php
$destaque = [
    'titulo'      => 'Melhor Separador',
    'tom'         => 'warning',
    'icone'       => 'bi-trophy',
    'badgeRank'   => '#1',                  // string ou '' p/ ocultar
    'idAvatar'    => 'melhor-sep-avatar',
    'idNome'      => 'melhor-sep-nome',
    'stats'       => [
        ['idValor' => '...', 'sufixo' => 'score',  'corValor' => '#fbbf24', 'destaque' => true],
        ['idValor' => '...', 'sufixo' => '',       'corValor' => '#4ade80', 'destaque' => true],
        ['idValor' => '...', 'sufixo' => 'itens'],
        ['idValor' => '...', 'sufixo' => '× líder'],
    ],
    'gridColumn'  => 'span 1',
];
```

Compõe `luz_card_painel`. O `badgeRank` vai no `headerExtra` (gradiente
laranja). O corpo é avatar circular gradiente roxo + bloco de stats com até N
linhas (configurável via array `stats`).

Em `stats`, `destaque: true` aplica `<strong>` com cor; sem `destaque`, é
`<span>` neutro com cor `#94a3b8`.

### 5.6 `listas/luz_tabela_paginada.php`

```php
$tabela = [
    'titulo'         => 'Ranking Geral de Separadores',
    'icone'          => 'bi-list-ol',
    'tom'            => 'info',
    'idTabela'       => 'ranking-separadores-tabela',
    'idCorpo'        => 'ranking-separadores-body',
    'idPaginacao'    => 'ranking-sep-paginacao',
    'colunas'        => ['Pos.', 'Nome', /* ... */],
    'alturaSkeleton' => '120px',
    'gridColumn'     => '',
    'gridRow'        => '',
];
```

Compõe `luz_card_painel` com:

- `headerExtra`: `<div class="dash-paginacao" id="{idPaginacao}" style="margin:0; font-size:0.9rem;"></div>`
- `corpo`: `<table class="dash-table" id="{idTabela}"><thead><tr>{colunas via show()}</tr></thead><tbody id="{idCorpo}"><tr><td colspan="{N}">{luz_skeleton altura={alturaSkeleton}}</td></tr></tbody></table>`

Cada coluna passa por `show()` (i18n do Genesis).

### 5.7 `layout/luz_aba_section.php`

```php
$aba = [
    'id'    => 'aba-visao-geral',     // obrigatório
    'chave' => 'visao-geral',         // obrigatório, vira data-aba
    'ativa' => false,                 // adiciona classe ' ativa'
    'corpo' => '',                    // HTML cru de toda a aba
];
```

Renderiza:

```html
<section class="dash-aba-section{ ativa?' ativa':''}"
         id="{id}" data-aba="{chave}" data-loaded="false">
  {corpo}
</section>
```

### 5.8 `layout/luz_grade_kpis.php`

```php
$grade = [
    'kpis'               => [        // array de arrays no formato $kpi do luz_card_kpi
        ['titulo' => 'Separações', 'icone' => 'bi-box-seam', 'tom' => 'info', 'chave' => 'separacoes', 'hint' => 'rotas no período'],
        // ... 6 itens
    ],
    'escalaPadrao'       => 1.3,     // mesclado em cada kpi (kpi['escala'] sobrescreve)
    'tituloEscalaPadrao' => 1.0,     // idem para tituloEscala
];
```

Renderiza:

```html
<div class="dash-kpi-grade">
  <?php foreach ($kpis_processados as $kpi) { require __DIR__ . '/../luz_card_kpi.php'; } ?>
</div>
```

Cada item recebe `escala` e `tituloEscala` mesclados a partir do padrão (não
sobrescreve se o item já especifica).

## 6. Plano de implementação

1. Criar diretórios `cards/`, `listas/`, `layout/`, `internos/` em
   `componentes/Missoes/Logistica/sinais/Dashboard/html/luz/`.
2. Escrever `internos/luz_tokens.php` com a paleta extraída.
3. Escrever `internos/luz_chip_titulo.php` e `internos/luz_skeleton.php`.
4. Escrever `cards/luz_card_painel.php` (consome `luz_tokens` + `luz_chip_titulo`).
5. Escrever `listas/luz_tabela_paginada.php` (compõe `luz_card_painel`,
   consome `luz_skeleton`).
6. Escrever `cards/luz_destaque_pessoa.php` (compõe `luz_card_painel`).
7. Escrever `layout/luz_aba_section.php` e `layout/luz_grade_kpis.php`.
8. Refatorar `luz_card_kpi.php` para consumir `internos/luz_tokens.php`
   (apenas a paleta — o cabeçalho inline permanece). Único arquivo existente
   alterado, output HTML byte-idêntico ao baseline.
9. Sanidade: abrir o dashboard e confirmar que nenhuma regressão visual surgiu
   (apenas `luz_card_kpi` muda, e foi refatorado pra render idêntico).

`dashboard.php` e `dashboard.js` permanecem intocados.

## 7. Validação

Como não há adoção dos novos componentes nesta entrega, a validação cobre só
duas frentes:

- **Visual:** o dashboard renderiza igual ao estado pré-refator (única
  alteração de runtime é a refatoração interna do `luz_card_kpi`).
- **Sintática:** cada novo componente roda sem erro PHP quando incluído com
  array vazio (defaults razoáveis em todos os campos exceto os marcados
  obrigatórios).

Não há testes automatizados específicos para componentes de view no Genesis
hoje — segue a convenção atual.

## 8. Trabalho futuro (fora do escopo)

- Migrar os 18 painéis hard-coded do `dashboard.php` para `luz_card_painel`.
- Migrar Ranking Separadores, Ranking Conferentes, Erros Separadores para
  `luz_tabela_paginada`.
- Migrar Melhor Separador / Conferente para `luz_destaque_pessoa`.
- Migrar as 3 instâncias da grade de KPIs para `luz_grade_kpis`.
- Embrulhar as 3 abas com `luz_aba_section`.
- Avaliar extrair o bloco `<style>` interno do `dashboard.php` para um arquivo
  CSS dedicado.
- Avaliar componentização análoga em `dashboard.js` (renderers de top3,
  tabelas paginadas, skeleton).
