191 lines
8.7 KiB
Plaintext
191 lines
8.7 KiB
Plaintext
<%# app/views/admin/edicao_lancamentos/show.html.erb %>
|
|
<div class="space-y-6" data-buscar-url="<%= buscar_admin_edicao_lancamento_path %>">
|
|
|
|
<%# Header %>
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-white">Editar Lançamento</h1>
|
|
<p class="text-gray-400 text-sm mt-0.5">Corrige status, motivo e comentários de uma entrega direto no SimpliRoute</p>
|
|
</div>
|
|
|
|
<%# Flash %>
|
|
<%= render 'shared/flash' %>
|
|
|
|
<%# Aviso de sincronização %>
|
|
<div class="p-4 bg-amber-900/20 border border-amber-500/40 rounded-xl text-amber-200/90 text-sm flex items-start gap-2">
|
|
<span class="text-base leading-none">⚠️</span>
|
|
<p>As alterações são gravadas no <strong>SimpliRoute na hora</strong>, mas o painel e as
|
|
consolidações só refletem <strong>após a próxima sincronização</strong> da base de rastreio.
|
|
Toda alteração fica registrada na auditoria.</p>
|
|
</div>
|
|
|
|
<%# Busca por NF %>
|
|
<div class="bg-[#111] border border-white/5 rounded-2xl p-5">
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Buscar entrega pela NF</label>
|
|
<div class="flex gap-2">
|
|
<input id="campo-nf" type="text" inputmode="numeric" placeholder="Ex: 79774"
|
|
class="flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl px-4 py-3 text-white
|
|
placeholder-gray-600 focus:outline-none focus:border-orange-500 min-h-[48px]">
|
|
<button id="btn-buscar" type="button"
|
|
class="px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold rounded-xl
|
|
transition-colors min-h-[48px] whitespace-nowrap">
|
|
Buscar
|
|
</button>
|
|
</div>
|
|
<p id="busca-erro" class="hidden mt-2 text-sm text-red-400"></p>
|
|
<p id="busca-loading" class="hidden mt-2 text-sm text-gray-400">Consultando SimpliRoute…</p>
|
|
</div>
|
|
|
|
<%# Formulário de edição (oculto até achar a visita) %>
|
|
<%= form_with url: atualizar_admin_edicao_lancamento_path, method: :patch,
|
|
html: { id: 'form-edicao', class: 'hidden bg-[#111] border border-white/5 rounded-2xl p-5 space-y-5' } do %>
|
|
<input type="hidden" name="visit_id" id="f-visit-id">
|
|
|
|
<%# Cabeçalho da visita (somente leitura) %>
|
|
<div class="border-b border-white/5 pb-4">
|
|
<p class="text-white font-semibold" id="f-titulo">—</p>
|
|
<p class="text-gray-500 text-sm" id="f-endereco">—</p>
|
|
<p class="text-gray-600 text-xs mt-1">NF <span id="f-nf">—</span> · visita <span id="f-id">—</span></p>
|
|
</div>
|
|
|
|
<div class="grid sm:grid-cols-2 gap-4">
|
|
<%# Status %>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-1.5">Status</label>
|
|
<select name="status" id="f-status"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
|
|
focus:outline-none focus:border-orange-500 min-h-[48px]">
|
|
<% { 'completed' => 'Completa', 'failed' => 'Falha', 'pending' => 'Pendente',
|
|
'partial' => 'Parcial', 'canceled' => 'Cancelada' }.each do |v, label| %>
|
|
<option value="<%= v %>"><%= label %></option>
|
|
<% end %>
|
|
</select>
|
|
</div>
|
|
|
|
<%# Motivo (só faz sentido em falha) %>
|
|
<div id="wrap-motivo">
|
|
<label class="block text-sm font-medium text-gray-300 mb-1.5">Motivo (insucesso)</label>
|
|
<select name="checkout_observation" id="f-motivo"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
|
|
focus:outline-none focus:border-orange-500 min-h-[48px]">
|
|
<option value="">— sem motivo —</option>
|
|
<% @motivos.each do |m| %>
|
|
<option value="<%= m['id'] %>"><%= m['label'] %></option>
|
|
<% end %>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<%# Comentário livre %>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-1.5">Comentário do checkout</label>
|
|
<textarea name="checkout_comment" id="f-comment" rows="2"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
|
|
placeholder-gray-600 focus:outline-none focus:border-orange-500"></textarea>
|
|
</div>
|
|
|
|
<%# Observações internas %>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-1.5">Observações (notes)</label>
|
|
<textarea name="notes" id="f-notes" rows="2"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
|
|
placeholder-gray-600 focus:outline-none focus:border-orange-500"></textarea>
|
|
</div>
|
|
|
|
<%# Data/geo do checkout (avançado) %>
|
|
<details class="rounded-xl border border-white/5 bg-[#0d0d0d] p-4">
|
|
<summary class="cursor-pointer text-sm text-gray-400">Data e localização do checkout (avançado)</summary>
|
|
<div class="grid sm:grid-cols-3 gap-4 mt-4">
|
|
<div>
|
|
<label class="block text-xs text-gray-400 mb-1.5">Data/hora</label>
|
|
<input type="datetime-local" name="checkout_time" id="f-checkout-time"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2.5 text-white
|
|
focus:outline-none focus:border-orange-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs text-gray-400 mb-1.5">Latitude</label>
|
|
<input type="text" inputmode="decimal" name="checkout_latitude" id="f-lat"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2.5 text-white
|
|
focus:outline-none focus:border-orange-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-xs text-gray-400 mb-1.5">Longitude</label>
|
|
<input type="text" inputmode="decimal" name="checkout_longitude" id="f-lng"
|
|
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2.5 text-white
|
|
focus:outline-none focus:border-orange-500">
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<div class="flex justify-end pt-2">
|
|
<button type="submit"
|
|
class="px-6 py-3 bg-green-600 hover:bg-green-500 text-white font-semibold rounded-xl
|
|
transition-colors min-h-[48px]">
|
|
Salvar no SimpliRoute
|
|
</button>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const wrap = document.querySelector('[data-buscar-url]');
|
|
const buscarU = wrap.dataset.buscarUrl;
|
|
const campo = document.getElementById('campo-nf');
|
|
const btn = document.getElementById('btn-buscar');
|
|
const erro = document.getElementById('busca-erro');
|
|
const loading = document.getElementById('busca-loading');
|
|
const form = document.getElementById('form-edicao');
|
|
|
|
const set = (id, v) => { document.getElementById(id).value = (v ?? ''); };
|
|
const txt = (id, v) => { document.getElementById(id).textContent = (v ?? '—'); };
|
|
|
|
function toggleMotivo() {
|
|
const falha = document.getElementById('f-status').value === 'failed';
|
|
document.getElementById('wrap-motivo').style.opacity = falha ? '1' : '0.45';
|
|
}
|
|
document.getElementById('f-status').addEventListener('change', toggleMotivo);
|
|
|
|
async function buscar() {
|
|
const nf = campo.value.trim();
|
|
erro.classList.add('hidden');
|
|
if (!nf) { erro.textContent = 'Informe o número da NF.'; erro.classList.remove('hidden'); return; }
|
|
|
|
loading.classList.remove('hidden');
|
|
form.classList.add('hidden');
|
|
try {
|
|
const resp = await fetch(buscarU + '?nf=' + encodeURIComponent(nf), { headers: { 'Accept': 'application/json' } });
|
|
const data = await resp.json();
|
|
if (!data.ok) {
|
|
erro.textContent = data.erro || 'Não foi possível localizar a entrega.';
|
|
erro.classList.remove('hidden');
|
|
return;
|
|
}
|
|
const v = data.visita;
|
|
set('f-visit-id', v.id);
|
|
txt('f-titulo', v.titulo);
|
|
txt('f-endereco', v.endereco);
|
|
txt('f-nf', v.nf);
|
|
txt('f-id', v.id);
|
|
set('f-status', v.status || 'completed');
|
|
set('f-motivo', v.checkout_observation || '');
|
|
set('f-comment', v.checkout_comment || '');
|
|
set('f-notes', v.notes || '');
|
|
set('f-lat', v.checkout_latitude ?? '');
|
|
set('f-lng', v.checkout_longitude ?? '');
|
|
// ISO -> valor de datetime-local (YYYY-MM-DDTHH:MM)
|
|
set('f-checkout-time', v.checkout_time ? String(v.checkout_time).slice(0, 16) : '');
|
|
toggleMotivo();
|
|
form.classList.remove('hidden');
|
|
} catch (e) {
|
|
erro.textContent = 'Erro de conexão ao buscar a NF.';
|
|
erro.classList.remove('hidden');
|
|
} finally {
|
|
loading.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
btn.addEventListener('click', buscar);
|
|
campo.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); buscar(); } });
|
|
})();
|
|
</script>
|