199 lines
10 KiB
JavaScript
199 lines
10 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'],
|
||
['ok', soma('artes')+' peças na folha',
|
||
Math.round(soma('aproveitamento')/ans.length)+'% da folha virou arte'],
|
||
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'],
|
||
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.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.decodeError || !a.src || !(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(()=>{
|
||
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">'+
|
||
(i[0]==='ok'?'✓':i[0]==='av'?'!':i[0]==='fix'?'↻':'×')+'</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();
|
||
}
|
||
});
|