// app/javascript/controllers/validacao_controller.js // Stimulus controller — Wizard Passo 2 (validação de entregas) // Atualização em tempo real: toggles, ações em massa, barra de progresso, resumo lateral. import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "linha", "checkbox", "selecionarTodos", "toggles", "barra", "percentual", "contadorClassificadas", "contadorTotal", "qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal" ] static values = { url: String, // POST classificar massaUrl: String, // POST classificar_em_massa motorista: String, total: Number } connect() { // Carrega estado inicial renderizado pelo servidor const el = document.getElementById("validacao-estado-inicial") if (el) { const estado = JSON.parse(el.textContent) this.atualizarResumo(estado) } } // ── Toggle individual ────────────────────────────────────── async classificar(event) { const btn = event.currentTarget const tracking = btn.dataset.tracking const tipo = btn.dataset.tipo btn.disabled = true try { const resp = await this.post(this.urlValue, { tracking_id: tracking, tipo: tipo, motorista: this.motoristaValue }) // resp.ativo indica se o pilar ficou ligado ou desligado (toggle) this.setToggle(tracking, tipo, resp.ativo) this.atualizarResumo(resp) } catch (e) { alert("Erro ao classificar entrega. Tente novamente.") } finally { btn.disabled = false } } // ── Marcar todos como Normal ─────────────────────────────── async marcarTodos(event) { const tipo = event.currentTarget.dataset.tipo if (!confirm(`Marcar TODAS as entregas como ${this.labelTipo(tipo)}?`)) return const resp = await this.post(this.massaUrlValue, { tipo: tipo, motorista: this.motoristaValue, tracking_ids: [] // vazio = todas }) this.linhaTargets.forEach(l => this.setToggle(l.dataset.tracking, tipo, true)) this.atualizarResumo(resp) } // ── Selecionar / desmarcar todos os checkboxes ───────────── selecionarTodos(event) { const marcar = event.currentTarget.checked this.checkboxTargets.forEach(c => { c.checked = marcar }) } // ── Marcar selecionados como X ───────────────────────────── async marcarSelecionados(event) { const tipo = event.currentTarget.dataset.tipo const ids = this.checkboxTargets.filter(c => c.checked).map(c => c.value) if (ids.length === 0) { alert("Selecione ao menos uma entrega (checkbox à esquerda).") return } const resp = await this.post(this.massaUrlValue, { tipo: tipo, motorista: this.motoristaValue, tracking_ids: ids }) ids.forEach(id => this.setToggle(id, tipo, true)) this.checkboxTargets.forEach(c => c.checked = false) if (this.hasSelecionarTodosTarget) this.selecionarTodosTarget.checked = false this.atualizarResumo(resp) } // ── Helpers ──────────────────────────────────────────────── async post(url, body) { const resp = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify(body) }) if (!resp.ok) throw new Error(`HTTP ${resp.status}`) return resp.json() } // Liga/desliga UM pilar (os demais pilares da entrega ficam como estão) setToggle(tracking, tipo, ativo) { const grupo = this.togglesTargets.find(t => t.dataset.tracking === tracking) if (!grupo) return const btn = grupo.querySelector(`.toggle-btn[data-tipo="${tipo}"]`) if (btn) { btn.classList.toggle("ring-2", ativo) btn.classList.toggle("ring-green-400", ativo) btn.classList.toggle("scale-105", ativo) btn.classList.toggle("opacity-50", !ativo) } // a linha está "classificada" se tiver qualquer pilar ligado const algumAtivo = grupo.querySelectorAll(".toggle-btn.ring-2").length > 0 const linha = this.linhaTargets.find(l => l.dataset.tracking === tracking) if (linha) linha.dataset.classificada = algumAtivo ? "1" : "0" } atualizarResumo(dados) { const porTipo = dados.por_tipo || {} this.qtdNormalTarget.textContent = porTipo["entrega_normal"] || 0 this.qtdRetiradaTarget.textContent = porTipo["retirada"] || 0 this.qtdBonusTarget.textContent = porTipo["bonus"] || 0 this.qtdDescontoTarget.textContent = porTipo["desconto"] || 0 const valor = dados.valor_total || 0 this.valorTotalTarget.textContent = "R$ " + valor.toFixed(2).replace(".", ",") const classificadas = dados.classificadas || 0 this.contadorClassificadasTarget.textContent = classificadas const total = this.totalValue || parseInt(this.contadorTotalTarget.textContent) const pct = total > 0 ? Math.round((classificadas / total) * 100) : 0 this.barraTarget.style.width = `${Math.min(pct, 100)}%` this.percentualTarget.textContent = `${Math.min(pct, 100)}%` if (pct >= 100) { this.barraTarget.classList.remove("bg-orange-500") this.barraTarget.classList.add("bg-green-500") } } labelTipo(tipo) { return { entrega_normal: "Normal", retirada: "Retirada", bonus: "Bônus", desconto: "Desconto" }[tipo] || tipo } }