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:
@@ -18,7 +18,7 @@
|
||||
try{
|
||||
if(!items.length){await transaction('readwrite',store=>store.delete(scope));return;}
|
||||
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.';}
|
||||
}
|
||||
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 refreshVersion = 0;
|
||||
function cartSnapshot() {
|
||||
const items=[...pedido,...(itemAtual?[itemAtual]:[])];
|
||||
const items=itensAPagar();
|
||||
return JSON.stringify({customer:cliente,delivery:entrega,items:items.map(item=>({
|
||||
mode:item.modo,metres:item.metros,grade:item.nota,production:item.production,
|
||||
quality:item.qualityStatus,
|
||||
@@ -127,7 +127,7 @@
|
||||
arquivos.forEach(f=>enviar(f).catch(()=>{}));
|
||||
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 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.
|
||||
@@ -208,7 +208,7 @@
|
||||
// The cart's package: billed metres and value. The charged freight is
|
||||
// quoted again by the server from the approved items.
|
||||
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)};
|
||||
};
|
||||
let freightQuoted = null;
|
||||
@@ -247,7 +247,7 @@
|
||||
if (!clienteOk() || !entrega.cotado) return;
|
||||
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.');
|
||||
const cart = [...pedido,...(itemAtual?[itemAtual]:[])];
|
||||
const cart = itensAPagar();
|
||||
if (!cart.length) return message('Adicione um item ao pedido.');
|
||||
const initialCart=cartSnapshot();
|
||||
busy = true;
|
||||
@@ -333,8 +333,7 @@
|
||||
event.target.disabled=true;
|
||||
try {
|
||||
const order=await api('/orders/dev-paid',{quote_id:draftId});
|
||||
pedido=[]; itemAtual=null; limpaPaineis();
|
||||
await window.dtfClearCart?.();
|
||||
await pagos();
|
||||
message('Pedido #'+order.number+' criado e disponível no Kanban.');
|
||||
await refresh();
|
||||
} catch(error) { message(error.message); event.target.disabled=false; }
|
||||
@@ -360,8 +359,7 @@
|
||||
const quote=await api('/quotes/'+draftId);
|
||||
if (quote.status==='paid') {
|
||||
clearInterval(waitTimer);
|
||||
pedido=[]; itemAtual=null; limpaPaineis();
|
||||
await window.dtfClearCart?.();
|
||||
await pagos();
|
||||
await refresh();
|
||||
} else if (quote.payment?.status==='rejected') {
|
||||
// Refused after the bank's confirmation or the provider's review.
|
||||
@@ -397,6 +395,11 @@
|
||||
actions.append(box);
|
||||
}
|
||||
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).
|
||||
async function escolhaPagamento(quote, version) {
|
||||
|
||||
@@ -1094,8 +1094,6 @@ footer a:hover{color:var(--laranja2)}
|
||||
|
||||
<div class="cxE">
|
||||
<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="cp"><label>CNPJ</label>
|
||||
<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' ||
|
||||
(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(){
|
||||
const items=[...pedido,...(itemAtual?[itemAtual]:[])];
|
||||
const items=itensAPagar();
|
||||
return items.length>0 && items.every(itemPodeEnviar);
|
||||
}
|
||||
function limpaPaineis(){
|
||||
@@ -28,9 +33,17 @@ function limpaPaineis(){
|
||||
// O carrinho junta itens de produtos diferentes e cobra uma vez só.
|
||||
function pintaPedido(){
|
||||
$('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>'+
|
||||
'<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',()=>{
|
||||
const i=+b.dataset.rmi, [item]=pedido.splice(i,1);
|
||||
pintaPedido();
|
||||
@@ -39,11 +52,12 @@ function pintaPedido(){
|
||||
});
|
||||
}));
|
||||
$('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 soma = itens + frete;
|
||||
const n = pedido.length + (itemAtual?1:0);
|
||||
const eco = [...pedido, ...(itemAtual?[itemAtual]:[])]
|
||||
const n = aPagar.length, noCarrinho = pedido.length + (itemAtual?1:0);
|
||||
const eco = aPagar
|
||||
.reduce((t,it)=>t+(TABELA[it.modo]-it.unit)*it.cob, 0);
|
||||
$('resLin').innerHTML =
|
||||
'<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>'+
|
||||
'<b><span style="color:var(--verde-ml)">Grátis</span></b></div>';
|
||||
$('carrTot').textContent=rs(soma);
|
||||
$('bPagarTx').textContent = (pedido.length + (itemAtual?1:0))>1
|
||||
? 'Pagar os '+(pedido.length+(itemAtual?1:0))+' itens de uma vez'
|
||||
$('bPagarTx').textContent = n>1
|
||||
? 'Pagar os '+n+' itens de uma vez'
|
||||
: 'Ir para o pagamento';
|
||||
$('bPagar').disabled = !cartPodeEnviar() || !entrega.cotado || !clienteOk() || !enderecoOk();
|
||||
if(pedido.length || itemAtual) $('carr').classList.add('on');
|
||||
if($('cartN')){ $('cartN').textContent=n>99?'99+':n; $('cartN').hidden=!n;
|
||||
$('cartLink').setAttribute('aria-label','Carrinho, '+n+(n===1?' item':' itens')); }
|
||||
if($('cartN')){ $('cartN').textContent=noCarrinho>99?'99+':noCarrinho; $('cartN').hidden=!noCarrinho;
|
||||
$('cartLink').setAttribute('aria-label','Carrinho, '+noCarrinho+(noCarrinho===1?' item':' itens')); }
|
||||
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,
|
||||
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},
|
||||
qualityStatus:$('ciente').classList.contains('recusa')?'rejected':
|
||||
$('ciente').classList.contains('on')?'warning':nota===0?'unverified':'ok',
|
||||
|
||||
@@ -302,6 +302,13 @@ section+section{border-top:0}
|
||||
.esvaziar:hover{color:#B8391C}
|
||||
.esvaziar[hidden]{display:none}
|
||||
.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: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}
|
||||
|
||||
Reference in New Issue
Block a user