# Spec: Botão Enviar Fixo + Modal de Confirmação — Tela Reordenar Rota

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

## Contexto

A tela de reordenação de rota (`editar_rota.php`) usa um layout mobile-first onde `#formRota` é o scroll container no mobile (`overflow-y: auto; height: 100%`). O botão "Enviar" estava no final do conteúdo, fora da área visível quando há muitas paradas. No desktop (≥900px), o viewport é o scroll container.

## Objetivo

1. Manter o botão de envio fixo na parte inferior da tela (mobile e desktop) — estilo Uber.
2. Adicionar modal de confirmação para evitar envios acidentais.
3. Permitir que o motorista registre uma justificativa opcional para a alteração de rota.

## Abordagem escolhida: `position: sticky` dentro de `#formRota`

`position: sticky; bottom: 0` no último filho de `#formRota`. No mobile, `#formRota` é o scroll container, então o elemento ancora no fundo do painel. No desktop (overflow: visible), o viewport é o scroll container e o sticky ancora na base da tela. Não usa `position: fixed` — evita conflito com o painel do mapa (Leaflet) e com o modal de sequência já existente.

## Arquivos modificados

| Arquivo | Mudança |
|---|---|
| `html/editar_rota.php` | Remove `<button type="submit">` do `.card`; adiciona `<input hidden>` de justificativa, barra `.enviar-fixo` e `<div class="enviar-modal-overlay">` |
| `html/_layout.php` | CSS da barra sticky, modal de confirmação e `padding-bottom` do `#formRota` no mobile |

## HTML — Barra sticky

Último filho de `#formRota`, fora de qualquer `.card`:

```html
<div class="enviar-fixo" id="enviarFixo">
  <button type="button" class="enviar-fixo-obs" id="btnAbrirConfirmacao"
          aria-label="Adicionar observação e enviar rota">
    📝
  </button>
  <button type="button" class="enviar-fixo-btn" id="btnEnviarRota">
    Enviar nova ordem para aprovação
  </button>
</div>
```

- O ícone 📝 e o botão "Enviar" abrem o mesmo modal de confirmação.
- Quando o motorista preenche a justificativa, o ícone ganha a classe `.tem-obs` (borda âmbar) como indicador visual.

## HTML — Modal de confirmação

Adicionado ao DOM de `editar_rota.php` junto ao `.seq-modal-overlay` já existente:

```html
<div class="enviar-modal-overlay oculto" id="enviarModalOverlay"
     role="dialog" aria-modal="true" aria-labelledby="enviarModalTitulo">
  <div class="enviar-modal" role="document">
    <div class="enviar-modal-titulo" id="enviarModalTitulo">Confirmar envio?</div>
    <div class="enviar-modal-sub">
      A nova sequência de entregas será enviada para aprovação.
    </div>
    <label class="enviar-modal-label" for="enviarModalTexto">
      📝 Motivo da alteração
      <span class="enviar-modal-opcional">(opcional)</span>
    </label>
    <textarea id="enviarModalTexto" class="enviar-modal-textarea"
              placeholder="Ex: cliente pediu mudança de horário…"
              rows="3"></textarea>
    <div class="enviar-modal-acoes">
      <button type="button"
              class="enviar-modal-btn enviar-modal-btn-cancelar"
              id="enviarModalCancelar">Cancelar</button>
      <button type="button"
              class="enviar-modal-btn enviar-modal-btn-ok"
              id="enviarModalConfirmar">Confirmar envio</button>
    </div>
  </div>
</div>
```

## HTML — Hidden field

Adicionado ao `#formRota` logo após `#rotaProposta`:

```html
<input type="hidden" name="justificativa_motorista" id="justificativaMotorista" value="">
```

## CSS (em `_layout.php`)

### Barra sticky

```css
.enviar-fixo {
  position: sticky;
  bottom: 0;
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 12px 0 16px;
  background: linear-gradient(to top, rgba(10,8,32,0.97) 70%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  margin-top: 8px;
}
.enviar-fixo-btn {
  flex: 1;
  margin-top: 0;
}
.enviar-fixo-obs {
  width: 52px;
  min-width: 52px;
  margin-top: 0;
  background: rgba(139, 92, 246, 0.12);
  border: 1px solid var(--border-strong);
  box-shadow: none;
  font-size: 1.3rem;
  padding: 0;
  border-radius: 10px;
}
.enviar-fixo-obs.tem-obs {
  border-color: var(--warning);
  background: rgba(245, 158, 11, 0.18);
  box-shadow: 0 0 10px rgba(245, 158, 11, 0.25);
}
```

### Padding-bottom do form

No mobile, o padding vai no scroll container (`#formRota`). No desktop, vai no próprio form (sem override de overflow):

```css
/* Mobile: form tem overflow-y:auto, precisamos de espaço abaixo da lista */
@media (max-width: 899px) {
  .layout-rota > #formRota {
    padding-bottom: 80px;
  }
}
/* Desktop: form rola com a página; espaço para o sticky não cobrir o último card */
@media (min-width: 900px) {
  #formRota {
    padding-bottom: 80px;
  }
}
```

### Modal de confirmação

Reutiliza as variáveis e animações do `.seq-modal` existente. Diferenças:

```css
.enviar-modal-overlay { /* idêntico ao .seq-modal-overlay */ }
.enviar-modal         { /* idêntico ao .seq-modal, max-width: 360px */ }

.enviar-modal-titulo { /* mesmo estilo que .seq-modal-titulo */ }
.enviar-modal-sub    { /* mesmo estilo que .seq-modal-sub */ }
.enviar-modal-acoes  { /* mesmo estilo que .seq-modal-acoes */ }
.enviar-modal-btn    { /* mesmo estilo que .seq-modal-btn */ }
.enviar-modal-btn-cancelar { /* mesmo estilo que .seq-modal-btn-cancelar */ }
.enviar-modal-btn-ok       { /* mesmo estilo que .seq-modal-btn-ok */ }

.enviar-modal-label {
  display: block;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--primary-soft);
  margin: 0 0 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.enviar-modal-opcional {
  font-size: 0.75rem;
  color: var(--muted-2);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.enviar-modal-textarea {
  width: 100%;
  background: rgba(15, 12, 41, 0.7);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  color: #ffffff;
  font-size: 0.95rem;
  padding: 12px 14px;
  resize: none;
  line-height: 1.5;
  font-family: inherit;
  transition: border-color 0.15s, box-shadow 0.15s;
  margin-bottom: 0;
}
.enviar-modal-textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3);
}
.enviar-modal-textarea::placeholder {
  color: rgba(196, 181, 253, 0.4);
}
```

## JavaScript

IIFE adicionada no final de `editar_rota.php`, após o bloco do SortableJS:

```js
(function () {
  var overlay    = document.getElementById('enviarModalOverlay');
  var textarea   = document.getElementById('enviarModalTexto');
  var btnObs     = document.getElementById('btnAbrirConfirmacao');
  var btnEnviar  = document.getElementById('btnEnviarRota');
  var btnOk      = document.getElementById('enviarModalConfirmar');
  var btnCancel  = document.getElementById('enviarModalCancelar');
  var hiddenJust = document.getElementById('justificativaMotorista');

  function abrirModal() {
    overlay.classList.remove('oculto');
    setTimeout(function () { textarea.focus(); }, 80);
  }

  function fecharModal() {
    overlay.classList.add('oculto');
  }

  function confirmarEnvio() {
    hiddenJust.value = textarea.value.trim();
    fecharModal();
    document.getElementById('formRota').requestSubmit();
  }

  textarea.addEventListener('input', function () {
    btnObs.classList.toggle('tem-obs', textarea.value.trim().length > 0);
  });

  btnEnviar.addEventListener('click', abrirModal);
  btnObs.addEventListener('click', abrirModal);
  btnOk.addEventListener('click', confirmarEnvio);
  btnCancel.addEventListener('click', fecharModal);
  overlay.addEventListener('click', function (ev) {
    if (ev.target === overlay) fecharModal();
  });
  textarea.addEventListener('keydown', function (ev) {
    if (ev.key === 'Escape') fecharModal();
  });
})();
```

`requestSubmit()` dispara o handler existente de `formRota.submit` (que serializa as paradas em `rotaProposta`), garantindo que `justificativa_motorista` vai junto no POST sem mudanças no backend.

## Fora do escopo

- Backend: salvar/exibir a `justificativa_motorista` no banco e nas views do gestor.
- i18n: textos somente em pt-br nesta iteração.
- Validação de conteúdo da justificativa (obrigatoriedade, tamanho máximo).
