# Design: Gráfico Timeline Gantt — Aba Separação

**Data:** 2026-05-15
**Status:** Aprovado

---

## Objetivo

Adicionar um gráfico de timeline horizontal (Gantt) na aba Separação do Dashboard Logístico. Cada linha representa uma missão de separação identificada pelo nome da rota; as barras mostram o intervalo de tempo `inicio → fim` coloridas por status.

---

## Endpoint de API

**Rota:** `GET /api/v1/dashboard/separacao/timeline`

**Params (query string):**

| Param | Tipo | Obrigatório | Descrição |
|-------|------|-------------|-----------|
| `from` | `Y-m-d` | sim | Data início (validado por FiltroPeriodoValidator) |
| `to` | `Y-m-d` | sim | Data fim |
| `tipo_separacao` | `todos\|retira\|entrega` | não | Filtro de tipo (padrão: `todos`) |

**Resposta:**
```json
{
  "data": {
    "rotas": [
      {
        "rota": "Nome da Rota X",
        "inicio": "2026-05-14T08:30:00",
        "fim": "2026-05-14T09:05:00",
        "status": "concluida",
        "duracao_min": 35
      },
      {
        "rota": "Nome da Rota Y",
        "inicio": "2026-05-14T09:00:00",
        "fim": null,
        "status": "andamento",
        "duracao_min": null
      }
    ]
  }
}
```

**Regras de negócio no backend:**
- Filtra `missao_logistica` por `tipo_missao_id = SEPARACAO` e `iniciado_em` dentro do intervalo `[from, to+1day)`.
- JOIN com `romaneios` via `referencia_id` (quando `referencia_tipo = 'ROMANEIO'`) para obter `nome_rota`. Fallback: `descricao` da missão.
- `status` derivado do campo `status` da missão: `concluida | andamento | cancelada`.
- `duracao_min`: `EXTRACT(EPOCH FROM (finalizado_em - iniciado_em)) / 60` — `null` se sem finalização.
- Aplica filtro `tipo_separacao` com a mesma lógica usada em outros endpoints (via `TipoSeparacaoValidator`).
- Retorna ordenado por `iniciado_em ASC`.
- Multi-tenant: `block_empresa_id` injetado via `customerControl`.

---

## Estrutura de Arquivos (Backend)

```
Dashboard/
  Handlers/
    SeparacaoTimelineHandler.php   ← novo
  Laboratorios/
    TimelineSeparacaoLaboratorio.php  ← novo
  Sondas/
    MissaoLogisticaSonda.php       ← adicionar método listarTimeline()
```

O Handler valida os params com `FiltroPeriodoValidator` + `TipoSeparacaoValidator`, chama o Laboratório, retorna JSON.

---

## Frontend

### Posição no layout

Novo card na aba Separação, inserido **após os cards de KPI** e **antes** dos gráficos de fluxo existentes.

### Período usado

`DashboardState.periodoCards()` — dia único quando botão de dia é clicado; intervalo completo quando calendário com range explícito.

### Filtro

Responde ao `tipoSeparacao` global (Entrega / Retira / Todos) exatamente como os outros cards da aba.

### Lógica de cor (calculada no JS)

| Status | Condição extra | Cor | Hex |
|--------|----------------|-----|-----|
| `concluida` | — | Verde | `#22c55e` |
| `andamento` | `duracao_min ≤ 30` | Azul | `#3b82f6` |
| `andamento` | `duracao_min ≤ 45` | Amarelo | `#f59e0b` |
| `andamento` | `duracao_min < 90` | Vermelho | `#ef4444` |
| `andamento` | `duracao_min ≥ 90` (ou sem `fim`) | Lilás | `#a78bfa` |
| `cancelada` | — | Roxo | `#8b5cf6` |

**Regra lilás:** quando `status === 'andamento'` e `fim` é `null` ou `duracao_min >= 90`, o `fim` usado no gráfico é `inicio + 90min`. O status exibido no tooltip continua "Em andamento".

**Cálculo de `duracao_min` no JS** para missões sem `fim`: `(Date.now() - inicio_ms) / 60000`.

### Chart — ApexCharts `rangeBar`

```js
{
  chart: { type: 'bar', height: <dinamico>, toolbar: false },
  plotOptions: { bar: { horizontal: true, rangeBarGroupRows: true } },
  xaxis: { type: 'datetime' },       // auto-fit à janela do dia
  yaxis: { labels: { maxWidth: 160 } },
  series: [{ data: [
    { x: 'Nome da Rota', y: [inicio_ms, fim_ms], fillColor: '#cor' },
    ...
  ]}],
  tooltip: { custom: ... }           // mostra rota, início, fim formatado, duração
}
```

### Scroll

- **8 linhas visíveis** fixas (`rowHeight ≈ 40px` → altura do chart `≈ 320px`).
- Wrapper com `overflow-y: auto; scrollbar-width: none; -webkit-scrollbar: display:none`.
- Chart renderizado com a altura total real (`n_rotas × 40px`), scroll no container.

### Legenda

6 pills coloridos abaixo do chart:

🟢 Concluída · 🔵 ≤ 30min · 🟡 ≤ 45min · 🔴 ≥ 46min · 🟪 Lilás (+90min) · 🟣 Cancelada

### Skeleton / empty state

- Skeleton de 320px enquanto carrega.
- Mensagem "Sem separações no período" se `rotas` vier vazio.

---

## Integração com sistema existente

- **Rota** registrada em `Routes.php` junto com os outros endpoints de dashboard.
- **Cache** via `DashboardApi.get('separacao/timeline', periodoCards)` — invalidado junto com os demais ao trocar período.
- **Recarregamento** automático quando `_aplicarFiltro` é chamado (tab reset para `loaded=false`).
- **`DASHBOARD_API_SEP_AWARE_PATHS`**: adicionar `'separacao/timeline'` para injeção automática de `tipo_separacao`.

---

## Fora de escopo

- Drill-down ao clicar numa barra.
- Exportação do gráfico.
- Granularidade por item (apenas nível de missão).
