!DOCTYPE html html lang=pt-BR head meta charset=UTF-8 meta name=viewport content=width=device-width, initial-scale=1 titleFormulário de Materiais — RJEStitle style body { font-family Arial, sans-serif; background #f5f5f5; margin 0; padding 0; } header { background #007bff; color white; padding 15px; } main { max-width 700px; margin 20px auto; background white; padding 20px; border-radius 8px; box-shadow 0 0 10px rgba(0,0,0,0.1); } h1 { font-size 1.2rem; margin 0; } label { display block; margin-top 10px; font-weight bold; } input, select, button { width 100%; padding 8px; margin-top 5px; box-sizing border-box; } .row { display flex; gap 10px; flex-wrap wrap; } .chip { border 1px solid #ccc; padding 8px; border-radius 6px; display flex; align-items center; gap 6px; background #f8f8f8; } .m-input { width 80px; } button { background #007bff; color white; border none; border-radius 4px; margin-top 20px; cursor pointer; } buttondisabled { opacity 0.6; cursor not-allowed; } .toast { position fixed; right 16px; bottom 16px; background #333; color white; padding 10px 14px; border-radius 8px; display none; } @media (max-width 600px) { main { margin 10px; padding 15px; } } style head body header h1📦 Lançamento de Materiais — RJESh1 header main form id=form !-- CIDADE -- label for=clusterCidade label select id=cluster required option value=Carregando cidades...option select !-- INM -- label for=inmINM (Ticket) label input id=inm required placeholder=Ex. INM-123456 !-- TÉCNICO -- label for=tecnicoEquipe Técnico label input id=tecnico required placeholder=Ex. Equipe Alfa — João !-- DATA -- label for=dataData label input id=data type=date required !-- CABOS -- labelTipos de Cabo (m)label div id=cabos class=rowdiv !-- CAIXAS -- labelCaixas de Emendalabel div class=row label class=chip input type=checkbox id=cx_pequena_chk Pequena label input type=number id=cx_pequena_qtd class=m-input min=0 step=1 placeholder=Qtd disabled label class=chip input type=checkbox id=cx_grande_chk Grande label input type=number id=cx_grande_qtd class=m-input min=0 step=1 placeholder=Qtd disabled div !-- OBSERVAÇÕES -- label for=obsObservaçõeslabel input id=obs placeholder=Local, OT, NAP... button type=submit id=btnEnviar Enviar button form main div id=toast class=toast Lançamento enviado! div script ===================================== CONFIGURAÇÃO RJES ===================================== const APP_URL = 'httpsscript.google.commacrossAKfycbzMzRZNAlMf1V7_H6TTbEhAx96FFuqgVaqREh3Axx0F9GUhV7pilGI28boG26Q9FLGJSAexec'; const BACKEND_URL = APP_URL; const CLUSTERS_URL = APP_URL; const TOKEN = 'Claro123'; ===================================== CARREGAR CIDADES ===================================== function receberClusters(lista) { const select = document.getElementById('cluster'); select.innerHTML = 'option value=Selecione a Cidadeoption'; if (!Array.isArray(lista) lista.length === 0) { const opt = document.createElement('option'); opt.value = ''; opt.textContent = 'Nenhuma cidade cadastrada'; select.appendChild(opt); return; } lista.forEach(nome = { const opt = document.createElement('option'); opt.value = nome; opt.textContent = nome; select.appendChild(opt); }); } (function carregarClusters() { const script = document.createElement('script'); script.src = CLUSTERS_URL + 'action=clusters&callback=receberClusters&t=' + Date.now(); script.onerror = function() { const select = document.getElementById('cluster'); select.innerHTML = 'option value=Erro ao carregar cidadesoption'; }; document.body.appendChild(script); })(); ===================================== TIPOS DE CABO ===================================== const tipos = [ 12, 24, 36, 48, 72, 96, 144 ]; const cabosDiv = document.getElementById('cabos'); const metragem = { m_12 0, m_24 0, m_36 0, m_48 0, m_72 0, m_96 0, m_144 0 }; Criar campos dos cabos tipos.forEach(n = { const chk = document.createElement('input'); chk.type = 'checkbox'; chk.id = `c_${n}`; const lbl = document.createElement('label'); lbl.textContent = `Cabo ${n}`; const input = document.createElement('input'); input.type = 'number'; input.id = `m_${n}`; input.placeholder = 'm'; input.className = 'm-input'; input.min = '0'; input.disabled = true; input.addEventListener('input', () = { metragem[`m_${n}`] = Number(input.value 0); }); chk.addEventListener('change', e = { input.disabled = !e.target.checked; if (!e.target.checked) { input.value = ''; metragem[`m_${n}`] = 0; } }); const wrap = document.createElement('div'); wrap.className = 'chip'; wrap.append( chk, lbl, input ); cabosDiv.appendChild(wrap); }); ===================================== CAIXAS ===================================== const cxPeqChk = document.getElementById('cx_pequena_chk'); const cxPeqQtd = document.getElementById('cx_pequena_qtd'); const cxGraChk = document.getElementById('cx_grande_chk'); const cxGraQtd = document.getElementById('cx_grande_qtd'); cxPeqChk.addEventListener('change', () = { cxPeqQtd.disabled = !cxPeqChk.checked; if (!cxPeqChk.checked) { cxPeqQtd.value = ''; } }); cxGraChk.addEventListener('change', () = { cxGraQtd.disabled = !cxGraChk.checked; if (!cxGraChk.checked) { cxGraQtd.value = ''; } }); ===================================== DATA ATUAL ===================================== function hojeISO() { const d = new Date(); const p = n = String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}`; } document.getElementById('data').value = hojeISO(); ===================================== MENSAGEM ===================================== function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.style.display = 'block'; setTimeout(() = { t.style.display = 'none'; }, 2000); } ===================================== LIMPAR FORMULÁRIO ===================================== function limparFormulario() { document.getElementById('form').reset(); document.getElementById('data').value = hojeISO(); Object.keys(metragem).forEach(k = { metragem[k] = 0; }); document.querySelectorAll( '#cabos input[type=checkbox]' ).forEach(c = { c.checked = false; }); document.querySelectorAll( '#cabos input[type=number]' ).forEach(i = { i.disabled = true; i.value = ''; }); cxPeqChk.checked = false; cxPeqQtd.disabled = true; cxPeqQtd.value = ''; cxGraChk.checked = false; cxGraQtd.disabled = true; cxGraQtd.value = ''; } ===================================== ENVIO ===================================== document .getElementById('form') .addEventListener('submit', async e = { e.preventDefault(); const btn = document.getElementById('btnEnviar'); const payload = { cluster document.getElementById('cluster').value.trim(), inm document.getElementById('inm').value.trim(), tecnico document.getElementById('tecnico').value.trim(), data document.getElementById('data').value, ...metragem, cx_pequena_qtd Number( cxPeqChk.checked (cxPeqQtd.value 0) 0 ), cx_grande_qtd Number( cxGraChk.checked (cxGraQtd.value 0) 0 ), obs document.getElementById('obs').value.trim(), token TOKEN }; CAMPOS OBRIGATÓRIOS if ( !payload.cluster !payload.inm !payload.tecnico !payload.data ) { alert( 'Preencha Cidade, INM, Técnico e Data.' ); return; } VALIDAR SE EXISTE MATERIAL const somaMetros = Object.values(metragem).reduce( (a, b) = a + b, 0 ); if ( somaMetros = 0 && payload.cx_pequena_qtd = 0 && payload.cx_grande_qtd = 0 ) { alert( 'Informe ao menos um cabo ou caixa.' ); return; } try { btn.disabled = true; btn.textContent = 'Enviando...'; await fetch( BACKEND_URL, { method 'POST', mode 'no-cors', headers { 'Content-Type' 'textplain;charset=UTF-8' }, body JSON.stringify(payload) } ); showToast('Lançamento enviado!'); limparFormulario(); } catch(err) { alert('Erro ao enviar ' + err); } finally { btn.disabled = false; btn.textContent = 'Enviar'; } }); script body html