import{initialConfiguration,evaluateConfiguration,evaluateNetwork}from'./planning.mjs'; const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)],h=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const K={basket:'asterrackit:v26:basket',compare:'asterrackit:v26:compare',plan:'asterrackit:v26:plan'};let tt; function toast(s){const t=$('.cx-toast');if(!t)return;t.textContent=s;t.hidden=false;clearTimeout(tt);tt=setTimeout(()=>t.hidden=true,5000);} function read(k,d){try{return JSON.parse(localStorage.getItem(k)||'null')??d;}catch{return d;}} function store(k,v){try{localStorage.setItem(k,JSON.stringify(v));return true;}catch{toast('Browser storage is unavailable or full. Export your draft to a file.');return false;}} const validId=id=>typeof id==='string'&&/^[a-z0-9-]{3,150}$/.test(id); function basket(){let b=read(K.basket,null);if(!b||b.version!==1||!Array.isArray(b.items))b={version:1,items:[]};return{version:1,items:b.items.filter(x=>x&&validId(x.id)&&Number.isInteger(x.quantity)&&x.quantity>0&&x.quantity<=9999).slice(0,100),configurations:Array.isArray(b.configurations)?b.configurations.filter(c=>c&&typeof c.text==='string'&&c.text.length<=30000).slice(0,10):[],notes:typeof b.notes==='string'?b.notes.slice(0,5000):'',destination:typeof b.destination==='string'?b.destination.slice(0,120):'',condition:typeof b.condition==='string'?b.condition.slice(0,100):'Confirm in quotation'};} function compared(){const a=read(K.compare,[]);return Array.isArray(a)?[...new Set(a.filter(validId))].slice(0,4):[];} function counts(){const b=basket();$$('[data-basket-count]').forEach(e=>e.textContent=b.items.length+b.configurations.length);} async function api(p,o){const r=await fetch('/explore/api/'+p,o);if(!r.ok)throw Error('Catalog request failed ('+r.status+'). Try again.');return r.json();} async function add(id,qty=1){if(!validId(id)||!Number.isInteger(qty)||qty<1||qty>9999)throw Error('Invalid identifier or quantity.');const r=await api('products?ids='+encodeURIComponent(id));if(r.items.length!==1)throw Error('This product is not in the current catalog.');const b=basket(),x=b.items.find(x=>x.id===id);if(x)x.quantity=Math.min(9999,x.quantity+qty);else{if(b.items.length>=100)throw Error('Export or remove lines before exceeding 100 distinct identifiers.');b.items.push({id,quantity:qty});}if(store(K.basket,b)){counts();toast(r.items[0].identifier+' added to your local quotation basket.');}} function compareAdd(id){const a=compared();if(a.includes(id)){toast('Already selected.');return;}if(a.length>=4){toast('Compare up to four records. Remove one from the comparison page first.');return;}a.push(id);if(store(K.compare,a))toast('Added to comparison. Open Compare in the navigation.');} function goCompare(a){store(K.compare,a);location.assign('/explore/compare'+(a.length?'?ids='+encodeURIComponent(a.join(',')):''));} let lastExportUrl; function download(n,s,type='text/plain;charset=utf-8'){ if(lastExportUrl)URL.revokeObjectURL(lastExportUrl); lastExportUrl=URL.createObjectURL(new Blob([s],{type})); let panel=$('#cx-export-preview'); if(!panel){panel=document.createElement('section');panel.id='cx-export-preview';panel.className='cx-panel';panel.setAttribute('aria-label','Exported draft');$('main').append(panel);} panel.innerHTML=`

Your draft is ready

Save the file or copy the draft below. Nothing has been sent.

Save ${h(n)} ↓`; const a=$('#cx-export-link',panel);a.href=lastExportUrl;a.download=n;$('#cx-export-text',panel).value=s;a.click(); panel.scrollIntoView({block:'nearest'});toast('Draft ready. Save the file or copy its contents.'); } function csv(v){v=String(v??'');if(/^[=+\-@\t\r]/.test(v))v="'"+v;return '"'+v.replaceAll('"','""')+'"';} function attach(text,kind,plan){if(text.length>30000)throw Error('Plan exceeds the draft size limit.');const b=basket();if(b.configurations.length>=10)throw Error('Export or remove a configuration before adding another.');b.configurations.push({id:crypto.randomUUID(),kind,text,plan,createdAt:new Date().toISOString()});if(store(K.basket,b)){counts();toast('Unvalidated planning draft attached to the quote basket.');}} let d={};try{d=JSON.parse($('#cx-data')?.textContent||'{}');}catch{toast('Page data could not be read.');}counts(); document.addEventListener('click',async e=>{const b=e.target.closest('button,a');if(!b)return;try{if(b.dataset.add){e.preventDefault();b.disabled=true;await add(b.dataset.add);b.disabled=false;}else if(b.dataset.compare){e.preventDefault();compareAdd(b.dataset.compare);}else if(b.hasAttribute('data-clear-compare'))goCompare([]);else if(b.dataset.removeCompare)goCompare(compared().filter(id=>id!==b.dataset.removeCompare));}catch(err){b.disabled=false;toast(err.message||'Action failed.');}}); window.addEventListener('storage',e=>{if([K.basket,K.compare].includes(e.key)){counts();if(d.kind==='basket')renderBasket();}}); const search=$('[data-suggest]'),box=$('#cx-suggestions');let timer,ab; if(search&&box){search.setAttribute('aria-controls','cx-suggestions');search.setAttribute('aria-expanded','false');search.addEventListener('input',()=>{clearTimeout(timer);ab?.abort();const q=search.value.trim();if(q.length<2){box.hidden=true;search.setAttribute('aria-expanded','false');return;}timer=setTimeout(async()=>{ab=new AbortController();try{const r=await api('search?size=8&q='+encodeURIComponent(q),{signal:ab.signal});box.innerHTML=r.items.map(p=>`${h(p.brand+' · '+p.identifier)}${h(p.name)}`).join('')||'

No suggestion. Press Enter for the full search.

';box.hidden=false;search.setAttribute('aria-expanded','true');}catch{box.hidden=true;search.setAttribute('aria-expanded','false');}},250);});search.addEventListener('keydown',e=>{if(e.key==='Escape'){box.hidden=true;search.setAttribute('aria-expanded','false');}if(e.key==='ArrowDown'){e.preventDefault();$('a',box)?.focus();}});document.addEventListener('click',e=>{if(!e.target.closest('.ar-header-search')){box.hidden=true;search.setAttribute('aria-expanded','false');}});} if(d.kind==='compare'){const u=new URL(location.href);if(u.searchParams.has('ids'))store(K.compare,d.ids||[]);else if(compared().length)location.replace('/explore/compare?ids='+encodeURIComponent(compared().join(',')));} function options(a,v){return a.map(x=>{const id=typeof x==='object'?x.id:x,t=typeof x==='object'?x.label:x;return ``;}).join('');} const select=(l,k,a,v)=>``; const number=(l,k,v,min,max,step=1)=>``; const group=(n,t,f,note='')=>`
${n}

${h(t)}

${f.join('')}
${note?'

'+h(note)+'

':''}
`; function planText(p,s,r){return ['ASTER RACKIT — SERVER PLANNING DRAFT','STATUS: '+r.status.toUpperCase(),'Not an OEM-approved BOM. Exact PNs remain pending.','',p.brand+' '+p.model,...r.bom.map(x=>x.quantity+' x '+x.description),'',`Chassis: ${s.chassis}`,`Controller: ${s.controller}`,`Boot: ${s.boot}`,`Network: ${s.network}`,`RAID math: ${s.raid}`,`Memory: ${r.summary.memoryGB} GB`,`Estimated member capacity: ${r.summary.raidCapacityTB} TB`,`Estimated planning load: ${r.summary.plannedLoadW??'unknown'} W`,`Remaining PSU arithmetic: ${r.summary.redundancyCapacityW} W`,'','POWER ASSUMPTIONS',JSON.stringify(r.summary.assumptions),'','WARNINGS',...r.warnings,'','REQUIRED REVIEW',...r.requires,'','No price, inventory allocation, order or entitlement is created.'].join('\n');} if(d.kind==='configurator'){ const p=d.profile;let s={...d.initial},result;const keys=Object.keys(s),numeric=keys.filter(k=>typeof s[k]==='number'); function importState(x){if(!x||typeof x!=='object'||x.profile!==p.id)throw Error('Load the matching platform before importing a plan.');const out=initialConfiguration(p);for(const k of keys)if(Object.hasOwn(x,k))out[k]=x[k];for(const k of numeric)if(typeof out[k]!=='number'||!Number.isFinite(out[k]))throw Error('Invalid numeric field: '+k);return out;} try{const raw=new URL(location.href).searchParams.get('state');if(raw){if(raw.length>18000)throw Error('Shared plan too large.');s=importState(JSON.parse(raw));}}catch(e){toast(e.message||'Invalid shared configuration.');} function form(){const ch=p.chassis.find(c=>c.id===s.chassis)||p.chassis[0];$('#cx-config-form').innerHTML=[ group('01','Processor and memory',[ select('Processor quantity','cpuCount',Array.from({length:p.cpu.max},(_,i)=>i+1),s.cpuCount),p.cpu.options?.length?select('CPU power class','cpuOption',p.cpu.options,s.cpuOption):number('CPU TDP assumption (W); 0 = unknown','cpuTdpW',s.cpuTdpW,0,1500),select('Capacity per DIMM (GB)','dimmGB',p.memory.capacitiesGB,s.dimmGB),number('Installed DIMMs','dimmCount',s.dimmCount,1,p.memory.maxDimms)],'Logical DIMM counts are not a physical slot-order diagram. Exact module PN and memory generation remain subject to review.'), group('02','Chassis and storage path',[ select('Chassis planning layout','chassis',p.chassis,s.chassis),select('Data-drive media','driveType',ch.driveTypes,s.driveType),number('Data members, excluding spares','driveCount',s.driveCount,0,ch.driveBays),number('Capacity per member (decimal TB)','driveTB',s.driveTB,.01,1000,.01),select('Controller path','controller',p.controllers,s.controller),select('Capacity calculation','raid',[{id:'none',label:'Raw / no RAID math'},{id:'raid0',label:'RAID 0'},{id:'raid1',label:'RAID 1 / two members'},{id:'raid5',label:'RAID 5'},{id:'raid6',label:'RAID 6'},{id:'raid10',label:'RAID 10'}],s.raid)],'RAID arithmetic assumes equal-size members. Spares, filesystem overhead and reserved capacity are excluded. Supported RAID implementation is a separate check.'), group('03','Accelerators and cooling',[ select('GPU planning option','gpu',p.gpu,s.gpu),number('GPU quantity','gpuCount',s.gpuCount,0,32),p.cooling?.length?select('Cooling profile','cooling',p.cooling,s.cooling):'

Exact cooling kit / PN requires review.

',number('Target ambient temperature (°C)','ambientC',s.ambientC,-20,80)],'A generic accelerator class is not an orderable PN. GPU selection triggers chassis, width, memory and power checks.'), group('04','Power, boot and network',[ select('PSU rating (W)','psuW',p.psuWatts,s.psuW),select('Installed power supplies','psuCount',Array.from({length:p.psuCount.max-p.psuCount.min+1},(_,i)=>i+p.psuCount.min),s.psuCount),select('Redundancy arithmetic','redundancy',[{id:'none',label:'No redundant-capacity assumption'},{id:'n-plus-one',label:'N+1: remove one supply'},{id:'n-plus-n',label:'N+N: half installed capacity'}],s.redundancy),select('Site input voltage','voltage',[{id:'unconfirmed',label:'Not yet confirmed'},{id:'100-127',label:'100–127 V AC; review required'},{id:'200-240',label:'200–240 V AC; review required'},{id:'dc',label:'DC; exact PSU review required'}],s.voltage),...(p.boot?.length?[select('Dedicated boot','boot',p.boot,s.boot)]:[]),...(p.networking?.length?[select('Adapter planning option','network',p.networking,s.network)]:[])],'Power ratings are not measured wall power. Review voltage derating, input current, PDU, cables and the actual supported redundancy topology.')].join(''); $$('[data-field]',$('#cx-config-form')).forEach(e=>e.addEventListener('change',()=>{const k=e.dataset.field;s[k]=numeric.includes(k)?Number(e.value):e.value;if(k==='cpuOption')s.cpuTdpW=p.cpu.options.find(o=>o.id===s.cpuOption)?.tdpW||0;if(k==='gpu'){s.gpuCount=p.gpu.find(g=>g.id===s.gpu)?.width==='none'?0:Math.max(1,s.gpuCount);$('[data-field="gpuCount"]',$('#cx-config-form')).value=s.gpuCount;}if(k==='chassis'){const c=p.chassis.find(c=>c.id===s.chassis);if(!c.driveTypes.includes(s.driveType)){s.driveType=c.driveTypes[0];toast('Media type reset for the new chassis. Other limits are re-evaluated.');}form();}summary();})); } function summary(){const r=result=evaluateConfiguration(p,s),x=r.summary;$('#cx-config-summary').innerHTML=`
${r.errors.length?'BLOCKED COMBINATION':'REVIEW REQUIRED'}

${h(p.brand+' '+p.model)}

${r.errors.length?'Resolve blocking checks before exporting an RFQ BOM.':'No modeled limit blocks this selection. This is not compatibility approval.'}

${r.errors.length?`

Blocking checks (${r.errors.length})

`:''}

Live planning summary

${h(x.memoryGB??'—')} GBInstalled memory
${x.raidCapacityTB==null?'—':x.raidCapacityTB.toFixed(2)} TBMathematical data capacity
${x.plannedLoadW==null?'Unknown':x.plannedLoadW+' W'}Estimated planning load
${h(x.redundancyCapacityW??'—')} WRemaining PSU arithmetic

Power assumptions: 8 W/DIMM; 15 W/drive; 150 W base system; 25% headroom. These are planning inputs, not manufacturer measurements.

${Array.from({length:p.memory.maxDimms},(_,i)=>`${i+1}`).join('')}

Logical count only. Follow the OEM physical population sequence.

Draft components

${r.bom.map(b=>`
${h(b.quantity)}×${h(b.description)}
`).join('')}
Warnings & required review (${r.warnings.length+r.requires.length})
`; $('#cx-config-quote').addEventListener('click',()=>{if(result.errors.length)return;try{attach(planText(p,s,result),'server',s);}catch(e){toast(e.message);}});$('#cx-config-download').addEventListener('click',()=>{if(!result.errors.length)download(p.id+'-planning-rfq.txt',planText(p,s,result));}); } form();summary(); $('#cx-profile').addEventListener('change', e=>e.target.form.requestSubmit()); $('[data-config-save]').addEventListener('click',()=>{if(store(K.plan,{version:1,configuration:s}))toast('Saved locally. Restored selections will be re-evaluated.');}); $('[data-config-restore]').addEventListener('click',()=>{try{const v=read(K.plan,null);if(!v)throw Error('No saved plan.');s=importState(v.configuration);form();summary();toast('Saved plan restored and checked.');}catch(e){toast(e.message);}}); $('[data-config-reset]').addEventListener('click',()=>{s=initialConfiguration(p);history.replaceState(null,'','/explore/configure?profile='+encodeURIComponent(p.id));form();summary();toast('Platform defaults restored.');}); $('[data-config-share]').addEventListener('click',async()=>{const u=new URL('/explore/configure',location.origin);u.searchParams.set('profile',p.id);u.searchParams.set('state',JSON.stringify(s));try{await navigator.clipboard.writeText(u.href);toast('Share link copied. No customer information is included.');}catch{download('configuration-link.txt',u.href);toast('Clipboard unavailable; link exported as a text file.');}}); $('[data-config-json]').addEventListener('click',()=>download(p.id+'-editable-plan.json',JSON.stringify({version:1,configuration:s},null,2),'application/json')); $('#cx-config-import').addEventListener('change',async e=>{try{const f=e.target.files[0];if(!f)return;if(f.size>30000)throw Error('Plan file exceeds 30 KB.');const v=JSON.parse(await f.text());s=importState(v.configuration||v);form();summary();toast('Imported and re-evaluated.');}catch(e){toast(e.message||'Invalid plan.');}finally{e.target.value='';}}); } if(d.kind==='network'){ const m=d.model,s={quantity:1,accessRequired:Number.isInteger(m.accessPortCount)?m.accessPortCount:0,uplinksRequired:0,uplink:m.uplinkOptions[0]?.id||'',features:[]}; $('#cx-network-form').innerHTML=group('01',m.name,[number('Device quantity','quantity',1,1,1000),number('Required access ports','accessRequired',s.accessRequired,0,100000),number('Required uplink ports','uplinksRequired',0,0,100000),select('Uplink / module planning option','uplink',m.uplinkOptions.length?m.uplinkOptions:[{id:'',label:'No separate module count recorded'}],s.uplink)],m.portSummary)+`

Features requiring review

${(m.featureSupport||[]).map(f=>``).join('')||'

No feature matrix supplied.

'}
`; function update(){const r=evaluateNetwork(m,s);$('#cx-network-summary').innerHTML=`
${r.status.toUpperCase()}

${h(m.name)}

${h(r.accessPorts??"Not recorded")}Arithmetic access-port total
${h(r.uplinkPorts??"Not recorded")}Selected uplink total

Checks & review

`;$('#cx-network-add').addEventListener('click',()=>{if(r.errors.length)return;try{attach(['ASTER RACKIT — NETWORK PLANNING DRAFT','NOT OEM-APPROVED',`${s.quantity} x ${m.name}`,'Module: '+s.uplink,'Access demand: '+s.accessRequired,'Uplink demand: '+s.uplinksRequired,'Features: '+s.features.join(', '),'',...r.notes].join('\n'),'network',{model:m.id,...s});}catch(e){toast(e.message);}});} $$('[data-field]',$('#cx-network-form')).forEach(e=>e.addEventListener('change',()=>{s[e.dataset.field]=e.dataset.field==='uplink'?e.value:Number(e.value);update();}));$$('[data-feature]').forEach(e=>e.addEventListener('change',()=>{s.features=$$('[data-feature]:checked').map(e=>e.dataset.feature);update();}));update(); } let basketMap=new Map(); async function renderBasket(){if(!$('#cx-basket-lines'))return;const b=basket();try{const r=b.items.length?await api('products?ids='+encodeURIComponent(b.items.map(x=>x.id).join(','))):{items:[]};basketMap=new Map(r.items.map(p=>[p.id,p]));$('#cx-basket-lines').innerHTML=b.items.length?`
${b.items.map(x=>{const p=basketMap.get(x.id);return ``;}).join('')}
Product identityEvidence depthQuantityAction
${p?`${h(p.identifier)}${h(p.brand+' · '+p.name)}`:'Unresolved catalog ID: '+h(x.id)}${p?p.technicalFieldCount+' recorded fields':'Review required'}
`:'

No product lines yet

Add catalog products, match part numbers below or import a saved basket.

Browse hardware →
'; $$('[data-basket-qty]').forEach(e=>e.addEventListener('change',()=>{const n=Number(e.value);if(!Number.isInteger(n)||n<1||n>9999){toast('Quantity must be an integer from 1 to 9999.');renderBasket();return;}const b=basket(),x=b.items.find(x=>x.id===e.dataset.basketQty);if(x)x.quantity=n;store(K.basket,b);counts();})); $$('[data-basket-remove]').forEach(e=>e.addEventListener('click',()=>{const b=basket();b.items=b.items.filter(x=>x.id!==e.dataset.basketRemove);store(K.basket,b);renderBasket();counts();})); $('#cx-basket-configs').innerHTML=b.configurations.length?b.configurations.map(c=>`
${h(c.kind||'Configuration')} draft · ${h(c.createdAt||'saved')}
${h(c.text)}
`).join(''):'

No attached plans. Build a server or network draft and add it after resolving blocking checks.

'; $$('[data-remove-config]').forEach(e=>e.addEventListener('click',()=>{const b=basket();b.configurations=b.configurations.filter(c=>c.id!==e.dataset.removeConfig);store(K.basket,b);renderBasket();counts();})); }catch(e){$('#cx-basket-lines').innerHTML='
'+h(e.message)+'
';}} function saveFields(){const b=basket();b.destination=$('#cx-destination')?.value.slice(0,120)||'';b.notes=$('#cx-project-notes')?.value.slice(0,5000)||'';b.condition=$('#cx-condition')?.value||'Confirm in quotation';store(K.basket,b);} function rfq(){saveFields();const b=basket();const categories=b.items.map(x=>basketMap.get(x.id)?.category);const server=categories.includes('servers')||b.configurations.some(c=>c.kind==='server');const network=categories.some(c=>['switches','routers','security','wireless','network-platforms'].includes(c))||b.configurations.some(c=>c.kind==='network');const scope=server?'SERVER CONFIGURATION — purchasing review':network?'NETWORK CONFIGURATION — purchasing review':'COMPONENTS — purchasing review';return ['ASTER RACKIT — CUSTOMER RFQ DRAFT',scope,'Not an order, offer, stock allocation or OEM-approved BOM.','Created: '+new Date().toISOString(),'Destination: '+b.destination,'Requested condition: '+b.condition,'','PRODUCTS',...b.items.map(x=>{const p=basketMap.get(x.id);return `${x.quantity} x ${p?.brand||''} ${p?.identifier||x.id}\n ${p?'https://www.aster-rackit.com'+p.href:'Unresolved ID — review required'}`;}),'','PROJECT NOTES',b.notes,'','CONFIGURATION DRAFTS',...b.configurations.map(c=>c.text),'','Confirm exact identity, compatibility, accessories, test evidence, price, lead time, warranty and delivery terms.','Send to sales@aster-rackit.com'].join('\n');} if(d.kind==='basket'){ const b=basket();$('#cx-destination').value=b.destination;$('#cx-project-notes').value=b.notes;if([...$('#cx-condition').options].some(o=>o.value===b.condition))$('#cx-condition').value=b.condition;for(const s of ['#cx-destination','#cx-project-notes','#cx-condition'])$(s).addEventListener('change',saveFields);renderBasket(); $('[data-basket-clear]').addEventListener('click',()=>{if(!confirm('Clear local product lines and configuration drafts?'))return;const b=basket();b.items=[];b.configurations=[];store(K.basket,b);renderBasket();counts();}); $('#cx-export-txt').addEventListener('click',()=>download('ASTER-RACKIT-RFQ.txt',rfq())); $('#cx-export-csv').addEventListener('click',()=>{saveFields();const b=basket(),rows=[['Manufacturer','Identifier','Quantity','Catalog URL','Review status'],...b.items.map(x=>{const p=basketMap.get(x.id);return[p?.brand||'',p?.identifier||x.id,x.quantity,p?'https://www.aster-rackit.com'+p.href:'','Current technical and commercial review required'];})];download('ASTER-RACKIT-BOM.csv','\uFEFF'+rows.map(r=>r.map(csv).join(',')).join('\r\n'),'text/csv;charset=utf-8');}); $('#cx-export-json').addEventListener('click',()=>{saveFields();download('ASTER-RACKIT-basket.json',JSON.stringify(basket(),null,2),'application/json');}); $('#cx-basket-import').addEventListener('change',async e=>{try{const f=e.target.files[0];if(!f)return;if(f.size>500000)throw Error('Basket file exceeds 500 KB.');const v=JSON.parse(await f.text());if(!v||v.version!==1||!Array.isArray(v.items)||v.items.length>100)throw Error('Expected a version 1 basket with up to 100 lines.');if(v.items.some(x=>!x||!validId(x.id)||!Number.isInteger(x.quantity)||x.quantity<1||x.quantity>9999))throw Error('Invalid identifier or quantity.');if(new Set(v.items.map(x=>x.id)).size!==v.items.length)throw Error('Duplicate product lines in import. Merge quantities first.');const r=v.items.length?await api('products?ids='+encodeURIComponent(v.items.map(x=>x.id).join(','))):{items:[]};if(r.items.length!==v.items.length)throw Error('Some imported identifiers do not exist in the current catalog.');const configs=Array.isArray(v.configurations)?v.configurations:[];if(configs.length>10||configs.some(c=>!c||typeof c.text!=='string'||c.text.length>29900))throw Error('Invalid configuration drafts.');if(!confirm('Replace this browser basket with the imported draft?'))return;const out={version:1,items:v.items,configurations:configs.map(c=>({id:crypto.randomUUID(),kind:'imported / unvalidated',text:'IMPORTED DRAFT — REVALIDATE BEFORE USE\n'+c.text,createdAt:new Date().toISOString()})),destination:String(v.destination||'').slice(0,120),notes:String(v.notes||'').slice(0,5000),condition:String(v.condition||'Confirm in quotation').slice(0,100)};store(K.basket,out);$('#cx-destination').value=out.destination;$('#cx-project-notes').value=out.notes;if([...$('#cx-condition').options].some(o=>o.value===out.condition))$('#cx-condition').value=out.condition;renderBasket();counts();toast('Imported. Configuration text remains unvalidated.');}catch(e){toast(e.message||'Invalid basket file.');}finally{e.target.value='';}}); $('#cx-mail').addEventListener('click',e=>{saveFields();const b=basket();e.currentTarget.href='mailto:sales@aster-rackit.com?subject='+encodeURIComponent('ASTER RACKIT hardware RFQ')+'&body='+encodeURIComponent('Please review my hardware inquiry. I will attach the exported BOM/RFQ file.\nDestination: '+b.destination+'\nRequested condition: '+b.condition+'\nThis is an inquiry, not an order.');}); $('#cx-send-existing').addEventListener('click',()=>{try{const text=rfq();if(text.length>100000)throw Error('Draft exceeds the existing form limit. Export it as a file instead.');const id=crypto.randomUUID();sessionStorage.setItem('asterrack:quote:'+id,JSON.stringify({version:1,text,createdAt:Date.now()}));location.assign('/quote?draft='+id);}catch(e){toast(e.message||'Session storage unavailable. Export the RFQ instead.');}}); $('#cx-bulk-match').addEventListener('click',async e=>{const lines=$('#cx-bulk').value.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);if(lines.length>100){toast('Paste up to 100 lines.');return;}const b=e.currentTarget;b.disabled=true;$('#cx-bulk-results').textContent='Matching identifiers…';const found=[];for(const line of lines){const [id,qty]=line.split('|').map(x=>x.trim()),q=qty===undefined?1:Number(qty);if(!id||id.length>240||!Number.isInteger(q)||q<1||q>9999){found.push({line,error:'Invalid identifier or quantity.'});continue;}try{found.push({line,q,...await api('lookup?q='+encodeURIComponent(id))});}catch(e){found.push({line,error:e.message});}}$('#cx-bulk-results').innerHTML=found.map(r=>`
${h(r.line)}${r.error?'

'+h(r.error)+'

':r.items.length?`

${r.ambiguous?'Multiple exact/alias matches. Select the correct purchasing unit.':'One exact/alias catalog match. Confirm before adding.'}

${r.items.map(p=>``).join('')}`:'

No exact identifier match. Try the model name or ask for sourcing review.

'}
`).join('');$$('[data-bulk-add]').forEach(b=>b.addEventListener('click',async()=>{try{b.disabled=true;await add(b.dataset.bulkAdd,Number(b.dataset.bulkQuantity));await renderBasket();}catch(e){toast(e.message);}finally{b.disabled=false;}}));b.disabled=false;}); }