All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m40s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 53s
- Buttons: add to cart (bag with plus), go to payment (lock), remove from the cart (bin). - Delivery: a pin for pickup in Franca, a truck for home delivery. - Payment: card and QR code on the methods, copy on the PIX code, a clock on its countdown, a check on the confirmation. - Status: a check when the files are sent, check, alert, redo and cross in the quality list instead of Unicode symbols, and a check on finished steps. - Kanban: a truck or pin for where an order goes, a speech bubble on the WhatsApp link, a download arrow on the file buttons. All Lucide shapes drawn inline, as in the header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
11 KiB
JavaScript
203 lines
11 KiB
JavaScript
/* Site DTF — The grade shown to the customer, and the reservations attached to it.
|
|
Extracted verbatim from the single inline script in web/index.html.
|
|
Loaded as classic scripts in the order listed there: they share one global
|
|
scope and run top to bottom, exactly as the original did. */
|
|
// ── enviar
|
|
// Avalia sozinho conforme o cliente sobe e preenche. Devolve o que ainda falta,
|
|
// para a fila dizer em texto em vez de travar num botão.
|
|
function avaliar(){
|
|
if(ehFolha()){
|
|
if(!folhas.length) return {pronto:false, falta:'Arraste sua folha montada para começar.'};
|
|
const pdfInvalido=folhas.find(x=>x.measurementError);
|
|
if(pdfInvalido) return {pronto:false, falta:pdfInvalido.f.name+': '+pdfInvalido.measurementError};
|
|
const larga=folhas.find(x=>x.med && x.med.larg > larguraFilme());
|
|
if(larga) return {pronto:false, falta:'A folha '+larga.f.name+' tem '+n1(larga.med.larg)+
|
|
' cm e não cabe no filme de '+n1(larguraFilme())+' cm.'};
|
|
const semMedida=folhas.filter(x=>!(x.m>0)).length;
|
|
if(semMedida) return {pronto:false, falta:'Informe o comprimento de '+semMedida+
|
|
' folha'+(semMedida>1?'s':'')+'.'};
|
|
metros=folhaTotalM();
|
|
reencaixado=false;
|
|
const ans=folhas.map(x=>x.an).filter(Boolean);
|
|
if(ans.length!==folhas.length){
|
|
// A nota precisa cobrir cada folha faturada. Uma folha sem análise não
|
|
// herda a nota/desconto de outra folha que o navegador conseguiu abrir.
|
|
const ausentes=folhas.filter(f=>!f.an);
|
|
const exts=[...new Set(ausentes.map(f=>extDe(f.f.name)))].join(', ');
|
|
mostraNota([
|
|
['av','Sem nota · '+exts+' sem análise completa',
|
|
ausentes.length+' de '+folhas.length+' folha(s) precisam de conferência manual'],
|
|
['er','Metro pela tabela · '+rs(TABELA[modo]),
|
|
'exportando em PNG ou PDF, cai até '+rs(pisoEscada())],
|
|
['fix','Alguém abre seu arquivo','a conferência é feita na mão, na sala']
|
|
], 0, folhas.length>1? folhas.length+' folhas' : folhas[0].f.name,
|
|
larguraFilme()+' cm · '+fmtM(metros)+' m · preço de tabela');
|
|
return {pronto:true};
|
|
}
|
|
const dpi=Math.min(...ans.map(a=>a.dpi));
|
|
const n=Math.max(6,Math.min(100,Math.round(dpi/DPI_AVISA*100)));
|
|
const soma=(k)=>ans.reduce((t,a)=>t+a[k],0);
|
|
const itens=[
|
|
ans.every(a=>a.vetor)
|
|
? ['ok','Arte vetorial','sem imagem embutida · imprime em qualquer tamanho']
|
|
: [dpi>=DPI_AVISA?'ok':dpi>=DPI_RECUSA?'av':'er','Resolução '+dpi+' DPI',
|
|
ans.some(a=>a.fonteDpi==='imagens')
|
|
? 'média por área das imagens dentro do PDF · pior em '+
|
|
Math.min(...ans.map(a=>a.res? a.res.pior : a.dpi))+' DPI'
|
|
: 'resolução do arquivo · arte ampliada antes de exportar não aparece aqui'],
|
|
ans.some(a=>a.grande) ? ['ok','Arquivo grande','peças, resíduos e fundo conferidos pela equipe'] : null,
|
|
ans.some(a=>a.grande) ? null : ['ok', soma('artes')+' peças na folha',
|
|
Math.round(soma('aproveitamento')/ans.length)+'% da folha virou arte'],
|
|
ans.some(a=>a.grande) ? null : soma('residuos') ? ['er','Resíduo de recorte',
|
|
soma('residuos')+' ponto'+(soma('residuos')>1?'s':'')+' abaixo de 2 mm · a impressora imprime']
|
|
: ['ok','Sem resíduo de recorte','nada solto na folha'],
|
|
ans.some(a=>a.grande) ? null : soma('encostadas') ? ['fix', soma('encostadas')+' peças a menos de 5 mm',
|
|
'se forem artes diferentes, separamos com 5 mm sem custo']
|
|
: ['ok','Espaço entre peças','nenhuma abaixo de 6 mm'],
|
|
ans.some(a=>a.grande) ? null : ans.some(a=>a.fundoChapado) ? ['er','Fundo chapado','a folha está sem transparência']
|
|
: ['ok','Fundo transparente','canal alfa conferido'],
|
|
null
|
|
].filter(Boolean);
|
|
mostraNota(itens, n, folhas.length>1? folhas.length+' folhas' : folhas[0].f.name,
|
|
larguraFilme()+' cm · '+fmtM(metros)+' m');
|
|
return {pronto:true};
|
|
}
|
|
if(!artes.length) return {pronto:false, falta:'Arraste suas artes para começar.'};
|
|
if(artes.some(a=>a.lendo!=null)) return {pronto:false, falta:'Gerando a prévia das artes grandes…'};
|
|
if(artes.some(a=>a.decodeError || !(a.src||a.semPrevia) || !(a.px>0) || !(a.py>0)))
|
|
return {pronto:false, falta:'Não conseguimos ler uma das imagens. Exporte-a novamente em PNG ou JPG antes de continuar.'};
|
|
if(artes.some(a=>!Number.isFinite(a.cm) || a.cm>larguraFilme()))
|
|
return {pronto:false, falta:'A largura da arte precisa caber no filme de '+
|
|
n1(larguraFilme())+' cm. Reduza a medida solicitada antes de continuar.'};
|
|
const sem=artes.filter(a=>!a.cm).length;
|
|
if(sem) return {pronto:false, falta:'Informe a largura de '+sem+' arte'+(sem>1?'s':'')+
|
|
' para ver a nota e o preço.'};
|
|
// metragem = altura da folha depois de montada, com os 5 mm entre peças
|
|
metros=+(montagemCm/100).toFixed(4);
|
|
if(!(metros>0)) return {pronto:false, falta:'Montando a folha…'};
|
|
const notas=artes.map(a=>Math.max(6,Math.min(100,Math.round(dpiDe(a)/300*100))));
|
|
const media=Math.round(notas.reduce((s,n)=>s+n,0)/notas.length), pior=Math.min(...notas);
|
|
mostraNota([
|
|
['ok','Montagem por nossa conta','5 mm garantidos'],
|
|
[cls(pior),'Pior arte em '+pior+'%',pior>=85?'todas em boa resolução':'reenviar maior sai mais barato']
|
|
],media,artes.length+' arte'+(artes.length>1?'s':''),
|
|
'folha montada por nós · '+fmtM(metros)+' m');
|
|
return {pronto:true};
|
|
}
|
|
|
|
// redesenha 220 ms depois da última mexida, para não avaliar a cada tecla
|
|
let tAval=null;
|
|
function agendaAvaliacao(){
|
|
clearTimeout(tAval);
|
|
tAval=setTimeout(()=>{
|
|
// o produto pode ter sido fechado nesses 220 ms (item foi para o carrinho)
|
|
if(!modo) return;
|
|
const r=avaliar();
|
|
const el=$('aoVivo');
|
|
if(!el) return;
|
|
if(r.pronto){
|
|
el.className='aoVivo pronto';
|
|
el.innerHTML='<span class="pt2"></span><span><b>Nota e preço calculados.</b> '+
|
|
'Confira abaixo e feche o pedido.</span>';
|
|
}else{
|
|
el.className='aoVivo';
|
|
el.innerHTML='<span class="pt2"></span><span>'+escapeHTML(r.falta)+'</span>';
|
|
limpaPaineis(); // o carrinho só some se o pedido estiver vazio
|
|
}
|
|
},220);
|
|
}
|
|
function mostraNota(itens,n,titulo,sub){
|
|
nota=n;
|
|
$('qual').classList.add('on');
|
|
$('qNome').textContent=titulo; $('qSub').textContent=sub;
|
|
const semNota = n===0;
|
|
$('pct').innerHTML = semNota? '—<small>/100</small>' : n+'<small>/100</small>';
|
|
const c = semNota? 'var(--fraco)' : n>=85?'var(--verde)':n>=50?'var(--laranja)':'var(--vermelho)';
|
|
$('pct').style.color=c;
|
|
$('qbi').style.width=(semNota?0:n)+'%'; $('qbi').style.background=c;
|
|
$('qitens').innerHTML=itens.map(i=>
|
|
'<div class="qi '+i[0]+'"><span class="c">'+
|
|
icone(i[0]==='ok'?'ok':i[0]==='av'?'alerta':i[0]==='fix'?'refazer':'fechar')+'</span>'+
|
|
'<div><b>'+escapeHTML(i[1])+'</b><span>'+escapeHTML(i[2])+'</span></div></div>').join('');
|
|
$('qmsg').style.display='none';
|
|
ressalvaResolucao();
|
|
carrinho();
|
|
}
|
|
|
|
// A resolução do arquivo não é a das artes dentro dele: um PDF em 300 DPI
|
|
// pode ter uma imagem inserida em 180, e é essa que sai serrilhada.
|
|
function ressalvaResolucao(){
|
|
const box=$('ciente'), chk=$('cienteOk');
|
|
let baixas=[], geral=300, recusadas=[];
|
|
if(ehFolha()){
|
|
// Uma régua só: o número que precifica é o número que bloqueia.
|
|
// Recusa quando a média por área fica abaixo de 96 DPI — a folha inteira não
|
|
// vale o filme. Se a média passa mas há peças ruins, é ressalva com aceite.
|
|
folhas.forEach((x,k)=>{
|
|
if(!x.an) return;
|
|
const r=x.an.res, cm=x.med? x.med.larg : larguraFilme();
|
|
const nome=(folhas.length>1? (k+1)+'. ':'')+x.f.name;
|
|
const extra = r ? {pior:r.pior, de:r.total,
|
|
quantas:r.quantasBaixas, parte:Math.round(r.fracBaixa*100),
|
|
quantasAviso:r.quantasAviso, parteAviso:Math.round(r.fracAviso*100)} : {};
|
|
if(x.an.dpi<DPI_RECUSA) recusadas.push(Object.assign({nome, dpi:x.an.dpi, cm}, extra));
|
|
else if(x.an.dpi<DPI_AVISA || (r&&r.quantasAviso))
|
|
baixas.push(Object.assign({nome, dpi:x.an.dpi, cm}, extra));
|
|
});
|
|
geral = folhas.some(x=>x.an) ? Math.max(...folhas.filter(x=>x.an).map(x=>x.an.dpi)) : 300;
|
|
}else{
|
|
const comTamanho=artes.filter(a=>a.cm>0);
|
|
recusadas=comTamanho.filter(a=>dpiDe(a)<DPI_RECUSA)
|
|
.map(a=>({nome:a.f.name, dpi:Math.round(dpiDe(a)), cm:a.cm}));
|
|
baixas=comTamanho.filter(a=>dpiDe(a)>=DPI_RECUSA && dpiDe(a)<DPI_AVISA)
|
|
.map(a=>({nome:a.f.name, dpi:Math.round(dpiDe(a)), cm:a.cm}));
|
|
const todas=comTamanho.map(a=>dpiDe(a));
|
|
geral = todas.length? Math.round(Math.max(...todas)) : 300;
|
|
}
|
|
// Abaixo de 96 DPI a sala não imprime. Não é desconto, é recusa —
|
|
// e não existe aceite que destrave.
|
|
if(recusadas.length){
|
|
$('cienteTxt').innerHTML=
|
|
'<p><b>A média da folha ficou abaixo de '+DPI_RECUSA+' DPI.</b> Nesse ponto a peça '+
|
|
'sai serrilhada a ponto de não valer o filme. Reenvie maior ou reduza a largura.</p>'+
|
|
'<div class="lista">'+recusadas.map(b=>'<div><b>'+escapeHTML(b.nome)+'</b> — '+
|
|
(b.quantas
|
|
? 'média de '+b.dpi+' DPI · pior imagem em '+b.pior+' DPI · '+b.quantas+
|
|
' de '+b.de+' imagens abaixo de '+DPI_RECUSA+' DPI, cobrindo '+b.parte+'% da arte'
|
|
: b.dpi+' DPI em '+n1(b.cm)+' cm · precisa de '+
|
|
Math.ceil(DPI_AVISA*b.cm/2.54)+' px de largura')+'</div>')
|
|
.join('')+'</div>';
|
|
$('cienteTit').textContent='Resolução abaixo do que imprimimos';
|
|
box.className='ciente on recusa'; chk.checked=false; $('qOk').disabled=true;
|
|
return;
|
|
}
|
|
$('cienteTit').textContent='Ressalva de resolução';
|
|
box.className='ciente';
|
|
if(!baixas.length){
|
|
box.classList.remove('on'); chk.checked=false; $('qOk').disabled=false; return;
|
|
}
|
|
const menor=baixas.reduce((p,b)=>b.dpi<p.dpi?b:p);
|
|
const media=Math.round(baixas.reduce((s,b)=>s+b.dpi,0)/baixas.length);
|
|
$('cienteTxt').innerHTML=
|
|
'<p><b>Parte da arte está abaixo de '+DPI_AVISA+' DPI.</b> Essas peças podem sair '+
|
|
'com traço serrilhado, e isso não tem conserto depois de impresso. '+
|
|
'O resto da folha sai normal.</p>'+
|
|
'<div class="lista">'+baixas.map(b=>'<div><b>'+escapeHTML(b.nome)+'</b> — '+
|
|
(b.quantasAviso
|
|
? 'média de '+b.dpi+' DPI · '+b.quantasAviso+' de '+b.de+' imagens abaixo de '+
|
|
DPI_AVISA+' DPI, cobrindo '+b.parteAviso+'% da arte'+
|
|
(b.quantas? ' · dessas, '+b.quantas+' abaixo de '+DPI_RECUSA+' DPI ('+
|
|
b.parte+'% da arte)' : '')
|
|
: b.dpi+' DPI'+(b.cm? ' em '+n1(b.cm)+' cm':''))+
|
|
'</div>').join('')+'</div>';
|
|
box.className='ciente on'; chk.checked=false; $('qOk').disabled=true;
|
|
}
|
|
$('cienteOk').addEventListener('change',e=>{
|
|
if($('ciente').classList.contains('recusa')) return; // recusa não se aceita
|
|
$('qOk').disabled=!e.target.checked;
|
|
if(itemAtual && itemAtual.qualityStatus==='warning'){
|
|
itemAtual.qualityAcknowledged=e.target.checked;
|
|
pintaEntrega();
|
|
}
|
|
});
|