!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