# Bottom Sheet de Ações por Parada + Observação Individual

> **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:** Substituir o comportamento direto do botão ⋮ por um bottom sheet com duas ações (otimizar com confirmação / observação por parada), e exibir a observação no card da rota e no Acompanhamento do gestor.

**Architecture:** Mudanças puramente de frontend. As observações são armazenadas em `data-observacao` nos `<li>` e serializadas dentro do array `rota_proposta` no submit — campo `observacao` por parada. `ConsultarEdicaoRotaLaboratorio` já retorna `rota_proposta` completo; o `central.php` lê `p.observacao` para renderizar abaixo do cliente. Zero migration, zero mudança de backend.

**Tech Stack:** PHP (template `editar_rota.php`), CSS inline em `_layout.php`, JS inline IIFE em `editar_rota.php`, jQuery inline em `central.php`

---

## Arquivos modificados

| Arquivo | Mudança |
|---|---|
| `html/_layout.php` | CSS: bottom sheet + `.parada-obs` + `.btn-otimizar-daqui.tem-obs` |
| `html/editar_rota.php` | PHP: `.parada-obs` nos `<li>`, `data-pode-otimizar`, HTML dos 3 novos overlays; JS: handler ⋮, bottom sheet, confirm modal, obs modal, serializer |
| `Central/html/central.php` | CSS: `.acompanhar-parada-obs`; JS: `obsMap` + célula cliente |

---

### Task 1: CSS em `_layout.php`

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/html/_layout.php:1623-1624`

- [ ] **Step 1: Inserir CSS do bottom sheet e das observações antes de `</style>`**

Localizar (linha 1623-1624):
```css
  .enviar-modal-btn-ok:hover { filter: brightness(1.12); }
</style>
```

Substituir por:
```css
  .enviar-modal-btn-ok:hover { filter: brightness(1.12); }

  /* ============ Bottom sheet de ações por parada ============ */
  .parada-sheet-overlay {
    position: fixed;
    inset: 0;
    z-index: 70;
    background: rgba(0,0,0,0.55);
    display: flex;
    align-items: flex-end;
    animation: seq-modal-fade 0.18s ease;
  }
  .parada-sheet-overlay.oculto { display: none; }
  .parada-sheet {
    width: 100%;
    background: #13103a;
    border-radius: 18px 18px 0 0;
    padding: 20px 16px 28px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    animation: seq-modal-pop 0.22s ease;
  }
  .parada-sheet-titulo {
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--primary-soft);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .parada-sheet-btn {
    width: 100%;
    text-align: left;
    background: rgba(139,92,246,0.1);
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    padding: 14px 16px;
    font-size: 1rem;
    color: #ffffff;
    cursor: pointer;
    margin-top: 0;
  }
  .parada-sheet-btn:active { background: rgba(139,92,246,0.22); }
  .parada-sheet-cancelar {
    width: 100%;
    background: transparent;
    border: none;
    color: var(--muted-2);
    font-size: 0.95rem;
    padding: 10px;
    margin-top: 4px;
    cursor: pointer;
    box-shadow: none;
  }

  /* ============ Observação por parada — card da rota ============ */
  .parada-obs {
    font-size: 0.78rem;
    color: rgba(251,191,36,0.85);
    font-style: italic;
    margin-top: 3px;
    word-break: break-word;
    white-space: pre-wrap;
  }
  .parada-obs:empty { display: none; }

  /* Indicador âmbar no ⋮ quando há observação */
  .btn-otimizar-daqui.tem-obs {
    border-color: var(--warning);
    background: rgba(245,158,11,0.15);
    color: var(--warning);
  }
</style>
```

- [ ] **Step 2: Verificar que o arquivo não tem erros de sintaxe PHP**

```bash
php -l componentes/Missoes/Logistica/sinais/Edicaorota/html/_layout.php
```

Saída esperada: `No syntax errors detected`

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/html/_layout.php
git commit -m "feat(edicaorota): CSS bottom sheet de ações por parada e observação"
```

---

### Task 2: PHP — `<li>` cards e novos overlays em `editar_rota.php`

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php`

- [ ] **Step 1: Adicionar `.parada-obs` após `.cliente` e mudar condição do botão ⋮**

Localizar (linhas 306-368):
```php
              <?php if (!empty($parada['cliente'])): ?>
              <div class="cliente"><?= htmlspecialchars((string) $parada['cliente']) ?></div>
              <?php endif; ?>
```

Substituir por:
```php
              <?php if (!empty($parada['cliente'])): ?>
              <div class="cliente"><?= htmlspecialchars((string) $parada['cliente']) ?></div>
              <?php endif; ?>
              <div class="parada-obs"></div>
```

Depois, localizar (linhas 365-367):
```php
              <?php if ($idx < count($rotaAtual) - 1): ?>
              <button type="button" class="btn-otimizar-daqui" title="Otimizar a partir desta parada" aria-label="Otimizar a partir desta parada">⋮</button>
              <?php endif; ?>
```

Substituir por:
```php
              <button type="button" class="btn-otimizar-daqui"
                      data-pode-otimizar="<?= $idx < count($rotaAtual) - 1 ? 'true' : 'false' ?>"
                      title="Ações desta parada" aria-label="Ações desta parada">⋮</button>
```

**Nota:** o botão agora aparece em TODAS as paradas (inclusive a última). A opção "Otimizar" no sheet fica oculta para a última via JS.

- [ ] **Step 2: Adicionar os 3 overlays ao DOM, após `</div><!-- /parada-main -->` + `.enviar-modal-overlay` existente**

Localizar o fechamento do `enviar-modal-overlay` (linha ~436):
```html
  </div>
</div>

<script>
window.__rotaOrigem
```

Substituir por:
```html
  </div>
</div>

<!-- Bottom sheet de ações por parada -->
<div class="parada-sheet-overlay oculto" id="paradaSheetOverlay" role="dialog" aria-modal="true">
  <div class="parada-sheet" id="paradaSheet">
    <div class="parada-sheet-titulo" id="paradaSheetTitulo">— —</div>
    <button type="button" class="parada-sheet-btn" id="paradaSheetOtimizar">
      ✏️ Otimizar rota daqui
    </button>
    <button type="button" class="parada-sheet-btn" id="paradaSheetObs">
      📝 Observação da parada
    </button>
    <button type="button" class="parada-sheet-cancelar" id="paradaSheetCancelar">
      Cancelar
    </button>
  </div>
</div>

<!-- Modal de confirmação de otimização -->
<div class="enviar-modal-overlay oculto" id="otimizarConfirmOverlay"
     role="dialog" aria-modal="true">
  <div class="enviar-modal" role="document">
    <div class="enviar-modal-titulo">Otimizar a partir daqui?</div>
    <div class="enviar-modal-sub">
      A ordem das entregas a partir de <strong id="otimizarConfirmCliente">—</strong>
      será recalculada pelo mapa.
    </div>
    <div class="enviar-modal-acoes">
      <button type="button" class="enviar-modal-btn enviar-modal-btn-cancelar"
              id="otimizarConfirmCancelar">Cancelar</button>
      <button type="button" class="enviar-modal-btn enviar-modal-btn-ok"
              id="otimizarConfirmOk">Otimizar</button>
    </div>
  </div>
</div>

<!-- Modal de observação individual da parada -->
<div class="enviar-modal-overlay oculto" id="paradaObsOverlay"
     role="dialog" aria-modal="true">
  <div class="enviar-modal" role="document">
    <div class="enviar-modal-titulo">📝 Observação da parada</div>
    <div class="enviar-modal-sub" id="paradaObsCliente">—</div>
    <textarea id="paradaObsTexto" class="enviar-modal-textarea"
              placeholder="Ex: entregar no fundo da loja, interfone 12…"
              rows="3"></textarea>
    <div class="enviar-modal-acoes">
      <button type="button" class="enviar-modal-btn enviar-modal-btn-cancelar"
              id="paradaObsCancelar">Cancelar</button>
      <button type="button" class="enviar-modal-btn enviar-modal-btn-ok"
              id="paradaObsSalvar">Salvar</button>
    </div>
  </div>
</div>

<script>
window.__rotaOrigem
```

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

```bash
php -l componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php
```

Saída esperada: `No syntax errors detected`

- [ ] **Step 4: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php
git commit -m "feat(edicaorota): HTML parada-obs, data-pode-otimizar e overlays de ações"
```

---

### Task 3: JS — handler ⋮, bottom sheet, modais e serializer em `editar_rota.php`

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php`

Este task adiciona uma nova IIFE **antes** do `<?php $conteudo = ob_get_clean();` (linha ~1774). A IIFE do `enviarModal` existente permanece intacta.

- [ ] **Step 1: Mudar o handler existente do clique em `.btn-otimizar-daqui`**

Localizar (linhas 1547-1553):
```js
  lista.addEventListener('click', function (e) {
    var btn = e.target.closest('.btn-otimizar-daqui');
    if (!btn || btn.disabled) return;
    e.stopPropagation();
    var li = btn.closest('li');
    if (li) otimizarDaqui(btn, li);
  });
```

Substituir por:
```js
  lista.addEventListener('click', function (e) {
    var btn = e.target.closest('.btn-otimizar-daqui');
    if (!btn || btn.disabled) return;
    e.stopPropagation();
    var li = btn.closest('li');
    if (li) abrirParadaSheet(btn, li);
  });
```

- [ ] **Step 2: Adicionar a IIFE do bottom sheet + modais logo antes de `<?php $conteudo = ob_get_clean();`**

Localizar:
```js
})();
</script>
<?php
$conteudo = ob_get_clean();
```

**Nota:** há duas `})();` no arquivo (uma do enviarModal IIFE e uma do expira/rascunho IIFE). A que precede `$conteudo = ob_get_clean()` é a do enviarModal. Localizar exatamente:
```js
  textarea.addEventListener('keydown', function (ev) {
    if (ev.key === 'Escape') fecharModal();
  });
})();
</script>
<?php
$conteudo = ob_get_clean();
```

Substituir por:
```js
  textarea.addEventListener('keydown', function (ev) {
    if (ev.key === 'Escape') fecharModal();
  });
})();
</script>
<script>
(function () {
  var _sheetBtn = null;
  var _sheetLi  = null;

  var sheetOverlay    = document.getElementById('paradaSheetOverlay');
  var sheetTitulo     = document.getElementById('paradaSheetTitulo');
  var btnSheetOtim    = document.getElementById('paradaSheetOtimizar');
  var btnSheetObs     = document.getElementById('paradaSheetObs');
  var btnSheetCancel  = document.getElementById('paradaSheetCancelar');

  var otimOverlay     = document.getElementById('otimizarConfirmOverlay');
  var otimCliente     = document.getElementById('otimizarConfirmCliente');
  var btnOtimCancel   = document.getElementById('otimizarConfirmCancelar');
  var btnOtimOk       = document.getElementById('otimizarConfirmOk');

  var obsOverlay      = document.getElementById('paradaObsOverlay');
  var obsCliente      = document.getElementById('paradaObsCliente');
  var obsTexto        = document.getElementById('paradaObsTexto');
  var btnObsCancel    = document.getElementById('paradaObsCancelar');
  var btnObsSalvar    = document.getElementById('paradaObsSalvar');

  if (!sheetOverlay || !otimOverlay || !obsOverlay) return;

  // ---- Bottom sheet ----

  window.abrirParadaSheet = function (btn, li) {
    _sheetBtn = btn;
    _sheetLi  = li;
    sheetTitulo.textContent =
      (li.querySelector('.cliente') || li.querySelector('.titulo'))
        ?.textContent?.trim() || 'Parada';
    // Ocultar "Otimizar" na última parada
    btnSheetOtim.style.display = btn.dataset.podeOtimizar === 'true' ? '' : 'none';
    sheetOverlay.classList.remove('oculto');
  };

  function fecharSheet() { sheetOverlay.classList.add('oculto'); }

  btnSheetCancel.addEventListener('click', fecharSheet);
  sheetOverlay.addEventListener('click', function (e) {
    if (e.target === sheetOverlay) fecharSheet();
  });

  // ---- Modal de confirmação de otimização ----

  btnSheetOtim.addEventListener('click', function () {
    fecharSheet();
    otimCliente.textContent =
      (_sheetLi?.querySelector('.cliente') || _sheetLi?.querySelector('.titulo'))
        ?.textContent?.trim() || '—';
    otimOverlay.classList.remove('oculto');
  });

  btnOtimCancel.addEventListener('click', function () {
    otimOverlay.classList.add('oculto');
  });
  otimOverlay.addEventListener('click', function (e) {
    if (e.target === otimOverlay) otimOverlay.classList.add('oculto');
  });
  btnOtimOk.addEventListener('click', function () {
    otimOverlay.classList.add('oculto');
    if (_sheetBtn && _sheetLi) otimizarDaqui(_sheetBtn, _sheetLi);
  });

  // ---- Modal de observação da parada ----

  btnSheetObs.addEventListener('click', function () {
    fecharSheet();
    obsCliente.textContent =
      (_sheetLi?.querySelector('.cliente') || _sheetLi?.querySelector('.titulo'))
        ?.textContent?.trim() || '—';
    obsTexto.value = _sheetLi?.dataset.observacao || '';
    obsOverlay.classList.remove('oculto');
    setTimeout(function () { obsTexto.focus(); }, 80);
  });

  btnObsCancel.addEventListener('click', function () {
    obsOverlay.classList.add('oculto');
  });
  obsOverlay.addEventListener('click', function (e) {
    if (e.target === obsOverlay) obsOverlay.classList.add('oculto');
  });
  btnObsSalvar.addEventListener('click', function () {
    var texto = obsTexto.value.trim();
    if (_sheetLi) {
      _sheetLi.dataset.observacao = texto;
      var obsEl = _sheetLi.querySelector('.parada-obs');
      if (obsEl) obsEl.textContent = texto;
      if (_sheetBtn) _sheetBtn.classList.toggle('tem-obs', texto.length > 0);
    }
    obsOverlay.classList.add('oculto');
  });

  document.addEventListener('keydown', function (e) {
    if (e.key !== 'Escape') return;
    if (!sheetOverlay.classList.contains('oculto'))  { fecharSheet(); return; }
    if (!otimOverlay.classList.contains('oculto'))   { otimOverlay.classList.add('oculto'); return; }
    if (!obsOverlay.classList.contains('oculto'))    { obsOverlay.classList.add('oculto'); return; }
  });
})();
</script>
<?php
$conteudo = ob_get_clean();
```

- [ ] **Step 3: Atualizar o submit serializer para incluir `observacao`**

Localizar (linhas 1141-1146):
```js
    var paradas = Array.prototype.map.call(lista.querySelectorAll('li'), function (li) {
      return {
        id: li.dataset.id,
        pedido_id: li.dataset.pedido || null,
      };
    });
```

Substituir por:
```js
    var paradas = Array.prototype.map.call(lista.querySelectorAll('li'), function (li) {
      return {
        id: li.dataset.id,
        pedido_id: li.dataset.pedido || null,
        observacao: li.dataset.observacao || null,
      };
    });
```

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

```bash
php -l componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php
```

Saída esperada: `No syntax errors detected`

- [ ] **Step 5: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php
git commit -m "feat(edicaorota): JS bottom sheet, modais de ações e observação por parada"
```

---

### Task 4: CSS + JS em `central.php` — observação na tabela do Acompanhamento

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php`

- [ ] **Step 1: Adicionar CSS `.acompanhar-parada-obs` após `.acompanhar-label`**

Localizar (linha ~3129):
```css
    .acompanhar-label { color: #93c5fd; }
    #acompanhar-motorista-modal .g-painel-flutuante-header h3.acompanhar-titulo { color: #93c5fd; }
```

Substituir por:
```css
    .acompanhar-label { color: #93c5fd; }
    #acompanhar-motorista-modal .g-painel-flutuante-header h3.acompanhar-titulo { color: #93c5fd; }
    .acompanhar-parada-obs {
        font-size: 0.78rem;
        color: rgba(251,191,36,0.8);
        font-style: italic;
        margin-top: 2px;
        white-space: normal;
        word-break: break-word;
    }
```

- [ ] **Step 2: Adicionar `obsMap` e atualizar célula "Cliente" na tabela**

Localizar (linhas ~5882-5904):
```js
        const $tbody = $('#acompanhar-tbody').empty();
        const ids = propondo.length ? propondo.map(p => String(p.id)) : atual.map(p => String(p.id));
        const vazio = '<span class="empty">—</span>';
        ids.forEach((id, idx) => {
            const m = atual.find(p => String(p.id) === id) || { cliente: '', pedido: id, valor: null };
```

Substituir por:
```js
        const $tbody = $('#acompanhar-tbody').empty();
        const ids = propondo.length ? propondo.map(p => String(p.id)) : atual.map(p => String(p.id));
        const obsMap = new Map(propondo.map(p => [String(p.id), p.observacao || null]));
        const vazio = '<span class="empty">—</span>';
        ids.forEach((id, idx) => {
            const m = atual.find(p => String(p.id) === id) || { cliente: '', pedido: id, valor: null };
```

Depois, localizar a célula do cliente (linhas ~5897-5904):
```js
            const cliente = m.cliente ? String(m.cliente) : vazio;
            const valor   = (typeof m.valor === 'number' && !isNaN(m.valor))
                ? m.valor.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
                : vazio;
            $tbody.append(`
                <tr>
                    <td class="text-center text-gray-500">${idx + 1}</td>
                    <td class="truncate max-w-[180px]">${cliente}</td>
```

Substituir por:
```js
            const cliente = m.cliente ? String(m.cliente) : vazio;
            const obsRaw  = obsMap.get(id);
            const obsHtml = obsRaw
                ? '<div class="acompanhar-parada-obs">' + $('<div>').text(obsRaw).html() + '</div>'
                : '';
            const valor   = (typeof m.valor === 'number' && !isNaN(m.valor))
                ? m.valor.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
                : vazio;
            $tbody.append(`
                <tr>
                    <td class="text-center text-gray-500">${idx + 1}</td>
                    <td class="max-w-[180px]">${cliente}${obsHtml}</td>
```

**Nota:** Remove `truncate` da célula do cliente para que o texto da observação (que quebra linha) não fique cortado.

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

```bash
php -l componentes/Missoes/Logistica/sinais/Central/html/central.php
```

Saída esperada: `No syntax errors detected`

- [ ] **Step 4: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(edicaorota): exibir observação por parada no Acompanhamento do gestor"
```

---

## Verificação manual

Após todos os tasks:

1. Abrir a tela do motorista (`/edicao-rota/<token>`)
2. Clicar no ⋮ de qualquer parada — bottom sheet deve deslizar de baixo
3. Clicar "✏️ Otimizar rota daqui" — modal de confirmação deve abrir; clicar "Cancelar" fecha sem ação; clicar "Otimizar" executa a otimização
4. Clicar no ⋮ novamente → "📝 Observação da parada" — textarea abre pré-vazia; digitar "entregar no fundo da loja"; clicar "Salvar"
5. Verificar que o texto aparece abaixo do nome do cliente no card, em âmbar itálico
6. Verificar que o ⋮ ganhou borda âmbar (`.tem-obs`)
7. Reabrir a obs → textarea já preenchida com o texto anterior
8. Enviar a rota (modal de confirmação geral)
9. No `central.php`, abrir Acompanhamento — verificar que a observação aparece abaixo do cliente na tabela, em âmbar itálico
10. Para a última parada da rota: clicar ⋮ — bottom sheet abre mas opção "Otimizar" está oculta, apenas "Observação" aparece
