feat: choose which cart items to pay, with a thumbnail of each
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 3m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m13s
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 3m22s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m13s
Each cart item has a checkbox and a thumbnail of its first file (or its extension when there is none). Only checked items are priced, quoted, uploaded and paid; an unchecked item stays in the cart, dimmed, for a later order, and stays there after the others are paid. The header count is the whole cart; the summary counts what is being paid. Also removes the "A conta nasce aqui…" and "Carrinho salvo neste navegador…" texts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,6 +100,12 @@ try{
|
|||||||
await site.fill('#fZap','11999999999');
|
await site.fill('#fZap','11999999999');
|
||||||
await site.fill('#fMail','local-browser@example.test');
|
await site.fill('#fMail','local-browser@example.test');
|
||||||
assert.equal(await site.eval('pedido[0].localFiles[0].name'),'local-test.cdr');
|
assert.equal(await site.eval('pedido[0].localFiles[0].name'),'local-test.cdr');
|
||||||
|
// An unchecked item stays in the cart but is not paid for now.
|
||||||
|
await site.click('[data-sel="0"]');
|
||||||
|
assert.deepEqual(await site.eval(`({marcado:pedido[0].marcado,pagar:itensAPagar().length,zero:$('carrTot').textContent.includes('0,00'),row:document.querySelector('.item').classList.contains('fora')})`),
|
||||||
|
{marcado:false,pagar:0,zero:true,row:true});
|
||||||
|
await site.click('[data-sel="0"]');
|
||||||
|
assert.equal(await site.eval('pedido[0].marcado===true && itensAPagar().length===1'),true);
|
||||||
assert.equal(await site.eval('pedido[0].total'),21.89);
|
assert.equal(await site.eval('pedido[0].total'),21.89);
|
||||||
assert.equal(await site.eval('document.getElementById("bPagar").disabled'),false);
|
assert.equal(await site.eval('document.getElementById("bPagar").disabled'),false);
|
||||||
await site.click('#bPagar');
|
await site.click('#bPagar');
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
try{
|
try{
|
||||||
if(!items.length){await transaction('readwrite',store=>store.delete(scope));return;}
|
if(!items.length){await transaction('readwrite',store=>store.delete(scope));return;}
|
||||||
await transaction('readwrite',store=>store.put({items,expires:Date.now()+86400000},scope));
|
await transaction('readwrite',store=>store.put({items,expires:Date.now()+86400000},scope));
|
||||||
notice.textContent='Carrinho salvo neste navegador por 24 horas. Você pode remover itens e adicionar outros após recarregar.';
|
notice.textContent='';
|
||||||
}catch(error){notice.textContent='Não foi possível salvar o carrinho neste navegador. Mantenha esta página aberta até enviar o pedido.';}
|
}catch(error){notice.textContent='Não foi possível salvar o carrinho neste navegador. Mantenha esta página aberta até enviar o pedido.';}
|
||||||
}
|
}
|
||||||
window.dtfClearCart=async()=>{clearTimeout(timer);if(database&&scope)await transaction('readwrite',store=>store.delete(scope));notice.textContent='';};
|
window.dtfClearCart=async()=>{clearTimeout(timer);if(database&&scope)await transaction('readwrite',store=>store.delete(scope));notice.textContent='';};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
let quotedCart = localStorage.getItem('dtf-quote-cart');
|
let quotedCart = localStorage.getItem('dtf-quote-cart');
|
||||||
let refreshVersion = 0;
|
let refreshVersion = 0;
|
||||||
function cartSnapshot() {
|
function cartSnapshot() {
|
||||||
const items=[...pedido,...(itemAtual?[itemAtual]:[])];
|
const items=itensAPagar();
|
||||||
return JSON.stringify({customer:cliente,delivery:entrega,items:items.map(item=>({
|
return JSON.stringify({customer:cliente,delivery:entrega,items:items.map(item=>({
|
||||||
mode:item.modo,metres:item.metros,grade:item.nota,production:item.production,
|
mode:item.modo,metres:item.metros,grade:item.nota,production:item.production,
|
||||||
quality:item.qualityStatus,
|
quality:item.qualityStatus,
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
arquivos.forEach(f=>enviar(f).catch(()=>{}));
|
arquivos.forEach(f=>enviar(f).catch(()=>{}));
|
||||||
pintaEnvio();
|
pintaEnvio();
|
||||||
}
|
}
|
||||||
const arquivosDoCarrinho=()=>[...pedido,...(busy&&itemAtual?[itemAtual]:[])].flatMap(it=>it.localFiles||[]);
|
const arquivosDoCarrinho=()=>[...pedido.filter(it=>it.marcado!==false),...(busy&&itemAtual?[itemAtual]:[])].flatMap(it=>it.localFiles||[]);
|
||||||
const gb=n=>(n/1073741824).toLocaleString('pt-BR',{maximumFractionDigits:1})+' GB';
|
const gb=n=>(n/1073741824).toLocaleString('pt-BR',{maximumFractionDigits:1})+' GB';
|
||||||
const mb=n=>n>=1073741824 ? gb(n) : Math.round(n/1048576)+' MB';
|
const mb=n=>n>=1073741824 ? gb(n) : Math.round(n/1048576)+' MB';
|
||||||
// Where the uploads stand, and roughly how long is left from the recent speed.
|
// Where the uploads stand, and roughly how long is left from the recent speed.
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
// The cart's package: billed metres and value. The charged freight is
|
// The cart's package: billed metres and value. The charged freight is
|
||||||
// quoted again by the server from the approved items.
|
// quoted again by the server from the approved items.
|
||||||
const pacote = () => {
|
const pacote = () => {
|
||||||
const items=[...pedido,...(itemAtual?[itemAtual]:[])];
|
const items=itensAPagar();
|
||||||
return {metres:items.reduce((t,it)=>t+(it.cob||0),0), cents:Math.round(items.reduce((t,it)=>t+(it.total||0),0)*100)};
|
return {metres:items.reduce((t,it)=>t+(it.cob||0),0), cents:Math.round(items.reduce((t,it)=>t+(it.total||0),0)*100)};
|
||||||
};
|
};
|
||||||
let freightQuoted = null;
|
let freightQuoted = null;
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
if (!clienteOk() || !entrega.cotado) return;
|
if (!clienteOk() || !entrega.cotado) return;
|
||||||
if (!enderecoOk()) return message('Preencha o endereço de entrega.');
|
if (!enderecoOk()) return message('Preencha o endereço de entrega.');
|
||||||
if (!cartPodeEnviar()) return message('Revise a qualidade e confirme a ressalva de cada item antes de enviar o pedido.');
|
if (!cartPodeEnviar()) return message('Revise a qualidade e confirme a ressalva de cada item antes de enviar o pedido.');
|
||||||
const cart = [...pedido,...(itemAtual?[itemAtual]:[])];
|
const cart = itensAPagar();
|
||||||
if (!cart.length) return message('Adicione um item ao pedido.');
|
if (!cart.length) return message('Adicione um item ao pedido.');
|
||||||
const initialCart=cartSnapshot();
|
const initialCart=cartSnapshot();
|
||||||
busy = true;
|
busy = true;
|
||||||
@@ -333,8 +333,7 @@
|
|||||||
event.target.disabled=true;
|
event.target.disabled=true;
|
||||||
try {
|
try {
|
||||||
const order=await api('/orders/dev-paid',{quote_id:draftId});
|
const order=await api('/orders/dev-paid',{quote_id:draftId});
|
||||||
pedido=[]; itemAtual=null; limpaPaineis();
|
await pagos();
|
||||||
await window.dtfClearCart?.();
|
|
||||||
message('Pedido #'+order.number+' criado e disponível no Kanban.');
|
message('Pedido #'+order.number+' criado e disponível no Kanban.');
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch(error) { message(error.message); event.target.disabled=false; }
|
} catch(error) { message(error.message); event.target.disabled=false; }
|
||||||
@@ -360,8 +359,7 @@
|
|||||||
const quote=await api('/quotes/'+draftId);
|
const quote=await api('/quotes/'+draftId);
|
||||||
if (quote.status==='paid') {
|
if (quote.status==='paid') {
|
||||||
clearInterval(waitTimer);
|
clearInterval(waitTimer);
|
||||||
pedido=[]; itemAtual=null; limpaPaineis();
|
await pagos();
|
||||||
await window.dtfClearCart?.();
|
|
||||||
await refresh();
|
await refresh();
|
||||||
} else if (quote.payment?.status==='rejected') {
|
} else if (quote.payment?.status==='rejected') {
|
||||||
// Refused after the bank's confirmation or the provider's review.
|
// Refused after the bank's confirmation or the provider's review.
|
||||||
@@ -397,6 +395,11 @@
|
|||||||
actions.append(box);
|
actions.append(box);
|
||||||
}
|
}
|
||||||
const cartOk=()=>!!quotedCart && quotedCart===cartSnapshot();
|
const cartOk=()=>!!quotedCart && quotedCart===cartSnapshot();
|
||||||
|
// What was paid leaves the cart; unchecked items stay for another order.
|
||||||
|
async function pagos() {
|
||||||
|
pedido=pedido.filter(it=>it.marcado===false); itemAtual=null; limpaPaineis();
|
||||||
|
if (pedido.length) pintaPedido(); else await window.dtfClearCart?.();
|
||||||
|
}
|
||||||
|
|
||||||
// Paying: card (the default, paid on this page) or PIX (paid on its own page).
|
// Paying: card (the default, paid on this page) or PIX (paid on its own page).
|
||||||
async function escolhaPagamento(quote, version) {
|
async function escolhaPagamento(quote, version) {
|
||||||
|
|||||||
@@ -1094,8 +1094,6 @@ footer a:hover{color:var(--laranja2)}
|
|||||||
|
|
||||||
<div class="cxE">
|
<div class="cxE">
|
||||||
<h4>Seus dados</h4>
|
<h4>Seus dados</h4>
|
||||||
<p class="subC">A conta nasce aqui, no pagamento. O CNPJ é o mesmo da nota fiscal,
|
|
||||||
e é por ele que suas artes ficam guardadas para o próximo pedido.</p>
|
|
||||||
<div class="campos">
|
<div class="campos">
|
||||||
<div class="cp"><label>CNPJ</label>
|
<div class="cp"><label>CNPJ</label>
|
||||||
<input id="fCnpj" autocomplete="off" inputmode="numeric" maxlength="18" placeholder="00.000.000/0000-00">
|
<input id="fCnpj" autocomplete="off" inputmode="numeric" maxlength="18" placeholder="00.000.000/0000-00">
|
||||||
|
|||||||
@@ -15,8 +15,13 @@ function itemPodeEnviar(item){
|
|||||||
return item.qualityStatus==='ok' || item.qualityStatus==='unverified' ||
|
return item.qualityStatus==='ok' || item.qualityStatus==='unverified' ||
|
||||||
(item.qualityStatus==='warning' && item.qualityAcknowledged===true);
|
(item.qualityStatus==='warning' && item.qualityAcknowledged===true);
|
||||||
}
|
}
|
||||||
|
// Os itens marcados no carrinho são os que vão para o pagamento; um item
|
||||||
|
// desmarcado fica guardado para depois. O item aberto no produto sempre vai.
|
||||||
|
function itensAPagar(){
|
||||||
|
return [...pedido.filter(it=>it.marcado!==false),...(itemAtual?[itemAtual]:[])];
|
||||||
|
}
|
||||||
function cartPodeEnviar(){
|
function cartPodeEnviar(){
|
||||||
const items=[...pedido,...(itemAtual?[itemAtual]:[])];
|
const items=itensAPagar();
|
||||||
return items.length>0 && items.every(itemPodeEnviar);
|
return items.length>0 && items.every(itemPodeEnviar);
|
||||||
}
|
}
|
||||||
function limpaPaineis(){
|
function limpaPaineis(){
|
||||||
@@ -28,9 +33,17 @@ function limpaPaineis(){
|
|||||||
// O carrinho junta itens de produtos diferentes e cobra uma vez só.
|
// O carrinho junta itens de produtos diferentes e cobra uma vez só.
|
||||||
function pintaPedido(){
|
function pintaPedido(){
|
||||||
$('itens').innerHTML = pedido.map((it,i)=>
|
$('itens').innerHTML = pedido.map((it,i)=>
|
||||||
'<div class="item"><div class="nm2"><b>'+escapeHTML(it.tit)+'</b><span>'+escapeHTML(it.desc)+'</span></div>'+
|
'<div class="item'+(it.marcado===false?' fora':'')+'">'+
|
||||||
|
'<label class="marca"><input type="checkbox" data-sel="'+i+'"'+(it.marcado===false?'':' checked')+
|
||||||
|
' aria-label="Incluir '+escapeHTML(it.tit)+' no pagamento"></label>'+
|
||||||
|
'<span class="miniItem">'+(it.mini ? '<img src="'+escapeHTML(it.mini)+'" alt="">' : '<i>'+escapeHTML(it.ext||'')+'</i>')+
|
||||||
|
(it.arquivos>1 ? '<em>+'+(it.arquivos-1)+'</em>' : '')+'</span>'+
|
||||||
|
'<div class="nm2"><b>'+escapeHTML(it.tit)+'</b><span>'+escapeHTML(it.desc)+'</span></div>'+
|
||||||
'<div class="vl2">'+rs(it.total)+'</div>'+
|
'<div class="vl2">'+rs(it.total)+'</div>'+
|
||||||
'<button type="button" class="x" data-rmi="'+i+'" aria-label="Remover '+escapeHTML(it.tit)+'">'+icone('lixeira')+'<span>Remover</span></button></div>').join('');
|
'<button type="button" class="x" data-rmi="'+i+'" aria-label="Remover '+escapeHTML(it.tit)+'">'+icone('lixeira')+'<span>Remover</span></button></div>').join('');
|
||||||
|
$('itens').querySelectorAll('[data-sel]').forEach(c=>c.addEventListener('change',()=>{
|
||||||
|
pedido[+c.dataset.sel].marcado=c.checked; pintaPedido();
|
||||||
|
}));
|
||||||
$('itens').querySelectorAll('[data-rmi]').forEach(b=>b.addEventListener('click',()=>{
|
$('itens').querySelectorAll('[data-rmi]').forEach(b=>b.addEventListener('click',()=>{
|
||||||
const i=+b.dataset.rmi, [item]=pedido.splice(i,1);
|
const i=+b.dataset.rmi, [item]=pedido.splice(i,1);
|
||||||
pintaPedido();
|
pintaPedido();
|
||||||
@@ -39,11 +52,12 @@ function pintaPedido(){
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
$('bEsvaziar').hidden = pedido.length+(itemAtual?1:0) < 2;
|
$('bEsvaziar').hidden = pedido.length+(itemAtual?1:0) < 2;
|
||||||
const itens = pedido.reduce((t,it)=>t+it.total,0) + (itemAtual? itemAtual.total : 0);
|
const aPagar = itensAPagar();
|
||||||
|
const itens = aPagar.reduce((t,it)=>t+it.total,0);
|
||||||
const frete = entrega.tipo==='frete' && entrega.cotado ? entrega.valor : 0;
|
const frete = entrega.tipo==='frete' && entrega.cotado ? entrega.valor : 0;
|
||||||
const soma = itens + frete;
|
const soma = itens + frete;
|
||||||
const n = pedido.length + (itemAtual?1:0);
|
const n = aPagar.length, noCarrinho = pedido.length + (itemAtual?1:0);
|
||||||
const eco = [...pedido, ...(itemAtual?[itemAtual]:[])]
|
const eco = aPagar
|
||||||
.reduce((t,it)=>t+(TABELA[it.modo]-it.unit)*it.cob, 0);
|
.reduce((t,it)=>t+(TABELA[it.modo]-it.unit)*it.cob, 0);
|
||||||
$('resLin').innerHTML =
|
$('resLin').innerHTML =
|
||||||
'<div class="l"><span>'+n+(n===1?' item':' itens')+'</span><b>'+rs(itens+eco)+'</b></div>'+
|
'<div class="l"><span>'+n+(n===1?' item':' itens')+'</span><b>'+rs(itens+eco)+'</b></div>'+
|
||||||
@@ -56,13 +70,13 @@ function pintaPedido(){
|
|||||||
'<div class="l"><span>Revisão e reencaixe</span>'+
|
'<div class="l"><span>Revisão e reencaixe</span>'+
|
||||||
'<b><span style="color:var(--verde-ml)">Grátis</span></b></div>';
|
'<b><span style="color:var(--verde-ml)">Grátis</span></b></div>';
|
||||||
$('carrTot').textContent=rs(soma);
|
$('carrTot').textContent=rs(soma);
|
||||||
$('bPagarTx').textContent = (pedido.length + (itemAtual?1:0))>1
|
$('bPagarTx').textContent = n>1
|
||||||
? 'Pagar os '+(pedido.length+(itemAtual?1:0))+' itens de uma vez'
|
? 'Pagar os '+n+' itens de uma vez'
|
||||||
: 'Ir para o pagamento';
|
: 'Ir para o pagamento';
|
||||||
$('bPagar').disabled = !cartPodeEnviar() || !entrega.cotado || !clienteOk() || !enderecoOk();
|
$('bPagar').disabled = !cartPodeEnviar() || !entrega.cotado || !clienteOk() || !enderecoOk();
|
||||||
if(pedido.length || itemAtual) $('carr').classList.add('on');
|
if(pedido.length || itemAtual) $('carr').classList.add('on');
|
||||||
if($('cartN')){ $('cartN').textContent=n>99?'99+':n; $('cartN').hidden=!n;
|
if($('cartN')){ $('cartN').textContent=noCarrinho>99?'99+':noCarrinho; $('cartN').hidden=!noCarrinho;
|
||||||
$('cartLink').setAttribute('aria-label','Carrinho, '+n+(n===1?' item':' itens')); }
|
$('cartLink').setAttribute('aria-label','Carrinho, '+noCarrinho+(noCarrinho===1?' item':' itens')); }
|
||||||
window.dispatchEvent(new Event('dtf-cart-changed'));
|
window.dispatchEvent(new Event('dtf-cart-changed'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +126,9 @@ function carrinho(){
|
|||||||
}
|
}
|
||||||
itemAtual={modo, tit:MODOS[modo].tit, nota, metros, cob, unit, total:tot,
|
itemAtual={modo, tit:MODOS[modo].tit, nota, metros, cob, unit, total:tot,
|
||||||
localFiles:(ehFolha()?folhas:artes).map(x=>x.f),
|
localFiles:(ehFolha()?folhas:artes).map(x=>x.f),
|
||||||
|
mini:(ehFolha()?folhas:artes).map(x=>x.miniSrc).find(Boolean)||null,
|
||||||
|
ext:extDe(((ehFolha()?folhas:artes)[0]||{f:{name:''}}).f.name),
|
||||||
|
arquivos:(ehFolha()?folhas:artes).length,
|
||||||
production:{version:2,film_width_cm:larguraFilme(),sources,...layout},
|
production:{version:2,film_width_cm:larguraFilme(),sources,...layout},
|
||||||
qualityStatus:$('ciente').classList.contains('recusa')?'rejected':
|
qualityStatus:$('ciente').classList.contains('recusa')?'rejected':
|
||||||
$('ciente').classList.contains('on')?'warning':nota===0?'unverified':'ok',
|
$('ciente').classList.contains('on')?'warning':nota===0?'unverified':'ok',
|
||||||
|
|||||||
@@ -302,6 +302,13 @@ section+section{border-top:0}
|
|||||||
.esvaziar:hover{color:#B8391C}
|
.esvaziar:hover{color:#B8391C}
|
||||||
.esvaziar[hidden]{display:none}
|
.esvaziar[hidden]{display:none}
|
||||||
.item{align-items:center}
|
.item{align-items:center}
|
||||||
|
.item .marca{flex:none;display:flex;align-items:center;cursor:pointer}
|
||||||
|
.item .marca input{width:20px;height:20px;margin:0;accent-color:var(--laranja);cursor:pointer}
|
||||||
|
.miniItem{position:relative;flex:none;width:52px;height:52px;border-radius:8px;border:1px solid var(--linha);background:repeating-conic-gradient(#F1F1F1 0 25%,#fff 0 50%) 0 0/12px 12px;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
||||||
|
.miniItem img{max-width:100%;max-height:100%;object-fit:contain}
|
||||||
|
.miniItem i{font-style:normal;font-size:12px;font-weight:700;color:var(--fraco)}
|
||||||
|
.miniItem em{position:absolute;right:2px;bottom:2px;padding:0 4px;border-radius:5px;background:rgba(3,6,11,.75);color:#fff;font-style:normal;font-size:11px;font-weight:700}
|
||||||
|
.item.fora .miniItem,.item.fora .nm2,.item.fora .vl2{opacity:.45}
|
||||||
.item .x{flex:none;height:36px;padding:0 12px;border-radius:8px;border:1px solid #D6D6D6;background:#fff;color:var(--texto2);font-size:15px;font-weight:600;font-family:inherit;cursor:pointer}
|
.item .x{flex:none;height:36px;padding:0 12px;border-radius:8px;border:1px solid #D6D6D6;background:#fff;color:var(--texto2);font-size:15px;font-weight:600;font-family:inherit;cursor:pointer}
|
||||||
.item .x:hover{border-color:#B8391C;color:#B8391C}
|
.item .x:hover{border-color:#B8391C;color:#B8391C}
|
||||||
.desfazer{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:80;display:flex;align-items:center;gap:16px;padding:12px 14px 12px 18px;border-radius:12px;background:var(--preto);color:#fff;font-size:16px;box-shadow:0 10px 30px rgba(3,6,11,.25);max-width:calc(100% - 32px);box-sizing:border-box}
|
.desfazer{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:80;display:flex;align-items:center;gap:16px;padding:12px 14px 12px 18px;border-radius:12px;background:var(--preto);color:#fff;font-size:16px;box-shadow:0 10px 30px rgba(3,6,11,.25);max-width:calc(100% - 32px);box-sizing:border-box}
|
||||||
|
|||||||
Reference in New Issue
Block a user