# Dashboard Logística — Mobile Responsivo — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Adicionar uma camada responsiva ao Dashboard Operacional de Logística para excelente visualização em mobile, sem alterar o layout desktop atual.

**Architecture:** Camada CSS isolada em `@media` (tiers ≤768px e ≤480px) adicionada ao fim do `<style>` principal de `dashboard.php`; adição cirúrgica e additiva de markup (wrapper de filtros + botão "Filtros" + backdrop) neutralizada no desktop via `display:contents`; drawer off-canvas como IIFE isolado em `dashboard.php`; config global `window.Apex` em `dashboard.js` para responsividade de todos os gráficos sem editar cada um; dispatch de `resize` em troca de aba/fechar drawer/orientação.

**Tech Stack:** PHP (view server-rendered), CSS3 (media queries, flexbox, grid), JavaScript vanilla (IIFE), ApexCharts (config global), Bootstrap Icons.

---

## Convenções deste plano

- **Spec:** `docs/superpowers/specs/2026-05-19-dashboard-logistica-mobile-responsivo-design.md`
- **Sem testes unitários para CSS.** Verificação = `php -l` (sintaxe), checagem visual nas larguras 360/390/414/768/landscape via DevTools + URL de dev (`https://dev-galpaotms.goldieapp.com.br/missao/logistica/dashboard`), confirmação de que o desktop (≥1200px) está visualmente idêntico, e a suíte smoke existente do sinal continua verde.
- **i18n:** strings via `show('...')`, igual a todo o resto do arquivo. As traduções desse sinal são colhidas por harvester (chaves hash + `solicitacoes` em `linguagens/{pt-br,en,es}.json`); **não** editar JSON à mão (mesmo padrão das strings atuais do dashboard).
- **Commits:** este projeto tem regra de **não commitar sem pedido explícito do usuário**. Os passos de commit abaixo só devem ser executados quando o usuário autorizar (ou em lote no fim, sob autorização). Não commitar automaticamente.
- **Arquivos tocados (somente estes 2):**
  - `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
  - `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js`
- **Comando de lint PHP:** `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
- **Comando smoke existente:** `php componentes/Missoes/Logistica/sinais/Dashboard/Testes/Handlers/KpisHandlerSmokeTddTest.php` (espera `TODOS OS TESTES PASSARAM`).

---

## Task 1: Markup additivo (wrapper de filtros + botão "Filtros" + backdrop) — desktop idêntico

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (~1470–1499 markup; ~1402 CSS)

- [ ] **Step 1: Ler o markup da barra**

Run: Read `dashboard.php` linhas 1440–1500. Confirmar que existe:
`</div>` fechando `.dash-tabs` (≈1470), depois `<div class="dash-tipo-sep" id="dash-tipo-sep" ...>` (≈1471), e `</div>` fechando `.dash-daterange-wrap` (≈1498) antes do `</div>` que fecha `.dash-tabs-row` (≈1499).

- [ ] **Step 2: Inserir botão "Filtros" + abrir wrapper antes do `.dash-tipo-sep`**

Edit em `dashboard.php`. `old_string` (o fechamento da `.dash-tabs` seguido da abertura do tipo-sep — usar o trecho único real lido no Step 1, exemplo):

```php
        </div>
        <div class="dash-tipo-sep" id="dash-tipo-sep" role="group" aria-label="<?= show('Filtro de tipo de separação') ?>">
```

`new_string`:

```php
        </div>
        <button type="button" class="dash-btn-filtros" id="dash-btn-filtros"
            aria-expanded="false" aria-controls="dash-filtros-mobile"
            title="<?= show('Filtros') ?>" aria-label="<?= show('Filtros') ?>">
            <i class="bi-sliders"></i><span><?= show('Filtros') ?></span>
        </button>
        <div class="dash-filtros-mobile" id="dash-filtros-mobile" aria-hidden="false">
        <div class="dash-tipo-sep" id="dash-tipo-sep" role="group" aria-label="<?= show('Filtro de tipo de separação') ?>">
```

- [ ] **Step 3: Fechar wrapper + inserir backdrop depois do `.dash-daterange-wrap`**

Edit em `dashboard.php`. `old_string` (fechamento do `.dash-daterange-wrap` seguido do fechamento da `.dash-tabs-row` — usar o trecho único real lido no Step 1, exemplo):

```php
        </div>
    </div>
```

(o primeiro `</div>` fecha `.dash-daterange-wrap`, o segundo fecha `.dash-tabs-row`)

`new_string`:

```php
        </div>
        </div><!-- /.dash-filtros-mobile -->
    </div>
    <div class="dash-drawer-backdrop" id="dash-drawer-backdrop" hidden></div>
```

> Se o `old_string` acima não for único, ancorar usando o comentário/atributo único imediatamente anterior ao fechamento do `.dash-daterange-wrap` (ex.: a linha do `<button ... id="btn-dash-info">` e seu `</button>` + os dois `</div>`), incluindo contexto suficiente para unicidade.

- [ ] **Step 4: Adicionar neutralizadores desktop no fim do `<style>` principal**

Read `dashboard.php` linhas 1390–1405. Localizar o **primeiro** `</style>` do arquivo (≈linha 1402, fecha o bloco de tokens/utilitários). Inserir imediatamente **antes** dele:

```css
    /* =========================================================
       MOBILE / RESPONSIVO — camada isolada (não altera desktop)
       Tiers: ≤768px (layout mobile) · ≤480px (ajuste fino)
       ========================================================= */

    /* Desktop: wrapper invisível (não cria caixa) + controles mobile ocultos.
       .dash-tipo-sep e .dash-daterange-wrap continuam flex-items diretos
       da .dash-tabs-row, exatamente como antes. */
    .dash-filtros-mobile { display: contents; }
    .dash-btn-filtros { display: none; }
    .dash-drawer-backdrop { display: none; }
```

- [ ] **Step 5: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
Expected: `No syntax errors detected`

- [ ] **Step 6: Verificar desktop inalterado**

Abrir a URL de dev em ≥1200px. Confirmar: barra de abas, filtro Entrega/Retira/Todos, label de período, botões de dia, calendário/reset/tema/info — todos posicionados **exatamente como antes** (o botão "Filtros" não aparece; wrapper é `display:contents`).
Expected: layout desktop idêntico ao atual.

- [ ] **Step 7: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php
git commit -m "feat(dashboard): markup additivo p/ drawer de filtros mobile (desktop inalterado)"
```

---

## Task 2: Drawer mobile — CSS off-canvas + IIFE de comportamento

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (bloco MOBILE ≈1402; IIFE perto do toggle de tema ≈1412–1435)

- [ ] **Step 1: Adicionar CSS do drawer (≤768px) no bloco MOBILE**

Edit em `dashboard.php`. Inserir logo após o bloco neutralizador da Task 1 (Step 4), ainda antes do `</style>`:

```css
    @media (max-width: 768px) {
        .dash-btn-filtros {
            display: inline-flex;
            align-items: center;
            gap: 0.4rem;
            height: 2.4rem;
            padding: 0 0.85rem;
            background: rgba(255, 255, 255, 0.04);
            border: 1px solid rgba(255, 255, 255, 0.10);
            color: rgba(255, 255, 255, 0.85);
            border-radius: 0.5rem;
            font-size: 0.82rem;
            font-weight: 600;
            cursor: pointer;
        }
        .dash-btn-filtros i { font-size: 0.95rem; }

        /* wrapper deixa de ser display:contents e vira painel off-canvas */
        .dash-filtros-mobile {
            display: flex !important;
            flex-direction: column;
            gap: 1rem;
            position: fixed;
            top: 0;
            right: 0;
            height: 100dvh;
            width: min(86vw, 360px);
            padding: 1.25rem 1rem calc(1.25rem + env(safe-area-inset-bottom));
            background: linear-gradient(160deg, #11132e 0%, #0d1030 100%);
            border-left: 1px solid rgba(255, 255, 255, 0.10);
            box-shadow: -12px 0 40px rgba(0, 0, 0, 0.5);
            transform: translateX(100%);
            transition: transform 0.25s ease;
            overflow-y: auto;
            z-index: 1050;
        }
        .dash-filtros-mobile.aberto { transform: translateX(0); }

        /* controles empilham e ficam tocáveis dentro do drawer */
        .dash-filtros-mobile .dash-tipo-sep,
        .dash-filtros-mobile .dash-daterange-wrap {
            width: 100%;
            margin-left: 0;
            flex-wrap: wrap;
        }
        .dash-filtros-mobile .dash-tipo-sep { display: flex; }
        .dash-filtros-mobile .dash-tipo-sep button { flex: 1 1 auto; justify-content: center; }
        .dash-filtros-mobile .dash-day-buttons { flex-wrap: wrap; gap: 0.35rem; }
        .dash-filtros-mobile .dash-day-btn { width: 2.6rem; height: 2.6rem; }

        .dash-drawer-backdrop {
            display: none;
            position: fixed;
            inset: 0;
            background: rgba(0, 0, 0, 0.55);
            z-index: 1040;
        }
        .dash-drawer-backdrop.aberto { display: block; }
        body.dash-drawer-open { overflow: hidden; }
    }
```

- [ ] **Step 2: Ler o IIFE do toggle de tema**

Read `dashboard.php` linhas 1405–1436. Confirmar o padrão `<script> (function(){ ... })(); </script>` do toggle de tema (termina ≈1435/1436).

- [ ] **Step 3: Adicionar IIFE do drawer logo após o `</script>` do toggle de tema**

Edit em `dashboard.php`. `old_string` = o `})();\n</script>` final do IIFE de tema (com contexto único — incluir a linha anterior real lida no Step 2). `new_string` = o mesmo trecho seguido de:

```php

<script>
    /* Drawer de filtros (mobile) — isolado, presentational. */
    (function () {
        var btn = document.getElementById('dash-btn-filtros');
        var panel = document.getElementById('dash-filtros-mobile');
        var backdrop = document.getElementById('dash-drawer-backdrop');
        if (!btn || !panel || !backdrop) return;

        function abrir() {
            panel.classList.add('aberto');
            backdrop.classList.add('aberto');
            backdrop.hidden = false;
            document.body.classList.add('dash-drawer-open');
            btn.setAttribute('aria-expanded', 'true');
            panel.setAttribute('aria-hidden', 'false');
            panel.setAttribute('tabindex', '-1');
            panel.focus();
        }

        function fechar() {
            panel.classList.remove('aberto');
            backdrop.classList.remove('aberto');
            document.body.classList.remove('dash-drawer-open');
            btn.setAttribute('aria-expanded', 'false');
            panel.setAttribute('aria-hidden', 'true');
            btn.focus();
            // ApexCharts recalcula largura após o reflow do fechamento.
            window.dispatchEvent(new Event('resize'));
        }

        btn.addEventListener('click', function () {
            if (panel.classList.contains('aberto')) fechar();
            else abrir();
        });
        backdrop.addEventListener('click', fechar);
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape' && panel.classList.contains('aberto')) fechar();
        });
    })();
</script>
```

- [ ] **Step 4: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
Expected: `No syntax errors detected`

- [ ] **Step 5: Verificar drawer no mobile**

DevTools em 390px: botão "Filtros" aparece na barra; clicar abre o painel da direita com tipo/período/calendário/reset/tema/info; backdrop escurece o fundo; scroll do body travado; clicar no backdrop, no botão de novo, e tecla Esc fecham; foco vai pro painel ao abrir e volta pro botão ao fechar.
Em ≥1200px: botão "Filtros" não aparece, nada muda.
Expected: drawer funcional no mobile, desktop intacto.

- [ ] **Step 6: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php
git commit -m "feat(dashboard): drawer off-canvas de filtros no mobile"
```

---

## Task 3: Abas com scroll horizontal + aba ativa visível

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (bloco MOBILE)
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js` (`DashboardAbas.trocarAba`, ≈1223)

- [ ] **Step 1: Adicionar CSS de scroll das abas no `@media (max-width: 768px)`**

Edit em `dashboard.php`, dentro do bloco `@media (max-width: 768px)` criado na Task 2 (antes do `}` que fecha o media query), adicionar:

```css
        .dash-tabs-row { flex-wrap: nowrap; align-items: center; gap: 0.5rem; margin: 0.9rem 0.9rem 1rem; }
        .dash-tabs {
            flex-wrap: nowrap;
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            scroll-snap-type: x proximity;
            scrollbar-width: none;
            min-width: 0;
        }
        .dash-tabs::-webkit-scrollbar { display: none; }
        .dash-tabs .dash-tab { flex: 0 0 auto; scroll-snap-align: start; }
```

- [ ] **Step 2: Ler `trocarAba`**

Read `dashboard.js` linhas 1223–1239. Confirmar o método `trocarAba(novaAba)` que faz o loop de `.dash-tab` com `classList.toggle('ativo', ...)`.

- [ ] **Step 3: Fazer a aba ativa rolar pra dentro da viewport + re-fit de charts**

Edit em `dashboard.js`. `old_string`:

```javascript
        // Tabs visuais
        document.querySelectorAll('.dash-tab').forEach(btn => {
            btn.classList.toggle('ativo', btn.dataset.tab === novaAba);
        });
```

`new_string`:

```javascript
        // Tabs visuais
        document.querySelectorAll('.dash-tab').forEach(btn => {
            const ativo = btn.dataset.tab === novaAba;
            btn.classList.toggle('ativo', ativo);
            if (ativo && typeof btn.scrollIntoView === 'function') {
                btn.scrollIntoView({ inline: 'center', block: 'nearest' });
            }
        });
        // Charts em aba antes oculta renderizam com width 0 — força recálculo.
        window.dispatchEvent(new Event('resize'));
```

- [ ] **Step 4: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (Expected: `No syntax errors detected`)
Run: `node --check componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js` (Expected: sem saída/erro; se `node` indisponível, pular e validar no browser console)

- [ ] **Step 5: Verificar no mobile**

DevTools 360px: as 6 abas ficam numa fileira que rola horizontalmente sem barra visível; clicar numa aba à direita a traz para o centro; nenhum overflow horizontal na página inteira.
≥1200px: abas inalteradas.
Expected: abas roláveis no mobile, desktop intacto.

- [ ] **Step 6: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js
git commit -m "feat(dashboard): abas com scroll horizontal e aba ativa centralizada no mobile"
```

---

## Task 4: Grade de KPIs mobile (2 col, compacto ≤480, órfã full-width) + gráfico de tendência

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (bloco MOBILE)

- [ ] **Step 1: CSS de KPIs e tendência no `@media (max-width: 768px)`**

Edit em `dashboard.php`, dentro do `@media (max-width: 768px)`, adicionar:

> **DOM verificado:** cards de KPI = `.g-card-kpi` (de `luz/luz_card_kpi.php`),
> filhos diretos de `.dash-kpi-grade`; internos são inline-styled sem classe.
> Por isso os seletores miram `.g-card-kpi` com `>` e `!important` (vence inline).

```css
        .dash-kpi-grade { grid-template-columns: repeat(2, 1fr) !important; gap: 0.6rem; }
        /* KPI órfã (nº ímpar) ocupa as 2 colunas — regra geral p/ qualquer aba.
           Cards são .g-card-kpi (luz_card_kpi.php), filhos diretos de .dash-kpi-grade. */
        .dash-kpi-grade > .g-card-kpi:last-child:nth-child(odd) { grid-column: 1 / -1 !important; }
        #chart-fluxo { min-height: 220px !important; }
        #chart-fluxo .dash-skeleton { height: 220px !important; }
```

- [ ] **Step 2: CSS de ajuste fino ≤480px (novo media query, após o de 768px)**

Edit em `dashboard.php`, após o fechamento do `@media (max-width: 768px)` (e antes do `</style>`), adicionar:

```css
    @media (max-width: 480px) {
        .dash-kpi-grade { gap: 0.5rem !important; }
        .dash-kpi-grade > .g-card-kpi {
            padding: 0.6rem !important;
            min-height: 96px !important;
            gap: 0.35rem !important;
        }
    }
```

- [ ] **Step 3: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
Expected: `No syntax errors detected`

- [ ] **Step 4: Verificar no mobile**

DevTools 390px e 360px na aba Visão Geral: 6 KPIs em 2 colunas, legíveis, sem corte; o gráfico "Fluxo: Separações vs Carregamentos" ocupa largura total com altura ~220px.
Testar a regra de órfã: em alguma aba/estado com nº ímpar de KPIs, o último card ocupa a linha inteira. (Se nenhuma aba tiver nº ímpar no momento, validar a regra reduzindo temporariamente para 5 cards no DevTools.)
≥1200px: KPIs em 5 colunas como hoje.
Expected: KPIs vitrine impecáveis no mobile, desktop intacto.

- [ ] **Step 5: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php
git commit -m "feat(dashboard): grade de KPIs responsiva (2col, orfa full-width) e tendencia mobile"
```

---

## Task 5: Config global de responsividade do ApexCharts

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js` (topo, após `'use strict';` ≈linha 19)

- [ ] **Step 1: Ler o topo do dashboard.js**

Read `dashboard.js` linhas 17–25. Confirmar `'use strict';` na linha 19 e que não há uso prévio de `window.Apex`.

- [ ] **Step 2: Inserir defaults globais do ApexCharts**

Edit em `dashboard.js`. `old_string`:

```javascript
'use strict';
```

`new_string`:

```javascript
'use strict';

// ===========================================================================
// ApexCharts — defaults globais de responsividade (mesclados em todo gráfico).
// Não edita gráficos individuais; opções locais sempre vencem.
// ===========================================================================
window.Apex = window.Apex || {};
window.Apex.responsive = (window.Apex.responsive || []).concat([{
    breakpoint: 768,
    options: {
        chart: { toolbar: { show: false } },
        dataLabels: { enabled: false },
        legend: { position: 'bottom', fontSize: '11px', itemMargin: { horizontal: 6, vertical: 2 } },
        xaxis: { tickAmount: 4, labels: { rotate: -45, style: { fontSize: '10px' } } },
        yaxis: { labels: { style: { fontSize: '10px' } } }
    }
}]);
```

- [ ] **Step 3: Verificar sintaxe**

Run: `node --check componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js`
Expected: sem erro (se `node` indisponível, validar no browser console — sem exceções no load).

- [ ] **Step 4: Verificar no mobile**

DevTools 390px: nos gráficos das abas (Visão Geral, Separação, Carregamento, Ocorrências, Pesagem) a legenda fica embaixo, sem data labels sobrepostos, sem toolbar, eixos com menos ticks e fontes menores.
≥1200px: gráficos idênticos ao atual (breakpoint 768 não dispara).
Expected: gráficos legíveis no mobile, desktop intacto.

- [ ] **Step 5: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js
git commit -m "feat(dashboard): ApexCharts responsivo global p/ mobile"
```

---

## Task 6: Conteúdo pesado — timelines/charts densos roláveis, grids 1 col, tabelas com scroll, tooltips, modais

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (bloco MOBILE)

- [ ] **Step 1: CSS de conteúdo pesado no `@media (max-width: 768px)`**

Edit em `dashboard.php`, dentro do `@media (max-width: 768px)`, adicionar:

> **Selectors verificados no DOM real** (a versão original deste passo usava
> classes inexistentes — Bootstrap `.modal-*`, `.dash-grid-2/3`, blanket
> `#card-*-timeline > *` — corrigido abaixo):

```css
        /* grids multi-coluna com classe colapsam no mobile.
           (.dash-grid-2/3/top3 são CSS morto; grids inline-styled sem classe
           ficam para o polish visual por aba — Task 8.) */
        .dash-grid-sep-top, .dash-grid-carg-top, .dash-info-grid { grid-template-columns: 1fr !important; }

        /* tabelas das ABAS: scroll horizontal preservando colunas.
           NÃO afeta a tabela do modal de ocorrências (#dash-modal-corpo),
           que tem thead sticky e scroll próprio. */
        .dash-aba-section .dash-table {
            display: block;
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            white-space: nowrap;
            width: 100%;
        }

        /* timelines Gantt: card rola horizontalmente; chart recebe min-width.
           Cards têm overflow:hidden (style embutido) → x vira auto; wrap interno
           tem overflow:visible inline (não clipa). */
        #card-geral-timeline, #card-sep-timeline, #card-carg-timeline {
            overflow-x: auto !important;
            padding-left: 0.9rem !important;
            padding-right: 0.9rem !important;
        }
        #geral-timeline-chart, #sep-timeline-chart, #carg-timeline-chart {
            min-width: 680px;
        }

        /* tooltips não estouram a viewport */
        .apexcharts-tooltip,
        .dash-tooltip-host .dash-tooltip-bolha {
            max-width: calc(100vw - 1rem) !important;
            white-space: normal !important;
        }

        /* modais em tela cheia — sistema real .dash-modal-overlay/.dash-modal-card
           e #dash-modal-ocorrencias (NÃO Bootstrap .modal-*). !important vence
           os estilos inline (width:min(1000px,100%); max-height:88vh; padding). */
        .dash-modal-overlay { padding: 0 !important; }
        #dash-modal-ocorrencias, #dash-modal-calculos { padding: 0 !important; }
        .dash-modal-card {
            width: 100% !important;
            max-width: 100% !important;
            min-width: 0 !important;
            max-height: 100dvh !important;
            border-radius: 0 !important;
            overflow-y: auto !important;
        }
        #dash-modal-ocorrencias > .dash-modal-card,
        #dash-modal-calculos > .dash-modal-card { height: 100dvh !important; }
        /* (ocorrências/cálculos mantêm o scroll interno do corpo de propósito) */
    }
```

> Os ids de timeline (`#card-geral-timeline`, `#card-sep-timeline`,
> `#card-carg-timeline`), chart containers (`#geral/sep/carg-timeline-chart`),
> sistema de modal (`.dash-modal-overlay`/`.dash-modal-card`/
> `#dash-modal-ocorrencias`) e grids classed (`.dash-grid-sep-top`/
> `.dash-grid-carg-top`/`.dash-info-grid`) foram **verificados no código**.
> Grids inline-styled sem classe → Task 8 (polish por aba).

- [ ] **Step 2: Verificar sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php`
Expected: `No syntax errors detected`

- [ ] **Step 3: Verificar no mobile**

DevTools 390px, percorrer todas as abas: grids viram 1 coluna; tabelas rolam horizontalmente mostrando todas as colunas; timelines Gantt rolam horizontalmente (não espremem ilegível); tooltips não estouram a tela; modais (drill-down, "Cálculos & Informações") abrem em tela cheia com corpo rolável.
≥1200px: tudo idêntico ao atual.
Expected: conteúdo pesado utilizável no mobile com paridade, desktop intacto.

- [ ] **Step 4: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php
git commit -m "feat(dashboard): reflow mobile de timelines, tabelas, grids, tooltips e modais"
```

---

## Task 7: Re-fit de charts em orientação + verificação integrada

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js` (bootstrap final, ≈5836)

- [ ] **Step 1: Ler o bootstrap de eventos**

Read `dashboard.js` linhas 5836–5868. Confirmar o bloco "Tabs — delegação de evento" e a sequência de init.

- [ ] **Step 2: Adicionar re-fit em mudança de orientação**

Edit em `dashboard.js`. `old_string`:

```javascript
    // Tabs — delegação de evento
    document.querySelectorAll('.dash-tab').forEach(btn => {
        btn.addEventListener('click', () => DashboardAbas.trocarAba(btn.dataset.tab));
    });
```

`new_string`:

```javascript
    // Tabs — delegação de evento
    document.querySelectorAll('.dash-tab').forEach(btn => {
        btn.addEventListener('click', () => DashboardAbas.trocarAba(btn.dataset.tab));
    });

    // Mudança de orientação: ApexCharts recalcula largura.
    window.addEventListener('orientationchange', function () {
        setTimeout(function () { window.dispatchEvent(new Event('resize')); }, 200);
    });
```

- [ ] **Step 3: Verificar sintaxe**

Run: `node --check componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js`
Expected: sem erro (ou validar no browser console sem exceções).

- [ ] **Step 4: Verificar re-fit**

DevTools modo dispositivo: girar retrato↔paisagem em 2–3 abas com gráfico; os gráficos reajustam a largura sem ficar cortados/comprimidos. Trocar de aba também reajusta (já coberto na Task 3).
Expected: charts sempre ajustados após rotação/troca de aba.

- [ ] **Step 5: Garantia de não-regressão de backend**

NOTA: o smoke `KpisHandlerSmokeTddTest.php` está **vermelho na baseline por motivo
pré-existente e não relacionado** — falta `require_once` de `TipoSeparacaoValidator`
no runner standalone (introduzido no commit `1783b35d`, 8 dias atrás). O smoke não
carrega `dashboard.php`/`.js`, então não serve de gate para este trabalho.
Garantia real de não-regressão de backend: **nenhuma lógica PHP foi alterada** —
mudanças são exclusivamente CSS/markup numa view + JS pareado. (Não corrigir o
smoke aqui: fora do escopo de 2 arquivos; é WIP/baseline do usuário.)

- [ ] **Step 6: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.js
git commit -m "feat(dashboard): re-fit de charts em mudanca de orientacao"
```

---

## Task 8: Polish por aba + verificação final

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (bloco MOBILE — só ajustes finos)

- [ ] **Step 1: Pente fino, aba por aba, nas larguras alvo**

Para cada largura (360, 390, 414/430, 768, e landscape) e cada aba **nesta ordem** — Visão Geral → Separação → Carregamento → Ocorrências → Pesagem → Ranking — verificar e anotar problemas: overflow horizontal da página, texto cortado, sobreposição, alvo de toque pequeno (<40px), card espremido, gráfico ilegível, tabela sem scroll, id de timeline/modal divergente do assumido na Task 6.

- [ ] **Step 2: Aplicar ajustes finos pontuais no bloco MOBILE**

Para cada problema da lista do Step 1, adicionar regra mínima dentro do `@media (max-width: 768px)` ou `@media (max-width: 480px)` existente (ex.: corrigir id real de timeline, reduzir um padding específico, forçar `flex-wrap` num header de card). Não criar novos arquivos; não tocar regra desktop. Cada ajuste é uma Edit pequena e específica.

- [ ] **Step 3: Verificar sintaxe e desktop**

Run: `php -l componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php` (Expected: `No syntax errors detected`)
Abrir ≥1200px e comparar com o estado original: layout desktop **idêntico** em todas as 6 abas.

- [ ] **Step 4: Verificação final consolidada**

- Visão Geral em 360–480px: KPIs legíveis (2 col, órfã full-width), tendência limpa, sem scroll horizontal da página. ✔
- Barra: abas rolam; drawer de filtros abre/fecha (botão/backdrop/Esc); scroll travado. ✔
- Todas as 6 abas utilizáveis no mobile (charts/timelines/tabelas com scroll quando preciso; modais full-screen). ✔
- Desktop (≥1200px) idêntico ao atual. ✔
- Não-regressão backend: nenhuma lógica PHP alterada (só CSS/markup + JS de view). O smoke `KpisHandlerSmokeTddTest.php` está vermelho por defeito pré-existente/não relacionado (commit `1783b35d`), não é gate válido aqui. ✔

- [ ] **Step 5: Commit** (somente sob autorização do usuário)

```bash
git add componentes/Missoes/Logistica/sinais/Dashboard/html/dashboard.php
git commit -m "polish(dashboard): ajustes finos responsivos por aba"
```

---

## Self-Review (preenchido pelo autor do plano)

**Spec coverage:**
- Spec §1 (estrutura/breakpoints) → Task 1 Step 4 + Task 4 Step 2 (tiers 768/480). ✔
- Spec §2 (barra: abas scroll + drawer) → Task 1 (markup), Task 2 (drawer), Task 3 (abas scroll). ✔
- Spec §3 (KPIs vitrine + órfã full-width + tendência) → Task 4. ✔
- Spec §4 (charts/timelines/tabelas/modais/grids/tooltips) → Task 5 (Apex global) + Task 6. ✔
- Spec §5 (JS: drawer IIFE, window.Apex, resize em aba/drawer/orientação) → Task 2 (IIFE+resize no fechar), Task 3 (resize em troca de aba), Task 5 (window.Apex), Task 7 (orientationchange). ✔
- Spec §6 (plano de teste/iteração, ordem Visão Geral primeiro) → Task 8. ✔
- Spec "fora do escopo" (sem mudança desktop, sem tabela→cards, sem sticky) → respeitado (neutralizadores desktop, tabela = scroll). ✔

**Placeholder scan:** Sem TBD/TODO. Todos os passos de código têm o código real. Os pontos "confirmar id real" (Task 6 nota, Task 8) são verificações legítimas de iteração, não placeholders de conteúdo. ✔

**Type/identifier consistency:** ids/classes usados de forma consistente entre tasks: `dash-btn-filtros`, `dash-filtros-mobile`, `dash-drawer-backdrop`, classe `aberto`, `body.dash-drawer-open`, `.dash-tabs`, `.dash-kpi-grade`, `window.Apex.responsive`. Drawer abre/fecha pela mesma classe `aberto` em todas as referências. ✔
