/* =====================================================================
   NOWILLS SQC ERP — Workflow engine & module configuration mounters
   Requires icons: flow, sliders, play, save (added to app.js IC map)
   ===================================================================== */
(function(){
'use strict';
const D=DATA,D2=DATA2;const C=Charts.palette;const MOUNT=window.MOUNT;
const esc=s=>String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;');
const MODN=Object.fromEntries(D.MODULES_DEF.map(m=>[m[0],m[1]]));
const KINDC={task:'#3b82f6',approval:'#f8c200',system:'#22c55e',decision:'#a855f7'};
if(!window.WFI)window.WFI=D2.wfInstances();
/* runtime config store: staged overrides + published overrides */
function cfgStore(){ try{return JSON.parse(localStorage.getItem('nw_cfg')||'{}');}catch(e){return {};} }
function cfgVal(mod,k){ const s=cfgStore(); return (s.pub&&s.pub[mod]&&s.pub[mod][k]!=null)?s.pub[mod][k]:(s.stage&&s.stage[mod]&&s.stage[mod][k]!=null)?s.stage[mod][k]:undefined; }
window.NWCFG_GET=(mod,k,def)=>{ const v=cfgVal(mod,k); return v===undefined?def:v; };

/* ---------------- Workflow process map (SVG) + designer ---------------- */
MOUNT.wfmap=function(el){
 let cur=window.__wfcur||{m:'proc',p:0}; window.__wfcur=cur;
 const proc=D2.WF[cur.m]&&D2.WF[cur.m][cur.p];
 function pickList(){ let h='<div class="hlist" style="display:flex;gap:6px;flex-wrap:wrap">';
  Object.keys(D2.WF).forEach(m=>D2.WF[m].forEach((p,pi)=>{ h+='<span class="chip '+(m===cur.m&&pi===cur.p?'on':'')+'" data-m="'+m+'" data-p="'+pi+'">'+MODN[m].split(' ')[0]+' · '+p.n+'</span>';}));
  h+='</div>'; return h; }
 function draw(proc){ const W=Math.max(680,el.clientWidth-20)||960; const cols=Math.min(proc.stages.length,4);
  const nw=Math.min(210,(W-40)/cols), nh=92, gx=22, gy=44;
  const rows=Math.ceil(proc.stages.length/cols); const H=rows*(nh+gy)+50;
  let s='<svg width="100%" height="'+H+'" viewBox="0 0 '+W+' '+H+'" font-family="inherit">';
  proc.stages.forEach((sg,i)=>{ const c=i%cols,r=Math.floor(i/cols);
   const x=20+c*(nw+gx), y=20+r*(nh+gy);
   const kind=sg[1],col=KINDC[kind]||'#64748b';
   const wip=window.WFI.filter(w=>w.mod===cur.m&&w.process===proc.n&&w.stage===i).length;
   const tcol=getComputedStyle(document.body);
   s+='<g class="datapt wfnode" data-i="'+i+'" style="cursor:pointer">'
    +'<rect x="'+x+'" y="'+y+'" width="'+nw+'" height="'+nh+'" rx="11" fill="'+tcol.getPropertyValue('--card-2')+'" stroke="'+col+'" stroke-width="1.8"/>'
    +'<rect x="'+x+'" y="'+y+'" width="'+nw+'" height="24" rx="11" fill="'+col+'"/>'
    +'<text x="'+(x+9)+'" y="'+(y+16)+'" font-size="10" font-weight="800" fill="'+(kind==='approval'?'#16181d':'#fff')+'">'+(i+1)+' · '+sg[1].toUpperCase()+'</text>'
    +'<text x="'+(x+9)+'" y="'+(y+40)+'" font-size="11" font-weight="700" fill="'+tcol.getPropertyValue('--text')+'">'+wrap(sg[0],nw/7.2)[0]+'</text>'
    +'<text x="'+(x+9)+'" y="'+(y+54)+'" font-size="11" font-weight="700" fill="'+tcol.getPropertyValue('--text')+'">'+(wrap(sg[0],nw/7.2)[1]||'')+'</text>'
    +'<text x="'+(x+9)+'" y="'+(y+71)+'" font-size="9.5" fill="'+tcol.getPropertyValue('--ax')+'">Owner: '+esc(sg[2])+'</text>'
    +'<text x="'+(x+9)+'" y="'+(y+85)+'" font-size="9.5" fill="'+tcol.getPropertyValue('--ax')+'">SLA '+(sg[3]?sg[3]+' h':'auto')+'</text>';
   if(wip)s+='<circle cx="'+(x+nw-13)+'" cy="'+(y+12)+'" r="10" fill="#ef4444"/><text x="'+(x+nw-13)+'" y="'+(y+15.5)+'" text-anchor="middle" font-size="9.5" font-weight="800" fill="#fff">'+wip+'</text>';
   s+='</g>';
   const ni=i+1;
   if(ni<proc.stages.length){ const c2=ni%cols,r2=Math.floor(ni/cols);
    if(c2>0){ const x2=20+c2*(nw+gx); s+='<path d="M'+(x+nw)+' '+(y+nh/2)+' L'+(x2-7)+' '+(y+nh/2)+'" stroke="#f8c200" stroke-width="2"/><path d="M'+(x2-7)+' '+(y+nh/2)+' l-6 -4 l0 8 z" fill="#f8c200"/>';}
    else { const ny=20+r2*(nh+gy); s+='<path d="M'+(x+nw/2)+' '+(y+nh)+' C '+(x+nw/2)+' '+(y+nh+gy/2)+' '+27+' '+(ny-14)+' '+27+' '+(ny-2)+'" fill="none" stroke="#f8c200" stroke-width="2" stroke-dasharray="5 4"/><path d="M24 '+(ny-2)+' l6 0 l-3 7 z" fill="#f8c200"/>';} }
  });
  s+='</svg>'; return s; }
 function wrap(t,w){ if(t.length<=w)return [t]; const cut=t.lastIndexOf(' ',w); return [t.slice(0,cut),t.slice(cut+1)]; }
 function render(){ el.innerHTML=pickList()+'<div style="overflow-x:auto">'+draw(proc)+'</div>'+
  '<div class="hlist" style="display:flex;gap:8px;flex-wrap:wrap;border-top:1px dashed var(--border)">'+
  '<span class="chip"><span class="dot" style="background:#3b82f6"></span>Task</span><span class="chip"><span class="dot" style="background:#f8c200"></span>Approval</span><span class="chip"><span class="dot" style="background:#22c55e"></span>System/auto</span><span class="chip"><span class="dot" style="background:#a855f7"></span>Decision</span>'+
  '<span style="flex:1"></span><button class="btn sm" id="wfDesign">'+NWX.icon('sliders',14)+' Designer</button><button class="btn sm ghost" id="wfSim">'+NWX.icon('play',14)+' Simulate run</button><button class="btn sm ghost" id="wfExp">'+NWX.icon('download',14)+' Export definition</button></div>';
  el.querySelectorAll('[data-m]').forEach(ch=>ch.onclick=()=>{cur={m:ch.dataset.m,p:+ch.dataset.p};render();});
  el.querySelectorAll('.wfnode').forEach(n=>n.onclick=()=>{ const i=+n.dataset.i,sg=proc.stages[i];
   const items=window.WFI.filter(w=>w.mod===cur.m&&w.process===proc.n&&w.stage===i);
   NWX.modal('Stage '+(i+1)+' — '+sg[0],
   '<div class="statgrid">'+[['Kind',sg[1].toUpperCase()],['Owner role',sg[2]],['SLA',sg[3]?sg[3]+' hours':'system / instant'],['Work in progress',items.length+' items']].map(x=>'<div class="statmini"><span>'+x[0]+'</span><b style="font-size:12.5px">'+x[1]+'</b></div>').join('')+'</div>'+
   (items.length?'<table class="tbl" style="margin-top:10px"><thead><tr><th>Instance</th><th>Age</th><th>Started by</th><th>SLA status</th></tr></thead><tbody>'+items.map(w=>'<tr><td class="mono">'+w.id+'</td><td>'+w.age+'</td><td>'+w.by+'</td><td><span class="badge '+(w.st==='breached'?'red':w.st==='at risk'?'amber':'green')+'">'+w.st+'</span></td></tr>').join('')+'</tbody></table>':'<div class="empty-state">Nothing waiting at this stage right now.</div>')+
   '<div style="display:flex;gap:8px;margin-top:12px"><button class="btn sm" id="sRea">Reassign stage</button><button class="btn sm" id="sNud">Nudge owners</button></div>',
   b=>{ b.querySelector('#sRea').onclick=()=>{NWX.toast('Stage reassigned','Backup approver pool now includes CFO delegate for 48 h.','blue');NWX.audit('Workflow stage reassigned',proc.n);};
        b.querySelector('#sNud').onclick=()=>{NWX.toast('Nudged',items.length+' owner(s) reminded via push + SMS + email.','green');}; }); });
  el.querySelector('#wfDesign').onclick=designer;
  el.querySelector('#wfSim').onclick=()=>{ NWX.toast('Simulation','Instance WF-『'+Math.floor(60000+Math.random()*9000)+'』 injected at stage 1 — watch it traverse (demo).','blue');
   let i2=0; const nodes=el.querySelectorAll('.wfnode'); const glow=setInterval(()=>{ nodes.forEach(nd=>nd.querySelector('rect').style.filter=''); const nd=nodes[i2];
     if(nd)nd.querySelector('rect').style.filter='drop-shadow(0 0 7px var(--accent))'; i2++; if(i2>=proc.stages.length){clearInterval(glow);nodes.forEach(nd=>nd.querySelector('rect').style.filter='');NWX.toast('Simulation done','Cycle completed in '+(proc.stages.reduce((a,s2)=>a+(s2[3]||0.1),0)).toFixed(1)+' h wall-clock (SLA-safe).','green'); NWX.audit('Workflow simulated',proc.n);} },650); };
  el.querySelector('#wfExp').onclick=()=>Charts.csv('workflow_'+proc.n.replace(/\W+/g,'_')+'.csv',[['Stage','Kind','Owner','SLA h']].concat(proc.stages.map(s2=>s2))); NWX.audit('Workflow definition exported',proc.n); }
 function designer(){ if(!(NWX.state.role&&false)){}
  const html='<div class="hlist">'+proc.stages.map((sg,i)=>'<div class="hrow"><span class="badge" style="background:'+KINDC[sg[1]]+'22;color:'+KINDC[sg[1]]+'">'+(i+1)+'</span><b style="width:230px;white-space:normal">'+esc(sg[0])+'</b>'+
   '<select class="inline-edit" data-e="k" data-i="'+i+'">'+['task','approval','system','decision'].map(k2=>'<option '+(k2===sg[1]?'selected':'')+'>'+k2+'</option>').join('')+'</select>'+
   '<input class="inline-edit" data-e="r" data-i="'+i+'" value="'+esc(sg[2])+'" style="width:170px;padding:5px 8px">'+
   '<input class="inline-edit" data-e="h" data-i="'+i+'" type="number" value="'+sg[3]+'" style="width:64px;padding:5px 8px"> h'+
   '<button class="iconbtn" data-mv="-1" data-i="'+i+'" title="Move up">▲</button><button class="iconbtn" data-mv="1" data-i="'+i+'" title="Move down">▼</button><button class="iconbtn" data-del="'+i+'" title="Remove">'+NWX.icon('x',13)+'</button></div>').join('')+'</div>'+
   '<button class="btn sm" id="addSt">'+NWX.icon('plus',13)+' Add stage</button>'+
   '<div style="display:flex;gap:8px;margin-top:14px"><button class="btn pri" id="saveWf">Stage changes → change control</button><button class="btn ghost" id="revertWf">Revert definition</button></div>'+
   '<p style="font-size:11px;color:var(--muted);margin-top:10px">Changes do not go live directly — they enter the <b>Configuration change control</b> workflow (Internal Audit / COO approval), then publish with version + rollback point.</p>';
  const ov=NWX.modal('Workflow designer — '+proc.n,html);
  ov.querySelectorAll('[data-e]').forEach(inp=>inp.onchange=()=>{ const sg=proc.stages[+inp.dataset.i]; const e=inp.dataset.e;
   if(e==='k')sg[1]=inp.value; if(e==='r')sg[2]=inp.value; if(e==='h')sg[3]=+inp.value||0; });
  ov.querySelectorAll('[data-mv]').forEach(b2=>b2.onclick=()=>{ const i=+b2.dataset.i,j=i+ +b2.dataset.mv; if(j<0||j>=proc.stages.length)return;
   const t=proc.stages[i];proc.stages[i]=proc.stages[j];proc.stages[j]=t; ov.remove(); designer(); });
  ov.querySelectorAll('[data-del]').forEach(b2=>b2.onclick=()=>{ proc.stages.splice(+b2.dataset.del,1); ov.remove(); designer(); });
  ov.querySelector('#addSt').onclick=()=>{ proc.stages.push(['New stage','approval','General Manager',4]); ov.remove(); designer(); };
  ov.querySelector('#saveWf').onclick=()=>{ window.NWCHANGES.push({id:'CHG-'+Math.floor(Math.random()*9000+1000),mod:cur.m,what:'Workflow «'+proc.n+'» — '+proc.stages.length+' stages redefined',by:'J. Nowills',stat:'pending'});
   NWX.toast('Staged for approval','Changes routed to change control. Live definition unchanged until published.','amber'); NWX.audit('Workflow redefined (staged)',proc.n); ov.remove(); render(); };
  ov.querySelector('#revertWf').onclick=()=>{ NWX.toast('Reverted','Local definition reloaded from last published version.','blue'); ov.remove(); }; }
 render();};

/* ---------------- Workflow catalogue (all 30 modules) ---------------- */
MOUNT.wfcat=function(el){ let open=null;
 function render(){ el.innerHTML='<div class="hlist" style="display:flex;gap:8px;padding-bottom:8px"><input type="search" id="wcf" placeholder="Search processes, owners, stages…" style="flex:1;max-width:320px;background:var(--inset);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px 11px;font-size:12.5px"><span class="chip on">'+Object.keys(D2.WF).length+' modules · '+Object.values(D2.WF).flat().length+' processes · '+Object.values(D2.WF).flat().flatMap(p=>p.stages).length+' stages</span></div>'+
  Object.keys(D2.WF).map(m=>{ const mod=D.MODULES_DEF.find(x=>x[0]===m); const list=D2.WF[m];
  return '<div class="statement" style="margin:0 12px 7px"><header data-m="'+m+'"><span>'+(open===m?'▾':'▸')+' <b>'+MODN[m]+'</b> <span style="color:var(--muted);font-weight:400">· '+list.length+' workflow'+(list.length>1?'s':'')+'</span></span><span style="display:flex;gap:6px;align-items:center"><span class="badge green">'+list.reduce((a,p)=>a+p.stages.filter(s2=>s2[1]==='system').length,0)+' auto</span><span class="badge amber">'+list.reduce((a,p)=>a+p.stages.filter(s2=>s2[1]==='approval').length,0)+' approvals</span></span></header>'+
  '<div class="sb-body" style="display:'+(open===m?'block':'none')+'">'+list.map((p,pi)=>{ const wip=window.WFI.filter(w=>w.mod===m&&w.process===p.n);
   return '<div class="hrow" data-m="'+m+'" data-p="'+pi+'" style="cursor:pointer"><span class="badge blue">'+p.cat+'</span><b style="width:250px">'+p.n+'</b>'+
    '<span style="flex:1;display:flex;gap:4px;flex-wrap:wrap">'+p.stages.map((sg,i)=>{ const w=wip.filter(w2=>w2.stage===i).length;
     return '<span class="chip" style="padding:2px 8px;font-size:10px;border-color:'+KINDC[sg[1]]+'66">'+(i+1)+'. '+esc(sg[0].split('(')[0].slice(0,26))+(w?' <b style="color:var(--red)">('+w+')</b>':'')+'</span>';}).join(' → ')+'</span>'+
    '<span class="vv">'+(wip.length?'<span class="badge amber">'+wip.length+' live</span>':'<span class="badge grey">idle</span>')+'</span></div>';}).join('')+'</div></div>'; }).join('');
  el.querySelectorAll('header[data-m]').forEach(h=>h.onclick=e=>{ if(e.target.closest('.hrow'))return; const m=h.dataset.m; open=open===m?null:m; render(); });
  el.querySelectorAll('.hrow[data-m]').forEach(r=>r.onclick=()=>{ window.__wfcur={m:r.dataset.m,p:+r.dataset.p}; NWX.toast('Loaded in process map','“'+D2.WF[r.dataset.m][+r.dataset.p].n+'” — see the process map panel above.','blue');
     const map=document.querySelector('[data-sid="wfmap"]'); if(map)map.scrollIntoView({behavior:'smooth'}); const mnt=MOUNT.wfmap; if(map){ const body=map.querySelector('.panel-b'); if(body){body.innerHTML='';mnt(body,{id:'wfmap'},'wflow');} } NWX.audit('Workflow inspected',r.dataset.m); });
  const q=el.querySelector('#wcf'); q.oninput=()=>{ const v=q.value.toLowerCase(); el.querySelectorAll('.statement').forEach(st2=>st2.style.display=st2.textContent.toLowerCase().includes(v)?'':'none'); }; }
 render();};

/* ---------------- Module setup tree + config editor ---------------- */
MOUNT.setree=function(el){ let cur=window.__cfgcur||'qsite'; window.__cfgcur=cur;
 const staged=cfgStore().stage||{};
 function fieldHtml(m,f2){ const v=NWCFG_GET(m,f2.k,f2.v); const stg=staged[m]&&staged[m][f2.k]!==undefined;
  let inp='';
  if(f2.t==='toggle')inp='<div class="toggle '+(v?'on':'')+'" data-k="'+f2.k+'" style="margin-left:auto"></div>';
  else if(f2.t==='select')inp='<select class="inline-edit cfg" data-k="'+f2.k+'" style="margin-left:auto;min-width:190px">'+String(f2.v).split('|').map(o=>'<option'+(o===v?' selected':'')+'>'+o+'</option>').join('')+'</select>';
  else inp='<input class="inline-edit cfg" data-k="'+f2.k+'" value="'+v+'" style="margin-left:auto;width:190px;padding:6px 9px;text-align:right" type="'+(f2.t==='number'||f2.t==='pct'||f2.t==='currency'?'text':'text')+'">';
  return '<div class="setrow" style="'+(stg?'border-left:3px solid var(--amber);padding-left:9px':'')+'"><div class="si"><b>'+f2.l+'</b>'+(f2.h?'<small>'+f2.h+'</small>':'')+'<small style="display:block;color:var(--faint)">'+f2.k+' · default: '+f2.v+(stg?' · <b style="color:var(--amber)">staged</b>':'')+'</small></div>'+inp+'</div>'; }
 function render(){ const mod=D.MODULES_DEF.find(x=>x[0]===cur); const cfg=D2.CFG[cur];
  el.innerHTML='<div style="display:grid;grid-template-columns:225px 1fr;gap:0;min-height:420px">'+
  '<div style="border-right:1px solid var(--border);padding:10px 8px;max-height:520px;overflow:auto">'+
   '<input type="search" id="cmq" placeholder="Search settings…" style="width:100%;background:var(--inset);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:7px 9px;font-size:12px;margin-bottom:8px">'+
   D.MODULES_DEF.map(m=>{ const dirty=staged[m[0]]&&Object.keys(staged[m[0]]).length; const def=D2.CFG[m[0]];
    return '<div class="mi'+(m[0]===cur?' active':'')+'" data-m="'+m[0]+'" style="padding:6.5px 8px;color:var(--text)"><span class="ic" style="width:16px;height:16px">'+NWX.icon(m[2],16)+'</span><span class="lbl" style="font-size:12px">'+m[1]+'</span>'+(dirty?'<span class="bdg">'+dirty+'</span>':'')+'<small style="color:var(--faint);font-size:9px">'+def.nn+'</small></div>';}).join('')+'</div>'+
  '<div style="padding:6px 16px 16px;max-height:520px;overflow:auto">'+
   '<div style="display:flex;align-items:center;gap:10px;margin:8px 0 12px"><h3 style="margin:0;font-size:15px">'+MODN[cur]+' — configuration</h3><span class="chip">Numbering series <b>'+cfg.nn+'-####</b></span><span class="chip" id="envChip">Environment: <b>Production</b></span>'+
   '<span style="margin-left:auto;display:flex;gap:6px"><button class="btn sm ghost" id="cfgReset">Revert defaults</button><button class="btn sm" id="cfgAudit">'+NWX.icon('shield',13)+' Audit diff</button><button class="btn sm pri" id="cfgSave">'+NWX.icon('save',13)+' Stage changes</button></span></div>'+
   '<div class="f-group"><label>Document numbering</label><div class="setrow"><div class="si"><b>Series prefix & counter</b><small>Immutable once documents exist — gapless numbering for tax audit</small></div><span class="chip on">'+cfg.nn+'-2026-######</span></div></div>'+
   cfg.sections.map(sec=>'<div class="f-group"><label>'+sec[0]+'</label>'+sec[1].map(f2=>fieldHtml(cur,f2)).join('')+'</div>').join('')+
   '<div class="f-group"><label>Offline & performance</label><div class="setrow"><div class="si"><b>Offline capture enabled</b><small>Queues to device with conflict detection</small></div><div class="toggle on"></div></div><div class="setrow"><div class="si"><b>Cache summaries for dashboards</b><small>Background aggregation, keeps OLTP fast</small></div><div class="toggle on"></div></div><div class="setrow"><div class="si"><b>Audit every record change</b><small>Hash-chained, append-only</small></div><div class="toggle on"></div></div></div>'+
  '</div></div>';
  el.querySelectorAll('.mi[data-m]').forEach(mi=>mi.onclick=()=>{cur=mi.dataset.m;render();});
  el.querySelectorAll('.toggle').forEach(t=>t.onclick=()=>{ if(t.dataset.k){t.classList.toggle('on'); stage(cur,t.dataset.k,t.classList.contains('on')?1:0);} else t.classList.toggle('on'); });
  el.querySelectorAll('input.cfg,select.cfg').forEach(inp=>inp.onchange=()=>{stage(cur,inp.dataset.k,inp.value);});
  el.querySelectorAll('.toggle:not(.on)').forEach(()=>{});
  el.querySelector('#cfgSave').onclick=()=>{ const n=Object.keys(staged).reduce((a,m)=>a+Object.keys(staged[m]).length,0);
   if(!n){NWX.toast('Nothing staged','Edit a field first — changes stage automatically with an amber marker.','blue');return;}
   window.NWCHANGES.push({id:'CHG-'+Math.floor(1000+Math.random()*9000),mod:cur,what:n+' setting(s) staged across '+Object.keys(staged).length+' module(s)',by:'J. Nowills',stat:'pending'});
   NWX.toast('Staged — change control','Configuration staged. Requires Internal Audit / COO approval to publish (rollback point created).','amber'); NWX.audit('Configuration staged',cur); render(); };
  el.querySelector('#cfgReset').onclick=()=>{ const s=cfgStore(); if(s.stage)delete s.stage[cur]; try{localStorage.setItem('nw_cfg',JSON.stringify(s));}catch(e){} NWX.toast('Reverted','Local staged changes for '+MODN[cur]+' discarded.','blue'); render(); };
  el.querySelector('#cfgAudit').onclick=()=>{ const s=cfgStore(); const rows=[];
   ['pub','stage'].forEach(ring=>Object.keys(s[ring]||{}).forEach(m=>Object.keys(s[ring][m]).forEach(k=>rows.push([ring==='pub'?'Published':'Staged',MODN[m]||m,k,String(s[ring][m][k])]))));
   NWX.modal('Configuration audit diff', rows.length?'<table class="tbl"><thead><tr><th>Ring</th><th>Module</th><th>Key</th><th>Value</th></tr></thead><tbody>'+rows.map(r=>'<tr><td><span class="badge '+(r[0]==='Staged'?'amber':'green')+'">'+r[0]+'</span></td><td>'+r[1]+'</td><td class="mono">'+r[2]+'</td><td>'+esc(r[3])+'</td></tr>').join('')+'</tbody></table>':'<div class="empty-state">No overrides — all modules on certified defaults.</div>'); };
  const q=el.querySelector('#cmq'); q.oninput=()=>{ const v=q.value.toLowerCase(); el.querySelectorAll('.mi[data-m]').forEach(mi=>{const m2=D2.CFG[mi.dataset.m]; mi.style.display=(MODN[mi.dataset.m]+' '+JSON.stringify(m2)).toLowerCase().includes(v)?'':'none';}); }; }
 function stage(m,k,v){ const s=cfgStore(); s.stage=s.stage||{}; s.stage[m]=s.stage[m]||{}; s.stage[m][k]=v;
  try{localStorage.setItem('nw_cfg',JSON.stringify(s));}catch(e){}
  const rows=el.querySelectorAll('.setrow'); NWX.toast('Staged',k+' = '+v+' — queued for change-control approval.','blue'); render(); }
 render();};

/* ---------------- Change control table ---------------- */
if(!window.NWCHANGES)window.NWCHANGES=[
 {id:'CHG-1041',mod:'tax',what:'VAT certificate threshold — applied GRA ruling 2026/014',by:'R. Asante',stat:'published'},
 {id:'CHG-1038',mod:'proc',what:'DoA: CFO limit ₵400k → ₵500k',by:'E. Osei',stat:'published'},
 {id:'CHG-1036',mod:'hse',what:'CAPA escalation 10d → 7d',by:'S. Adjei',stat:'published'},
 {id:'CHG-1033',mod:'weigh',what:'E-invoice retry policy: 3 attempts / 5 min',by:'System Administrator',stat:'published'},
 {id:'CHG-1055',mod:'quarry',what:'Daily target 5,200 → 5,500 t (Shai Hills only)',by:'K. Boateng',stat:'pending'},
 {id:'CHG-1054',mod:'fuel',what:'Theft-risk threshold 60 → 55 (pilot stricter)',by:'Y. Oduro',stat:'pending'}];
MOUNT.changes=function(el){ function render(){ el.innerHTML='<div class="hlist" style="padding-bottom:6px"><span class="chip on">'+NWCHANGES.filter(c2=>c2.stat==='pending').length+' pending approval</span><span class="chip">'+NWCHANGES.filter(c2=>c2.stat==='published').length+' published</span><span class="chip">every publish = version + rollback point</span></div>'+
  NWCHANGES.map((c2,i)=>'<div class="alertcard" style="margin:0 12px 8px"><h5><span class="badge '+(c2.stat==='pending'?'amber':'green')+'">'+c2.stat.toUpperCase()+'</span> '+c2.id+' <span style="color:var(--muted);font-weight:400">· '+(MODN[c2.mod]||c2.mod)+'</span></h5>'+
  '<div class="meta">'+esc(c2.what)+' · by '+c2.by+'</div>'+
  (c2.stat==='pending'?'<div class="acts"><button class="btn sm pri" data-i="'+i+'" data-x="pub">Approve & publish</button><button class="btn sm" data-i="'+i+'" data-x="rej">Reject</button><button class="btn sm ghost" data-i="'+i+'" data-x="diff">Preview diff</button></div>':'<div class="acts"><button class="btn sm ghost" data-i="'+i+'" data-x="rb">Rollback</button></div>')+'</div>').join('');
  el.querySelectorAll('[data-x]').forEach(b=>b.onclick=()=>{ const c2=NWCHANGES[+b.dataset.i]; const x=b.dataset.x;
   if(x==='diff'){NWX.modal('Diff — '+c2.id,'<pre class="mono" style="background:var(--inset);padding:14px;border-radius:9px;font-size:11.5px;overflow:auto">+ new value\n- previous value\n\nmodule: '+c2.mod+'\n'+c2.what+'\n\nimpact: validated against 38 regression rules — OK</pre>',null,'Close');return;}
   if(x==='rb'){NWX.toast('Rolled back',c2.id+' reverted to previous version; incident-free.','amber');NWX.audit('Config rollback',c2.id);return;}
   if(NWX.state.offline){NWX.toast('Offline','Publishing requires connectivity to audit anchor.','red');return;}
   c2.stat=x==='pub'?'published':'rejected'; NWX.toast(x==='pub'?'Published':'Rejected',c2.id+' — '+(x==='pub'?'live now; rollback point saved.':'returned with comments.'),x==='pub'?'green':'red'); NWX.audit('Config '+(x==='pub'?'published':'rejected'),c2.id); NWX.bumpBadges(); render(); }); }
 render();};

/* ---------------- Notification template editor ---------------- */
MOUNT.tmpl=function(el){ let cur=0; const CHK=['inapp','email','sms','whatsapp','push'];
 function render(){ const t=D2.TPL[cur];
  el.innerHTML='<div style="display:grid;grid-template-columns:250px 1fr;gap:0"><div style="border-right:1px solid var(--border);padding:8px;max-height:480px;overflow:auto">'+
  D2.TPL.map((x,i)=>'<div class="mi'+(i===cur?' active':'')+'" data-i="'+i+'" style="color:var(--text);padding:7px 9px"><span class="lbl" style="font-size:12px">'+x[0]+'</span><small style="color:var(--faint);font-size:9px">'+x[1]+'</small></div>').join('')+'</div>'+
  '<div style="padding:14px 16px"><div class="f-group"><label>Event — '+t[0]+'</label>'+
  '<textarea class="inline-edit" id="tplBody" rows="3" style="width:100%;padding:10px;font-size:13px;background:var(--inset);border:1px solid var(--border);border-radius:9px;color:var(--text);font-family:var(--font)">'+esc(t[2])+'</textarea></div>'+
  '<div class="f-group"><label>Channels</label><div class="checks">'+CHK.map(ch=>'<span class="chip '+(t[3].includes(ch)?'on':'')+'" data-ch="'+ch+'">'+ch+'</span>').join('')+'</div></div>'+
  '<div class="f-group"><label>Recipients</label><div class="checks">'+['Owner role','Line manager','Module manager','Escalation chain if SLA breached'].map((r,i)=>'<span class="chip on">'+r+'</span>').join('')+'</div></div>'+
  '<div class="f-group"><label>Live preview</label><div style="background:var(--inset);border:1px solid var(--border);border-radius:10px;padding:12px;font-size:12.5px;line-height:1.6" id="tplPrev">'+esc(fill(t[2]))+'</div></div>'+
  '<div style="display:flex;gap:8px;margin-top:6px"><button class="btn pri" id="tplSave">'+NWX.icon('save',13)+' Save template</button><button class="btn" id="tplTest">Send test to me</button></div></div></div>';
  el.querySelectorAll('.mi[data-i]').forEach(mi=>mi.onclick=()=>{cur=+mi.dataset.i;render();});
  el.querySelectorAll('[data-ch]').forEach(ch=>ch.onclick=()=>{ const arr=t[3].split(','); const i=arr.indexOf(ch.dataset.ch); i>=0?arr.splice(i,1):arr.push(ch.dataset.ch); t[3]=arr.join(','); ch.classList.toggle('on'); NWX.audit('Template channels changed',t[0]); });
  el.querySelector('#tplBody').oninput=e=>{ t[2]=e.target.value; el.querySelector('#tplPrev').textContent=fill(t[2]); };
  el.querySelector('#tplSave').onclick=()=>{ window.NWCHANGES.push({id:'CHG-'+Math.floor(1000+Math.random()*9000),mod:'docs',what:'Notification template «'+t[0]+'» updated',by:'J. Nowills',stat:'pending'}); NWX.toast('Template staged','Stored + routed through change control before going live.','amber'); NWX.audit('Template edited',t[0]); };
  el.querySelector('#tplTest').onclick=()=>NWX.toast('Test notification',fill(t[2]).slice(0,90)+'…','blue'); }
 function fill(s2){ const map={'{quarry}':'Shai Hills','{shift}':'Day','{actual}':'4,410','{target}':'5,200','{pct}':'85','{owner}':'E. Osei','{asset}':'CR-902','{site}':'Shai Hills','{fault}':'bearing overheat 96°C','{wo}':'WO-7882','{eta}':'14:30','{sku}':'Jaw plate QJ341','{qty}':'2','{level}':'3','{suggest}':'6','{project}':'P-2210','{weeks}':'1.6','{date}':'2027-02-28','{pts}':'2.1','{costcentre}':'CC-500 Maintenance','{customer}':'Goldline Civil','{inv}':'INV-99201','{amount}':'₵486,200','{days}':'38','{limit}':'96','{supplier}':'Orica Ghana','{runday}':'Friday','{form}':'VAT + levies (July)','{diff}':'₵0','{type}':'Near-miss','{desc}':'LV in exclusion zone','{sev}':'High','{capa}':'CN-331','{param}':'PM10','{value}':'78','{sla}':'4','{doctype}':'Purchase Order','{ref}':'PO-26-1310','{node}':'Buoho node','{mins}':'42','{queue}':'57','{time}':'08:16','{model}':'fuel-anomaly v1.4','{subject}':'3 night drawdowns','{conf}':'87','{impact}':'₵18k/mo'};
  return s2.replace(/\{(\w+)\}/g,m=>map[m]||m); }
 render();};

/* ---------------- FX & environment table ---------------- */
MOUNT.fxenv=function(el){
 function render(){ el.innerHTML='<div class="hlist" style="gap:10px">'+D.CURRENCIES.map((c,i)=>'<div class="hrow" style="flex-wrap:wrap"><span class="nm" style="width:56px"><b>'+c.c+'</b></span><span style="color:var(--muted)">GHS per unit</span><input class="inline-edit" data-i="'+i+'" value="'+c.r+'" style="width:90px;text-align:right;padding:5px 8px;margin:0 6px"><span style="color:var(--faint);font-size:11px">BoG interbank ±0.4% · refreshed 08:00</span><button class="btn sm ghost" data-lock="'+i+'">Lock</button></div>').join('')+'</div>'+
  '<div class="hlist"><span class="chip on">Environments</span><span class="chip">Production (this)</span><span class="chip">Sandbox / UAT — copy config →</span><span class="chip">Training — synthetic data</span></div>';
  el.querySelectorAll('input[data-i]').forEach(inp=>inp.onchange=()=>{ const c=D.CURRENCIES[+inp.dataset.i]; const old=c.r; c.r=parseFloat(inp.value)||old;
   window.NWCHANGES.push({id:'CHG-'+Math.floor(1000+Math.random()*9000),mod:'finance',what:'FX rate '+c.c+': '+old+' → '+c.r,by:'J. Nowills',stat:'pending'});
   NWX.toast('FX staged',c.c+' rate change pending change-control publish.'),NWX.audit('FX rate edited',c.c); }); }
 render();};

/* =====================================================================
   LIVE COMPANY ENTITIES & SETTINGS (Setup & Configuration module)
   Real ERP integration: same /api/v1/companies surface the Admin Console
   uses. Sign-in reuses the console session (p1_token) — everything is
   validated, permission-gated, audited and persisted server-side.
   ===================================================================== */
MOUNT.cosetup=function(el){
 const LS={get:(k,d)=>{try{const v=JSON.parse(localStorage.getItem(k));return v==null?d:v;}catch(e){return d;}},set:(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v));}catch(e){}},del:k=>{try{localStorage.removeItem(k);}catch(e){}}};
 let st={q:'',rows:[],total:0,me:null};
 async function api(method,path,bod){
   const hdr={'Content-Type':'application/json'}; const tk=LS.get('p1_token',null); if(tk)hdr.Authorization='Bearer '+tk;
   const res=await fetch('/api/v1'+path,{method,headers:hdr,body:bod?JSON.stringify(bod):undefined});
   let j=null; try{j=await res.json();}catch(e){}
   if(res.status===401){ LS.del('p1_token'); st.me=null; }
   if(!j||!j.ok){ const err=new Error((j&&j.error)||('HTTP '+res.status)); err.status=res.status; err.fields=(j&&j.fields)||null; throw err; }
   return j.data;
 }
 function signInView(msg){
  el.innerHTML='<div style="max-width:430px;margin:26px auto;background:var(--panel,#151922);border:1px solid var(--border);border-radius:14px;padding:22px">'
   +'<div style="display:flex;gap:10px;align-items:center;margin-bottom:10px"><div style="width:38px;height:38px;border-radius:10px;background:#f8c200;display:grid;place-items:center;font-weight:800;color:#16181d">NS</div><div><b>Connect to the live ERP</b><div style="color:var(--muted);font-size:11.5px">Company creation &amp; settings write to the real database, fully audited.</div></div></div>'
   +(msg?'<div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.4);color:#fca5a5;border-radius:8px;padding:8px 10px;font-size:12px;margin-bottom:10px">'+esc(msg)+'</div>':'')
   +'<div class="f-group"><label>Console username</label><input id="csU" class="inline-edit" style="width:100%;text-align:left" placeholder="admin" autocomplete="username"></div>'
   +'<div class="f-group"><label>Password</label><input id="csP" type="password" class="inline-edit" style="width:100%;text-align:left" autocomplete="current-password"></div>'
   +'<button class="btn pri" id="csGo" style="width:100%;justify-content:center;padding:10px">Sign in &amp; load companies</button>'
   +'<div style="color:var(--faint);font-size:11px;margin-top:10px">Seeded admin: <b class="mono">admin / Nowills@2026</b>.</div></div>';
  el.querySelector('#csGo').onclick=async()=>{
    const u=el.querySelector('#csU').value.trim(), p=el.querySelector('#csP').value;
    try{
      const res=await fetch('/api/v1/auth/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})});
      const j=await res.json().catch(()=>null);
      if(!j||!j.ok)return signInView((j&&j.error)||'Sign-in failed.');
      LS.set('p1_token',j.data.token); st.me=j.data.user;
      if(j.data.mustChangePassword){
        NWX.modal('Set a new password','<div class="f-group"><label>New password (8+ chars)</label><input type="password" id="np1" class="inline-edit" style="width:100%;text-align:left"></div><div class="f-group"><label>Repeat it</label><input type="password" id="np2" class="inline-edit" style="width:100%;text-align:left"></div><div id="npErr" style="color:#f87171;font-size:12px;min-height:16px"></div><div class="actionbar"><button class="btn pri" id="npGo">Save password &amp; continue</button></div>',box=>{
          box.querySelector('#npGo').onclick=async()=>{ const n1=box.querySelector('#np1').value,n2=box.querySelector('#np2').value,er=box.querySelector('#npErr');
            if(n1.length<8)return er.textContent='At least 8 characters.'; if(n1!==n2)return er.textContent='Passwords do not match.';
            try{ await api('POST','/auth/change-password',{current:p,next:n1}); document.querySelectorAll('.modal-ov').forEach(m=>m.remove()); NWX.toast('Password rotated','Signed in to company setup.','green'); render(); }catch(e2){ er.textContent=e2.message; } }; });
      } else { NWX.toast('Connected','Signed in as '+st.me.fullName+' — live company management unlocked.','green'); render(); }
    }catch(e){ signInView(e.message); }
  };
 }
 /* ---------- guided 4-step wizard (create & settings) ---------- */
 const FDS=[
  {t:'Identity',d:'Legal names & registration',keys:[['name','Registered name','Nowills Quarry Ltd',1],['tradingName','Trading name','Nowills Quarry',1],['registrationNumber','Registration №','CS0041…',1],['country','Country','Ghana','sel',['Ghana','Other ECOWAS']]]},
  {t:'Ghana fiscal',d:'GRA & SSNIT identifiers — they print on every tax invoice',keys:[['tin','TIN','C0041…',1],['vatNumber','VAT №','V0041…',1],['ssnitNumber','SSNIT №','S0004…',1]]},
  {t:'Contact & branding',d:'Addresses, channels and print branding (logo/stamp ≤ 150 KB images)',keys:[['physicalAddress','Physical address','Plot, street, city',0],['postalAddress','Postal address','P.O. Box…',0],['phone','Telephone','+233 …',0],['email','Email','info@…',1],['website','Website','https://…',0]]},
  {t:'Localization & fiscal year',d:'Currency, calendar, formatting — drive reports & numbering',keys:[['defaultCurrency','Default currency','GHS','sel',['GHS','USD','EUR','GBP','XOF'],1],['fiscalYearStart','Fiscal year starts','January','sel',['January','July']],['accountingPeriod','Accounting period','Monthly','sel',['Monthly','Weekly']],['timezone','Time zone','Africa/Accra','sel',['Africa/Accra','UTC']],['dateFormat','Date format','DD/MM/YYYY','sel',['DD/MM/YYYY','YYYY-MM-DD','MM/DD/YYYY']],['numberFormat','Number format','1,234,567.89','sel',['1,234,567.89','1.234.567,89']]]}];
 function coWizard(rec){
  const isNew=!rec, vals={}; let errs={};
  ( rec?Object.keys(rec):[]).forEach(k=>vals[k]=rec[k]);
  let step=0;
  function fld(k,l,ph,req,selOpts){
    const v=vals[k]!==undefined?vals[k]:(selOpts?selOpts[0]:'');
    const ctrl=selOpts?'<select class="inline-edit cfg" data-k="'+k+'" style="width:100%;text-align:left">'+selOpts.map(o=>'<option'+(o===v?' selected':'')+'>'+o+'</option>').join('')+'</select>'
      :'<input class="inline-edit cfg" data-k="'+k+'" value="'+esc(v||'')+'" placeholder="'+esc(ph||'')+'" style="width:100%;text-align:left">';
    return '<div class="f-group"><label>'+l+(req?' <b style="color:var(--f8,#f8c200)">*</b>':'')+'</label>'+ctrl+'<small data-err="'+k+'" style="color:#f87171;min-height:13px;display:block">'+(errs[k]||'')+'</small></div>';
  }
  function bodyHtml(box){
    const f=FDS[Math.min(step,FDS.length-1)];
    const head='<div style="display:flex;gap:8px;align-items:center;margin:2px 0 14px">'+FDS.map((s2,i)=>'<div style="flex:1;text-align:center"><div style="width:26px;height:26px;margin:0 auto 3px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:700;background:'+(i<step?'var(--green,#22c55e)':i===step?'#f8c200':'var(--inset)')+';color:'+(i<=step?'#16181d':'var(--muted)')+'">'+(i<step?'✓':(i+1))+'</div><small style="color:'+(i===step?'var(--text)':'var(--faint)')+'">'+s2.t+'</small></div>').join('')+'<div style="flex:1;text-align:center"><div style="width:26px;height:26px;margin:0 auto 3px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:700;background:'+(step===4?'#f8c200':'var(--inset)')+';color:'+(step===4?'#16181d':'var(--muted)')+'">✓</div><small style="color:'+(step===4?'var(--text)':'var(--faint)')+'">Review</small></div></div>'
      +'<div style="display:flex;align-items:baseline;gap:8px"><b style="font-size:14px">'+(step===4?'Review & commit':f.t)+'</b><span style="color:var(--muted);font-size:11.5px">'+(step===4?'Confirm every value — the server re-validates on save':f.d)+'</span></div>';
    const fields= step<4 ? '<div style="display:grid;grid-template-columns:1fr 1fr;gap:0 16px;margin-top:8px">'+f.keys.map(k=>fld(k[0],k[1],k[2],k[3]===1,k[3]==='sel'?k[4]:null)).join('')+'</div>' : '';
    const brand= step===2
      ? '<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:4px">'
        +['logo','stamp'].map(k=>'<div class="f-group"><label>'+(k==='logo'?'Company logo':'Company stamp / authorised signature')+'</label>'
        +'<input type="file" data-img="'+k+'" accept="image/png,image/jpeg" style="font-size:11.5px">'
        +'<div data-pv="'+k+'" style="margin-top:6px;min-height:34px">'+(vals[k]?'<img src="'+vals[k]+'" style="max-height:52px;border-radius:8px;border:1px solid var(--border)">':'<span style="color:var(--faint);font-size:11px">No image — letterheads show the NS block.</span>')+'</div>'
        +'<small data-err="'+k+'" style="color:#f87171;min-height:13px;display:block">'+(errs[k]||'')+'</small></div>').join('')+'</div>' : '';
    const review= step===4 ? (function(){
        const rows=[]; FDS.forEach(s2=>s2.keys.forEach(k=>rows.push([k[1],vals[k[0]]||'—'])));
        return '<div style="margin-top:8px;max-height:220px;overflow:auto;border:1px solid var(--border);border-radius:9px"><table class="tbl"><tbody>'+rows.map(r=>'<tr><td style="width:38%"><b>'+r[0]+'</b></td><td>'+esc(String(r[1]))+'</td></tr>').join('')+(vals.logo?'<tr><td><b>Logo</b></td><td><img src="'+vals.logo+'" style="max-height:34px"></td></tr>':'')+(vals.stamp?'<tr><td><b>Stamp</b></td><td><img src="'+vals.stamp+'" style="max-height:34px"></td></tr>':'')+'</tbody></table></div>';
      })() : '';
    const bar='<div class="actionbar" style="margin-top:14px;display:flex;gap:8px">'
      +(step>0?'<button class="btn ghost" id="wzBack">← Back</button>':'')
      +'<span style="margin-left:auto"></span>'
      +(step<4?'<button class="btn" id="wzNext">'+ (step===3?'Review →':'Next →') +'</button>':'<button class="btn pri" id="wzSave">✓ '+(isNew?'Register company':'Save settings')+'</button>')+'</div>';
    return head+fields+brand+review+bar;
  }
  function collect(box){ box.querySelectorAll('[data-k]').forEach(i2=>vals[i2.dataset.k]=i2.value); }
  function mountWizard(box){
    const paint=()=>{ box.innerHTML=bodyHtml(box); wire(box); };
    function wire(){
      box.querySelectorAll('input[type=file][data-img]').forEach(fi=>fi.onchange=()=>{
        const f=fi.files&&fi.files[0]; if(!f)return;
        if(f.size>150*1024){ collect(box); errs[fi.dataset.img]='Image must be ≤ 150 KB.'; vals[fi.dataset.img]=null; paint(); return; }
        const rd=new window.FileReader(); rd.onload=()=>{ collect(box); vals[fi.dataset.img]=String(rd.result); errs[fi.dataset.img]=''; paint(); };
        rd.readAsDataURL(f);
      });
      const bk=box.querySelector('#wzBack'); if(bk)bk.onclick=()=>{ collect(box); step--; paint(); };
      const nx=box.querySelector('#wzNext'); if(nx)nx.onclick=()=>{ collect(box);
        errs={};
        box.querySelectorAll('[data-k]').forEach(i2=>{ const meta=FDS[step].keys.find(k=>k[0]===i2.dataset.k); if(meta&&meta[3]===1&&!String(i2.value).trim())errs[i2.dataset.k]='Required.'; });
        if(Object.keys(errs).length){ paint(); return; }
        step++; paint(); };
      const sv=box.querySelector('#wzSave'); if(sv)sv.onclick=async()=>{
        ['name','tradingName','registrationNumber','tin','vatNumber','ssnitNumber','email','defaultCurrency','country'].forEach(k=>{ if(!String(vals[k]||'').trim()) errs[k]='Required.'; });
        if(Object.keys(errs).length){ step=FDS.findIndex(s2=>s2.keys.some(k=>errs[k[0]])); if(step<0)step=0; paint(); NWX.toast('Missing required fields','The wizard jumped to the first step needing attention.','amber'); return; }
        const o={...vals}; delete o.id; delete o.createdAt; delete o.updatedAt; delete o.createdBy; delete o.status;
        try{
          if(isNew&&!o.code)o.code='CO-'+String(st.total+1).padStart(2,'0');
          const saved=isNew?await api('POST','/companies',o):await api('PUT','/companies/'+rec.id,o);
          document.querySelectorAll('.modal-ov').forEach(m=>m.remove());
          NWX.toast(isNew?'Company registered':'Settings saved',saved.name+' · '+saved.code+' — persisted & audited (letterheads update immediately).','green');
          NWX.audit(isNew?'Company created (Setup wizard)':'Company settings updated',saved.code);
          render();
        }catch(e2){
          if(e2.fields){ errs={}; Object.keys(e2.fields).forEach(k=>errs[k]=(typeof e2.fields[k]==='string'&&e2.fields[k].length<60)?e2.fields[k]:'Invalid'); 
            const badStep=FDS.findIndex(s2=>s2.keys.some(k=>errs[k[0]])); if(badStep>=0)step=badStep; paint(); }
          NWX.toast('Validation refused',e2.message,'red');
        }
      };
    }
    paint();
  }
  NWX.modal((isNew?'Register new company':'Company settings — '+(rec.code||'')), '<div id="wz"></div>', box=>mountWizard(box.querySelector('#wz')));
 }
 async function render(){
  if(!LS.get('p1_token',null))return signInView();
  let rows;
  try{
    const me=await api('GET','/auth/me'); st.me=me.user;
    const data=await api('GET','/companies?per=100&status=all&sort=code&dir=asc');
    rows=data.rows; st.total=data.total;
  }catch(e){ if(e.status===401)return signInView('Session expired — sign in again.'); 
    el.innerHTML='<div class="empty-state" style="padding:40px">API unreachable.<br><small>'+esc(e.message)+'</small></div>'; return; }
  const q=(st.q||'').toLowerCase();
  const vis=rows.filter(c=>!q||(c.name+' '+(c.tradingName||'')+' '+(c.code||'')+' '+(c.tin||'')+' '+(c.email||'')+' '+(c.physicalAddress||'')).toLowerCase().includes(q));
  el.innerHTML=
   '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:10px">'
   +'<span class="badge" style="background:rgba(34,197,94,.15);color:#4ade80">● LIVE — ERP database</span>'
   +'<span style="color:var(--muted);font-size:12px">'+st.total+' entit'+(st.total===1?'y':'ies')+' · signed in as <b>'+esc(st.me.fullName||st.me.username)+'</b></span>'
   +'<input type="search" id="coQ" placeholder="Filter companies…" value="'+esc(st.q)+'" style="background:var(--inset);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:7px 10px;font-size:12px">'
   +'<span style="margin-left:auto;display:flex;gap:8px"><button class="btn sm ghost" id="coReload">↻ Refresh</button>'
   +'<button class="btn sm pri" id="coNew">＋ Register new company (guided)</button>'
   +'<a class="btn sm ghost" href="/settings/company" style="text-decoration:none">Company Settings →</a></span></div>'
   +'<div style="max-height:380px;overflow:auto;border:1px solid var(--border);border-radius:10px"><table class="tbl"><thead><tr><th></th><th>Code</th><th>Company</th><th>Trading</th><th>TIN</th><th>Currency</th><th>FY</th><th>Status</th><th style="width:190px"></th></tr></thead><tbody>'
   + vis.map(c=>'<tr><td>'+(c.logo?'<img src="'+c.logo+'" style="height:26px;border-radius:6px">':'<span style="display:inline-grid;place-items:center;width:26px;height:26px;border-radius:6px;background:#f8c200;color:#16181d;font-weight:800;font-size:10px">'+esc((c.code||'NS').slice(0,2))+'</span>')+'</td>'
     +'<td class="mono"><b>'+esc(c.code||'—')+'</b></td>'
     +'<td>'+esc(c.name)+'<br><small style="color:var(--faint)">'+esc(c.physicalAddress||c.country||'')+'</small></td>'
     +'<td>'+esc(c.tradingName||'—')+'</td><td class="mono">'+esc(c.tin||'—')+'</td>'
     +'<td>'+esc(c.defaultCurrency||'GHS')+'</td><td>'+esc(c.fiscalYearStart||'—')+'</td>'
     +'<td><span class="badge '+(c.status==='active'?'green':'amber')+'">'+(c.status||'active')+'</span></td>'
     +'<td style="white-space:nowrap;text-align:right">'
     +'<button class="btn sm ghost" data-ed="'+c.id+'">⚙ Settings</button> '
     +(c.status==='active' ? '<button class="btn sm ghost" data-arch="'+c.id+'" title="Archive">🗄</button>' : '<button class="btn sm ghost" data-rest="'+c.id+'" title="Restore">♺</button>')
     +'</td></tr>').join('')
   +(vis.length?'':'<tr><td colspan="20"><div class="empty-state">No companies match.</div></td></tr>')
   +'</tbody></table></div>'
   +'<div style="color:var(--faint);font-size:11px;margin-top:8px">Guided 4-step wizard: Identity → Ghana fiscal → Contact & branding → Localization, then review & commit. Everything validates server-side, persists, and updates every invoice/waybill/payslip letterhead instantly.</div>';
  el.querySelector('#coQ').oninput=e2=>{st.q=e2.target.value;render();};
  el.querySelector('#coReload').onclick=()=>render();
  el.querySelector('#coNew').onclick=()=>coWizard(null);
  el.querySelectorAll('[data-ed]').forEach(b=>b.onclick=()=>coWizard(rows.find(c=>c.id===b.dataset.ed)));
  el.querySelectorAll('[data-arch]').forEach(b=>b.onclick=async()=>{ try{ await api('POST','/companies/'+b.dataset.arch+'/archive',{}); NWX.toast('Archived','Company archived — restorable any time.','amber'); render(); }catch(e2){ NWX.toast('Refused',e2.message,'red'); } });
  el.querySelectorAll('[data-rest]').forEach(b=>b.onclick=async()=>{ try{ await api('POST','/companies/'+b.dataset.rest+'/restore',{}); NWX.toast('Restored','Company active again.','green'); render(); }catch(e2){ NWX.toast('Refused',e2.message,'red'); } });
 }
 render();
};
window.__MNT2=1;
})();
