/* AI-COS CalLab Pro — operational registers and ISO/IEC 17025 workflows */
const D=()=>window.DB;
const idFor=(prefix,rows)=>prefix+String(rows.length+1).padStart(3,'0');
const money=n=>Number(n||0).toLocaleString('en-SA',{minimumFractionDigits:2})+' SAR';
const fieldOptions=(key)=>({customer:D().customers.map(x=>x.id),owner:D().staff.map(x=>x.id),tech:D().staff.filter(x=>x.role==='Calibration Technician').map(x=>x.id),standard:D().standards.map(x=>x.id),discipline:D().disciplines.map(x=>x.name),status:['Active','Draft','Open','Closed'],stage:['Open','Root cause','Action','Verification','Closed']}[key]||[]);
const pretty=(v,key)=>{if(v==null||v==='')return '—';if(v instanceof Date)return D().fmt(v);if(['date','due','review','trainingDue','recvd','calDate'].includes(key))return D().fmt(v);if(key==='customer')return (D().custById(v)||{}).name||v;if(['owner','tech','approvedBy'].includes(key))return (D().staffById(v)||{}).name||v;if(Array.isArray(v))return v.join(', ');return String(v)};
function PageHead({title,sub,icon='grid',action='Add',onAdd,right}){return <div className="row" style={{justifyContent:'space-between',marginBottom:18,alignItems:'flex-start'}}><div><div className="row"><span className="kpi-ico" style={{background:'var(--primary-50)',color:'var(--primary-600)'}}><Icon name={icon}/></span><div><div className="h-page">{title}</div><div className="h-sub">{sub}</div></div></div></div><div className="row">{right}{onAdd&&<Btn variant="primary" icon="plus" onClick={onAdd}>{action}</Btn>}</div></div>}
function Tabs({items,value,onChange}){return <div className="tabs" style={{marginBottom:16}}>{items.map(x=><div key={x} className={'tab'+(value===x?' on':'')} onClick={()=>onChange(x)}>{x}</div>)}</div>}
function openRecordForm(cfg,row){const fields=cfg.formFields.map(f=>{const base=typeof f==='string'?{key:f,label:f.replace(/([A-Z])/g,' $1').replace(/^./,x=>x.toUpperCase())}:f;const opts=base.options||fieldOptions(base.key);return {...base,type:base.type||(opts.length?'select':'text'),options:opts,value:row?row[base.key]:base.value}});window.openForm({title:(row?'Edit ':'Add ')+cfg.title.replace(/s$/,''),icon:cfg.icon,fields,onSave:v=>{const clean={...v};fields.forEach(f=>{if(f.type==='number')clean[f.key]=Number(clean[f.key]);if(f.type==='date'&&clean[f.key])clean[f.key]=new Date(clean[f.key]);});if(row)dbUpdate(cfg.dataKey,'id',row.id,clean);else dbAdd(cfg.dataKey,{id:cfg.idPrefix? idFor(cfg.idPrefix,D()[cfg.dataKey]):idFor('REC-',D()[cfg.dataKey]),...clean});},done:row?'Record updated':'Record created'});}
function makeRegister(cfg){return function Register(){const [q,setQ]=useState('');const rows=(D()[cfg.dataKey]||[]).filter(r=>JSON.stringify(r).toLowerCase().includes(q.toLowerCase()));const values=(cfg.kpis||[]).map(k=>({...k,value:typeof k.value==='function'?k.value(D()[cfg.dataKey],D()):k.value}));return <div className="page page-wide rise"><PageHead title={cfg.title} sub={cfg.sub} icon={cfg.icon} onAdd={()=>openRecordForm(cfg)}/>{values.length>0&&<div className="kpi-grid" style={{gridTemplateColumns:`repeat(${Math.min(4,values.length)},1fr)`,marginBottom:16}}>{values.map((k,i)=><Kpi key={i} {...k}/>)}</div>}<div className="card"><div className="card-head"><div className="searchbox" style={{width:360}}><Icon name="search"/><input value={q} onChange={e=>setQ(e.target.value)} placeholder={`Search ${cfg.title.toLowerCase()}…`}/></div><span className="spacer"/><Badge kind="neutral">{rows.length} records</Badge></div><table className="tbl"><thead><tr>{cfg.columns.map(c=><th key={c.key}>{c.label}</th>)}<th>Actions</th></tr></thead><tbody>{rows.map(r=><tr key={r.id}>{cfg.columns.map(c=><td key={c.key}>{c.render?c.render(r[c.key],r):c.key===cfg.statusKey?<StatusBadge status={r[c.key]}/>:pretty(r[c.key],c.key)}</td>)}<td><div className="row"><Btn size="sm" icon="eye" onClick={()=>openRecordForm(cfg,r)}>Edit</Btn>{cfg.rowActions&&cfg.rowActions(r)}<Btn size="sm" icon="x" onClick={()=>{if(confirm(`Delete ${r.id}?`))dbRemove(cfg.dataKey,'id',r.id)}}>Delete</Btn></div></td></tr>)}</tbody></table>{!rows.length&&<EmptyState icon={cfg.icon} title="No matching records" desc="Change the search or create a record."/>}</div></div>}}
const c=(key,label,render)=>({key,label:label||key,render});
const Customers=makeRegister({title:'Customers',sub:'Customer accounts, contacts and commercial profiles',icon:'users',dataKey:'customers',idPrefix:'C-',statusKey:'tier',columns:[c('id','Code'),c('name','Customer'),c('sector','Sector'),c('site','Site'),c('contact','Contact'),c('tier','Tier')],formFields:['name','sector','site','contact',{key:'email',type:'email'},'phone','tier'],kpis:[{label:'Customers',icon:'users',value:r=>r.length},{label:'Enterprise AMC',icon:'building',tone:'ok',value:r=>r.filter(x=>x.tier==='Enterprise AMC').length},{label:'Average rating',icon:'star',value:r=>(r.reduce((a,x)=>a+x.rating,0)/r.length).toFixed(1),unit:'/5'}]});
const Standards=makeRegister({title:'Reference standards',sub:'Metrological assets, due dates and traceability',icon:'gauge',dataKey:'standards',idPrefix:'STD-',statusKey:'status',columns:[c('id','Standard'),c('name','Description'),c('discipline','Discipline'),c('traceability','Traceability'),c('due','Calibration due'),c('status','Status')],formFields:['name','discipline','serial','traceability','certNo','calibratedBy',{key:'due',type:'date'},'status'],kpis:[{label:'Standards',icon:'gauge',value:r=>r.length},{label:'Due ≤30 days',icon:'clock',tone:'warn',value:r=>r.filter(x=>(new Date(x.due)-D().TODAY)/864e5<=30).length},{label:'Valid',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.status==='Valid').length}]});
const Complaints=makeRegister({title:'Complaints',sub:'Customer feedback, investigation and resolution',icon:'chat',dataKey:'complaints',idPrefix:'CMP-26-',statusKey:'status',columns:[c('id','Complaint'),c('customer','Customer'),c('subject','Subject'),c('date','Received'),c('status','Status')],formFields:['customer','subject',{key:'date',type:'date'},{key:'status',type:'select',options:['Received','Investigating','Resolved','Closed']}],kpis:[{label:'Open',icon:'chat',tone:'warn',value:r=>r.filter(x=>x.status!=='Closed').length},{label:'Closed',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.status==='Closed').length}]});
const Risk=makeRegister({title:'Risk & opportunity register',sub:'ISO 9001 risk-based thinking and treatment controls',icon:'shield',dataKey:'risks',idPrefix:'RSK-',statusKey:'status',columns:[c('id','Risk'),c('title','Description'),c('likelihood','L'),c('impact','I'),c('score','Score',(_,r)=>{const s=r.likelihood*r.impact;return <Badge kind={s>=15?'bad':s>=8?'warn':'ok'}>{s}</Badge>}),c('treatment','Treatment'),c('owner','Owner'),c('status','Status')],formFields:['title',{key:'likelihood',type:'number'},{key:'impact',type:'number'},'treatment','owner',{key:'status',type:'select',options:['Open','Controlled','Closed']}],kpis:[{label:'High risks',icon:'alert',tone:'bad',value:r=>r.filter(x=>x.likelihood*x.impact>=15).length},{label:'Controlled',icon:'shield',tone:'ok',value:r=>r.filter(x=>x.status==='Controlled').length},{label:'Opportunities',icon:'sparkles',value:(_,d)=>d.opportunities.length}]});
const Competency=makeRegister({title:'Competency & authorization',sub:'ISO/IEC 17025 personnel competence and training matrix',icon:'users',dataKey:'staff',idPrefix:'U-',statusKey:'role',columns:[c('id','Staff'),c('name','Name'),c('role','Role'),c('authorized','Authorized disciplines',v=><div className="wrap" style={{gap:5}}>{(v||[]).map(x=><span className="tag" key={x}>{x}</span>)}</div>),c('trainingDue','Training due',v=><Badge kind={(new Date(v)-D().TODAY)/864e5<30?'bad':'neutral'}>{D().fmt(v)}</Badge>)],formFields:['name',{key:'role',type:'select',options:D().roles},{key:'authorized',type:'checks',options:D().disciplines.map(x=>x.name)},{key:'trainingDue',type:'date'}],kpis:[{label:'Personnel',icon:'users',value:r=>r.length},{label:'Authorized technicians',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.role==='Calibration Technician'&&x.authorized.length).length},{label:'Training due ≤60d',icon:'clock',tone:'warn',value:r=>r.filter(x=>(new Date(x.trainingDue)-D().TODAY)/864e5<=60).length}]});
const Suppliers=makeRegister({title:'Approved suppliers',sub:'External provider approval and performance monitoring',icon:'truck',dataKey:'suppliers',idPrefix:'SUP-',columns:[c('id','Supplier'),c('name','Name'),c('scope','Scope'),c('score','Score',v=><Badge kind={v>=90?'ok':v>=80?'warn':'bad'}>{v}%</Badge>),c('approved','Approval',v=><Badge kind={v?'ok':'bad'}>{v?'Approved':'Not approved'}</Badge>),c('lastPO','Last PO')],formFields:['name','scope',{key:'score',type:'number'},{key:'approved',type:'select',options:['true','false']},'lastPO'],kpis:[{label:'Approved',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.approved).length},{label:'Average score',icon:'chart',value:r=>Math.round(r.reduce((a,x)=>a+x.score,0)/r.length),unit:'%'}]});
const Inventory=makeRegister({title:'Inventory',sub:'Calibration consumables, spares and minimum stock control',icon:'box',dataKey:'inventory',idPrefix:'INV-',statusKey:'status',columns:[c('id','Item'),c('name','Description'),c('stock','Stock',(_,r)=><Badge kind={r.stock<r.min?'bad':'ok'}>{r.stock} {r.unit}</Badge>),c('min','Minimum'),c('status','Status')],formFields:['name',{key:'stock',type:'number'},{key:'min',type:'number'},'unit'],kpis:[{label:'Items',icon:'box',value:r=>r.length},{label:'Low stock',icon:'alert',tone:'bad',value:r=>r.filter(x=>x.stock<x.min).length},{label:'Stock healthy',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.stock>=x.min).length}]});
const ncrCfg={title:'NCR & CAPA',sub:'Nonconformity, root cause, corrective action and effectiveness',icon:'flag',dataKey:'ncrs',idPrefix:'NCR-26-',statusKey:'stage',columns:[c('id','NCR'),c('title','Issue'),c('severity','Severity'),c('stage','Stage'),c('owner','Owner'),c('due','Due')],formFields:['title',{key:'severity',type:'select',options:['Minor','Major','Critical']},'owner',{key:'due',type:'date'},{key:'stage',type:'select',options:['Open','Root cause','Action','Verification','Closed']}],kpis:[{label:'Open NCR',icon:'flag',tone:'bad',value:r=>r.filter(x=>x.stage!=='Closed').length},{label:'Major',icon:'alert',tone:'warn',value:r=>r.filter(x=>x.severity==='Major').length},{label:'Closed',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.stage==='Closed').length}],rowActions:r=>r.stage!=='Closed'&&<Btn size="sm" variant="soft" icon="arrowR" onClick={()=>{const s=['Open','Root cause','Action','Verification','Closed'];dbUpdate('ncrs','id',r.id,{stage:s[s.indexOf(r.stage)+1],status:s[s.indexOf(r.stage)+1]});}}>Advance</Btn>};
const NCRRegister=makeRegister(ncrCfg);
const capaCfg={title:'CAPA actions',sub:'Corrective action and effectiveness verification',icon:'refresh',dataKey:'capas',idPrefix:'CAPA-26-',statusKey:'stage',columns:[c('id','CAPA'),c('title','Action'),c('stage','Stage'),c('owner','Owner'),c('due','Due'),c('status','Status')],formFields:['title','owner',{key:'due',type:'date'},{key:'stage',type:'select',options:['Open','Root cause','Action','Verification','Closed']}],kpis:[],rowActions:r=>r.stage!=='Closed'&&<Btn size="sm" variant="soft" icon="arrowR" onClick={()=>{const s=['Open','Root cause','Action','Verification','Closed'],next=s[Math.min(s.length-1,Math.max(0,s.indexOf(r.stage))+1)];dbUpdate('capas','id',r.id,{stage:next,status:next==='Closed'?'Closed':'In progress'});}}>Advance</Btn>};
const CAPARegister=makeRegister(capaCfg);
function NCR(){const [tab,setTab]=useState('NCR register'),Register=tab==='NCR register'?NCRRegister:CAPARegister;return <><div style={{position:'fixed',right:40,top:75,zIndex:5}}><div className="seg">{['NCR register','CAPA actions'].map(x=><button key={x} className={tab===x?'on':''} onClick={()=>setTab(x)}>{x}</button>)}</div></div><Register/></>}
const Documents=makeRegister({title:'Controlled documents',sub:'L1–L4 QMS hierarchy, publication and revision control',icon:'documents',dataKey:'documents',idPrefix:'ACL-',statusKey:'status',columns:[c('code','Document'),c('level','Level'),c('title','Title'),c('ver','Revision'),c('owner','Owner'),c('review','Review'),c('status','Status')],formFields:['code',{key:'level',type:'select',options:['L1 Quality Manual','L2 Procedures','L3 Work Instructions','L4 Forms']},'title','ver','owner',{key:'review',type:'date'},{key:'status',type:'select',options:['Draft','Published','Review due','Superseded']}],kpis:[{label:'Published',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.status==='Published').length},{label:'Review due',icon:'clock',tone:'warn',value:r=>r.filter(x=>x.status==='Review due').length},{label:'Controlled documents',icon:'documents',value:r=>r.length}],rowActions:r=><Btn size="sm" variant="soft" icon={r.status==='Published'?'refresh':'check'} onClick={()=>dbUpdate('documents','id',r.id,r.status==='Published'?{status:'Draft',ver:'Rev '+(Number(r.ver.replace(/\D/g,''))+1)}:{status:'Published'})}>{r.status==='Published'?'Revise':'Publish'}</Btn>});
function Billing(){const [tab,setTab]=useState('Quotations');const cfg=tab==='Quotations'?{title:'Quotations & billing',sub:'Commercial proposals and receivables in Saudi Riyal',icon:'receipt',dataKey:'quotations',idPrefix:'QT-26-',statusKey:'status',columns:[c('id','Quotation'),c('customer','Customer'),c('date','Date'),c('amount','Amount',money),c('status','Status')],formFields:['customer',{key:'date',type:'date'},{key:'amount',type:'number'},{key:'status',type:'select',options:['Draft','Sent','Accepted','Rejected']}],kpis:[]}:{title:'Invoices',sub:'Commercial proposals and receivables in Saudi Riyal',icon:'receipt',dataKey:'invoices',idPrefix:'INV-26-',statusKey:'status',columns:[c('id','Invoice'),c('customer','Customer'),c('quote','Quote'),c('date','Date'),c('due','Due'),c('amount','Amount',money),c('status','Status')],formFields:['customer','quote',{key:'date',type:'date'},{key:'due',type:'date'},{key:'amount',type:'number'},{key:'status',type:'select',options:['Draft','Sent','Paid','Overdue']}],kpis:[]};const Register=makeRegister(cfg);return <><div style={{position:'fixed',zIndex:5,right:40,top:75}}><div className="seg">{['Quotations','Invoices'].map(x=><button key={x} className={tab===x?'on':''} onClick={()=>setTab(x)}>{x}</button>)}</div></div><Register/></>}
const Invoices=Billing;
function Audit(){const [tab,setTab]=useState('Programme');const cfg=tab==='Programme'?{title:'Internal audit',sub:'ISO/IEC 17025 internal audit programme and findings',icon:'check-shield',dataKey:'audits',idPrefix:'IA-26-',statusKey:'status',columns:[c('id','Audit'),c('area','Area'),c('auditor','Auditor'),c('date','Date'),c('findings','Findings'),c('status','Status')],formFields:['area','auditor',{key:'date',type:'date'},{key:'status',type:'select',options:['Planned','Scheduled','Actions open','Closed']}],kpis:[]}:{title:'Audit findings',sub:'Observations, NCRs and opportunities for improvement',icon:'flag',dataKey:'findings',idPrefix:'FND-26-',statusKey:'status',columns:[c('id','Finding'),c('audit','Audit'),c('type','Type'),c('title','Finding'),c('status','Status')],formFields:['audit',{key:'type',type:'select',options:['NCR','Observation','OFI']},'title',{key:'status',type:'select',options:['Open','Closed']}],kpis:[]};const R=makeRegister(cfg);return <><div style={{position:'fixed',right:40,top:75,zIndex:5}}><div className="seg">{['Programme','Findings'].map(x=><button key={x} className={tab===x?'on':''} onClick={()=>setTab(x)}>{x}</button>)}</div></div><R/></>}
function Management(){const [tab,setTab]=useState('Reviews');const reviews=D().managementReviews;return <div className="page page-wide rise"><PageHead title="Management review" sub="QMS performance inputs, decisions and assigned actions" icon="chart" onAdd={()=>window.openForm({title:'Schedule management review',fields:[{key:'title',label:'Title'},{key:'date',label:'Date',type:'date'},{key:'status',label:'Status',type:'select',options:['Scheduled','Closed']},{key:'agenda',label:'Agenda',type:'textarea'}],onSave:v=>dbAdd('managementReviews',{id:'MR-26-'+(reviews.length+1),...v,agenda:String(v.agenda).split(','),actions:[]})})}/><Tabs items={['Reviews','Actions']} value={tab} onChange={setTab}/><div className="card">{tab==='Reviews'?<table className="tbl"><thead><tr><th>Review</th><th>Date</th><th>Agenda</th><th>Actions</th><th>Status</th></tr></thead><tbody>{reviews.map(x=><tr key={x.id}><td><b>{x.title}</b><div className="cell-sub mono">{x.id}</div></td><td>{D().fmt(x.date)}</td><td>{x.agenda.join(' · ')}</td><td>{x.actions.length}</td><td><StatusBadge status={x.status}/></td></tr>)}</tbody></table>:<table className="tbl"><thead><tr><th>Review</th><th>Management action</th><th>Status</th></tr></thead><tbody>{reviews.flatMap(r=>r.actions.map((a,i)=><tr key={r.id+i}><td className="mono">{r.id}</td><td>{a}</td><td><StatusBadge status={r.status==='Closed'?'Closed':'Open'}/></td></tr>))}</tbody></table>}</div></div>}
function Receiving(){const jobs=D().jobs;const receive=()=>window.openForm({title:'Receive equipment',sub:'Create job, condition record and barcode',icon:'box',fields:[{key:'customer',label:'Customer',type:'select',options:D().customers.map(x=>x.id)},{key:'asset',label:'Existing asset',type:'select',options:['New equipment',...D().assets.map(x=>x.id)]},{key:'type',label:'New equipment type'},{key:'serial',label:'New serial number'},{key:'condition',label:'Condition',type:'select',options:['Good','Damaged','Questionable']},{key:'accessories',label:'Accessories'},{key:'damageNotes',label:'Damage notes',type:'textarea'},{key:'photos',label:'Photos count',type:'number'}],onSave:v=>{let asset=v.asset;if(v.asset==='New equipment'){asset=idFor('AST-',D().assets);dbAdd('assets',{id:asset,customer:v.customer,type:v.type,serial:v.serial,condition:v.condition,discipline:'Gas Detection',status:'Received'});}dbAdd('jobs',{id:'JC-26-'+(1000+jobs.length+1),customer:v.customer,assets:[asset],discipline:[(D().assetById(asset)||{}).discipline||'Gas Detection'],priority:'Normal',recvd:new Date(),due:new Date(+D().TODAY+7*864e5),tech:null,location:'Laboratory',photos:Number(v.photos),stage:'Received',accessories:v.accessories,damageNotes:v.damageNotes});},done:'Equipment received and barcode created'});return <div className="page page-wide rise"><PageHead title="Equipment receiving" sub="Condition, accessories, photographs and chain-of-custody intake" icon="box" action="Receive equipment" onAdd={receive}/><div className="card"><table className="tbl"><thead><tr><th>Barcode</th><th>Job</th><th>Customer</th><th>Equipment</th><th>Condition</th><th>Photos</th><th>Actions</th></tr></thead><tbody>{jobs.filter(x=>x.stage==='Received').map(j=>{const a=D().assetById(j.assets[0])||{};return <tr key={j.id}><td><div style={{width:120,height:28,background:'repeating-linear-gradient(90deg,#111 0 2px,transparent 2px 4px,#111 4px 5px,transparent 5px 8px)'}}/><div className="mono" style={{fontSize:9}}>{j.id}</div></td><td className="mono cell-strong">{j.id}</td><td>{pretty(j.customer,'customer')}</td><td>{a.type||j.assets[0]}<div className="cell-sub">{a.serial}</div></td><td><StatusBadge status={a.condition||'Good'}/></td><td>{j.photos||0}</td><td><Btn size="sm" icon="download" onClick={()=>printReceiving(j)}>Print receiving report</Btn></td></tr>})}</tbody></table></div></div>}
function printReceiving(j){const a=D().assetById(j.assets[0])||{},cst=D().custById(j.customer)||{};printHtml('Receiving report',`<h1>Equipment Receiving Report</h1><h3>${j.id}</h3><p><b>Customer:</b> ${cst.name}</p><p><b>Equipment:</b> ${a.type||j.assets[0]} — ${a.serial||''}</p><p><b>Condition:</b> ${a.condition||'Good'}</p><div class="barcode"></div><p>${j.id}</p><hr><p>Received under ISO/IEC 17025 chain-of-custody controls.</p>`)}
function ContractReview(){const jobs=D().jobs.filter(x=>x.stage==='Received'||x.stage==='Contract Review');const review=j=>window.openForm({title:`ISO 17025 contract review · ${j.id}`,sub:'Clause 7.1 capability and requirement review',fields:['scope','method','capability','delivery','resources','risk'].map(key=>({key,label:key[0].toUpperCase()+key.slice(1),type:'select',options:['OK','NOT OK']})).concat([{key:'notes',label:'Review notes',type:'textarea'}]),submitLabel:'Approve & plan',onSave:v=>{if(Object.entries(v).filter(([k])=>k!=='notes').every(([,x])=>x==='OK'))dbUpdate('jobs','id',j.id,{stage:'Planned',contractReview:v});else{dbUpdate('jobs','id',j.id,{stage:'Contract Review',contractReview:v});toast('Review retained: one or more checks are NOT OK','warn');}}});return <div className="page rise"><PageHead title="Contract review" sub="ISO/IEC 17025 clause 7.1 review before accepting work" icon="check-shield"/><div className="card"><table className="tbl"><thead><tr><th>Job</th><th>Customer</th><th>Scope</th><th>Due</th><th>Stage</th><th>Action</th></tr></thead><tbody>{jobs.map(j=><tr key={j.id}><td className="mono cell-strong">{j.id}</td><td>{pretty(j.customer,'customer')}</td><td>{j.discipline.join(', ')}</td><td>{D().fmt(j.due)}</td><td><StatusBadge status={j.stage}/></td><td><Btn variant="primary" size="sm" icon="check-shield" onClick={()=>review(j)}>Review</Btn></td></tr>)}</tbody></table></div></div>}
function Planning(){const stages=['Received','Planned','In Calibration','Technical Review','Certified'];const assign=j=>window.openForm({title:`Assign ${j.id}`,fields:[{key:'tech',label:'Technician',type:'select',options:D().staff.filter(x=>x.role==='Calibration Technician').map(x=>x.id)},{key:'standard',label:'Reference standard',type:'select',options:D().standards.filter(x=>j.discipline.includes(x.discipline)).map(x=>x.id)},{key:'start',label:'Start date',type:'date'},{key:'due',label:'Due date',type:'date'}],onSave:v=>dbUpdate('jobs','id',j.id,{...v,due:new Date(v.due),stage:'In Calibration'})});return <div className="page page-wide rise"><PageHead title="Laboratory planning" sub="Visual job flow, resource assignment and due-date control" icon="calendar"/><div style={{display:'flex',gap:12,overflowX:'auto',alignItems:'flex-start'}}>{stages.map(s=><div key={s} style={{minWidth:235,flex:1}}><div className="row" style={{justifyContent:'space-between',marginBottom:8}}><b>{s}</b><Badge kind="neutral">{D().jobs.filter(j=>j.stage===s).length}</Badge></div>{D().jobs.filter(j=>j.stage===s).map(j=><div className="card card-pad" key={j.id} style={{marginBottom:10}}><div className="row"><b className="mono">{j.id}</b><span className="spacer"/><StatusBadge status={j.priority}/></div><div style={{marginTop:7}}>{(D().custById(j.customer)||{}).short}</div><div className="wrap" style={{gap:4,margin:'7px 0'}}>{j.discipline.map(x=><span className="tag" key={x}>{x}</span>)}</div><div className="row"><Avatar id={j.tech} size="sm"/><span className="faint" style={{fontSize:11,flex:1}}>{D().fmt(j.due)}</span>{s==='Planned'&&<Btn size="sm" icon="users" onClick={()=>assign(j)}>Assign</Btn>}</div></div>)}</div>)}</div></div>}
function MyJobs({onNav}){const tech=window.ROLE==='Calibration Technician'?D().staff.find(x=>x.role==='Calibration Technician'):D().staff.find(x=>x.role==='Calibration Technician'),rows=D().jobs.filter(j=>j.tech===tech.id);return <div className="page rise"><PageHead title="My jobs" sub={`Assigned calibration work for ${tech.name}`} icon="briefcase"/><div className="card"><table className="tbl"><thead><tr><th>Job</th><th>Customer</th><th>Discipline</th><th>Stage</th><th>Due</th><th></th></tr></thead><tbody>{rows.map(j=><tr key={j.id}><td className="mono cell-strong">{j.id}</td><td>{pretty(j.customer,'customer')}</td><td>{j.discipline.join(', ')}</td><td><StatusBadge status={j.stage}/></td><td>{D().fmt(j.due)}</td><td><Btn variant="primary" size="sm" onClick={()=>onNav&&onNav('execution')}>Open execution</Btn></td></tr>)}</tbody></table></div></div>}
function Execution(){const rows=D().jobs.filter(x=>['In Calibration','Technical Review'].includes(x.stage));const worksheet=j=>{const a=D().assetById(j.assets[0])||{},b=D().uncertaintyBudgets.find(x=>x.discipline===a.discipline)||D().uncertaintyBudgets[0];window.openForm({title:`Calibration worksheet · ${j.id}`,sub:`${a.type||a.id} · ${a.serial||''}`,fields:[0,1,2].flatMap(i=>[{key:'nom'+i,label:`Point ${i+1} nominal`,type:'number'},{key:'found'+i,label:`Point ${i+1} as-found`,type:'number'},{key:'left'+i,label:`Point ${i+1} as-left`,type:'number'}]).concat([{key:'temp',label:'Temperature °C',type:'number'},{key:'rh',label:'Relative humidity %',type:'number'},{key:'standard',label:'Reference standard',type:'select',options:D().standards.filter(x=>x.discipline===a.discipline).map(x=>x.id)},{key:'uncertainty',label:'Expanded uncertainty',type:'readonly',value:`±${b.expanded} (k=${b.k})`},{key:'tolerance',label:'Tolerance',type:'readonly',value:String(parseFloat(a.accuracy)||2)},{key:'observations',label:'Observations',type:'textarea'},{key:'techSign',label:'Technician e-signature',type:'sign',signer:(D().staffById(j.tech)||{}).name},{key:'managerSign',label:'Technical Manager e-signature',type:'sign',signer:(D().staff.find(x=>x.role==='Lab / Technical Manager')||{}).name}]),onSave:v=>{const nom=[0,1,2].map(i=>Number(v['nom'+i])),found=[0,1,2].map(i=>Number(v['found'+i])),left=[0,1,2].map(i=>Number(v['left'+i])),tol=Number(parseFloat(a.accuracy)||2),result=left.every((x,i)=>Math.abs(x-nom[i])<=tol)?'Pass':'Fail';const ws={id:idFor('WS-26-',D().worksheets),job:j.id,asset:a.id,nominal:nom,asFound:found,asLeft:left,tolerance:tol,env:{temp:Number(v.temp),rh:Number(v.rh)},standard:v.standard,uncertainty:`±${b.expanded} (k=${b.k})`,result,tech:j.tech,techSign:v.techSign,managerSign:v.managerSign,observations:v.observations};dbAdd('worksheets',ws);dbUpdate('jobs','id',j.id,{stage:'Technical Review',worksheet:ws.id});},done:'Worksheet saved for technical review'});};const approve=j=>{const ws=D().worksheets.find(x=>x.job===j.id),cert={id:'ACL-26-'+String(D().certificates.length+1).padStart(5,'0'),job:j.id,asset:j.assets[0],discipline:j.discipline[0],calDate:new Date(),due:D().addM(new Date(),12),result:ws?ws.result:'Pass',uncertainty:ws?ws.uncertainty:'Per approved budget',standardsUsed:[ws?ws.standard:j.standard],tech:j.tech,approvedBy:(D().staff.find(x=>x.role==='Lab / Technical Manager')||{}).id,status:'Valid'};dbAdd('certificates',cert);dbUpdate('jobs','id',j.id,{stage:'Certified'});toast(`${cert.id} issued`,'ok')};return <div className="page page-wide rise"><PageHead title="Jobs & execution" sub="Controlled worksheets, calculations, e-signatures and technical review" icon="workflow"/><div className="card"><table className="tbl"><thead><tr><th>Job</th><th>Customer</th><th>Asset</th><th>Technician</th><th>Stage</th><th>Actions</th></tr></thead><tbody>{rows.map(j=><tr key={j.id}><td className="mono cell-strong">{j.id}</td><td>{pretty(j.customer,'customer')}</td><td>{j.assets.map(x=>(D().assetById(x)||{}).type||x).join(', ')}</td><td>{pretty(j.tech,'tech')}</td><td><StatusBadge status={j.stage}/></td><td><div className="row">{j.stage==='In Calibration'&&<Btn size="sm" variant="primary" icon="flask" onClick={()=>worksheet(j)}>Open worksheet</Btn>}{j.stage==='Technical Review'&&['Lab / Technical Manager','System Administrator'].includes(window.ROLE)&&<Btn size="sm" variant="primary" icon="certificate" onClick={()=>approve(j)}>Approve & issue certificate</Btn>}</div></td></tr>)}</tbody></table></div></div>}
function printHtml(title,body){const w=window.open('','_blank','width=900,height=900');if(!w)return toast('Pop-up blocked by the browser','warn');w.document.write(`<!doctype html><title>${title}</title><style>body{font:14px Arial;color:#102a45;margin:35px;line-height:1.5}h1{color:#1f56cf}.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.box{border:1px solid #ccd6e3;padding:12px}.barcode{width:220px;height:55px;background:repeating-linear-gradient(90deg,#111 0 2px,transparent 2px 4px,#111 4px 6px,transparent 6px 9px)}table{width:100%;border-collapse:collapse;margin:14px 0}th,td{border:1px solid #ccd6e3;padding:8px;text-align:left}.qr{width:95px;height:95px;border:5px double #111;display:grid;place-items:center;font-size:9px;text-align:center}.sig{height:70px;border-bottom:1px solid #555}.foot{margin-top:26px;border-top:2px solid #1f56cf;padding-top:10px;font-size:11px}@media print{button{display:none}}</style><button onclick="print()">Print</button>${body}`);w.document.close()}
function printCertificate(cert){const j=D().jobs.find(x=>x.id===cert.job)||{},a=D().assetById(cert.asset)||{},cu=D().custById(j.customer)||{},ws=D().worksheets.find(x=>x.job===j.id)||{},stds=(cert.standardsUsed||[]).map(D().stdById).filter(Boolean);const resultRows=(ws.nominal||[0,50,100]).map((n,i)=>`<tr><td>${n}</td><td>${(ws.asFound||[0,49.5,99.2])[i]}</td><td>${(ws.asLeft||[0,50,100])[i]}</td><td>${cert.result}</td></tr>`).join('');printHtml(cert.id,`<header><h1>${D().company.name}</h1><b>${D().company.lab}</b><p>${D().company.address}</p></header><hr><h2>Calibration Certificate ${cert.id}</h2><div class="grid"><div class="box"><b>Customer</b><br>${cu.name||'—'}<br>${cu.site||''}</div><div class="box"><b>Equipment</b><br>${a.type||a.id}<br>${a.manufacturer||''} ${a.model||''}<br>Serial: ${a.serial||'—'}<br>Range: ${a.range||'—'}</div></div><h3>Results</h3><table><tr><th>Nominal</th><th>As found</th><th>As left</th><th>Result</th></tr>${resultRows}</table><p><b>Measurement uncertainty:</b> ${cert.uncertainty}</p><h3>Standards used</h3><table><tr><th>Standard</th><th>Certificate</th><th>Calibrated by</th><th>Traceability</th></tr>${stds.map(s=>`<tr><td>${s.id} — ${s.name}</td><td>${s.certNo}</td><td>${s.calibratedBy}</td><td>${s.traceability}</td></tr>`).join('')}</table><p>Measurements are traceable to SI units via ${stds.map(s=>s.traceability).join(', ')||'the laboratory traceability chain'}.</p><p><b>Environmental conditions:</b> ${(ws.env||{}).temp||22} °C / ${(ws.env||{}).rh||45}% RH</p><h3>Statement of Conformity: ☑ ${cert.result==='Pass'?'PASS':'FAIL'} &nbsp; ☐ ${cert.result==='Pass'?'FAIL':'PASS'}</h3><div class="grid"><div><div class="sig"></div>Technician: ${pretty(cert.tech,'tech')}</div><div><div class="sig"></div>Technical Manager: ${pretty(cert.approvedBy,'approvedBy')}</div></div><div class="qr">QR<br>${cert.id}</div><div class="foot">Issued in accordance with ISO/IEC 17025:2017. This certificate may not be reproduced except in full without written approval.</div>`)}
function Certificates(){const [q,setQ]=useState('');const [status,setStatus]=useState('All');const rows=D().certificates.filter(x=>(status==='All'||x.status===status)&&JSON.stringify(x).toLowerCase().includes(q.toLowerCase()));return <div className="page page-wide rise"><PageHead title="Certificates" sub="Controlled calibration certificates and print-ready A4 reports" icon="certificate"/><div className="card"><div className="card-head"><div className="searchbox"><Icon name="search"/><input value={q} onChange={e=>setQ(e.target.value)} placeholder="Search certificates…"/></div><div className="field" style={{width:180}}><select value={status} onChange={e=>setStatus(e.target.value)}>{['All','Valid','Pending approval'].map(x=><option key={x}>{x}</option>)}</select></div></div><table className="tbl"><thead><tr><th>Certificate</th><th>Customer</th><th>Equipment</th><th>Discipline</th><th>Result</th><th>Status</th><th></th></tr></thead><tbody>{rows.map(x=>{const j=D().jobs.find(j=>j.id===x.job)||{},a=D().assetById(x.asset)||{};return <tr key={x.id}><td className="mono cell-strong">{x.id}</td><td>{pretty(j.customer,'customer')}</td><td>{a.type||x.asset}</td><td>{x.discipline}</td><td><StatusBadge status={x.result}/></td><td><StatusBadge status={x.status}/></td><td><Btn size="sm" icon="download" onClick={()=>printCertificate(x)}>Print</Btn></td></tr>})}</tbody></table></div></div>}
function Environment(){const latest=D().envLog[D().envLog.length-1],metrics=[['Temperature','temp','°C','thermometer'],['Relative humidity','rh','%','droplet'],['Pressure','pressure','hPa','gauge']];const excursions=D().envLog.filter(x=>metrics.some(([,k])=>x[k]<D().envLimits[k][0]||x[k]>D().envLimits[k][1]));return <div className="page page-wide rise"><PageHead title="Laboratory environment" sub="24-hour monitoring against approved facility limits" icon="thermometer"/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}>{metrics.map(([label,k,u,ico])=><Kpi key={k} label={label} value={latest[k].toFixed(1)} unit={u} icon={ico} tone={latest[k]<D().envLimits[k][0]||latest[k]>D().envLimits[k][1]?'bad':'ok'} sub={`Limit ${D().envLimits[k].join('–')} ${u}`}/>)}</div><div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:16,marginBottom:16}}>{metrics.map(([label,k,u])=><Trend key={k} label={label} data={D().envLog.map(x=>x[k])} limits={D().envLimits[k]} unit={u}/>)}</div><div className="card"><div className="card-head"><div className="h-sec">Excursions</div></div><table className="tbl"><thead><tr><th>Time</th><th>Temperature</th><th>RH</th><th>Pressure</th><th>Assessment</th></tr></thead><tbody>{excursions.map(x=><tr key={x.t}><td className="mono">{x.t}</td><td>{x.temp.toFixed(1)} °C</td><td>{x.rh.toFixed(1)}%</td><td>{x.pressure.toFixed(1)} hPa</td><td><Badge kind="bad">Outside limit</Badge></td></tr>)}</tbody></table></div></div>}
function Trend({label,data,limits,unit}){const min=Math.min(...data,limits[0]),max=Math.max(...data,limits[1]),y=v=>95-(v-min)/(max-min||1)*80,pts=data.map((v,i)=>`${10+i/(data.length-1)*280},${y(v)}`).join(' ');return <div className="card card-pad"><div className="row"><b>{label}</b><span className="spacer"/><span className="faint">24 h · {unit}</span></div><svg viewBox="0 0 300 110" style={{width:'100%',height:150}} aria-label={`${label} 24-hour trend`}><rect x="10" y={y(limits[1])} width="280" height={Math.max(1,y(limits[0])-y(limits[1]))} fill="var(--ok-bg)"/><line x1="10" y1={y(limits[0])} x2="290" y2={y(limits[0])} stroke="var(--ok)" strokeDasharray="4 3"/><line x1="10" y1={y(limits[1])} x2="290" y2={y(limits[1])} stroke="var(--ok)" strokeDasharray="4 3"/><polyline points={pts} fill="none" stroke="var(--primary-600)" strokeWidth="2.5"/></svg></div>}
function Uncertainty(){const [sel,setSel]=useState(D().uncertaintyBudgets[0]);return <div className="page page-wide rise"><PageHead title="Measurement uncertainty" sub="Approved discipline budgets and contributors" icon="chart"/><div className="split"><div className="card"><table className="tbl"><thead><tr><th>Budget</th><th>Discipline</th><th>Method</th><th>Combined</th><th>k</th><th>Expanded</th></tr></thead><tbody>{D().uncertaintyBudgets.map(x=><tr key={x.id} className="clickable" onClick={()=>setSel(x)}><td className="mono cell-strong">{x.id}</td><td>{x.discipline}</td><td>{x.method}</td><td>{x.combined}</td><td>{x.k}</td><td><Badge kind="info">±{x.expanded}</Badge></td></tr>)}</tbody></table></div><div className="card card-pad"><SectionHead title={`${sel.discipline} budget`} sub={sel.method}/>{sel.contributors.map((x,i)=><div className="lrow" key={i}><span className="kpi-ico" style={{background:'var(--info-bg)',color:'var(--primary-600)'}}>{x.type}</span><div style={{flex:1}}><b>{x.source}</b><div className="cell-sub">Type {x.type}</div></div><b className="mono">{x.value}</b></div>)}<div className="divider"/><div className="row"><span>Combined uncertainty</span><b className="spacer"/><b>{sel.combined}</b></div><div className="row"><span>Coverage factor</span><b className="spacer"/><b>k={sel.k}</b></div><div className="row"><span>Expanded uncertainty</span><b className="spacer"/><Badge kind="info">±{sel.expanded}</Badge></div></div></div></div>}
function Traceability(){const [id,setId]=useState(D().certificates[0]?.id),cert=D().certificates.find(x=>x.id===id),j=cert&&D().jobs.find(x=>x.id===cert.job),a=cert&&D().assetById(cert.asset),stds=cert?(cert.standardsUsed||[]).map(D().stdById).filter(Boolean):[];const nodes=cert?[`${a?.type||a?.id} · customer asset`,`${cert.id} · calibration certificate`,...stds.map(s=>`${s.id} · ${s.name}`),...stds.map(s=>`${s.calibratedBy} · ${s.certNo}`),`${[...new Set(stds.map(s=>s.traceability))].join(' / ')} → SI units / BIPM`]:[];return <div className="page rise"><PageHead title="Measurement traceability" sub="Certificate-to-SI chain evidence" icon="link"/><div className="field" style={{maxWidth:420,marginBottom:20}}><label>Certificate</label><select value={id} onChange={e=>setId(e.target.value)}>{D().certificates.map(x=><option key={x.id}>{x.id}</option>)}</select></div><div className="card card-pad" style={{maxWidth:760}}>{nodes.map((n,i)=><div className="row" key={i} style={{alignItems:'stretch'}}><div className="col" style={{alignItems:'center',gap:0}}><span style={{width:34,height:34,borderRadius:'50%',display:'grid',placeItems:'center',background:'var(--primary-600)',color:'#fff',fontWeight:800}}>{i+1}</span>{i<nodes.length-1&&<i style={{width:2,height:42,background:'var(--primary-100)'}}/>}</div><div style={{padding:'7px 0'}}><b>{n}</b>{i===0&&<div className="cell-sub">{pretty(j?.customer,'customer')}</div>}</div></div>)}</div></div>}
function Delivery(){const rows=D().jobs.filter(x=>['Certified','Invoiced'].includes(x.stage));const deliver=j=>window.openForm({title:`Deliver & close · ${j.id}`,fields:[{key:'method',label:'Dispatch method',type:'select',options:['Customer collection','Courier','Laboratory vehicle']},{key:'score',label:'Feedback score',type:'select',options:['1','2','3','4','5']},{key:'comment',label:'Customer feedback',type:'textarea'}],onSave:v=>{dbUpdate('jobs','id',j.id,{stage:'Delivered',delivery:v});dbAdd('feedback',{id:idFor('FB-',D().feedback),customer:j.customer,score:Number(v.score),comment:v.comment,date:new Date()});}});return <div className="page rise"><PageHead title="Delivery & dispatch" sub="Certified equipment awaiting controlled release" icon="truck"/><div className="card"><table className="tbl"><thead><tr><th>Job</th><th>Customer</th><th>Assets</th><th>Stage</th><th>Due</th><th></th></tr></thead><tbody>{rows.map(j=><tr key={j.id}><td className="mono cell-strong">{j.id}</td><td>{pretty(j.customer,'customer')}</td><td>{j.assets.length}</td><td><StatusBadge status={j.stage}/></td><td>{D().fmt(j.due)}</td><td><Btn variant="primary" size="sm" icon="truck" onClick={()=>deliver(j)}>Deliver & close</Btn></td></tr>)}</tbody></table></div></div>}
function Portal(){const [customer,setCustomer]=useState(D().customers[0].id),[tab,setTab]=useState('My equipment');const tabs=['My equipment','Jobs','Certificates','Invoices','Raise complaint','Request calibration'],jobs=D().jobs.filter(x=>x.customer===customer),certs=D().certificates.filter(x=>jobs.some(j=>j.id===x.job));useEffect(()=>{if(tab==='Raise complaint'){window.openForm({title:'Raise complaint',fields:[{key:'subject',label:'Subject'},{key:'details',label:'Details',type:'textarea'}],onSave:v=>dbAdd('complaints',{id:idFor('CMP-26-',D().complaints),customer,subject:v.subject,details:v.details,status:'Received',date:new Date()})});setTab('Jobs')}if(tab==='Request calibration'){window.openForm({title:'Request calibration',fields:[{key:'asset',label:'Equipment',type:'select',options:D().assets.filter(x=>x.customer===customer).map(x=>x.id)},{key:'priority',label:'Priority',type:'select',options:['Normal','High','Urgent']},{key:'notes',label:'Service notes',type:'textarea'}],onSave:v=>{const a=D().assetById(v.asset);dbAdd('jobs',{id:'JC-26-'+(1000+D().jobs.length+1),customer,assets:[v.asset],discipline:[a.discipline],priority:v.priority,recvd:new Date(),due:new Date(+D().TODAY+7*864e5),tech:null,location:'Laboratory',photos:0,stage:'Received',notes:v.notes})}});setTab('Jobs')}},[tab]);return <div className="page page-wide rise"><PageHead title="Customer portal" sub="Equipment, live job status, certificates and account self-service" icon="building" right={<div className="field" style={{width:280}}><select value={customer} onChange={e=>setCustomer(e.target.value)}>{D().customers.map(x=><option key={x.id} value={x.id}>{x.name}</option>)}</select></div>}/><Tabs items={tabs} value={tab} onChange={setTab}/>{tab==='My equipment'&&<SimpleTable columns={['id','type','manufacturer','serial','due','status']} rows={D().assets.filter(x=>x.customer===customer)}/>} {tab==='Jobs'&&<div className="card">{jobs.map(j=><div className="lrow" key={j.id}><b className="mono" style={{width:120}}>{j.id}</b><div style={{flex:1}}><div className="row" style={{gap:2}}>{D().stages.map((s,i)=>{const on=i<=D().stages.indexOf(j.stage);return <React.Fragment key={s}><span title={s} style={{width:17,height:17,borderRadius:'50%',background:on?'var(--primary-600)':'var(--border)',color:'#fff',fontSize:9,display:'grid',placeItems:'center'}}>{i+1}</span>{i<D().stages.length-1&&<i style={{height:2,flex:1,background:on?'var(--primary-100)':'var(--border)'}}/>}</React.Fragment>})}</div><div className="cell-sub">{j.discipline.join(', ')} · due {D().fmt(j.due)}</div></div><StatusBadge status={j.stage}/></div>)}</div>}{tab==='Certificates'&&<div className="card"><table className="tbl"><thead><tr><th>Certificate</th><th>Discipline</th><th>Result</th><th>Valid to</th><th></th></tr></thead><tbody>{certs.map(x=><tr key={x.id}><td className="mono cell-strong">{x.id}</td><td>{x.discipline}</td><td><StatusBadge status={x.result}/></td><td>{D().fmt(x.due)}</td><td><Btn size="sm" icon="download" onClick={()=>printCertificate(x)}>Print</Btn></td></tr>)}</tbody></table></div>}{tab==='Invoices'&&<SimpleTable columns={['id','quote','date','due','amount','status']} rows={D().invoices.filter(x=>x.customer===customer)} moneyKeys={['amount']}/>}</div>}
function SimpleTable({columns,rows,moneyKeys=[]}){return <div className="card"><table className="tbl"><thead><tr>{columns.map(x=><th key={x}>{x.replace(/([A-Z])/g,' $1')}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={r.id||i}>{columns.map(x=><td key={x}>{x==='status'?<StatusBadge status={r[x]}/>:moneyKeys.includes(x)?money(r[x]):pretty(r[x],x)}</td>)}</tr>)}</tbody></table>{!rows.length&&<EmptyState title="Nothing here yet" desc="Records will appear when available."/>}</div>}
function AICOSConsole(){return <div className="page page-wide rise"><PageHead title="AI-COS Console" sub="Role-aware business intelligence grounded in live ERP/LIMS records" icon="sparkles"/><div className="ai-note" style={{marginBottom:16}}><Icon name="shield"/><div><b>Governed AI assistance</b><div className="muted">Advisory only · cites its sources · respects your role permissions · never acts without your confirmation</div></div></div><div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:12,marginBottom:18}}>{D().aicosInsights.map((x,i)=><div className="card card-pad" key={i}><span className="ai-badge-ico"><Icon name={x.icon}/></span><b style={{display:'block',marginTop:8}}>{x.title}</b><div className="muted" style={{fontSize:12.5}}>{x.detail}</div><div className="faint" style={{fontSize:10.5,marginTop:6}}>Source: {x.cite}</div></div>)}</div><AIConversation/></div>}
const Leads=makeRegister({title:'Sales leads',sub:'Qualified calibration opportunities and estimated SAR value',icon:'trend',dataKey:'leads',idPrefix:'LD-26-',statusKey:'stage',columns:[c('id','Lead'),c('customer','Customer'),c('contact','Contact'),c('discipline','Discipline'),c('est','Estimate',money),c('stage','Stage'),c('closeDate','Close date')],formFields:['customer','contact','discipline',{key:'est',type:'number'},{key:'stage',type:'select',options:['New','Qualified','Quoted','Won','Lost']},{key:'closeDate',type:'date'}],kpis:[{label:'Pipeline',icon:'trend',value:r=>money(r.reduce((a,x)=>a+x.est,0))},{label:'Won',icon:'check-circle',tone:'ok',value:r=>r.filter(x=>x.stage==='Won').length}]});
const Contracts=makeRegister({title:'Framework contracts',sub:'Annual agreements, SLA and renewal risk',icon:'documents',dataKey:'contracts',idPrefix:'CT-26-',statusKey:'status',columns:[c('id','Contract'),c('customer','Customer'),c('type','Type'),c('value','Value',money),c('end','Expiry'),c('sla','SLA'),c('assets','Assets'),c('status','Renewal',(_,r)=><Badge kind={(new Date(r.end)-D().TODAY)/864e5<=60?'bad':'ok'}>{(new Date(r.end)-D().TODAY)/864e5<=60?'Expiring ≤60d':'Active'}</Badge>)],formFields:['customer','type',{key:'value',type:'number'},{key:'start',type:'date'},{key:'end',type:'date'},'sla',{key:'assets',type:'number'}],kpis:[{label:'Contract value',icon:'receipt',value:r=>money(r.reduce((a,x)=>a+x.value,0))},{label:'Renewal risk',icon:'calendar',tone:'bad',value:r=>r.filter(x=>(new Date(x.end)-D().TODAY)/864e5<=60).length}]});
function aging(x){const n=Math.max(0,Math.floor((D().TODAY-new Date(x.due))/864e5));return n>90?'90+':n>60?'60':n>30?'30':'Current'}
function Receivables(){const rows=D().invoices.filter(x=>x.status!=='Paid'),total=rows.reduce((a,x)=>a+(x.total||x.amount),0);return <div className="page page-wide rise"><PageHead title="Receivables & collections" sub="Customer balances, aging and review-controlled collection actions" icon="trend"/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Outstanding" value={total.toLocaleString()} unit="SAR" icon="receipt" tone="warn"/>{['Current','30','60'].map(k=><Kpi key={k} label={k+' days'} value={rows.filter(x=>aging(x)===k).reduce((a,x)=>a+(x.total||x.amount),0).toLocaleString()} unit="SAR" icon="clock"/>)}</div><SimpleTable columns={['id','customer','due','total','status']} rows={rows.map(x=>({...x,total:x.total||x.amount,aging:aging(x)}))} moneyKeys={['total']}/></div>}
function Payables(){const [tab,setTab]=useState('Supplier invoices');return <div className="page page-wide rise"><PageHead title="Payables & purchasing" sub="Supplier invoices and purchase orders" icon="receipt"/><Tabs items={['Supplier invoices','Purchase orders']} value={tab} onChange={setTab}/><SimpleTable columns={tab==='Supplier invoices'?['id','supplier','amount','due','status']:['id','supplier','items','amount','status','date']} rows={tab==='Supplier invoices'?D().payables:D().purchaseOrders} moneyKeys={['amount']}/></div>}
const Expenses=makeRegister({title:'Expenses',sub:'Job and operational expense approvals',icon:'briefcase',dataKey:'expenses',idPrefix:'EX-26-',statusKey:'status',columns:[c('id','Expense'),c('category','Category'),c('amount','Amount',money),c('by','Submitted by'),c('job','Job'),c('date','Date'),c('status','Status')],formFields:['category',{key:'amount',type:'number'},'by','job',{key:'date',type:'date'}],kpis:[{label:'Submitted',icon:'clock',tone:'warn',value:r=>r.filter(x=>x.status==='Submitted').length},{label:'Approved total',icon:'check-circle',tone:'ok',value:r=>money(r.filter(x=>x.status==='Approved').reduce((a,x)=>a+x.amount,0))}],rowActions:r=>r.status==='Submitted'&&<Btn size="sm" variant="soft" onClick={()=>dbUpdate('expenses','id',r.id,{status:'Approved'})}>Approve</Btn>});
function VAT(){const inv=D().invoices[1],buyer=D().custById(inv.customer),out=D().invoices.reduce((a,x)=>a+x.vat,0);return <div className="page rise"><PageHead title="VAT / ZATCA (Fatoora)" sub="Saudi e-invoicing and VAT return summary" icon="qr"/><div className="card card-pad" style={{marginBottom:16}}><div className="row"><div><div className="h-sec">Tax invoice {inv.id}</div><div>{D().company.name} · VAT {D().company.vatNumber}</div><div>{buyer.name} · VAT {buyer.vatNumber}</div></div><span className="spacer"/><Badge kind="ok">Phase 2 compliant</Badge><div style={{width:92,height:92,border:'8px double var(--ink)',display:'grid',placeItems:'center',textAlign:'center',fontSize:10}}>ZATCA<br/>QR</div></div><table className="tbl"><tbody><tr><td>Calibration services</td><td className="num">{money(inv.amount)}</td></tr><tr><td>VAT 15%</td><td className="num">{money(inv.vat)}</td></tr><tr><td><b>Total</b></td><td className="num"><b>{money(inv.total)}</b></td></tr></tbody></table></div><div className="card card-pad"><SectionHead title="VAT return summary"/><div className="row"><span>Output VAT from issued invoices</span><b className="spacer"/><b>{money(out)}</b></div><div className="row"><span>Standard-rated supplies</span><b className="spacer"/><b>{money(D().invoices.reduce((a,x)=>a+x.amount,0))}</b></div></div></div>}
function Budgeting(){return <div className="page page-wide rise"><PageHead title="Budgeting" sub="Annual SAR budget versus year-to-date actual" icon="chart"/><div className="card"><table className="tbl"><thead><tr><th>Category</th><th>Budget</th><th>Actual YTD</th><th>Variance</th><th>Utilization</th></tr></thead><tbody>{D().budgets.map(x=><tr key={x.category}><td className="cell-strong">{x.category}</td><td>{money(x.budget)}</td><td>{money(x.actual)}</td><td><Badge kind={x.actual>x.budget?'bad':'ok'}>{Math.round((x.budget-x.actual)/x.budget*100)}%</Badge></td><td style={{minWidth:180}}><Bar pct={x.actual/x.budget*100} color={x.actual>x.budget?'var(--bad)':'var(--primary-600)'}/></td></tr>)}</tbody></table></div></div>}
const Employees=makeRegister({title:'Employees',sub:'Saudi workforce master data and competencies',icon:'users',dataKey:'employees',idPrefix:'E-',columns:[c('id','Employee'),c('name','Name'),c('iqama','Iqama'),c('nationality','Nationality'),c('position','Position'),c('department','Department'),c('competencies','Competencies',v=><div className="wrap" style={{gap:4}}>{v.map(x=><span className="tag" key={x}>{x}</span>)}</div>),c('trainingDue','Training due')],formFields:['name','iqama','nationality','position','department',{key:'competencies',type:'checks',options:D().disciplines.map(x=>x.name)},{key:'trainingDue',type:'date'}],kpis:[{label:'Headcount',icon:'users',value:r=>r.length},{label:'Saudi',icon:'building',tone:'ok',value:r=>r.filter(x=>x.nationality==='Saudi').length}]});
const Training=makeRegister({title:'Training records',sub:'Competence development and renewal tracking',icon:'certificate',dataKey:'trainingRecords',idPrefix:'TR-26-',statusKey:'status',columns:[c('id','Record'),c('employee','Employee'),c('course','Course'),c('due','Renewal due'),c('status','Status')],formFields:['employee','course',{key:'due',type:'date'},{key:'status',type:'select',options:['Valid','Renewal due']}],kpis:[]});
const Leave=makeRegister({title:'Leave requests',sub:'Employee leave review and balances',icon:'calendar',dataKey:'leaveRequests',idPrefix:'LV-26-',statusKey:'status',columns:[c('id','Request'),c('employee','Employee'),c('type','Type'),c('from','From'),c('to','To'),c('status','Status')],formFields:['employee','type',{key:'from',type:'date'},{key:'to',type:'date'}],kpis:[],rowActions:r=>r.status==='Submitted'&&<><Btn size="sm" variant="soft" onClick={()=>dbUpdate('leaveRequests','id',r.id,{status:'Approved'})}>Approve</Btn><Btn size="sm" onClick={()=>dbUpdate('leaveRequests','id',r.id,{status:'Rejected'})}>Reject</Btn></>});
function Attendance(){return <div className="page rise"><PageHead title="Attendance" sub="Daily attendance and timesheet hours" icon="clock"/><SimpleTable columns={['id','employee','date','status','hours']} rows={D().attendance}/></div>}
function Nitaqat(){const saudi=D().employees.filter(x=>x.nationality==='Saudi').length,data=[{k:'Saudi',n:saudi,c:'#15a06b'},{k:'Non-Saudi',n:D().employees.length-saudi,c:'#2f6fed'}],deps=[...new Set(D().employees.map(x=>x.department))];return <div className="page rise"><PageHead title="Saudization / Nitaqat" sub="Headcount ratio and department mix" icon="building"/><div className="dash-row-2" style={{display:'grid',gridTemplateColumns:'1fr 1.5fr',gap:16}}><div className="card card-pad"><DDonut data={data} label="PEOPLE"/><div style={{textAlign:'center'}}><Badge kind="ok">Green band · {Math.round(saudi/D().employees.length*100)}% Saudi</Badge></div></div><div className="card"><table className="tbl"><thead><tr><th>Department</th><th>Saudi</th><th>Non-Saudi</th></tr></thead><tbody>{deps.map(k=><tr key={k}><td>{k}</td><td>{D().employees.filter(x=>x.department===k&&x.nationality==='Saudi').length}</td><td>{D().employees.filter(x=>x.department===k&&x.nationality!=='Saudi').length}</td></tr>)}</tbody></table></div></div></div>}
const GoodsReceipt=makeRegister({title:'Goods & equipment movements',sub:'PO receipts, stock issues and customer equipment custody',icon:'box',dataKey:'goodsMovements',idPrefix:'GM-26-',statusKey:'type',columns:[c('id','Movement'),c('type','Type'),c('ref','Reference'),c('item','Item'),c('qty','Qty'),c('by','Handled by'),c('date','Date')],formFields:[{key:'type',type:'select',options:['Receipt','Issue','Check-in','Check-out']},'ref','item',{key:'qty',type:'number'},'by',{key:'date',type:'date'}],kpis:[{label:'Movements',icon:'box',value:r=>r.length},{label:'Customer check-ins',icon:'check',value:r=>r.filter(x=>x.type==='Check-in').length}]});
const Proficiency=makeRegister({title:'Proficiency testing',sub:'Interlaboratory comparison performance and z-scores',icon:'flask',dataKey:'proficiency',idPrefix:'PT-26-',statusKey:'result',columns:[c('id','Scheme'),c('scheme','Description'),c('discipline','Discipline'),c('provider','Provider'),c('date','Date'),c('zScore','z-score',v=><Badge kind={Math.abs(v)<=2?'ok':'warn'}>{v}</Badge>),c('result','Result')],formFields:['scheme','discipline','provider',{key:'date',type:'date'},{key:'zScore',type:'number'},{key:'result',type:'select',options:['Satisfactory','Questionable','Pending']}],kpis:[]});
const Incidents=makeRegister({title:'HSE incidents',sub:'Near misses, incidents and first-aid records',icon:'helmet',dataKey:'incidents',idPrefix:'HSE-26-',statusKey:'status',columns:[c('id','Record'),c('type','Type'),c('location','Location'),c('date','Date'),c('severity','Severity'),c('status','Status')],formFields:['type','location',{key:'date',type:'date'},'severity',{key:'status',type:'select',options:['Open','Closed']}],kpis:[{label:'Open',icon:'alert',tone:'warn',value:r=>r.filter(x=>x.status==='Open').length}]});
const Quotations=Billing;
Object.assign(window,{makeRegister,PageHead,Customers,Receiving,ContractReview,Billing,Quotations,Delivery,Invoices,Planning,MyJobs,Execution,Certificates,Standards,Uncertainty,Environment,Traceability,NCR,Complaints,Risk,Audit,Management,Documents,Competency,Suppliers,Inventory,Portal,AICOSConsole,Leads,Contracts,Receivables,Payables,Expenses,VAT,Budgeting,Employees,Training,Leave,Attendance,Nitaqat,GoodsReceipt,Proficiency,Incidents,printCertificate});
