function paintopsAddCalcToQuote(items, panelId){
var claimed=[];
var skipped=0;
items.forEach(function(item){
var slot=null;
// An item can name a specific slot to always claim (currently
// just Labor Days, forcing slot 1) instead of searching, slot
// 1 is pre-filled by estimates.php itself with a default
// crew-based labor guess before any calculator ever runs (see
// $reqWorkersDesc/$totalLaborSuggested there), so it's neither
// empty nor tag-matched by any calculator's own previous entry.
// Without this, a calculator whose whole job IS computing Total
// Labor Cost would skip right past slot 1 (reading as "already
// occupied by something else") and leave that stale default
// sitting there untouched.
if(item.forceSlot){slot=item.forceSlot;
// Even a forced slot has to respect what's already been claimed
// THIS SAME batch, without this, if some other item's own
// tag/empty search already grabbed this exact slot earlier in
// this forEach (see the isEmpty check below skipping slot 1 for
// everyone but Labor Days now, but this stays as a second line
// of defense), a forceSlot item would silently stomp over it
// with no warning instead of at least landing in skipped/alert.
if(claimed.indexOf(slot)!==-1){slot=null;}
}
// The itemize card has 20 slots total: 1-5 always visible, 6-20
// rendered but hidden (display:none) until something needs them
// (see the "+ Add Line" button and the 15-row loop in
// estimates.php). Slots past 5 still exist in the DOM either way,
// so the tag-match/empty-slot search below works on them exactly
// like slots 1-5, claiming one just also has to un-hide its row.
if(slot===null){
for(var n=1;n<=20;n++){
if(claimed.indexOf(n)!==-1){continue;}
var dd=document.getElementById('supplyDesc'+n);
if(dd&&item.tag&&dd.value.indexOf(item.tag)===0){slot=n;break;}
}
if(slot===null){
// Starts at slot 2, not 1, slot 1 is reserved for whichever
// item explicitly claims it via forceSlot (currently just Labor
// Days). Before this, any calculator's plain empty-slot search
// could land in slot 1 whenever the job's default labor line
// happened to be blank (e.g. 0 estimated days), fine on its
// own, but the FIRST TIME Labor Days was then also calculated
// and added, its forceSlot claim doesn't check what's already
// there (see above) and would silently overwrite whatever had
// landed in slot 1, with no warning. That's what made Cabinet &
// Trim's numbers seem to need adding twice, the first add
// "worked" (landed in slot 1) and then got quietly wiped out.
for(var m=2;m<=20;m++){
if(claimed.indexOf(m)!==-1){continue;}
var d2=document.getElementById('supplyDesc'+m);var c2=document.getElementById('supplyCost'+m);
if(!d2||!c2){continue;}
var costVal=parseFloat(c2.value);
var isEmpty=d2.value.trim()===''&&(!c2.value||isNaN(costVal)||costVal===0);
if(isEmpty){slot=m;break;}
}
}
}
if(slot===null){skipped++;return;}
claimed.push(slot);
var row=document.getElementById('supplyRow'+slot);if(row){row.style.display='';}
var d=document.getElementById('supplyDesc'+slot);var c=document.getElementById('supplyCost'+slot);
if(d){d.value=item.desc;}
if(c){c.value=item.cost;c.dispatchEvent(new Event('input',{bubbles:true}));}
});
var box=document.getElementById('quote1');if(box){box.style.display='block';}
var panel=document.getElementById(panelId);if(panel){panel.classList.remove('show');}
var card=document.querySelector('.paintops-itemize-card');if(card&&card.scrollIntoView){card.scrollIntoView({behavior:'smooth',block:'center'});}
if(skipped>0){window.alert(skipped+' item(s) didn\'t fit, the itemized Supply Cost fields are already full. Add the rest by hand, or clear a slot and click again.');}
}
// Every calculator's own "Add These Numbers to Quote" button
// calls THIS function directly (not paintopsAddCalcToQuote()
// with just its own items), there's no separate "Add ALL"
// button anymore; clicking any one calculator's button adds
// every calculator's numbers at once. Reads from every
// .paintops-calc-items-holder[data-calc-items] element on the
// page (that class/attribute lives on each calculator's own
// button, see e.g. paintops_render_paint_sqft_calculator()),
// including ones whose modal is currently closed (display:none
// doesn't remove an element from the DOM, so querySelectorAll
// still finds it), that's what makes this genuinely "add
// everything I've calculated so far" regardless of which
// button was actually clicked.
function paintopsAddAllCalcToQuote(){
var holders=document.querySelectorAll('.paintops-calc-items-holder[data-calc-items]');
var allItems=[];
holders.forEach(function(h){
try{
var items=JSON.parse(h.getAttribute('data-calc-items'));
if(Array.isArray(items)){allItems=allItems.concat(items);}
}catch(e){}
});
if(allItems.length===0){window.alert('Calculate at least one calculator above first, then click Add ALL.');return;}
paintopsAddCalcToQuote(allItems,null);
document.querySelectorAll('.paintops-modal-overlay.show').forEach(function(p){p.classList.remove('show');});
}
// Every calculator now shares ONE <form method="get">
// (paintops_render_category_calculators()) that reloads the page
// to compute results server-side (see the file-level docblock in
// includes/trade-calculators.php). That form already replays the
// page's *other* params (job id, csaction, etc.) forward as hidden
// fields, this does the same thing for the one thing that's never
// in $_GET on its own: the itemize card's own live field values
// (which live in a separate POST form, for the actual "Save Quote"
// button). Right before the shared calc form submits, snapshot all
// 20 supplyDesc/supplyCost fields' current values into it as extra
// hidden inputs, so they ride along in its GET request and
// estimates.php can prefer them over whatever it would otherwise
// show (see the "isset($_GET['supplyDesc1'])" override there),
// that's what keeps a not-yet-saved "Add These Numbers to Quote"
// edit intact across a subsequent Calculate on a different
// section of the same shared form. Wired once via
// a flag on window regardless of how many times this shared
// script gets loaded, since addEventListener() isn't itself
// idempotent the way a repeated identical function declaration is.
if(!window.paintopsCalcSnapshotWired){
window.paintopsCalcSnapshotWired=true;
document.addEventListener('submit',function(e){
var f=e.target;
if(!f||!f.classList||!f.classList.contains('paintops-calc-form')){return;}
for(var n=1;n<=20;n++){
var d=document.getElementById('supplyDesc'+n);
var c=document.getElementById('supplyCost'+n);
if(d){var hd=document.createElement('input');hd.type='hidden';hd.name=(n<=5?('supplyDesc'+n):'supplyExtraDesc[]');hd.value=d.value;f.appendChild(hd);}
if(c){var hc=document.createElement('input');hc.type='hidden';hc.name=(n<=5?('supplyCost'+n):'supplyExtraCost[]');hc.value=c.value;f.appendChild(hc);}
}
},true);
}
