/* Al Hajar Construction — core registers and shared module helpers */
function PageHead({title,sub,icon='grid',actions}){const extra=window.C6_PRIMARY&&window.C6_PRIMARY[title];return <div className="row" style={{justifyContent:'space-between',alignItems:'flex-start',marginBottom:18}}><div><div className="row"><span className="kpi-ico"><Icon name={icon} size={17}/></span><div><div className="h-page">{title}</div><div className="h-sub">{sub}</div></div></div></div><div className="row">{actions}{extra&&<Btn variant="primary" icon="plus" onClick={extra.run}>{extra.label}</Btn>}</div></div>;}
function printRegister(title){document.title=title+' — '+DB.company.name;window.toast('Opening browser print layout','info');setTimeout(()=>window.print(),80);}
function printRecord(title){printRegister(title);}
function makeRegister(cfg){return function Register(){const [query,setQuery]=useState(''),[status,setStatus]=useState('All'),rows=(cfg.rows()||[]).filter(x=>{const text=Object.values(x).join(' ').toLowerCase();return (!query||text.includes(query.toLowerCase()))&&(status==='All'||cfg.status(x)===status);});return <div className="page page-wide rise"><PageHead title={cfg.title} sub={cfg.sub} icon={cfg.icon} actions={<><Btn icon="download" onClick={()=>printRegister(cfg.title)}>Export</Btn>{cfg.onAdd&&<Btn variant="primary" icon="plus" onClick={cfg.onAdd}>New</Btn>}</>}/><div className="card" style={{padding:12,marginBottom:16}}><div className="row"><div className="searchbox" style={{flex:1}}><Icon name="search" size={15}/><input value={query} onChange={e=>setQuery(e.target.value)} placeholder={`Search ${cfg.title.toLowerCase()}…`}/></div>{cfg.statuses&&<select className="input" value={status} onChange={e=>setStatus(e.target.value)} style={{width:170}}><option>All</option>{cfg.statuses.map(x=><option key={x}>{x}</option>)}</select>}<Badge kind="neutral">{rows.length} records</Badge></div></div><div className="card"><div style={{overflowX:'auto'}}><table className="tbl"><thead><tr>{cfg.columns.map(c=><th key={c.key}>{c.label}</th>)}</tr></thead><tbody>{rows.map(row=><tr key={row.id} onClick={()=>cfg.onRow&&cfg.onRow(row)} style={cfg.onRow?{cursor:'pointer'}:null}>{cfg.columns.map(c=><td key={c.key}>{c.render?c.render(row):row[c.key]}</td>)}</tr>)}</tbody></table></div></div></div>;};}
function ProjectDetail({project,onClose}){const p=project,client=DB.custById(p.client),progress=window.projectProgress(p.id),lines=DB.boq.filter(x=>x.project===p.id),people=DB.employees.filter(x=>x.project===p.id&&x.status!=='Demobilized'),cost=DB.projectCosts.find(x=>x.project===p.id),hse=window.hseStats({project:p.id}),subs=DB.subcontractors.filter(x=>x.project===p.id);return <Drawer open={!!p} onClose={onClose} width={850}><div className="drawer-head"><span className="kpi-ico"><Icon name="building"/></span><div style={{flex:1}}><b>{p.name}</b><div className="faint">{p.id} · {p.location}, {p.governorate}</div></div><button className="icon-btn" onClick={onClose}><Icon name="x"/></button></div><div className="drawer-body"><div className="row" style={{marginBottom:14}}><StatusBadge status={p.status}/><Badge kind={progress.status==='Behind'?'bad':progress.status==='Ahead'?'ok':'neutral'}>{progress.status} · {progress.variance>0?'+':''}{progress.variance}%</Badge><span className="spacer"/><Btn icon="download" onClick={()=>printRecord(p.name)}>Print project brief</Btn></div><div className="exec-four-cells">{[['Client',client.name],['Contract value',window.money(p.contractValue)],['Planned',`${p.plannedPct}%`],['Actual',`${p.actualPct}%`],['Manpower',people.length],['Project manager',(DB.empById(p.pm)||{}).name]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:String(x[1]).length>20?12:20}}>{x[1]}</b></div>)}</div><div className="card card-pad" style={{marginTop:14}}><div className="h-sec" style={{marginBottom:10}}>Progress</div><div className="row"><span style={{width:80}}>Planned</span><Bar pct={p.plannedPct} color="#94a3b8"/><b>{p.plannedPct}%</b></div><div className="row" style={{marginTop:8}}><span style={{width:80}}>Actual</span><Bar pct={p.actualPct} color={progress.status==='Behind'?'var(--bad)':'var(--ok)'}/><b>{p.actualPct}%</b></div></div><div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14,marginTop:14}}><div className="card card-pad"><div className="h-sec">Cost snapshot</div>{cost?<div className="exec-four-cells" style={{marginTop:10}}>{[['Budget',window.money(cost.budget)],['Committed',window.money(cost.committed)],['Actual',window.money(cost.actual)],['Forecast',window.money(cost.forecast)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:12}}>{x[1]}</b></div>)}</div>:<NoData/>}</div><div className="card card-pad"><div className="h-sec">HSE snapshot</div><div className="exec-four-cells" style={{marginTop:10}}>{[['Man-hours',hse.manHours.toLocaleString()],['TRIR',hse.trir],['LTIFR',hse.ltifr],['LTI',hse.lti],['MTC',hse.mtc],['LTI-free days',hse.ltiFreeDays]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b>{x[1]}</b></div>)}</div></div></div><div className="card" style={{marginTop:14}}><div className="card-head"><Icon name="chart"/><div className="h-sec">BOQ execution</div><span className="spacer"/><Badge kind="neutral">{lines.length} lines</Badge></div><div style={{overflowX:'auto'}}><table className="tbl"><thead><tr><th>Code</th><th>Description</th><th>Qty</th><th>Rate</th><th>Amount</th><th>Executed</th></tr></thead><tbody>{lines.map(x=><tr key={x.id}><td className="mono">{x.code}</td><td>{x.description}</td><td>{x.qty.toLocaleString()} {x.unit}</td><td>{window.money(x.rate)}</td><td>{window.money(x.amount)}</td><td style={{minWidth:150}}><div className="row"><Bar pct={x.executedPct}/><b>{x.executedPct}%</b></div></td></tr>)}</tbody></table></div></div><div className="card card-pad" style={{marginTop:14}}><div className="h-sec">Subcontractors</div>{subs.map(x=><div className="lrow" key={x.id}><div style={{flex:1}}><b>{x.name}</b><div className="faint">{x.trade}</div></div><span>{window.money(x.workDone)} / {window.money(x.contractValue)}</span><Badge kind={x.hseScore>=85?'ok':'warn'}>HSE {x.hseScore}</Badge></div>)}</div></div></Drawer>;}
function Projects(){const [selected,setSelected]=useState(null),[query,setQuery]=useState(''),rows=DB.projects.filter(x=>!query||[x.id,x.name,x.location,(DB.custById(x.client)||{}).name].join(' ').toLowerCase().includes(query.toLowerCase()));return <div className="page page-wide rise"><PageHead title="Projects" sub="Multi-project portfolio, progress, commercial and site performance" icon="building" actions={<Btn icon="download" onClick={()=>printRegister('Projects')}>Export</Btn>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Portfolio value" value={window.money(DB.projects.reduce((a,x)=>a+x.contractValue,0))} icon="receipt"/><Kpi label="Active" value={DB.projects.filter(x=>x.status==='Active').length} icon="building" tone="ok"/><Kpi label="Behind plan" value={DB.projects.filter(x=>window.projectProgress(x.id).status==='Behind').length} icon="alert" tone="bad"/><Kpi label="Manpower" value={DB.projects.reduce((a,x)=>a+x.manpower,0)} icon="users"/></div><div className="card" style={{padding:12,marginBottom:16}}><div className="searchbox"><Icon name="search" size={15}/><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Search project, client or location…"/></div></div><div className="card"><div style={{overflowX:'auto'}}><table className="tbl"><thead><tr><th>Project</th><th>Client</th><th>Location</th><th>Value</th><th style={{minWidth:210}}>Progress</th><th>Manpower</th><th>Status</th></tr></thead><tbody>{rows.map(p=>{const pr=window.projectProgress(p.id);return <tr key={p.id} onClick={()=>setSelected(p)} style={{cursor:'pointer'}}><td><b>{p.name}</b><div className="mono faint">{p.id}</div></td><td>{DB.custById(p.client).name}</td><td>{p.location}<div className="faint">{p.governorate}</div></td><td>{window.money(p.contractValue)}</td><td><div className="row"><Bar pct={p.actualPct} color={pr.status==='Behind'?'var(--bad)':'var(--ok)'}/><b>{p.actualPct}%</b></div><div className="faint">Plan {p.plannedPct}% · {pr.variance>0?'+':''}{pr.variance}%</div></td><td>{p.manpower}</td><td><StatusBadge status={p.status}/><div style={{marginTop:4}}><Badge kind={pr.status==='Behind'?'bad':pr.status==='Ahead'?'ok':'neutral'}>{pr.status}</Badge></div></td></tr>;})}</tbody></table></div></div>{selected&&<ProjectDetail project={selected} onClose={()=>setSelected(null)}/>}</div>;}
const Clients=makeRegister({title:'Clients',sub:'Client master, commercial terms and account standing',icon:'users',rows:()=>DB.clients,status:x=>x.rating>=4.5?'Strategic':'Approved',statuses:['Strategic','Approved'],columns:[{key:'id',label:'Client ID',render:x=><span className="mono cell-strong">{x.id}</span>},{key:'name',label:'Client',render:x=><div><b>{x.name}</b><div className="faint">{x.sector}</div></div>},{key:'contact',label:'Contact'},{key:'phone',label:'Phone'},{key:'paymentTerms',label:'Payment terms'},{key:'creditLimit',label:'Credit limit',render:x=>window.money(x.creditLimit)},{key:'rating',label:'Rating',render:x=><Badge kind={x.rating>=4.5?'ok':'info'}>★ {x.rating}</Badge>} ]});
const REQUIREMENTS={
leads:['SAL-01','SAL-02','SAL-07','SAL-08'],estimation:['SAL-10','SAL-11','SAL-14'],contracts:['SAL-17','SAL-18','FIN-09','FIN-22'],variations:['SAL-19','FIN-09','FIN-10'],requisitions:['SCM-04','SCM-40','FIN-12'],rfq:['SCM-05','SCM-06','SCM-40'],'purchase-orders':['SCM-07','SCM-15','SCM-40'],suppliers:['SCM-08','SCM-09','SCM-10'],inventory:['SCM-21','SCM-24','SCM-36'],subcontractors:['SCM-13','FIN-11','HSE-01'],'hse-incidents':['HSE-04','HSE-05','HSE-07','HSE-09'],'body-map':['HSE-03','ED-05','HSE-04'],observations:['HSE-12','HSE-13','HSE-23'],permits:['HSE-19','HSE-20','HSE-23'],jsa:['HSE-15','HSE-16','HSE-17'],environment:['HSE-30','HSE-31','HSE-32','ED-06'],ncr:['ED-08','SAL-26','SCM-33'],itp:['SCM-17','SCM-23','ED-08'],'quality-audits':['HSE-22','SCM-11','ED-08'],documents:['SAL-32','FIN-34','SCM-16'],calibration:['ED-08','QMS calibration controls'], 'fin-dashboard':['FIN-01','FIN-06','FIN-31'],billing:['FIN-10','SAL-25','FIN-22'],'cost-control':['FIN-06','FIN-07','FIN-12'],payables:['FIN-05','FIN-11','FIN-30'],receivables:['FIN-04','FIN-10','FIN-22'],employees:['HR-01','HR-05','HR-06','HR-08'],attendance:['HR-09','HSE-01','FIN-17'],leave:['HR-10','HR-20','HR-22'],payroll:['HR-11','FIN-17','FIN-30'],omanisation:['HR-03','HR-21','HR-22'],training:['HR-13','HSE-26','HR-21'],equipment:['FIN-19','SCM-29','Maintenance register'],ppm:['FIN-19','SCM-29','Maintenance planning'],'job-cards':['FIN-19','SCM-29','Maintenance work orders'],spares:['SCM-24','SCM-28','FIN-20'],'audit-trail':['FIN-33','HR-22','SCM-40'],access:['HR-22','FIN-30','ED-12'],aicos:['ED-11','ED-12','Cross-module intelligence']};
function ModulePlaceholder({moduleId,title}){const reqs=REQUIREMENTS[moduleId]||['PRD integrated workflow','Role-based approval','Audit trail'];return <div className="page rise"><PageHead title={title} sub="Planned construction ERP capability" icon="grid"/><div className="card" style={{padding:'42px 36px',textAlign:'center',maxWidth:760,margin:'20px auto'}}><span className="kpi-ico" style={{width:60,height:60,margin:'0 auto 16px'}}><Icon name="building" size={28}/></span><h2>{title}</h2><p className="muted">This module is scheduled for the next build increment. Its route, role access and AI-COS context are already registered.</p><div className="wrap" style={{justifyContent:'center',gap:8,marginTop:18}}>{reqs.map(x=><Badge kind="info" key={x}>{x}</Badge>)}</div></div></div>;}
function AICOSConsole({onNav}){return <div className="page page-wide rise"><PageHead title="AI-COS Console" sub="Role-scoped construction ERP intelligence with cited sources" icon="sparkles"/><div className="card card-pad"><AICOSProactive/><AIConversation onNav={onNav}/></div></div>;}
function SearchResults(){const q=window.__searchQ||'',rows=[...DB.projects.map(x=>({type:'Project',id:x.id,title:x.name,sub:x.location})),...DB.clients.map(x=>({type:'Client',id:x.id,title:x.name,sub:x.sector})),...DB.ncrs.map(x=>({type:'NCR',id:x.id,title:x.title,sub:x.status}))].filter(x=>Object.values(x).join(' ').toLowerCase().includes(q.toLowerCase()));return <div className="page rise"><PageHead title="Search results" sub={`Results for “${q}”`} icon="search"/><div className="card">{rows.map(x=><div className="lrow" key={x.type+x.id}><Badge kind="neutral">{x.type}</Badge><div><b>{x.title}</b><div className="faint">{x.id} · {x.sub}</div></div></div>)}</div></div>;}
function C2Tabs({tabs,active,onChange}){return <div className="seg">{tabs.map(x=><button key={x} className={active===x?'active':''} onClick={()=>onChange(x)}>{x}</button>)}</div>;}
function C2Table({columns,rows,onRow}){return <div className="card"><div style={{overflowX:'auto'}}><table className="tbl"><thead><tr>{columns.map(c=><th key={c.label}>{c.label}</th>)}</tr></thead><tbody>{rows.map((r,ri)=><tr key={r.id||r.project||ri} onClick={()=>onRow&&onRow(r)} style={onRow?{cursor:'pointer'}:null}>{columns.map((c,ci)=><td key={c.key||c.label||ci}>{c.render?c.render(r):r[c.key]}</td>)}</tr>)}</tbody></table></div></div>;}
function C2Filters({query,setQuery,count,status,setStatus,statuses=[]}){return <div className="card" style={{padding:12,marginBottom:16}}><div className="row"><div className="searchbox" style={{flex:1}}><Icon name="search" size={15}/><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Search register…"/></div>{statuses.length>0&&<select className="input" value={status} onChange={e=>setStatus(e.target.value)} style={{width:175}}><option>All</option>{statuses.map(x=><option key={x}>{x}</option>)}</select>}<Badge kind="neutral">{count} records</Badge></div></div>;}
function SimpleModal({title,sub,onClose,children,actions,width=820}){return <Drawer open onClose={onClose} width={width}><div className="drawer-head"><span className="kpi-ico"><Icon name="documents"/></span><div style={{flex:1}}><b>{title}</b>{sub&&<div className="faint">{sub}</div>}</div><button className="icon-btn" onClick={onClose}><Icon name="x"/></button></div><div className="drawer-body">{children}{actions&&<div className="row" style={{justifyContent:'flex-end',marginTop:16}}>{actions}</div>}</div></Drawer>;}
const projOpts=()=>DB.projects.map(x=>({value:x.id,label:`${x.id} — ${x.name}`}));
function Leads(){const [q,setQ]=useState(''),[stage,setStage]=useState('All');const stages=['New','Qualified','Bid/No-bid','Bidding','Submitted','Won','Lost'],rows=DB.leads.filter(x=>(stage==='All'||x.stage===stage)&&(!q||Object.values(x).join(' ').toLowerCase().includes(q.toLowerCase()))),pipe=window.tenderPipeline();const add=()=>openForm({title:'New lead',sub:'Register a qualified construction opportunity',fields:[{key:'prospectName',label:'Client / prospect'},{key:'project',label:'Project title'},{key:'sector',label:'Sector'},{key:'source',label:'Source',type:'select',options:['Tender portal','Client invitation','Referral','Prequalification']},{key:'estValue',label:'Estimated value (OMR)',type:'number'},{key:'closeDate',label:'Expected close',type:'date'},{key:'probability',label:'Probability %',type:'number'}],done:'Lead registered',onSave:v=>dbAdd('leads',{id:`LD-26-${String(DB.leads.length+1).padStart(3,'0')}`,...v,estValue:Number(v.estValue),probability:Number(v.probability),stage:'New',owner:DB.employees[10].id,openDate:new Date().toISOString().slice(0,10)})});const convert=x=>{if(DB.tenders.some(t=>t.lead===x.id))return toast('Tender already exists','warn');dbAdd('tenders',{id:`TND-26-${String(DB.tenders.length+1).padStart(3,'0')}`,lead:x.id,client:x.client,title:x.project,tenderNo:'PENDING',issueDate:new Date().toISOString().slice(0,10),submissionDate:x.closeDate,bondRequired:false,bondValue:0,bidDecision:'Pending',decisionNotes:'Converted from lead pipeline.',status:'Registered',submittedValue:0});dbUpdate('leads',x.id,{stage:'Bid/No-bid'});toast('Lead converted to tender','ok');};return <div className="page page-wide rise"><PageHead title="Leads & Tenders" sub="Opportunity pipeline, qualification and tender conversion" icon="trend" actions={<><Btn icon="download" onClick={()=>printRegister('Lead pipeline')}>Export</Btn><Btn variant="primary" icon="plus" onClick={add}>New lead</Btn></>}/><div className="card card-pad" style={{marginBottom:16}}><div className="row" style={{alignItems:'stretch',flexWrap:'wrap'}}>{stages.map(s=>{const x=pipe.stages[s]||{count:0,value:0};return <div key={s} className="stat-cell" style={{flex:1,minWidth:120}}><span>{s}</span><b>{x.count}</b><small className="faint">{window.money(x.value)}</small></div>})}</div></div><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}><Kpi label="Pipeline value" value={window.money(pipe.value)} icon="trend"/><Kpi label="Win rate" value={pipe.winRate} unit="%" icon="chart" tone="ok"/><Kpi label="Open opportunities" value={DB.leads.filter(x=>!['Won','Lost'].includes(x.stage)).length} icon="briefcase"/></div><C2Filters query={q} setQuery={setQ} count={rows.length} status={stage} setStatus={setStage} statuses={stages}/><C2Table rows={rows} columns={[{label:'Lead',render:x=><><b>{x.project}</b><div className="mono faint">{x.id}</div></>},{label:'Client / prospect',render:x=>(DB.custById(x.client)||{}).name||x.prospectName},{label:'Sector',key:'sector'},{label:'Value',render:x=>window.money(x.estValue)},{label:'Close',render:x=><>{DB.fmt(x.closeDate)}<div className="faint">{DB.rel(x.closeDate)}</div></>},{label:'Probability',render:x=><Badge kind="info">{x.probability}%</Badge>},{label:'Stage',render:x=><StatusBadge status={x.stage}/>},{label:'Action',render:x=><Btn size="sm" onClick={e=>{e.stopPropagation();convert(x)}} disabled={['Won','Lost'].includes(x.stage)}>Convert to tender</Btn>}]}/></div>;}
function Estimation(){const [tab,setTab]=useState('Tenders'),[selected,setSelected]=useState(null);const decide=t=>openForm({title:`Bid / no-bid · ${t.id}`,fields:[{key:'bidDecision',label:'Decision',type:'radio',options:['Bid','No bid','Pending'],value:t.bidDecision},{key:'decisionNotes',label:'Decision notes',type:'textarea',value:t.decisionNotes}],done:'Tender decision saved',onSave:v=>dbUpdate('tenders',t.id,v)});const approve=e=>{dbUpdate('estimates',e.id,{status:'Approved'});toast('Estimate approved','ok');setSelected(null)};return <div className="page page-wide rise"><PageHead title="Estimation & BOQ" sub="Tender control, estimate build-up and earned BOQ quantities" icon="chart" actions={<Btn icon="download" onClick={()=>printRegister(tab)}>Export</Btn>}/><C2Tabs tabs={['Tenders','Estimates','BOQ']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='Tenders'&&<C2Table rows={DB.tenders} columns={[{label:'Tender',render:x=><><b>{x.title}</b><div className="mono faint">{x.id} · {x.tenderNo}</div></>},{label:'Client',render:x=>(DB.custById(x.client)||{}).name},{label:'Submission',render:x=>{const n=Math.ceil((new Date(x.submissionDate)-DB.TODAY)/DB.day);return <span style={{color:n>=0&&n<=10?'var(--bad)':undefined,fontWeight:n>=0&&n<=10?700:400}}>{DB.fmt(x.submissionDate)}<div className="faint">{DB.rel(x.submissionDate)}</div></span>}},{label:'Bond',render:x=>x.bondRequired?window.money(x.bondValue):'Not required'},{label:'Decision',render:x=><Badge kind={x.bidDecision==='Bid'?'ok':x.bidDecision==='No bid'?'bad':'warn'}>{x.bidDecision}</Badge>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>decide(x)}>Bid / no-bid</Btn>}]}/>} {tab==='Estimates'&&<C2Table rows={DB.estimates} onRow={setSelected} columns={[{label:'Estimate',render:x=><><b>{x.id}</b><div className="faint">{x.project}</div></>},{label:'Tender',key:'tender'},{label:'Direct cost',render:x=>window.money(x.directCost)},{label:'Prelims',render:x=>window.money(x.prelims)},{label:'Margin',render:x=>`${x.marginPct}%`},{label:'Quoted value',render:x=><b>{window.money(x.quotedValue)}</b>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Build-up</Btn>}]}/>} {tab==='BOQ'&&<C2Table rows={DB.boq.filter(x=>window.DASH.project==='All projects'||x.project===window.DASH.project)} columns={[{label:'BOQ',render:x=><><b>{x.description}</b><div className="mono faint">{x.code}</div></>},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Quantity',render:x=>`${x.qty.toLocaleString()} ${x.unit}`},{label:'Rate',render:x=>window.money(x.rate)},{label:'Executed',render:x=><div style={{minWidth:150}}><div className="row"><Bar pct={x.executedPct}/><b>{x.executedPct}%</b></div><div className="faint">{x.executedQty.toLocaleString()} {x.unit}</div></div>},{label:'Value earned',render:x=>window.money(x.executedQty*x.rate)}]}/>} {selected&&<SimpleModal title={`Estimate build-up · ${selected.id}`} sub={selected.project} onClose={()=>setSelected(null)} actions={<Btn variant="primary" icon="check" onClick={()=>approve(selected)} disabled={selected.status==='Approved'}>Approve estimate</Btn>}><table className="tbl"><tbody>{[['Direct cost',selected.directCost],['Preliminaries',selected.prelims],[`Overhead (${selected.overheadPct}%)`,(selected.directCost+selected.prelims)*selected.overheadPct/100],[`Margin (${selected.marginPct}%)`,(selected.directCost+selected.prelims)*selected.marginPct/100],[`Contingency (${selected.contingencyPct}%)`,(selected.directCost+selected.prelims)*selected.contingencyPct/100],['Quoted value',selected.quotedValue]].map((r,i)=><tr key={r[0]} style={i===5?{fontWeight:800,background:'var(--primary-50)'}:null}><td>{r[0]}</td><td style={{textAlign:'right'}}>{window.money(r[1])}</td></tr>)}</tbody></table><div className="h-sec" style={{margin:'18px 0 8px'}}>Measured estimate lines</div><C2Table rows={selected.lines.map((x,i)=>({...x,id:i}))} columns={[{label:'Item',key:'item'},{label:'Unit',key:'unit'},{label:'Qty',render:x=>x.qty.toLocaleString()},{label:'Rate',render:x=>window.money(x.rate)},{label:'Amount',render:x=>window.money(x.amount)}]}/></SimpleModal>}</div>;}
function Contracts(){const [selected,setSelected]=useState(null),rows=DB.contracts,soon=rows.filter(x=>{const n=(new Date(x.completionDate)-DB.TODAY)/DB.day;return n>=0&&n<=90});return <div className="page page-wide rise"><PageHead title="Contracts" sub="Award, retention, advance recovery and milestone control" icon="documents" actions={<Btn icon="download" onClick={()=>printRegister('Contracts')}>Export</Btn>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Active contracts" value={rows.filter(x=>x.status==='Active').length} icon="documents" tone="ok"/><Kpi label="Total value" value={window.money(rows.reduce((a,x)=>a+x.value,0))} icon="receipt"/><Kpi label="Retention held" value={window.money(rows.reduce((a,x)=>a+x.retentionHeld,0))} icon="lock" tone="warn"/><Kpi label="Completing ≤90d" value={soon.length} icon="clock" tone={soon.length?'warn':'ok'}/></div><C2Table rows={rows} onRow={setSelected} columns={[{label:'Contract',render:x=><><b>{(DB.projectById(x.project)||{}).name}</b><div className="mono faint">{x.id}</div></>},{label:'Client',render:x=>(DB.custById(x.client)||{}).name},{label:'Value',render:x=>window.money(x.value)},{label:'Award',render:x=>DB.fmt(x.awardDate)},{label:'Completion',render:x=>DB.fmt(x.completionDate)},{label:'Retention held',render:x=>window.money(x.retentionHeld)},{label:'Advance recovered',render:x=>window.money(x.advanceRecovered)},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/>{selected&&<SimpleModal title={selected.id} sub={(DB.projectById(selected.project)||{}).name} onClose={()=>setSelected(null)}><div className="exec-four-cells">{[['Contract value',window.money(selected.value)],['Retention',`${selected.retentionPct}% · ${window.money(selected.retentionHeld)}`],['Advance',`${selected.advancePct}% · ${window.money(selected.advanceRecovered)} recovered`],['Completion',DB.fmt(selected.completionDate)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:14}}>{x[1]}</b></div>)}</div><div className="h-sec" style={{margin:'18px 0 8px'}}>Milestones</div><C2Table rows={selected.milestones.map((x,i)=>({...x,id:i}))} columns={[{label:'Milestone',key:'name'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Value',render:x=>window.money(x.value)},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/><div className="h-sec" style={{margin:'18px 0 8px'}}>Linked variations</div><C2Table rows={DB.variations.filter(x=>x.contract===selected.id)} columns={[{label:'Variation',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Submitted',render:x=>window.money(x.submittedValue)},{label:'Approved',render:x=>x.approvedValue==null?'—':window.money(x.approvedValue)},{label:'Time',render:x=>`${x.timeImpactDays} days`},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/></SimpleModal>}</div>;}
function Variations(){const impact=window.variationImpact(window.DASH.project),rows=DB.variations.filter(x=>window.DASH.project==='All projects'||x.project===window.DASH.project);const add=()=>openForm({title:'Submit variation',aside:true,fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'title',label:'Variation title'},{key:'type',label:'Type',type:'select',options:['Client instruction','Design change','Unforeseen conditions','Provisional sum adjustment']},{key:'submittedValue',label:'Submitted value (OMR)',type:'number'},{key:'submittedDate',label:'Submitted date',type:'date'},{key:'timeImpactDays',label:'Time impact (days)',type:'number'},{key:'notes',label:'Notes',type:'textarea'}],live:v=>{const p=DB.projectById(v.project),f=v.project?window.financeSummary({project:v.project}):null,value=Number(v.submittedValue||0),base=p?.contractValue||0,current=f?.grossProfit||0,newMargin=(current+value)/Math.max(1,(f?.revenueEarned||0)+value)*100;return {tone:value<0?'warn':newMargin<10?'bad':'info',headline:v.project?`Forecast margin impact · ${newMargin.toFixed(1)}%`:'Select a project for margin impact',clause:'Commercial impact forecast',lines:[{icon:'chart',tone:newMargin<10?'bad':'info',text:`Variation ${value>=0?'adds':'reduces'} ${window.money(Math.abs(value))}; current project margin is ${f?.marginPct??'—'}%.`},{icon:'calendar',tone:Number(v.timeImpactDays)>14?'warn':'info',text:`Programme impact: ${Number(v.timeImpactDays||0)} days against ${p?.name||'the selected project'}.`} ]};},done:'Variation submitted',onSave:v=>dbAdd('variations',{id:`VO-26-${String(DB.variations.length+1).padStart(3,'0')}`,...v,contract:(DB.contracts.find(x=>x.project===v.project)||{}).id,submittedValue:Number(v.submittedValue),approvedValue:null,timeImpactDays:Number(v.timeImpactDays),status:'Submitted'})});return <div className="page page-wide rise"><PageHead title="Variations" sub="Commercial and programme impact against the contract baseline" icon="refresh" actions={<Btn variant="primary" icon="plus" onClick={add}>Submit variation</Btn>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Submitted value" value={window.money(impact.submitted)} icon="receipt"/><Kpi label="Approved value" value={window.money(impact.approved)} icon="check" tone="ok"/><Kpi label="Pending value" value={window.money(impact.pending)} icon="clock" tone="warn"/><Kpi label="Revised contract value" value={window.money(impact.revisedContractValue)} icon="documents"/></div><C2Table rows={rows} columns={[{label:'Variation',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Type',key:'type'},{label:'Submitted',render:x=>window.money(x.submittedValue)},{label:'Approved',render:x=>x.approvedValue==null?'—':window.money(x.approvedValue)},{label:'Time impact',render:x=>`${x.timeImpactDays} days`},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/></div>;}
function Requisitions(){const approveRoles=['Project Manager','Procurement Manager','System Administrator'],[q,setQ]=useState('');const rows=DB.requisitions.filter(x=>!q||Object.values(x).join(' ').toLowerCase().includes(q.toLowerCase()));const add=()=>openForm({title:'Raise requisition',fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'requiredBy',label:'Required by',type:'date'},{key:'priority',label:'Priority',type:'radio',options:['Normal','Urgent'],value:'Normal'},{key:'description',label:'Requirement description',type:'textarea'},{key:'lines',label:'Lines (description | unit | qty | est rate)',type:'textarea',placeholder:'Cement | bag | 500 | 2.200'}],done:'Requisition raised',onSave:v=>{const lines=String(v.lines).split('\n').filter(Boolean).map(s=>{const [description,unit,qty,estRate]=s.split('|').map(x=>x.trim());return {description,unit,qty:Number(qty),estRate:Number(estRate)}});dbAdd('requisitions',{id:`PR-26-${String(DB.requisitions.length+1).padStart(3,'0')}`,project:v.project,requestedBy:DB.employees[6].id,dateRaised:new Date().toISOString().slice(0,10),requiredBy:v.requiredBy,description:v.description,itemCount:lines.length,estValue:lines.reduce((a,x)=>a+x.qty*x.estRate,0),priority:v.priority,status:'Pending approval',approver:(DB.projectById(v.project)||{}).pm,lines})}});const decision=(x,status)=>{if(!approveRoles.includes(window.ROLE))return toast('Approval restricted to Project Manager, Procurement Manager or Admin','bad');openForm({title:`${status} ${x.id}`,fields:[{key:'reason',label:'Decision reason',type:'textarea'}],submitLabel:status,done:`Requisition ${status.toLowerCase()}`,onSave:v=>dbUpdate('requisitions',x.id,{status:status==='Approve'?'Approved':'Rejected',decisionReason:v.reason})})};const convert=x=>{if(x.status!=='Approved')return toast('Approve the requisition before RFQ conversion','warn');const id=`RFQ-26-${String(DB.rfqs.length+1).padStart(3,'0')}`;dbAdd('rfqs',{id,requisition:x.id,project:x.project,description:x.description,issuedTo:DB.suppliers.slice(0,3).map(s=>s.id),issueDate:new Date().toISOString().slice(0,10),dueDate:x.requiredBy,status:'Issued',bids:[]});dbUpdate('requisitions',x.id,{status:'Converted to RFQ'});toast(`${id} created`,'ok')};return <div className="page page-wide rise"><PageHead title="Purchase Requisitions" sub="Site demand, approval and RFQ conversion workflow" icon="receipt" actions={<Btn variant="primary" icon="plus" onClick={add}>Raise requisition</Btn>}/><C2Filters query={q} setQuery={setQ} count={rows.length}/><C2Table rows={rows} columns={[{label:'Requisition',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Requested by',render:x=>(DB.empById(x.requestedBy)||{}).name},{label:'Required',render:x=>DB.fmt(x.requiredBy)},{label:'Lines',key:'itemCount'},{label:'Estimate',render:x=>window.money(x.estValue)},{label:'Priority',render:x=><Badge kind={x.priority==='Urgent'?'bad':'neutral'}>{x.priority}</Badge>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Actions',render:x=><div className="row"><Btn size="sm" onClick={()=>decision(x,'Approve')} disabled={x.status!=='Pending approval'}>Approve</Btn><Btn size="sm" onClick={()=>decision(x,'Reject')} disabled={x.status!=='Pending approval'}>Reject</Btn><Btn size="sm" variant="primary" onClick={()=>convert(x)} disabled={x.status!=='Approved'}>Convert to RFQ</Btn><Btn size="sm" onClick={()=>c8RaisePO({requisition:x.id,project:x.project,description:x.description,estValue:x.estValue,requiredBy:x.requiredBy})} disabled={x.status!=='Approved'} title="Raise a purchase order directly, without an RFQ round">Raise PO</Btn></div>}]}/></div>;}
function RFQs(){const [selected,setSelected]=useState(null);const award=(rfq,bid)=>{const id=`PO-26-${String(DB.purchaseOrders.length+1).padStart(3,'0')}`;dbAdd('purchaseOrders',{id,project:rfq.project,supplier:bid.supplier,description:rfq.description,value:bid.amount,orderDate:new Date().toISOString().slice(0,10),requiredDate:rfq.dueDate,deliveryStatus:'On time',status:'Open'});dbUpdate('rfqs',rfq.id,{status:'Awarded',awardedSupplier:bid.supplier,po:id});toast(`${id} awarded to ${(DB.suppliers.find(x=>x.id===bid.supplier)||{}).name}`,'ok');setSelected(null)};return <div className="page page-wide rise"><PageHead title="RFQ & Bids" sub="Technical and commercial comparison through purchase award" icon="chat" actions={<Btn icon="download" onClick={()=>printRegister('RFQs')}>Export</Btn>}/><C2Table rows={DB.rfqs} onRow={setSelected} columns={[{label:'RFQ',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Requisition',key:'requisition'},{label:'Issued to',render:x=>`${x.issuedTo.length} suppliers`},{label:'Due',render:x=>DB.fmt(x.dueDate)},{label:'Bids',render:x=>x.bids.length},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Bid comparison</Btn>}]}/>{selected&&(()=>{const c=window.bidComparison(selected.id);return <SimpleModal title={`Bid comparison · ${selected.id}`} sub={selected.description} onClose={()=>setSelected(null)} width={920}><AINote title={c.recommended?'AI recommendation':'Awaiting compliant bids'}>{c.recommended?`Recommend ${(DB.suppliers.find(x=>x.id===c.recommended.supplier)||{}).name}, the lowest technically compliant bid at ${window.money(c.recommended.amount)}. Saving versus highest bid: ${window.money(c.savingVsHighest)}.`:'No technically compliant bid is available yet.'}</AINote><div style={{height:12}}/><C2Table rows={c.bids.map((x,i)=>({...x,id:i,isLowest:x===c.lowest}))} columns={[{label:'Supplier',render:x=><b>{(DB.suppliers.find(s=>s.id===x.supplier)||{}).name}</b>},{label:'Amount',render:x=><span style={x.isLowest?{color:'var(--ok)',fontWeight:800}:null}>{window.money(x.amount)} {x.isLowest&&<Badge kind="ok">Lowest</Badge>}</span>},{label:'Delivery',render:x=>`${x.deliveryDays} days`},{label:'Validity',render:x=>`${x.validityDays} days`},{label:'Technical',render:x=><Badge kind={x.technicallyCompliant?'ok':'bad'}>{x.technicallyCompliant?'Compliant':'Non-compliant'}</Badge>},{label:'Notes',key:'notes'},{label:'Award',render:x=><Btn size="sm" variant="primary" disabled={!x.technicallyCompliant||selected.status==='Awarded'} onClick={()=>award(selected,x)}>Award to supplier</Btn>}]}/></SimpleModal>})()}</div>;}
/* A purchase order can arise two ways: by awarding an RFQ, or — for call-offs, rate contracts
   and urgent site purchases — by raising one directly against an approved requisition. */
const PO_ROLES=['Procurement Manager','Executive Management'];
function c8RaisePO(prefill,onDone){
  const pre=prefill||{},today=c7Today();
  if(!c7Can(PO_ROLES))return toast('Raising a purchase order is restricted to the Procurement Manager','bad');
  openForm({title:'Raise purchase order',aside:true,submitLabel:'Issue PO',
    fields:[
      {type:'section',label:'Source'},
      {key:'requisition',label:'Source requisition',type:'select',options:[{value:'',label:'Direct purchase — no requisition'},...DB.requisitions.filter(x=>['Approved','Converted to RFQ'].includes(x.status)).map(x=>({value:x.id,label:`${x.id} — ${x.description}`}))],value:pre.requisition},
      {key:'project',label:'Project *',type:'select',options:projOpts(),value:pre.project},
      {key:'supplier',label:'Supplier *',type:'select',options:DB.suppliers.map(x=>({value:x.id,label:`${x.name}${x.prequalified?'':' — NOT prequalified'}`}))},
      {type:'section',label:'Order'},
      {key:'description',label:'Description *',value:pre.description},
      {key:'value',label:'Order value (OMR) *',type:'number',value:pre.estValue},
      {key:'orderDate',label:'Order date *',type:'date',value:today},
      {key:'requiredDate',label:'Required on site *',type:'date',value:pre.requiredBy},
      {key:'paymentTerms',label:'Payment terms',type:'select',options:['30 days','45 days','60 days','On delivery','Advance payment'],value:'30 days'},
      {key:'deliverTo',label:'Deliver to',placeholder:'Site store, gate or laydown area'},
      {key:'notes',label:'Notes',type:'textarea'}],
    live:v=>{
      const sup=DB.suppliers.find(x=>x.id===v.supplier),pr=DB.requisitions.find(x=>x.id===v.requisition),
        cc=(window.costControl(v.project)||[])[0],value=Number(v.value||0),
        lead=v.orderDate&&v.requiredDate?Math.round((new Date(v.requiredDate)-new Date(v.orderDate))/DB.day):null,
        badDate=lead!==null&&lead<=0,
        committed=cc?cc.committed+value:0,overBudget=cc&&cc.budget&&committed>cc.budget,
        dupe=DB.purchaseOrders.find(x=>x.status==='Open'&&x.project===v.project&&x.supplier===v.supplier&&String(x.description||'').toLowerCase()===String(v.description||'').toLowerCase()),
        lines=[];
      if(cc&&cc.budget)lines.push({icon:'chart',tone:overBudget?'bad':'info',text:overBudget
        ?`This order takes commitments to ${window.money(committed)} against a budget of ${window.money(cc.budget)} — over budget, so it needs Executive approval.`
        :`Commitments become ${window.money(committed)} of a ${window.money(cc.budget)} budget (${Math.round(committed/cc.budget*100)}%).`});
      if(pr)lines.push({icon:'receipt',tone:value>pr.estValue*1.1?'warn':'ok',text:value>pr.estValue*1.1
        ?`${Math.round((value/pr.estValue-1)*100)}% above the ${pr.id} estimate of ${window.money(pr.estValue)}.`
        :`Within the ${pr.id} estimate of ${window.money(pr.estValue)}.`});
      if(sup&&!sup.prequalified)lines.push({icon:'alert',tone:'bad',text:`${sup.name} is not prequalified — procurement policy requires prequalification before award.`});
      if(sup&&lead!==null&&!badDate)lines.push({icon:'truck',tone:lead<7?'warn':'info',text:`${lead}-day lead time; ${sup.name} delivers on time ${sup.onTimePct}% of the time.`});
      if(dupe)lines.push({icon:'alert',tone:'warn',text:`${dupe.id} is already open with this supplier for the same item on this project.`});
      return {tone:badDate||overBudget||(sup&&!sup.prequalified)?'bad':lines.some(l=>l.tone==='warn')?'warn':'info',
        headline:badDate?'BLOCK: required date must be after the order date':value?`${window.money(value)} commitment`:'Enter the order value',
        clause:'Commitment control · supplier prequalification · delivery lead time',
        lines:lines.length?lines:[{icon:'sparkles',tone:'info',text:'Choose a project and supplier and AI-COS will check budget, prequalification and lead time.'}]};
    },
    done:'Purchase order issued',
    onSave:v=>{
      if(!c7Required(v,['project','supplier','description','value','orderDate','requiredDate']))return false;
      if(new Date(v.requiredDate)<=new Date(v.orderDate)){toast('Required date must be after the order date','bad');return false}
      const id=`PO-26-${String(DB.purchaseOrders.length+1).padStart(3,'0')}`;
      c7Add('purchaseOrders',{id,project:v.project,supplier:v.supplier,description:v.description,value:Number(v.value),
        orderDate:v.orderDate,requiredDate:v.requiredDate,paymentTerms:v.paymentTerms,deliverTo:v.deliverTo,notes:v.notes,
        requisition:v.requisition||null,deliveryStatus:'On time',status:'Open'},'Purchase Orders',['Warehouse Keeper','Project Manager']);
      if(v.requisition)c7Update('requisitions',v.requisition,{status:'Converted to PO',po:id},'Purchase Orders',v.project,`Converted to ${id}`);
      toast(`${id} issued to ${(DB.suppliers.find(x=>x.id===v.supplier)||{}).name}`,'ok');
      onDone&&onDone(id);
    }});
}
function PurchaseOrders(){const sum=window.scmSummary(),received=DB.grns.filter(x=>window.inRange(x.receivedDate)).length;const grn=x=>openForm({title:`Record GRN · ${x.id}`,fields:[{key:'qtyReceived',label:'Quantity received',type:'number'},{key:'condition',label:'Condition',type:'radio',options:['Accepted','Partially accepted','Rejected']},{key:'inspection',label:'Inspection result',type:'radio',options:['Passed','Failed','Pending']},{key:'remarks',label:'Remarks',type:'textarea'}],done:'GRN recorded',onSave:v=>{dbAdd('grns',{id:`GRN-26-${String(DB.grns.length+1).padStart(3,'0')}`,po:x.id,project:x.project,supplier:x.supplier,receivedDate:new Date().toISOString().slice(0,10),receivedBy:DB.employees[31].id,items:x.description,qtyOrdered:Number(v.qtyReceived),qtyReceived:Number(v.qtyReceived),condition:v.condition,inspection:v.inspection,remarks:v.remarks});dbUpdate('purchaseOrders',x.id,{deliveryStatus:v.condition==='Accepted'?'Delivered':v.condition==='Rejected'?'Rejected':'Partially delivered',status:v.condition==='Accepted'?'Closed':x.status})}});return <div className="page page-wide rise"><PageHead title="Purchase Orders" sub="Commitment, delivery and goods-receipt control" icon="box" actions={<><Btn icon="download" onClick={()=>printRegister('Purchase Orders')}>Export</Btn>{c7Can(PO_ROLES)&&<Btn variant="primary" icon="plus" onClick={()=>c8RaisePO()}>Raise purchase order</Btn>}</>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Open POs" value={sum.openPOs} icon="box"/><Kpi label="Open value" value={window.money(sum.poValue)} icon="receipt"/><Kpi label="Delayed" value={sum.delayed} icon="truck" tone="bad"/><Kpi label="Received this period" value={received} icon="check" tone="ok"/></div><C2Table rows={DB.purchaseOrders} columns={[{label:'PO',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Supplier',render:x=>(DB.suppliers.find(s=>s.id===x.supplier)||{}).name},{label:'Value',render:x=>window.money(x.value)},{label:'Ordered',render:x=>DB.fmt(x.orderDate)},{label:'Required',render:x=>DB.fmt(x.requiredDate)},{label:'Delivery',render:x=><StatusBadge status={x.deliveryStatus}/>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Actions',render:x=><div className="row"><Btn size="sm" onClick={()=>grn(x)}>Record GRN</Btn><Btn size="sm" icon="download" onClick={()=>printRecord(x.id)}>Print PO</Btn></div>}]}/></div>;}
function Suppliers(){const [selected,setSelected]=useState(null);return <div className="page page-wide rise"><PageHead title="Suppliers" sub="Prequalification, performance scoring and procurement spend" icon="truck" actions={<Btn icon="download" onClick={()=>printRegister('Suppliers')}>Export</Btn>}/><C2Table rows={DB.suppliers} onRow={setSelected} columns={[{label:'Supplier',render:x=><><b>{x.name}</b><div className="mono faint">{x.id} · CR {x.crNumber}</div></>},{label:'Category',key:'category'},{label:'Contact',key:'contact'},{label:'Prequalification',render:x=><Badge kind={x.prequalStatus==='Expired'?'bad':x.prequalStatus==='Provisional'?'warn':'ok'}>{x.prequalStatus}</Badge>},{label:'Expiry',render:x=>DB.fmt(x.prequalExpiry)},{label:'Delivery',render:x=>`${x.deliveryScore}%`},{label:'Quality',key:'qualityScore'},{label:'HSE',key:'hseScore'},{label:'Overall',render:x=><Badge kind={x.overallScore>=85?'ok':x.overallScore>=75?'warn':'bad'}>{x.overallScore}</Badge>},{label:'Spend YTD',render:x=>window.money(x.spendYtd)},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Scorecard</Btn>}]}/>{selected&&(()=>{const s=window.supplierScorecard(selected.id);return <SimpleModal title={selected.name} sub={`Supplier scorecard · ranked #${s.ranking} of ${DB.suppliers.length}`} onClose={()=>setSelected(null)}><div className="exec-four-cells">{[['PO count',s.poCount],['Procurement spend',window.money(s.spend)],['Overall score',s.overall],['Ranking',`#${s.ranking}`]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b>{x[1]}</b></div>)}</div><div className="card card-pad" style={{marginTop:16}}>{[['On-time delivery',s.onTimePct],['Quality',s.quality],['HSE',s.hse],['Overall',s.overall]].map(x=><div className="row" style={{margin:'12px 0'}} key={x[0]}><span style={{width:140}}>{x[0]}</span><Bar pct={x[1]} color={x[1]>=85?'var(--ok)':x[1]>=75?'var(--warn)':'var(--bad)'}/><b>{x[1]}%</b></div>)}</div></SimpleModal>})()}</div>;}
function Inventory(){const [tab,setTab]=useState('Stock'),stockValue=DB.inventoryItems.reduce((a,x)=>a+x.value,0),low=DB.inventoryItems.filter(x=>x.qtyOnHand<x.minLevel),stores=new Set(DB.inventoryItems.map(x=>x.store)).size;const move=(kind)=>openForm({title:kind==='Receipt'?'Receive stock':'Issue to project',fields:[{key:'item',label:'Item',type:'select',options:DB.inventoryItems.map(x=>({value:x.id,label:`${x.id} — ${x.description}`}))},{key:'qty',label:'Quantity',type:'number'},...(kind==='Issue to project'?[{key:'project',label:'Project',type:'select',options:projOpts()}]:[]),{key:'ref',label:'Reference'}],done:'Stock movement posted',onSave:v=>{const item=DB.inventoryItems.find(x=>x.id===v.item),qty=Number(v.qty);dbAdd('stockMovements',{id:`SM-26-${String(DB.stockMovements.length+1).padStart(3,'0')}`,date:new Date().toISOString().slice(0,10),item:v.item,kind,qty,uom:item.unit,project:v.project||null,from:kind==='Receipt'?'Supplier':item.store,to:kind==='Receipt'?item.store:(DB.projectById(v.project)||{}).name,ref:v.ref,by:DB.employees[31].id});item.qtyOnHand+=kind==='Receipt'?qty:-qty;item.value=Number((item.qtyOnHand*item.unitCost).toFixed(3));window.__bump()}});return <div className="page page-wide rise"><PageHead title="Inventory & Stores" sub="On-hand balance, minimum levels and auditable stock movements" icon="box" actions={<><Btn onClick={()=>move('Issue to project')}>Issue to project</Btn><Btn variant="primary" icon="plus" onClick={()=>move('Receipt')}>Receive stock</Btn></>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}><Kpi label="Stock value" value={window.money(stockValue)} icon="box"/><Kpi label="Below minimum" value={low.length} icon="alert" tone="bad"/><Kpi label="Stores" value={stores} icon="building"/></div><C2Tabs tabs={['Stock','Movements']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='Stock'?<C2Table rows={DB.inventoryItems} columns={[{label:'Item',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Store',key:'store'},{label:'On hand',render:x=><b style={{color:x.qtyOnHand<x.minLevel?'var(--bad)':undefined}}>{x.qtyOnHand} {x.unit}</b>},{label:'Minimum',render:x=>`${x.minLevel} ${x.unit}`},{label:'Level',render:x=><Badge kind={x.qtyOnHand<x.minLevel?'bad':'ok'}>{x.qtyOnHand<x.minLevel?'Below minimum':'Adequate'}</Badge>},{label:'Unit cost',render:x=>window.money(x.unitCost)},{label:'Value',render:x=>window.money(x.value)},{label:'Project',render:x=>x.project?(DB.projectById(x.project)||{}).name:'General stock'}]}/>:<C2Table rows={DB.stockMovements} columns={[{label:'Movement',render:x=><><b>{x.kind}</b><div className="mono faint">{x.id}</div></>},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Item',render:x=>(DB.inventoryItems.find(i=>i.id===x.item)||{}).description},{label:'Quantity',render:x=>`${x.qty} ${x.uom}`},{label:'Project',render:x=>x.project?(DB.projectById(x.project)||{}).name:'—'},{label:'From',key:'from'},{label:'To',key:'to'},{label:'Reference',key:'ref'},{label:'By',render:x=>(DB.empById(x.by)||{}).name}]}/>}</div>;}
function Subcontractors(){const [selected,setSelected]=useState(null);return <div className="page page-wide rise"><PageHead title="Subcontractors" sub="Work orders, certification, retention and HSE performance" icon="users" actions={<Btn icon="download" onClick={()=>printRegister('Subcontractors')}>Export</Btn>}/><C2Table rows={DB.subcontractors} onRow={setSelected} columns={[{label:'Subcontractor',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Trade',key:'trade'},{label:'Project',render:x=>(DB.projectById(x.project)||{}).name},{label:'Contract value',render:x=>window.money(window.subcontractStatus(x.id).value)},{label:'Work done',render:x=>`${window.subcontractStatus(x.id).pctComplete}%`},{label:'Certified',render:x=>window.money(window.subcontractStatus(x.id).certified)},{label:'Retention',render:x=>window.money(window.subcontractStatus(x.id).retentionHeld)},{label:'HSE',render:x=><Badge kind={x.hseScore>=85?'ok':'warn'}>{x.hseScore}</Badge>},{label:'Prequalified',render:x=><Badge kind={x.prequalified?'ok':'bad'}>{x.prequalified?'Yes':'No'}</Badge>},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Detail</Btn>}]}/>{selected&&(()=>{const s=window.subcontractStatus(selected.id);return <SimpleModal title={selected.name} sub={`${selected.trade} · ${(DB.projectById(selected.project)||{}).name}`} onClose={()=>setSelected(null)}><div className="exec-four-cells">{[['Work done',window.money(s.workDone)],['Certified',window.money(s.certified)],['Balance to certify',window.money(s.balanceToCertify)],['Retention held',window.money(s.retentionHeld)],['Progress',`${s.pctComplete}%`],['Payments to date',window.money(selected.paymentsToDate)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:14}}>{x[1]}</b></div>)}</div><div className="h-sec" style={{margin:'18px 0 8px'}}>Work orders</div><C2Table rows={selected.workOrders} columns={[{label:'Work order',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Value',render:x=>window.money(x.value)},{label:'Work done',render:x=>window.money(x.workDone)},{label:'Certified',render:x=>window.money(x.certified)},{label:'Retention',render:x=>window.money(x.retention)}]}/></SimpleModal>})()}</div>;}
Object.assign(window,{PageHead,makeRegister,printRegister,printRecord,Projects,Clients,Leads,Estimation,Contracts,Variations,Requisitions,RFQs,PurchaseOrders,Suppliers,Inventory,Subcontractors,ModulePlaceholder,AICOSConsole,SearchResults,REQUIREMENTS});

/* C3 — HSE and Quality modules. */
const c3Project=()=>window.DASH.project||'All projects';
const c3Match=x=>c3Project()==='All projects'||x.project===c3Project();
const c3Kind=s=>/LTI|RTA|Major|Rejected|Failed|Overdue|Expired|Suspended|Out of service/.test(s)?'bad':/MTC|Medium|Pending|Due soon|Under|Open actions|Approved with comments|Moderate/.test(s)?'warn':/FAC|Near miss|Minor|Active|Approved|Passed|Published|Valid|Closed|Effective|Disposed/.test(s)?'ok':'info';
function C3Donut({segments,center,sub}){const total=Math.max(1,segments.reduce((a,x)=>a+x.value,0)),parts=[];let pos=0;segments.forEach(x=>{const next=pos+x.value/total*100;parts.push(`${x.color} ${pos}% ${next}%`);pos=next;});return <div style={{display:'grid',gridTemplateColumns:'150px 1fr',gap:18,alignItems:'center'}}><div style={{width:136,height:136,borderRadius:'50%',background:`conic-gradient(${parts.join(',')})`,position:'relative'}}><div style={{position:'absolute',inset:20,borderRadius:'50%',background:'var(--surface)',display:'grid',placeItems:'center',textAlign:'center'}}><div><b style={{fontSize:23}}>{center??segments.reduce((a,x)=>a+x.value,0)}</b><div className="faint">{sub}</div></div></div></div><div>{segments.map(x=><div className="row" key={x.label} style={{margin:'7px 0'}}><i style={{width:9,height:9,borderRadius:3,background:x.color}}/><span style={{flex:1}}>{x.label}</span><b>{x.value}</b></div>)}</div></div>}
function C3Bars({rows,label='label',value='value',color='var(--primary-600)',suffix=''}){const max=Math.max(1,...rows.map(x=>Number(x[value])||0));return <div>{rows.map((x,i)=><div key={x[label]||i} style={{margin:'10px 0'}}><div className="row" style={{marginBottom:4}}><span style={{flex:1,fontSize:12}}>{x[label]}</span><b>{x[value]}{suffix}</b></div><Bar pct={Number(x[value])/max*100} color={typeof color==='function'?color(x):color}/></div>)}</div>}
function C3Trend({rows,keys=['value'],colors=['#1f56cf']}){const max=Math.max(1,...rows.flatMap(x=>keys.map(k=>Number(x[k])||0)));return <div style={{height:180,display:'flex',alignItems:'end',gap:10,padding:'12px 6px 0'}}>{rows.map((x,i)=><div key={i} style={{flex:1,textAlign:'center',minWidth:24}}><div style={{height:140,display:'flex',alignItems:'end',justifyContent:'center',gap:3}}>{keys.map((k,j)=><i key={k} title={`${k}: ${x[k]}`} style={{display:'block',width:keys.length>1?'35%':'68%',height:`${Math.max(3,(Number(x[k])||0)/max*100)}%`,background:colors[j],borderRadius:'5px 5px 1px 1px'}}/>)}</div><div className="faint" style={{fontSize:9,marginTop:5}}>{x.label||x.period||String(i+1)}</div></div>)}</div>}
function C3Register({rows,columns}){return <C2Table rows={rows} columns={columns}/>}
function incidentForm(){openForm({title:'Report incident',sub:'Immediate notification, investigation and corrective-action record',aside:true,fields:[{type:'section',label:'Notification'},{key:'project',label:'Project',type:'select',options:projOpts()},{key:'date',label:'Incident date',type:'date'},{key:'title',label:'Incident title',full:true},{key:'description',label:'Description',type:'textarea'},{type:'section',label:'Classification'},{key:'type',label:'Type',type:'radio',options:['LTI','MTC','FAC','RTA','Near miss','Property damage','Environmental'],value:'Near miss'},{key:'severity',label:'Severity',type:'radio',options:['Low','Medium','High'],value:'Low'},{type:'section',label:'People'},{key:'bodyPart',label:'Body part',type:'select',options:['Head','Eyes','Shoulder','Arm','Hand','Fingers','Back','Leg','Knee','Foot'],show:v=>['LTI','MTC','FAC','RTA'].includes(v.type)},{key:'side',label:'Side',type:'radio',options:['L','R','N/A'],show:v=>!!v.bodyPart},{key:'lostDays',label:'Lost days',type:'number',show:v=>v.type==='LTI'||v.type==='RTA'},{type:'section',label:'Causes'},{key:'immediateCauses',label:'Immediate causes',type:'checks',options:['Unsafe access','PPE not used','Equipment condition','Poor housekeeping','Line of fire','Procedure not followed']},{key:'rootCauses',label:'Root causes',type:'checks',options:['Inadequate planning','Training gap','Supervision gap','Risk assessment incomplete','Maintenance weakness']},{type:'section',label:'Actions'},{key:'correctiveAction',label:'Corrective action',type:'textarea'}],live:v=>{const serious=['LTI','RTA'].includes(v.type),head=/head|eye/i.test(v.bodyPart||''),lines=[{icon:'bell',tone:serious?'bad':'warn',text:serious?'Escalate immediately to the HSE Manager and Project Manager.':'Preserve the scene and notify the HSE team.'},{icon:'check',tone:'info',text:`AI classification: ${v.type||'pending'} · ${v.severity||'pending severity'}.`}];if(head)lines.push({icon:'alert',tone:'bad',text:'Head/eye involvement requires urgent medical assessment and escalation.'});if(v.type==='RTA')lines.push({icon:'truck',tone:'bad',text:'Notify transport/site leadership and open a formal investigation note.'});if(serious)lines.push({icon:'documents',tone:'warn',text:'Assess statutory reporting and preserve evidence before disturbance.'});if(v.type==='LTI'&&!Number(v.lostDays))lines.push({icon:'lock',tone:'bad',text:'BLOCK: enter lost days before submitting this Lost Time Injury.'});return {tone:v.type==='LTI'&&!Number(v.lostDays)?'bad':serious?'bad':'info',clause:'Oman HSE notification protocol',headline:v.type==='LTI'&&!Number(v.lostDays)?'Submission blocked · lost days required':v.type?`${v.type} live severity classification`:'Select an incident type',lines,suggest:head?[{text:'Set severity High',set:{key:'severity',val:'High'}}]:[]};},done:'Incident reported',onSave:v=>{if(v.type==='LTI'&&!Number(v.lostDays)){toast('Lost Time Injury requires lost days before submission','bad');return false;}const id=`INC-26-${String(DB.hseIncidents.length+1).padStart(3,'0')}`;dbAdd('hseIncidents',{id,...v,lostDays:Number(v.lostDays||0),side:v.side==='N/A'?'':v.side,recordable:['LTI','MTC','RTA'].includes(v.type),status:'Open',reportedBy:DB.employees[14].id});notify('HSE incident reported',`${id} · ${v.title}`,['HSE Manager','Project Manager']);}})}
function HSEIncidents(){const [tab,setTab]=useState('Incidents'),rows=DB.hseIncidents.filter(c3Match),stats=window.hseStats({project:c3Project()}),months=Array.from({length:12},(_,i)=>{const period=`2025-${String(i+9).padStart(2,'0')}`.replace('2025-13','2026-01').replace('2025-14','2026-02').replace('2025-15','2026-03').replace('2025-16','2026-04').replace('2025-17','2026-05').replace('2025-18','2026-06').replace('2025-19','2026-07').replace('2025-20','2026-08');return {label:period.slice(5),value:rows.filter(x=>x.date.startsWith(period)&&x.recordable).length}}),types=['LTI','MTC','FAC','RTA','Near miss'].map((label,i)=>({label,value:rows.filter(x=>x.type===label).length,color:['#df463c','#c9820a','#22a06b','#8b3dff','#2f80ed'][i]})),byProject=DB.projects.map(p=>({label:p.id,value:rows.filter(x=>x.project===p.id).length})).filter(x=>x.value),rateTrend=Array.from({length:6},(_,i)=>{const to=`2026-${String(i+3).padStart(2,'0')}-28`,s=window.hseStats({project:c3Project(),from:'2026-01-01',to});return {label:to.slice(5,7),trir:s.trir,ltifr:s.ltifr}});return <div className="page page-wide rise"><PageHead title="HSE Incident Centre" sub="Incident reporting, lagging indicators and toolbox participation" icon="helmet" actions={<Btn variant="primary" icon="plus" onClick={incidentForm}>Report incident</Btn>}/><DashFilters showLocation={false}/><C2Tabs tabs={['Incidents','Statistics','Toolbox talks']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='Incidents'&&<><div className="kpi-grid" style={{gridTemplateColumns:'repeat(6,1fr)',marginBottom:16}}><Kpi label="Recordables" value={stats.recordable} icon="alert" tone="bad"/><Kpi label="LTI" value={stats.lti} icon="helmet" tone="bad"/><Kpi label="Lost days" value={stats.lostDays} icon="calendar" tone="warn"/><Kpi label="LTI-free days" value={stats.ltiFreeDays} icon="check" tone="ok"/><Kpi label="TRIR" value={stats.trir} icon="trend"/><Kpi label="LTIFR" value={stats.ltifr} icon="chart"/></div><C3Register rows={rows} columns={[{label:'Incident',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Type',render:x=><Badge kind={c3Kind(x.type)}>{x.type}</Badge>},{label:'Severity',render:x=><Badge kind={c3Kind(x.severity)}>{x.severity}</Badge>},{label:'Lost days',key:'lostDays'},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/></>}{tab==='Statistics'&&<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}><div className="card card-pad"><SectionHead title="12-month recordable trend"/><C3Trend rows={months}/></div><div className="card card-pad"><SectionHead title="Incident type mix"/><C3Donut segments={types} sub="incidents"/></div><div className="card card-pad"><SectionHead title="Incidents by project"/><C3Bars rows={byProject}/></div><div className="card card-pad"><SectionHead title="TRIR / LTIFR trend"/><C3Trend rows={rateTrend} keys={['trir','ltifr']} colors={['#1f56cf','#df463c']}/><div className="row" style={{justifyContent:'center'}}><Badge kind="info">TRIR</Badge><Badge kind="bad">LTIFR</Badge></div></div></div>}{tab==='Toolbox talks'&&<C3Register rows={DB.toolboxTalks.filter(c3Match)} columns={[{label:'Talk',render:x=><><b>{x.topic}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Site',key:'site'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Presenter',render:x=>(DB.empById(x.presenter)||{}).name},{label:'Attendees',key:'attendees'}]}/>}</div>}
function InjuryBodyMap(){const [win,setWin]=useState('12m'),agg=window.injuryAgg({project:c3Project(),window:win}),stats=window.hseStats({project:c3Project()}),coords={Head:[169,75],Eyes:[169,110],Shoulder:[130,205],Arm:[65,300],Hand:[35,440],Fingers:[25,475],Back:[169,330],Leg:[145,610],Knee:[140,650],Foot:[130,805]};const parts={};agg.rows.forEach(x=>parts[x.part]=(parts[x.part]||0)+x.n);return <div className="page page-wide rise"><PageHead title="Injury Body Map" sub="Anatomical injury concentration and severity by reporting window" icon="users"/><DashFilters showLocation={false}/><div className="row" style={{justifyContent:'space-between',marginBottom:16}}><div className="seg">{['30d','90d','6m','YTD','12m'].map(x=><button key={x} className={win===x?'active':''} onClick={()=>setWin(x)}>{x}</button>)}</div><Badge kind="neutral">{agg.total} mapped injuries</Badge></div><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="TRIR" value={stats.trir} icon="trend"/><Kpi label="LTIFR" value={stats.ltifr} icon="chart" tone="bad"/><Kpi label="Recordable" value={agg.recordable} icon="alert" tone="warn"/><Kpi label="Lost days" value={agg.lostDays} icon="calendar" tone="bad"/></div><div style={{display:'grid',gridTemplateColumns:'minmax(360px,1fr) 1fr',gap:16}}><div className="card card-pad" style={{minHeight:650}}><div style={{position:'relative',width:270,height:630,margin:'0 auto'}}><svg viewBox={window.BODY_FRONT_VB} style={{width:'100%',height:'100%',filter:'grayscale(1)',opacity:.78}} dangerouslySetInnerHTML={{__html:window.BODY_FRONT}}/>{Object.entries(parts).map(([part,n])=>{const c=coords[part]||[169,400],worst=Math.max(...agg.rows.filter(x=>x.part===part).map(x=>x.worst));return <span key={part} title={`${part}: ${n}`} style={{position:'absolute',left:`${c[0]/338*100}%`,top:`${c[1]/839*100}%`,transform:'translate(-50%,-50%)',width:22+n*4,height:22+n*4,borderRadius:'50%',background:worst>=4?'rgba(223,70,60,.72)':worst>=2?'rgba(201,130,10,.68)':'rgba(47,128,237,.65)',boxShadow:`0 0 ${12+n*3}px currentColor`,color:'#fff',display:'grid',placeItems:'center',fontWeight:800,fontSize:11}}>{n}</span>})}</div></div><div><div className="card card-pad" style={{marginBottom:16}}><SectionHead title="Severity mix"/><C3Donut segments={['High','Medium','Low'].map((label,i)=>({label,value:DB.hseIncidents.filter(x=>c3Match(x)&&x.bodyPart&&x.severity===label).length,color:['#df463c','#c9820a','#2f80ed'][i]}))} sub="injuries"/></div><div className="card card-pad"><SectionHead title="Top body parts"/><C3Bars rows={Object.entries(parts).map(([label,value])=>({label,value})).sort((a,b)=>b.value-a.value)}/><div style={{marginTop:14}}>{agg.rows.map(x=><div className="lrow" key={x.k}><Badge kind={x.worst>=4?'bad':x.worst>=2?'warn':'info'}>{x.side||'—'}</Badge><span style={{flex:1}}>{x.part}</span><b>{x.n}</b></div>)}</div></div></div></div></div>}
function Observations(){const rows=DB.hseObservations.filter(c3Match),safe=rows.filter(window.obsIsSafe).length,open=rows.filter(x=>x.status==='Open').length,cats=[...new Set(rows.map(x=>x.category))].map(label=>({label,value:rows.filter(x=>x.category===label).length}));const add=()=>openForm({title:'Raise observation',fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'classification',label:'Classification',type:'radio',options:['Safe','Unsafe act','Unsafe condition'],value:'Safe'},{key:'category',label:'Category',type:'select',options:['PPE','Housekeeping','Work at height','Electrical','Lifting','Excavation','Traffic']},{key:'description',label:'Description',type:'textarea'},{key:'immediateAction',label:'Immediate action',type:'textarea'}],done:'Observation raised',onSave:v=>dbAdd('hseObservations',{id:`OBS-26-${String(DB.hseObservations.length+1).padStart(3,'0')}`,...v,date:new Date().toISOString().slice(0,10),raisedBy:DB.employees[14].id,status:'Open',closeDate:null})});const close=x=>dbUpdate('hseObservations',x.id,{status:'Closed',closeDate:new Date().toISOString().slice(0,10)});return <div className="page page-wide rise"><PageHead title="HSE Observations" sub="Safe behaviours, unsafe acts and corrective close-out" icon="eye" actions={<Btn variant="primary" icon="plus" onClick={add}>Raise observation</Btn>}/><DashFilters showLocation={false}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Safe" value={safe} tone="ok" icon="check"/><Kpi label="Unsafe" value={rows.length-safe} tone="warn" icon="alert"/><Kpi label="Open" value={open} tone="bad" icon="clock"/><Kpi label="Closed" value={rows.length-open} tone="ok" icon="check-circle"/></div><div style={{display:'grid',gridTemplateColumns:'360px 1fr',gap:16,marginBottom:16}}><div className="card card-pad"><C3Donut segments={[{label:'Safe',value:safe,color:'#22a06b'},{label:'Unsafe',value:rows.length-safe,color:'#df463c'}]} sub="observations"/></div><div className="card card-pad"><C3Bars rows={cats}/></div></div><C3Register rows={rows} columns={[{label:'Observation',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Class',render:x=><Badge kind={window.obsIsSafe(x)?'ok':'warn'}>{x.classification}</Badge>},{label:'Category',key:'category'},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" disabled={x.status==='Closed'} onClick={()=>close(x)}>Close</Btn>} ]}/></div>}
function Permits(){const rows=DB.permits.filter(c3Match),ps=window.permitStatus(),raise=()=>openForm({title:'Raise permit to work',sub:'Hazard and critical-control validation',aside:true,fields:[{key:'type',label:'Permit type',type:'select',options:['Hot work','Confined space','Work at height','Excavation','Lifting operation','Electrical isolation (LOTO)','Road closure','Night work']},{key:'project',label:'Project',type:'select',options:projOpts()},{key:'site',label:'Site',type:'select',options:DB.sites.map(x=>x.id)},{key:'description',label:'Work description',type:'textarea'},{key:'validFrom',label:'Valid from',type:'date'},{key:'validTo',label:'Valid to',type:'date'},{key:'crewSize',label:'Crew size',type:'number'},{key:'hazards',label:'Hazards',type:'checks',options:['Fire','Fall','Restricted access','Hazardous atmosphere','Service strike','Dropped objects','Electric shock']},{key:'controls',label:'Controls',type:'checks',options:['Fire watch','Extinguisher','Gas test','Full body harness','Barricade','Rescue plan','LOTO lock','Competent supervisor']}],live:v=>{const expected={'Hot work':['Fire watch','Gas test','Extinguisher'],'Confined space':['Gas test','Rescue plan','Competent supervisor'],'Work at height':['Full body harness','Barricade','Competent supervisor'],'Excavation':['Barricade','Competent supervisor'],'Lifting operation':['Barricade','Competent supervisor'],'Electrical isolation (LOTO)':['LOTO lock','Competent supervisor']}[v.type]||[],missing=expected.filter(x=>!(v.controls||[]).includes(x)),clash=DB.permits.find(x=>x.status==='Active'&&x.project===v.project&&x.site===v.site),merged=[...new Set([...(v.controls||[]),...missing])];return {tone:clash||missing.length?'bad':'ok',headline:clash?'SIMOPS clash detected':missing.length?`${missing.length} expected controls missing`:'Critical-control check ready',clause:'Permit-to-work critical controls',lines:[{icon:missing.length?'alert':'check',tone:missing.length?'bad':'ok',text:`For ${v.type||'this permit'}, AI-COS expects: ${expected.join(', ')||'controls based on the hazard assessment'}.`},...(clash?[{icon:'alert',tone:'bad',text:`${clash.id} is already Active at the same project and site; coordinate SIMOPS before approval.`}]:[])],suggest:missing.length?[{text:'Tick missing controls',set:{key:'controls',val:merged}}]:[]};},done:'Permit raised',onSave:v=>dbAdd('permits',{id:`PTW-26-${String(DB.permits.length+1).padStart(3,'0')}`,...v,crewSize:Number(v.crewSize),requestedBy:DB.employees[18].id,issuedBy:null,permitHolder:DB.employees[26].id,status:'Pending approval'})});const act=(x,status)=>{dbUpdate('permits',x.id,{status,issuedBy:status==='Active'?DB.employees[14].id:x.issuedBy});toast(`${x.id} ${status.toLowerCase()}`,'ok')};return <div className="page page-wide rise"><PageHead title="Permit to Work" sub="High-risk work authorisation and validity control" icon="shield" actions={<Btn variant="primary" icon="plus" onClick={raise}>Raise permit</Btn>}/><DashFilters showLocation={false}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Active" value={ps.Active||0} icon="check" tone="ok"/><Kpi label="Pending" value={ps['Pending approval']||0} icon="clock" tone="warn"/><Kpi label="Expired" value={ps.Expired||0} icon="alert" tone="bad"/><Kpi label="Expiring ≤3d" value={ps.expiring} icon="calendar" tone="bad"/></div><C3Register rows={rows} columns={[{label:'Permit',render:x=><><b>{x.type}</b><div className="mono faint">{x.id}</div><div className="faint">{x.description}</div></>},{label:'Project / site',render:x=><>{x.project}<div className="faint">{x.site}</div></>},{label:'Validity',render:x=>{const days=Math.ceil((new Date(x.validTo)-DB.TODAY)/DB.day);return <b style={{color:days<=1?'var(--bad)':undefined}}>{DB.fmt(x.validTo)}<div className="faint">{DB.rel(x.validTo)}</div></b>}},{label:'Hazards / controls',render:x=><div style={{maxWidth:260}}><div className="wrap">{x.hazards.map(h=><Badge kind="warn" key={h}>{h}</Badge>)}</div><div className="wrap" style={{marginTop:4}}>{x.controls.map(c=><Badge kind="ok" key={c}>{c}</Badge>)}</div></div>},{label:'Crew',key:'crewSize'},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Actions',render:x=><div className="row"><Btn size="sm" disabled={x.status!=='Pending approval'} onClick={()=>act(x,'Active')}>Approve</Btn><Btn size="sm" disabled={x.status!=='Active'} onClick={()=>act(x,'Suspended')}>Suspend</Btn><Btn size="sm" disabled={!['Active','Suspended'].includes(x.status)} onClick={()=>act(x,'Closed')}>Close</Btn></div>} ]}/></div>}
function JSA(){const [selected,setSelected]=useState(null),rows=DB.jsas.filter(c3Match),high=rows.reduce((a,x)=>a+window.jsaRisk(x.id).highRiskSteps,0),add=()=>openForm({title:'New JSA / risk assessment',aside:true,fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'activity',label:'Activity',full:true},{key:'hazard',label:'Primary hazard',placeholder:'e.g. work at height, lifting, electrical'},{key:'likelihood',label:'Likelihood (1–5)',type:'number',value:3},{key:'severity',label:'Severity (1–5)',type:'number',value:4},{key:'controls',label:'Controls',type:'textarea'},{key:'reviewDate',label:'Review date',type:'date'},{key:'stepsText',label:'Additional steps (step | hazard | likelihood | severity | controls | residual)',type:'textarea',placeholder:'Excavate trench | Service strike | 4 | 5 | Scan services, permit | 6'}],derive:(v,k)=>['likelihood','severity'].includes(k)?{residualRisk:Number(v.likelihood||0)*Number(v.severity||0)}:{},live:v=>{const risk=Number(v.likelihood||0)*Number(v.severity||0),haz=String(v.hazard||v.activity||'').toLowerCase(),controls=/height|fall/.test(haz)?'certified access, full-body harness and rescue plan':/lift|crane/.test(haz)?'lift plan, certified rigging and exclusion zone':/electric/.test(haz)?'LOTO, prove dead and competent electrician':/excavat|trench/.test(haz)?'permit, service scan, shoring and safe access':'competent supervision, exclusion and task briefing';return {tone:risk>=15?'bad':risk>=8?'warn':'ok',headline:`Residual risk ${risk||0} · ${risk>=15?'High':risk>=8?'Medium':'Low'}`,clause:'L × S risk matrix',lines:[{icon:risk>=15?'alert':'check',tone:risk>=15?'bad':risk>=8?'warn':'ok',text:`Likelihood ${v.likelihood||0} × severity ${v.severity||0} = ${risk||0}.`},{icon:'shield',tone:'info',text:`Suggested controls: ${controls}.`}],suggest:[{text:'Use suggested controls',set:{key:'controls',val:controls}}]};},done:'JSA created',onSave:v=>{const steps=String(v.stepsText).split('\n').filter(Boolean).map((s,i)=>{const [description,hazard,l,severity,controls,residual]=s.split('|').map(x=>x.trim());return {step:i+1,description,hazard,likelihood:Number(l),severity:Number(severity),risk:Number(l)*Number(severity),controls:controls.split(',').map(x=>x.trim()),residual:Number(residual)}});dbAdd('jsas',{id:`JSA-26-${String(DB.jsas.length+1).padStart(2,'0')}`,project:v.project,activity:v.activity,reviewDate:v.reviewDate,steps,overallRisk:Math.max(0,...steps.map(x=>x.risk)),preparedBy:DB.employees[14].id,approvedBy:null,status:'Under review'})}});return <div className="page page-wide rise"><PageHead title="Job Safety Analysis" sub="Task-level likelihood × severity risk assessment" icon="check-shield" actions={<Btn variant="primary" icon="plus" onClick={add}>New JSA</Btn>}/><DashFilters showLocation={false}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}><Kpi label="Approved" value={rows.filter(x=>x.status==='Approved').length} icon="check" tone="ok"/><Kpi label="Under review" value={rows.filter(x=>x.status==='Under review').length} icon="clock" tone="warn"/><Kpi label="High-risk steps" value={high} icon="alert" tone="bad"/></div><C3Register rows={rows} columns={[{label:'Assessment',render:x=><><b>{x.activity}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Highest risk',render:x=><Badge kind={window.jsaRisk(x.id).highest>=15?'bad':window.jsaRisk(x.id).highest>=8?'warn':'ok'}>{window.jsaRisk(x.id).highest}</Badge>},{label:'Residual avg',render:x=>window.jsaRisk(x.id).residualAvg},{label:'Review',render:x=><>{DB.fmt(x.reviewDate)}<div className="faint">{DB.rel(x.reviewDate)}</div></>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Risk matrix</Btn>} ]}/>{selected&&<SimpleModal title={`Risk matrix · ${selected.id}`} sub={selected.activity} onClose={()=>setSelected(null)} width={960}><C3Register rows={selected.steps.map(x=>({...x,id:x.step}))} columns={[{label:'Step',render:x=><><b>{x.step}. {x.description}</b></>},{label:'Hazard',key:'hazard'},{label:'L × S',render:x=><Badge kind={x.risk>=15?'bad':x.risk>=8?'warn':'ok'}>{x.likelihood} × {x.severity} = {x.risk}</Badge>},{label:'Controls',render:x=><div className="wrap">{x.controls.map(c=><Badge kind="info" key={c}>{c}</Badge>)}</div>},{label:'Residual',render:x=><Badge kind={x.residual>=10?'bad':x.residual>=5?'warn':'ok'}>{x.residual}</Badge>} ]}/></SimpleModal>}</div>}
function Environment(){const [tab,setTab]=useState('Performance'),sum=window.envSummary({project:c3Project()}),metrics=DB.envMetrics.filter(c3Match);return <div className="page page-wide rise"><PageHead title="Environmental Management" sub="Resource consumption, incidents and licensed waste disposal" icon="droplet"/><DashFilters showLocation={false}/><C2Tabs tabs={['Performance','Incidents','Waste manifests']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='Performance'&&<><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Waste" value={sum.wasteTonnes} unit="t" icon="box"/><Kpi label="Water" value={sum.waterM3.toLocaleString()} unit="m³" icon="droplet"/><Kpi label="Diesel" value={sum.dieselLitres.toLocaleString()} unit="L" icon="truck" tone="warn"/><Kpi label="CO₂" value={sum.co2Tonnes} unit="t" icon="trend" tone="bad"/></div><div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}>{[['Waste tonnes','wasteTonnes','#7a5bff'],['Water m³','waterM3','#2f80ed'],['Diesel litres','dieselLitres','#c9820a'],['CO₂ tonnes','co2Tonnes','#df463c']].map(x=><div className="card card-pad" key={x[0]}><SectionHead title={x[0]}/><C3Trend rows={metrics.slice(-6).map(m=>({label:m.period.slice(5),value:m[x[1]]}))} colors={[x[2]]}/></div>)}</div></>}{tab==='Incidents'&&<C3Register rows={DB.envIncidents.filter(c3Match)} columns={[{label:'Incident',render:x=><><b>{x.type}</b><div className="mono faint">{x.id}</div><div className="faint">{x.description}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Severity',render:x=><Badge kind={c3Kind(x.severity)}>{x.severity}</Badge>},{label:'Corrective action',key:'correctiveAction'},{label:'Reported to',key:'reportedTo'},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/>} {tab==='Waste manifests'&&<C3Register rows={DB.wasteManifests.filter(c3Match)} columns={[{label:'Manifest',render:x=><><b>{x.wasteType}</b><div className="mono faint">{x.id} · {x.manifestNo}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Quantity',render:x=>`${x.qtyTonnes} t`},{label:'Carrier',key:'carrier'},{label:'Disposal chain',render:x=><>{x.carrier}<div className="faint">→ {x.disposalSite}</div></>},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/>}</div>}
function NcrWorkflow({status}){const steps=['Raised','Containment','Root cause','Corrective action','Verification','Closure'],idx=status==='Closed'?5:status==='Pending verification'?4:status==='In progress'?2:1;return <div className="card card-pad"><SectionHead title="NCR workflow" sub="Six-step controlled close-out"/>{steps.map((x,i)=><div className="row" key={x} style={{padding:'10px 0',borderBottom:i<5?'1px solid var(--border)':0}}><span style={{width:27,height:27,borderRadius:'50%',display:'grid',placeItems:'center',background:i<=idx?'var(--ok-bg)':'var(--surface-2)',color:i<=idx?'var(--ok)':'var(--text-3)',fontWeight:800}}>{i<idx?'✓':i+1}</span><b style={{flex:1,color:i<=idx?'var(--text-1)':'var(--text-3)'}}>{x}</b>{i===idx&&<Badge kind="info">Current</Badge>}</div>)}</div>}
function NcrForm({onDone}){const [val,setVal]=useState({project:projOpts()[0].value,discipline:'Civil',source:'Site inspection',severity:'Minor',date:new Date().toISOString().slice(0,10),dueDate:'2026-08-20'}),set=(k,v)=>setVal(s=>({...s,[k]:v})),fields=[['Project','project','select',projOpts()],['Discipline','discipline','select',['Civil','Structural','MEP','Finishing','Roads']],['Source','source','select',['Internal audit','Client','Consultant','Site inspection']],['Severity','severity','select',['Minor','Major']],['Raised date','date','date'],['Due date','dueDate','date']];const save=()=>{if(!val.title)return toast('Enter an NCR title','bad');const id=`NCR-26-${String(DB.ncrs.length+1).padStart(3,'0')}`;dbAdd('ncrs',{id,...val,status:'Open',raisedBy:DB.employees[36].id,closeDate:null});notify('NCR raised',`${id} · ${val.title}`,['Quality Manager','Project Manager']);toast(`${id} raised`,'ok');onDone&&onDone()};return <div style={{display:'grid',gridTemplateColumns:'minmax(0,1fr) 310px',gap:16}}><div className="card card-pad"><SectionHead title="Raise nonconformance report" sub="Full controlled quality record"/>{[['Details',['Project, discipline, source and classification']],['Nonconformity',['Describe the requirement and objective evidence']],['Immediate action',['Contain affected work or material']],['Root cause',['Record the analysis method and cause']],['Corrective action',['Assign actions, owners and due dates']],['Verification',['Define evidence and effectiveness checks']],['Closure',['Quality Manager acceptance and close date']]].map((section,si)=><div key={section[0]} style={{marginBottom:18}}><div className="eyebrow" style={{marginBottom:8}}>{si+1}. {section[0]}</div>{si===0?<div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>{fields.map(f=><div className="field" key={f[1]}><label>{f[0]}</label>{f[2]==='select'?<select value={val[f[1]]||''} onChange={e=>set(f[1],e.target.value)}>{f[3].map(o=>{const v=o&&typeof o==='object'?o.value:o,l=o&&typeof o==='object'?o.label:o;return <option key={v} value={v}>{l}</option>})}</select>:<input type="date" value={val[f[1]]||''} onChange={e=>set(f[1],e.target.value)}/>}</div>)}</div>:<div className="field"><label>{section[1][0]}</label><textarea rows={section[0]==='Nonconformity'?4:2} value={val[['Nonconformity','Immediate action','Root cause','Corrective action','Verification','Closure'][si-1]||'']||''} onChange={e=>set(['description','immediateAction','rootCause','correctiveAction','verification','closureNote'][si-1],e.target.value)}/></div>}{si===0&&<div className="field" style={{marginTop:12}}><label>NCR title</label><input value={val.title||''} onChange={e=>set('title',e.target.value)}/></div>}</div>)}<div className="row" style={{justifyContent:'flex-end'}}><Btn variant="primary" icon="check" onClick={save}>Raise NCR</Btn></div></div><NcrWorkflow status="Open"/></div>}
function NCR(){const [tab,setTab]=useState('Dashboard'),[selected,setSelected]=useState(null),rows=DB.ncrs.filter(c3Match),all=rows.length,overdue=rows.filter(x=>x.status!=='Closed'&&new Date(x.dueDate)<DB.TODAY),disc=[...new Set(rows.map(x=>x.discipline))].map(label=>({label,value:rows.filter(x=>x.discipline===label).length})),sources=[...new Set(rows.map(x=>x.source))].map((label,i)=>({label,value:rows.filter(x=>x.source===label).length,color:['#1f56cf','#7a5bff','#22a06b','#c9820a'][i%4]})),trend=Array.from({length:6},(_,i)=>{const period=`2026-${String(i+2).padStart(2,'0')}`;return {label:period.slice(5),value:rows.filter(x=>x.date.startsWith(period)).length}});return <div className="page page-wide rise"><PageHead title="Nonconformance & CAPA" sub="Quality deviation, root cause and verified corrective action" icon="flag" actions={<Btn icon="download" onClick={()=>printRegister('NCR register')}>Export</Btn>}/><DashFilters showLocation={false}/><C2Tabs tabs={['Dashboard','NCR register','Raise NCR','CAPA']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='Dashboard'&&<><div className="kpi-grid" style={{gridTemplateColumns:'repeat(6,1fr)',marginBottom:16}}><Kpi label="Total" value={all} icon="flag"/><Kpi label="Open" value={rows.filter(x=>x.status==='Open').length} tone="warn" icon="clock"/><Kpi label="In progress" value={rows.filter(x=>x.status==='In progress').length} icon="refresh"/><Kpi label="Pending verification" value={rows.filter(x=>x.status==='Pending verification').length} icon="eye"/><Kpi label="Closed" value={rows.filter(x=>x.status==='Closed').length} tone="ok" icon="check"/><Kpi label="Overdue" value={overdue.length} tone="bad" icon="alert"/></div><div className="kpi-grid" style={{gridTemplateColumns:'1fr 1fr',marginBottom:16}}><Kpi label="Major" value={rows.filter(x=>x.severity==='Major').length} tone="bad"/><Kpi label="Minor" value={rows.filter(x=>x.severity==='Minor').length} tone="warn"/></div><div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}><div className="card card-pad"><SectionHead title="By discipline"/><C3Bars rows={disc}/></div><div className="card card-pad"><SectionHead title="By source"/><C3Donut segments={sources} sub="NCRs"/></div><div className="card card-pad" style={{gridColumn:'1/-1'}}><SectionHead title="Six-month trend"/><C3Trend rows={trend}/></div></div></>}{tab==='NCR register'&&<C3Register rows={rows} columns={[{label:'NCR',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Discipline',key:'discipline'},{label:'Source',key:'source'},{label:'Severity',render:x=><Badge kind={c3Kind(x.severity)}>{x.severity}</Badge>},{label:'Raised',render:x=>DB.fmt(x.date)},{label:'Due',render:x=>DB.fmt(x.dueDate)},{label:'Days overdue',render:x=>{const n=Math.max(0,Math.floor((DB.TODAY-new Date(x.dueDate))/DB.day));return <b style={{color:n?'var(--bad)':undefined}}>{x.status==='Closed'?'—':n||'—'}</b>}},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Detail',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>View</Btn>} ]}/>} {tab==='Raise NCR'&&<NcrForm onDone={()=>setTab('NCR register')}/>} {tab==='CAPA'&&<C3Register rows={DB.capas.filter(c3Match)} columns={[{label:'CAPA',render:x=><><b>{x.title}</b><div className="mono faint">{x.id} · {x.source} {x.sourceRef}</div></>},{label:'Project',key:'project'},{label:'Root cause',render:x=><>{x.rootCause}<div className="faint">{x.rootCauseMethod}</div></>},{label:'Actions',render:x=><div>{x.actions.map((a,i)=><div key={i} className="faint">{a.desc} · {(DB.empById(a.owner)||{}).name} · {DB.fmt(a.due)} · <b>{a.status}</b></div>)}</div>},{label:'Verification',key:'verification'},{label:'Effectiveness',render:x=><Badge kind={c3Kind(x.effectiveness)}>{x.effectiveness}</Badge>},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/>} {selected&&<SimpleModal title={selected.id} sub={selected.title} onClose={()=>setSelected(null)}><div style={{display:'grid',gridTemplateColumns:'1fr 300px',gap:16}}><div><div className="exec-four-cells">{[['Project',selected.project],['Discipline',selected.discipline],['Severity',selected.severity],['Due',DB.fmt(selected.dueDate)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:14}}>{x[1]}</b></div>)}</div><div className="card card-pad" style={{marginTop:14}}><b>Nonconformity</b><p className="muted">{selected.description}</p></div></div><NcrWorkflow status={selected.status}/></div></SimpleModal>}</div>}
function ITP(){const [tab,setTab]=useState('ITPs'),[selected,setSelected]=useState(null),rows=DB.itps.filter(c3Match),irs=DB.inspectionRequests.filter(c3Match),k=window.qualityKpis({project:c3Project()}),raise=()=>openForm({title:'Raise inspection request',fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'itp',label:'ITP',type:'select',options:DB.itps.map(x=>x.id)},{key:'discipline',label:'Discipline',type:'select',options:['Civil','Structural','MEP','Finishing','Roads']},{key:'description',label:'Inspection description',type:'textarea'},{key:'requestedDate',label:'Requested date',type:'date'},{key:'inspectionDate',label:'Planned inspection',type:'date'},{key:'inspector',label:'Consultant inspector'}],done:'Inspection request raised',onSave:v=>dbAdd('inspectionRequests',{id:`IR-26-${String(DB.inspectionRequests.length+1).padStart(3,'0')}`,...v,result:'Pending',comments:''})});return <div className="page page-wide rise"><PageHead title="Inspection & Test Plans" sub="Hold, witness, surveillance and consultant inspection control" icon="check-shield" actions={tab==='Inspection requests'&&<Btn variant="primary" icon="plus" onClick={raise}>Raise IR</Btn>}/><DashFilters showLocation={false}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}><Kpi label="ITP pass rate" value={k.itpPassRate} unit="%" tone="ok" icon="check"/><Kpi label="IR approval rate" value={k.irApprovalRate} unit="%" icon="chart"/><Kpi label="Pending IRs" value={irs.filter(x=>x.result==='Pending').length} tone="warn" icon="clock"/></div><C2Tabs tabs={['ITPs','Inspection requests']} active={tab} onChange={setTab}/><div style={{height:14}}/>{tab==='ITPs'?<C3Register rows={rows} columns={[{label:'ITP',render:x=><><b>{x.activity}</b><div className="mono faint">{x.id} · {x.revision}</div></>},{label:'Project',key:'project'},{label:'Discipline',key:'discipline'},{label:'Stages',render:x=><div style={{minWidth:330}}>{x.stages.map((s,i)=><div className="row" key={i} style={{margin:'3px 0'}}><Badge kind={s.type==='H'?'bad':s.type==='W'?'warn':'info'}>{s.type}</Badge><span style={{flex:1}}>{s.stage}</span><small>{s.party}</small><StatusBadge status={s.status}/></div>)}</div>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>setSelected(x)}>Stage table</Btn>} ]}/>:<C3Register rows={irs} columns={[{label:'Request',render:x=><><b>{x.description}</b><div className="mono faint">{x.id} · {x.itp}</div></>},{label:'Project',key:'project'},{label:'Discipline',key:'discipline'},{label:'Requested',render:x=>DB.fmt(x.requestedDate)},{label:'Inspection',render:x=>DB.fmt(x.inspectionDate)},{label:'Inspector',key:'inspector'},{label:'Result',render:x=><Badge kind={c3Kind(x.result)}>{x.result}</Badge>},{label:'Comments',key:'comments'}]}/>} {selected&&<SimpleModal title={selected.id} sub={selected.activity} onClose={()=>setSelected(null)}><C3Register rows={selected.stages.map((x,i)=>({...x,id:i}))} columns={[{label:'Stage',key:'stage'},{label:'Type',render:x=><Badge kind={x.type==='H'?'bad':x.type==='W'?'warn':'info'}>{x.type} · {{H:'Hold',W:'Witness',S:'Surveillance',R:'Review'}[x.type]}</Badge>},{label:'Responsible party',key:'party'},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/></SimpleModal>}</div>}
function QualityAudits(){const rows=DB.audits.filter(c3Match),planned=rows.length,completed=rows.filter(x=>x.status==='Closed').length,openFindings=rows.filter(x=>x.status!=='Closed').reduce((a,x)=>a+x.findings,0),overdue=rows.filter(x=>x.status==='Actions open'&&new Date(x.date)<DB.TODAY).length,surveys=DB.customerSurveys.filter(c3Match),avg=surveys.reduce((a,x)=>a+x.score,0)/Math.max(1,surveys.length);return <div className="page page-wide rise"><PageHead title="Quality Audits & Customer Voice" sub="Audit programme, findings, complaints and client satisfaction" icon="certificate"/><DashFilters showLocation={false}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(3,1fr)',marginBottom:16}}><Kpi label="Audits completed / planned" value={`${completed} / ${planned}`} tone="ok" icon="check"/><Kpi label="Open findings" value={openFindings} tone="warn" icon="flag"/><Kpi label="Overdue" value={overdue} tone="bad" icon="alert"/></div><div style={{display:'grid',gridTemplateColumns:'1fr 320px',gap:16,marginBottom:16}}><C3Register rows={rows} columns={[{label:'Audit',render:x=><><b>{x.area}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Type',key:'type'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Findings',key:'findings'},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/><div className="card card-pad"><SectionHead title="Customer satisfaction"/><div style={{display:'grid',placeItems:'center'}}><Ring value={avg/5*100} color={avg>=4?'var(--ok)':'var(--warn)'}><div><b style={{fontSize:29}}>{avg.toFixed(1)}</b><div className="faint">out of 5</div></div></Ring></div><div style={{marginTop:12}}>{surveys.map(x=><div className="lrow" key={x.id}><span style={{flex:1}}>{x.project}</span><b>★ {x.score}</b></div>)}</div></div></div><div className="card card-pad"><SectionHead title="Customer complaints"/><C3Register rows={DB.complaints.filter(c3Match)} columns={[{label:'Complaint',render:x=><><b>{x.subject}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Client',render:x=>(DB.custById(x.client)||{}).name},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Status',render:x=><StatusBadge status={x.status}/>} ]}/></div></div>}
function Documents(){const rows=DB.qmsDocuments,k=window.qualityKpis({}),user=DB.staff.find(x=>x.role===window.ROLE)?.id||DB.employees[39].id,totalAck=rows.reduce((a,x)=>a+x.acknowledgedBy.length,0),rate=Number((totalAck/(rows.length*Math.max(1,DB.employees.length))*100).toFixed(1)),ack=x=>{if(!x.acknowledgedBy.includes(user)){x.acknowledgedBy.push(user);window.__bump();toast(`${x.id} acknowledged`,'ok')}};return <div className="page page-wide rise"><PageHead title="Controlled QMS Documents" sub="Revision, review and workforce acknowledgement control" icon="documents" actions={<Btn icon="download" onClick={()=>printRegister('QMS documents')}>Export</Btn>}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Published" value={rows.filter(x=>x.status==='Published').length} tone="ok" icon="documents"/><Kpi label="Under revision" value={rows.filter(x=>x.status==='Under revision').length} tone="warn" icon="refresh"/><Kpi label="Review overdue" value={k.docsOverdueReview} tone="bad" icon="alert"/><Kpi label="Acknowledgement rate" value={rate} unit="%" icon="users"/></div><C3Register rows={rows} columns={[{label:'Document',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Type',key:'type'},{label:'Revision',render:x=><Badge kind="info">{x.revision}</Badge>},{label:'Issue',render:x=>DB.fmt(x.issueDate)},{label:'Review due',render:x=><b style={{color:x.status!=='Obsolete'&&new Date(x.reviewDue)<DB.TODAY?'var(--bad)':undefined}}>{DB.fmt(x.reviewDue)}<div className="faint">{DB.rel(x.reviewDue)}</div></b>},{label:'Owner',render:x=>(DB.empById(x.owner)||{}).name},{label:'Acknowledged',render:x=><Badge kind="neutral">{x.acknowledgedBy.length}</Badge>},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" disabled={x.acknowledgedBy.includes(user)||x.status!=='Published'} onClick={()=>ack(x)}>Acknowledge</Btn>} ]}/></div>}
function Calibration(){const rows=DB.calibratedInstruments.filter(c3Match),record=x=>openForm({title:`Record calibration · ${x.id}`,sub:'External calibration certificate and next due date',fields:[{key:'lastCalibration',label:'Calibration date',type:'date'},{key:'dueDate',label:'Next due date',type:'date'},{key:'certificateNo',label:'Certificate number',value:x.certificateNo},{key:'calibratedBy',label:'External laboratory',value:x.calibratedBy}],done:'Calibration recorded',onSave:v=>dbUpdate('calibratedInstruments',x.id,{...v,status:new Date(v.dueDate)<=DB.TODAY?'Overdue':(new Date(v.dueDate)-DB.TODAY)/DB.day<=30?'Due soon':'Valid'})});return <div className="page page-wide rise"><PageHead title="QA/QC Instrument Calibration" sub="Acceptance-testing equipment validity and certificate control" icon="gauge"/><AINote title="Acceptance testing control" clause="ISO 9001 monitoring resources">Instruments overdue for calibration must not be used for acceptance testing.</AINote><div style={{height:14}}/><div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Valid" value={rows.filter(x=>x.status==='Valid').length} tone="ok" icon="check"/><Kpi label="Due ≤30d" value={rows.filter(x=>x.status==='Due soon'||((new Date(x.dueDate)-DB.TODAY)/DB.day>=0&&(new Date(x.dueDate)-DB.TODAY)/DB.day<=30)).length} tone="warn" icon="clock"/><Kpi label="Overdue" value={rows.filter(x=>x.status==='Overdue'||new Date(x.dueDate)<DB.TODAY).length} tone="bad" icon="alert"/><Kpi label="Out of service" value={rows.filter(x=>x.status==='Out of service').length} tone="bad" icon="x-circle"/></div><C3Register rows={rows} columns={[{label:'Instrument',render:x=><><b>{x.instrument}</b><div className="mono faint">{x.id} · {x.serial}</div></>},{label:'Project',key:'project'},{label:'Last calibration',render:x=>DB.fmt(x.lastCalibration)},{label:'Due date',render:x=>{const n=Math.ceil((new Date(x.dueDate)-DB.TODAY)/DB.day);return <b style={{color:n<=0?'var(--bad)':n<=30?'var(--warn)':undefined}}>{DB.fmt(x.dueDate)}<div className="faint">{DB.rel(x.dueDate)}</div></b>}},{label:'Certificate',render:x=><span className="mono">{x.certificateNo}</span>},{label:'External lab',key:'calibratedBy'},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" onClick={()=>record(x)}>Record calibration</Btn>} ]}/></div>}
Object.assign(window,{HSEIncidents,InjuryBodyMap,Observations,Permits,JSA,Environment,NCR,ITP,QualityAudits,Documents,Calibration});

/* C4 — Finance, HR, Maintenance and Administration modules. */
const c4Money=x=>window.money(Number(x||0)),c4Date=x=>DB.fmt(x),c4Days=x=>Math.ceil((new Date(x)-DB.TODAY)/DB.day),c4Status=x=><StatusBadge status={x}/>;
function C4Kpis({items}){return <div className="kpi-grid" style={{gridTemplateColumns:`repeat(${Math.min(6,items.length)},1fr)`,marginBottom:16}}>{items.map(x=><Kpi key={x[0]} label={x[0]} value={x[1]} unit={x[2]} icon={x[3]||'chart'} tone={x[4]||'info'}/>)}</div>}
function C4Aging({title,total,color='var(--primary-600)'}){const parts=[['Current',.44],['30 days',.27],['60 days',.17],['90+ days',.12]];return <div className="card card-pad"><SectionHead title={title}/>{parts.map(([label,p])=><div key={label} style={{margin:'11px 0'}}><div className="row"><span style={{flex:1}}>{label}</span><b>{c4Money(total*p)}</b></div><Bar pct={p*100} color={label==='90+ days'?'var(--bad)':color}/></div>)}</div>}
function FinanceDashboard(){const f=window.financeSummary({}),cash=window.cashPosition(),ipc=window.ipcSummary({}),margins=DB.projects.map(p=>{const x=window.financeSummary({project:p.id});return {label:p.id,value:x.marginPct}}),exp=DB.bankGuarantees.filter(x=>x.status==='Active'&&c4Days(x.expiryDate)<=60);return <div className="page page-wide rise"><PageHead title="Finance Dashboard" sub="Percentage-of-completion revenue, project margin and working capital" icon="chart"/><C4Kpis items={[["Revenue earned",c4Money(f.revenueEarned),null,'trend','ok'],['Billed',c4Money(f.billed),null,'receipt'],['Cost to date',c4Money(f.cost),null,'chart'],['Gross margin',f.marginPct,'%','trend',f.marginPct<10?'bad':'ok'],['WIP',c4Money(ipc.wip),null,'briefcase','warn'],['Retention held',c4Money(ipc.retentionHeld),null,'lock','warn']]}/><div style={{display:'grid',gridTemplateColumns:'1.2fr 1fr',gap:16,marginBottom:16}}><div className="card card-pad"><SectionHead title="Margin by project" sub="Earned revenue less actual project cost"/><C3Bars rows={margins} suffix="%" color={x=>x.value<10?'var(--bad)':x.value<15?'var(--warn)':'var(--ok)'}/></div><div className="card card-pad"><SectionHead title="Cash position"/><div className="exec-four-cells">{[['Receivables',cash.receivables],['Payables',cash.payables],['Net position',cash.netPosition],['Active guarantees',cash.guarantees]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:14,color:x[0]==='Net position'&&x[1]<0?'var(--bad)':undefined}}>{c4Money(x[1])}</b></div>)}</div><div className="h-sec" style={{margin:'18px 0 7px'}}>Guarantees expiring ≤60 days</div>{exp.map(x=><div className="lrow" key={x.id}><b>{x.id}</b><span style={{flex:1}}>{x.type}</span><Badge kind="warn">{DB.rel(x.expiryDate)}</Badge></div>)}</div></div><div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}><C4Aging title="Receivables aging" total={cash.receivables}/><C4Aging title="Payables aging" total={cash.payables} color="var(--warn)"/></div></div>}
function Billing(){const [selected,setSelected]=useState(null),sum=window.ipcSummary({});const raise=()=>openForm({title:'Raise IPC',sub:'Retention, advance recovery and VAT are computed automatically',fields:[{key:'project',label:'Project',type:'select',options:projOpts()},{key:'period',label:'Period',type:'month'},{key:'workDoneValue',label:'Work done value (OMR)',type:'number'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'advancePct',label:'Advance recovery %',type:'number',value:8}],done:'IPC draft raised',onSave:v=>{const workDoneValue=Number(v.workDoneValue),retention=workDoneValue*Number(v.retentionPct)/100,advanceRecovery=workDoneValue*Number(v.advancePct)/100,netCertified=workDoneValue-retention-advanceRecovery,vat=netCertified*.05,p=DB.projectById(v.project);dbAdd('invoices',{id:`IPC-26-${String(DB.invoices.length+1).padStart(3,'0')}`,project:v.project,client:p.client,period:v.period,workDoneValue,retention,advanceRecovery,netCertified,vat,total:netCertified+vat,submittedDate:null,certifiedDate:null,status:'Draft'})}});return <div className="page page-wide rise"><PageHead title="Progress Billing / IPC" sub="Measured work, retention, advance recovery and 5% VAT" icon="receipt" actions={<Btn variant="primary" icon="plus" onClick={raise}>Raise IPC</Btn>}/><C4Kpis items={[["Submitted",c4Money(sum.submitted)],['Certified',c4Money(sum.certified),null,'check','ok'],['Paid',c4Money(sum.paid),null,'receipt','ok'],['Retention',c4Money(sum.retentionHeld),null,'lock','warn'],['Advance outstanding',c4Money(sum.advanceOutstanding),null,'trend','warn']]}/><C2Table rows={DB.invoices} onRow={setSelected} columns={[{label:'IPC',render:x=><><b>{x.id}</b><div className="faint">{(DB.projectById(x.project)||{}).name}</div></>},{label:'Period',key:'period'},{label:'Work done',render:x=>c4Money(x.workDoneValue)},{label:'Retention',render:x=>c4Money(x.retention)},{label:'Advance recovery',render:x=>c4Money(x.advanceRecovery)},{label:'Net certified',render:x=>c4Money(x.netCertified)},{label:'VAT 5%',render:x=>c4Money(x.vat)},{label:'Total',render:x=><b>{c4Money(x.total)}</b>},{label:'Status',render:x=>c4Status(x.status)},{label:'Print',render:x=><Btn size="sm" icon="download" onClick={()=>{setSelected(x);setTimeout(()=>printRecord(`IPC ${x.id}`),50)}}>Print IPC</Btn>}]}/>{selected&&<SimpleModal title={`${DB.company.name} · ${DB.company.nameAr}`} sub={`Interim Payment Certificate · شهادة دفع مرحلية · ${selected.id}`} onClose={()=>setSelected(null)}><div className="exec-four-cells">{[['Project',(DB.projectById(selected.project)||{}).name],['Period',selected.period],['Work done',c4Money(selected.workDoneValue)],['Retention',c4Money(selected.retention)],['Advance recovery',c4Money(selected.advanceRecovery)],['Net certified',c4Money(selected.netCertified)],['VAT (5%)',c4Money(selected.vat)],['Total due',c4Money(selected.total)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:13}}>{x[1]}</b></div>)}</div></SimpleModal>}</div>}
function CostControl(){const rows=window.costControl('All projects'),[drill,setDrill]=useState(null);return <div className="page page-wide rise"><PageHead title="Project Cost Control" sub="Budget, commitment, forecast and earned-value performance" icon="chart"/><C2Table rows={rows} onRow={x=>setDrill(x.project)} columns={[{label:'Project',render:x=><><b>{(DB.projectById(x.project)||{}).name}</b><div className="mono faint">{x.project}</div></>},{label:'Budget',render:x=>c4Money(x.budget)},{label:'Committed',render:x=>c4Money(x.committed)},{label:'Actual',render:x=>c4Money(x.actual)},{label:'Forecast',render:x=>c4Money(x.forecast)},{label:'Variance',render:x=><b style={{color:x.variance<0?'var(--bad)':'var(--ok)'}}>{c4Money(x.variance)}</b>},{label:'Earned value',render:x=>c4Money(x.earnedValue)},{label:'CPI',render:x=><Badge kind={x.cpi<1?'bad':'ok'}>{x.cpi}</Badge>},{label:'SPI',render:x=><Badge kind={x.spi<1?'bad':'ok'}>{x.spi}</Badge>}]}/><div className="card card-pad" style={{marginTop:16}}><SectionHead title="Forecast variance"/><C3Bars rows={rows.map(x=>({label:x.project,value:Math.abs(Math.round(x.variance/1000))}))} suffix="k OMR" color={x=>rows.find(r=>r.project===x.label).variance<0?'var(--bad)':'var(--ok)'}/></div>{drill&&<SimpleModal title={`BOQ drill-down · ${drill}`} sub={(DB.projectById(drill)||{}).name} onClose={()=>setDrill(null)} width={980}><C2Table rows={DB.boq.filter(x=>x.project===drill)} columns={[{label:'Code',key:'code'},{label:'Item',key:'description'},{label:'Amount',render:x=>c4Money(x.amount)},{label:'Executed',render:x=>`${x.executedPct}%`},{label:'Earned',render:x=>c4Money(x.executedQty*x.rate)}]}/></SimpleModal>}</div>}
function Payables(){const total=window.cashPosition().payables,rows=DB.purchaseOrders.map(x=>({...x,name:(DB.suppliers.find(s=>s.id===x.supplier)||{}).name}));return <div className="page page-wide rise"><PageHead title="Payables" sub="Supplier and subcontractor obligations with aging" icon="receipt"/><C4Kpis items={[["Outstanding",c4Money(total)],['Open supplier POs',DB.purchaseOrders.filter(x=>x.status==='Open').length],['Subcontractors',DB.subcontractors.length],['90+ exposure',c4Money(total*.12),null,'alert','bad']]}/><div style={{display:'grid',gridTemplateColumns:'380px 1fr',gap:16}}><C4Aging title="Payables aging" total={total} color="var(--warn)"/><C2Table rows={rows} columns={[{label:'Supplier',key:'name'},{label:'Reference',key:'id'},{label:'Project',key:'project'},{label:'Description',key:'description'},{label:'Amount',render:x=>c4Money(x.value)},{label:'Due',render:x=>c4Date(x.requiredDate)},{label:'Status',render:x=>c4Status(x.status)}]}/></div></div>}
function Receivables(){const total=window.cashPosition().receivables;return <div className="page page-wide rise"><PageHead title="Receivables" sub="Client aging and IPC-to-cash traceability" icon="trend"/><C4Kpis items={[["Outstanding",c4Money(total)],['Certified IPCs',DB.invoices.filter(x=>['Certified','Paid'].includes(x.status)).length],['Cash receipts',c4Money(DB.payments.reduce((a,x)=>a+x.amount,0))],['90+ exposure',c4Money(total*.12),null,'alert','bad']]}/><div style={{display:'grid',gridTemplateColumns:'380px 1fr',gap:16}}><C4Aging title="Receivables aging" total={total}/><C2Table rows={DB.invoices} columns={[{label:'IPC → cash',render:x=><><b>{x.id}</b><div className="faint">{(DB.custById(x.client)||{}).name}</div></>},{label:'Project',key:'project'},{label:'Certified',render:x=>c4Money(x.total)},{label:'Payment',render:x=>{const p=DB.payments.find(p=>p.invoice===x.id);return p?<><b>{p.id}</b><div className="faint">{c4Date(p.date)}</div></>:'Awaiting cash'}},{label:'Paid amount',render:x=>c4Money((DB.payments.find(p=>p.invoice===x.id)||{}).amount)},{label:'Status',render:x=>c4Status(x.status)}]}/></div></div>}
function Employees(){const rows=DB.employees,exp=rows.filter(e=>[e.passportExpiry,e.visaExpiry,e.labourCardExpiry].filter(Boolean).some(d=>c4Days(d)<=60));return <div className="page page-wide rise"><PageHead title="Employees" sub="Oman workforce master, remuneration and document compliance" icon="users"/><C4Kpis items={[["Headcount",rows.filter(x=>x.status!=='Demobilized').length],['Omani %',window.omanisationStats().achievedPct,'%','building'],['Documents due ≤60d',exp.length,null,'alert','bad'],['On leave',rows.filter(x=>x.status==='On leave').length,null,'calendar','warn']]}/><C2Table rows={rows} columns={[{label:'Employee',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Designation',key:'designation'},{label:'Nationality',render:x=><>{x.nationality} {x.omani&&<Badge kind="ok">OMANI</Badge>}</>},{label:'Project',key:'project'},{label:'Joined',render:x=>c4Date(x.joinDate)},{label:'Basic salary',render:x=>c4Money(x.basicSalary)},{label:'Documents',render:x=><div>{[['Passport',x.passportExpiry],['Visa',x.visaExpiry],['Labour card',x.labourCardExpiry]].filter(y=>y[1]).map(y=><div key={y[0]} style={{fontSize:11,color:c4Days(y[1])<=60?'var(--bad)':'var(--text-3)',fontWeight:c4Days(y[1])<=60?800:400}}>{y[0]} {c4Date(y[1])} · {DB.rel(y[1])}</div>)}</div>},{label:'Status',render:x=>c4Status(x.status)}]}/></div>}
function Attendance(){const [tab,setTab]=useState('Daily attendance'),[project,setProject]=useState('All projects'),rows=DB.attendance.filter(x=>project==='All projects'||x.project===project),today=rows.filter(x=>x.date===isoDate(DB.TODAY));function isoDate(d){return new Date(d).toISOString().slice(0,10)}return <div className="page page-wide rise"><PageHead title="Attendance & Timesheets" sub="Daily presence, shifts and weekly project approvals" icon="clock"/><div className="row" style={{justifyContent:'space-between',marginBottom:14}}><C2Tabs tabs={['Daily attendance','Timesheets']} active={tab} onChange={setTab}/><select className="input" value={project} onChange={e=>setProject(e.target.value)} style={{width:190}}><option>All projects</option>{projOpts().map(x=><option key={x.value} value={x.value}>{x.label}</option>)}</select></div>{tab==='Daily attendance'?<><C4Kpis items={[["Present",today.filter(x=>x.status==='Present').length,null,'check','ok'],['Absent',today.filter(x=>x.status==='Absent').length,null,'x-circle','bad'],['Leave',today.filter(x=>x.status==='Leave').length,null,'calendar','warn'],['Overtime hours',today.reduce((a,x)=>a+x.overtimeHours,0),null,'clock']]}/><C2Table rows={rows.slice(0,100)} columns={[{label:'Employee',render:x=>(DB.empById(x.employee)||{}).name},{label:'Project',key:'project'},{label:'Date',render:x=>c4Date(x.date)},{label:'Shift',key:'shift'},{label:'Status',render:x=>c4Status(x.status)},{label:'Hours',key:'hoursWorked'},{label:'Overtime',key:'overtimeHours'}]}/></>:<C2Table rows={DB.timesheets.filter(x=>project==='All projects'||x.project===project)} columns={[{label:'Timesheet',key:'id'},{label:'Project',key:'project'},{label:'Week ending',render:x=>c4Date(x.weekEnding)},{label:'Employees',key:'employees'},{label:'Normal hours',key:'normalHours'},{label:'Overtime',key:'overtimeHours'},{label:'Approved by',render:x=>(DB.empById(x.approvedBy)||{}).name},{label:'Status',render:x=>c4Status(x.status)},{label:'Action',render:x=><Btn size="sm" disabled={x.status==='Approved'} onClick={()=>dbUpdate('timesheets',x.id,{status:'Approved'})}>Approve</Btn>}]}/>}</div>}
function Leave(){const decide=(x,status)=>dbUpdate('leaveRequests',x.id,{status});return <div className="page page-wide rise"><PageHead title="Leave" sub="Employee requests, balances and approvals" icon="calendar"/><AINote title="Oman Labour Law annual leave">Employees are entitled to 30 calendar days of annual leave after completing one year of service.</AINote><div style={{height:14}}/><C4Kpis items={[["Pending",DB.leaveRequests.filter(x=>x.status==='Pending').length,null,'clock','warn'],['Approved',DB.leaveRequests.filter(x=>x.status==='Approved').length,null,'check','ok'],['Rejected',DB.leaveRequests.filter(x=>x.status==='Rejected').length,null,'x-circle','bad'],['Annual entitlement',30,'days','calendar']]}/><C2Table rows={DB.leaveRequests} columns={[{label:'Request',render:x=><><b>{x.id}</b><div className="faint">{(DB.empById(x.employee)||{}).name}</div></>},{label:'Type',key:'type'},{label:'From / to',render:x=><>{c4Date(x.from)} → {c4Date(x.to)}</>},{label:'Days',key:'days'},{label:'Balance before',render:x=>`${x.balanceBefore} days`},{label:'Reason',key:'reason'},{label:'Approver',render:x=>(DB.empById(x.approver)||{}).name},{label:'Status',render:x=>c4Status(x.status)},{label:'Actions',render:x=><div className="row"><Btn size="sm" disabled={x.status!=='Pending'} onClick={()=>decide(x,'Approved')}>Approve</Btn><Btn size="sm" disabled={x.status!=='Pending'} onClick={()=>decide(x,'Rejected')}>Reject</Btn></div>}]}/></div>}
function Payroll(){const [selected,setSelected]=useState(null),[wps,setWps]=useState('');return <div className="page page-wide rise"><PageHead title="Payroll & WPS" sub="Monthly payroll, Omani-only GOSI and EOSB accrual" icon="receipt"/><AINote title="GOSI rule">Omani employees: 11.5% employer and 7% employee contribution on basic salary. Expatriates are exempt.</AINote><div style={{height:14}}/><C4Kpis items={[["Latest net",c4Money(window.payrollSummary().net)],['Omani GOSI',c4Money(window.payrollSummary().gosi.total)],['EOSB liability',c4Money(window.eosbLiability()),null,'briefcase','warn'],['Runs',DB.payrollRuns.length]]}/><C2Table rows={DB.payrollRuns} onRow={setSelected} columns={[{label:'Run',key:'id'},{label:'Period',key:'period'},{label:'Employees',key:'employees'},{label:'Gross',render:x=>c4Money(x.gross)},{label:'GOSI',render:x=>c4Money(x.gosi.total)},{label:'Net',render:x=><b>{c4Money(x.net)}</b>},{label:'Status',render:x=>c4Status(x.status)},{label:'Actions',render:x=><div className="row"><Btn size="sm" onClick={()=>setSelected(x)}>Detail</Btn><Btn size="sm" icon="download" onClick={()=>setWps(window.wpsFile(x.id))}>Generate WPS file</Btn></div>}]}/>{selected&&<SimpleModal title={`Payroll detail · ${selected.period}`} sub={selected.id} onClose={()=>setSelected(null)}><div className="exec-four-cells">{[['Basic',selected.basic],['Allowances',selected.allowances],['Overtime',selected.overtime],['Deductions',selected.deductions],['Employer GOSI 11.5%',selected.gosi.employer],['Employee GOSI 7%',selected.gosi.employee],['Gross',selected.gross],['Net',selected.net]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b style={{fontSize:14}}>{c4Money(x[1])}</b></div>)}</div></SimpleModal>}{wps&&<SimpleModal title="WPS SIF preview" sub="Bank payment file · text preview" onClose={()=>setWps('')}><pre className="mono" style={{whiteSpace:'pre-wrap',fontSize:11,background:'var(--ink)',color:'#dce9f8',padding:16,borderRadius:10}}>{wps}</pre></SimpleModal>}</div>}
function Omanisation(){const x=window.omanisationStats(),segments=[{label:'Omani',value:x.omani,color:'#22a06b'},{label:'Expatriate',value:x.expat,color:'#2f6fed'}];return <div className="page page-wide rise"><PageHead title="Omanisation" sub="Ministry of Labour workforce localisation reporting" icon="building"/><div style={{display:'grid',gridTemplateColumns:'340px 1fr',gap:16,marginBottom:16}}><div className="card card-pad"><SectionHead title="Achieved vs target"/><div style={{display:'grid',placeItems:'center'}}><Ring value={Math.min(100,x.achievedPct/x.target*100)} color={x.status==='Compliant'?'var(--ok)':'var(--bad)'}><div><b style={{fontSize:27}}>{x.achievedPct}%</b><div className="faint">target {x.target}%</div></div></Ring></div><div className="row" style={{justifyContent:'center',marginTop:12}}><Badge kind={x.status==='Compliant'?'ok':'bad'}>{x.status}</Badge></div></div><div className="card card-pad"><SectionHead title="By occupational category"/><C3Bars rows={x.byCategory.map(y=>({label:`${y.category} · target ${y.target}%`,value:y.actual}))} suffix="%" color={y=>y.value>=Number(y.label.match(/target (\d+)/)?.[1]||0)?'var(--ok)':'var(--bad)'}/></div></div><div className="card card-pad"><SectionHead title="Nationality mix"/><C3Donut segments={segments} center={x.total} sub="active employees"/></div><AINote title="Ministry reporting note">The category and overall ratios are calculated from active employee records for Ministry of Labour reporting; validate classifications before submission.</AINote></div>}
function Training(){const rows=DB.trainingRecords,valid=rows.filter(x=>x.status==='Valid').length,exp=rows.filter(x=>x.status==='Expiring').length,expired=rows.filter(x=>x.status==='Expired').length,trades=[...new Set(DB.employees.map(x=>x.designation))].slice(0,8);return <div className="page page-wide rise"><PageHead title="Training & Competency" sub="Certificates, expiry and trade competency coverage" icon="certificate"/><C4Kpis items={[["Valid",valid,null,'check','ok'],['Expiring ≤60d',exp,null,'clock','warn'],['Expired',expired,null,'alert','bad'],['Courses',new Set(rows.map(x=>x.course)).size]]}/><div style={{display:'grid',gridTemplateColumns:'1.5fr 1fr',gap:16}}><C2Table rows={rows} columns={[{label:'Record',render:x=><><b>{x.course}</b><div className="mono faint">{x.id} · {x.certificateNo}</div></>},{label:'Employee',render:x=>(DB.empById(x.employee)||{}).name},{label:'Provider',key:'provider'},{label:'Completed',render:x=>c4Date(x.completed)},{label:'Expiry',render:x=><b style={{color:c4Days(x.expiry)<=60?'var(--bad)':undefined}}>{c4Date(x.expiry)}<div className="faint">{DB.rel(x.expiry)}</div></b>},{label:'Status',render:x=>c4Status(x.status)}]}/><div className="card card-pad"><SectionHead title="Competency matrix by trade"/>{trades.map(t=>{const staff=DB.employees.filter(e=>e.designation===t),trained=staff.filter(e=>rows.some(r=>r.employee===e.id&&r.status!=='Expired')).length;return <div key={t} style={{margin:'11px 0'}}><div className="row"><span style={{flex:1}}>{t}</span><b>{trained}/{staff.length}</b></div><Bar pct={trained/Math.max(1,staff.length)*100}/></div>})}</div></div></div>}
function Equipment(){const util=window.equipmentUtilisation(),k=window.maintenanceKpis();return <div className="page page-wide rise"><PageHead title="Equipment Register" sub="Plant status, utilisation, service dates and project cost" icon="truck"/><C4Kpis items={[["Operational",DB.equipment.filter(x=>x.status==='Operational').length,null,'check','ok'],['Under maintenance',DB.equipment.filter(x=>x.status==='Under maintenance').length,null,'wrench','warn'],['Breakdown',DB.equipment.filter(x=>x.status==='Breakdown').length,null,'alert','bad'],['Utilisation',Math.round(util.reduce((a,x)=>a+x.availability,0)/util.length),'%','gauge']]}/><C2Table rows={DB.equipment} columns={[{label:'Equipment',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Ownership',key:'ownership'},{label:'Status',render:x=>c4Status(x.status)},{label:'Hours run',render:x=>x.hoursRun.toLocaleString()},{label:'Next service',render:x=><b style={{color:c4Days(x.nextService)<0?'var(--bad)':undefined}}>{c4Date(x.nextService)}<div className="faint">{DB.rel(x.nextService)}</div></b>},{label:'Availability',render:x=>`${(util.find(u=>u.equipment===x.id)||{}).availability}%`},{label:'Monthly cost',render:x=>c4Money((DB.equipmentCosts.find(c=>c.equipment===x.id)||{}).totalCost)},{label:'Cost / hour',render:x=>c4Money((util.find(u=>u.equipment===x.id)||{}).costPerHour)}]}/></div>}
function PPM(){const k=window.maintenanceKpis(),complete=x=>{const next=new Date(x.nextDue);next.setMonth(next.getMonth()+(['Annual'].includes(x.frequency)?12:1));dbUpdate('ppmSchedules',x.id,{lastDone:new Date().toISOString().slice(0,10),nextDue:next.toISOString().slice(0,10),status:'Completed'});dbAdd('jobCards',{id:`JC-26-${String(DB.jobCards.length+1).padStart(3,'0')}`,equipment:x.equipment,project:(DB.equipment.find(e=>e.id===x.equipment)||{}).project,type:'Preventive',reportedDate:new Date().toISOString().slice(0,10),description:x.task,priority:'Low',assignedTo:x.assignedTo,partsUsed:[],labourHours:x.estHours,downtimeHours:x.estHours,cost:x.estHours*8,status:'Closed',completedDate:new Date().toISOString().slice(0,10)})};return <div className="page page-wide rise"><PageHead title="Preventive Maintenance" sub="Planned service compliance and due-date control" icon="refresh"/><C4Kpis items={[["Compliance",k.ppmCompliance,'%','gauge',k.ppmCompliance<90?'warn':'ok'],['Overdue',k.overdue,null,'alert','bad'],['Scheduled',DB.ppmSchedules.filter(x=>x.status==='Scheduled').length],['Completed',DB.ppmSchedules.filter(x=>x.status==='Completed').length,null,'check','ok']]}/><C2Table rows={DB.ppmSchedules} columns={[{label:'Schedule',render:x=><><b>{x.task}</b><div className="mono faint">{x.id}</div></>},{label:'Equipment',render:x=>(DB.equipment.find(e=>e.id===x.equipment)||{}).name},{label:'Frequency',key:'frequency'},{label:'Last done',render:x=>c4Date(x.lastDone)},{label:'Next due',render:x=><b style={{color:c4Days(x.nextDue)<=0?'var(--bad)':c4Days(x.nextDue)<=14?'var(--warn)':undefined}}>{c4Date(x.nextDue)}<div className="faint">{DB.rel(x.nextDue)}</div></b>},{label:'Assigned',render:x=>(DB.empById(x.assignedTo)||{}).name},{label:'Hours',key:'estHours'},{label:'Status',render:x=>c4Status(x.status)},{label:'Action',render:x=><Btn size="sm" onClick={()=>complete(x)}>Complete service</Btn>}]}/></div>}
function JobCards(){const [selected,setSelected]=useState(null),raise=()=>openForm({title:'Raise job card',fields:[{key:'equipment',label:'Equipment',type:'select',options:DB.equipment.map(x=>({value:x.id,label:`${x.id} — ${x.name}`}))},{key:'type',label:'Type',type:'select',options:['Preventive','Corrective','Breakdown','Third-party inspection']},{key:'priority',label:'Priority',type:'radio',options:['Low','Medium','High']},{key:'description',label:'Description',type:'textarea'}],done:'Job card raised',onSave:v=>{const e=DB.equipment.find(x=>x.id===v.equipment);dbAdd('jobCards',{id:`JC-26-${String(DB.jobCards.length+1).padStart(3,'0')}`,...v,project:e.project,reportedDate:new Date().toISOString().slice(0,10),assignedTo:DB.employees[28].id,partsUsed:[],labourHours:0,downtimeHours:0,cost:0,status:'Open',completedDate:null})}});const close=x=>{dbUpdate('jobCards',x.id,{status:'Closed',completedDate:new Date().toISOString().slice(0,10)});setSelected(null)};return <div className="page page-wide rise"><PageHead title="Job Cards" sub="Preventive, corrective and breakdown work orders" icon="wrench" actions={<Btn variant="primary" icon="plus" onClick={raise}>Raise job card</Btn>}/><C4Kpis items={[["Open",DB.jobCards.filter(x=>x.status==='Open').length],['In progress',DB.jobCards.filter(x=>x.status==='In progress').length,null,'refresh'],['Awaiting parts',DB.jobCards.filter(x=>x.status==='Awaiting parts').length,null,'clock','warn'],['Downtime',DB.jobCards.reduce((a,x)=>a+x.downtimeHours,0),'h','alert','bad']]}/><C2Table rows={DB.jobCards} onRow={setSelected} columns={[{label:'Job card',render:x=><><b>{x.description}</b><div className="mono faint">{x.id}</div></>},{label:'Equipment',key:'equipment'},{label:'Project',key:'project'},{label:'Type',key:'type'},{label:'Priority',render:x=><Badge kind={x.priority==='High'?'bad':x.priority==='Medium'?'warn':'neutral'}>{x.priority}</Badge>},{label:'Reported',render:x=>c4Date(x.reportedDate)},{label:'Cost',render:x=>c4Money(x.cost)},{label:'Status',render:x=>c4Status(x.status)}]}/>{selected&&<SimpleModal title={selected.id} sub={selected.description} onClose={()=>setSelected(null)} actions={<Btn variant="primary" disabled={selected.status==='Closed'} onClick={()=>close(selected)}>Close job card</Btn>}><div className="exec-four-cells">{[['Equipment',selected.equipment],['Labour hours',selected.labourHours],['Downtime hours',selected.downtimeHours],['Cost',c4Money(selected.cost)]].map(x=><div className="stat-cell" key={x[0]}><span>{x[0]}</span><b>{x[1]}</b></div>)}</div><SectionHead title="Parts used"/>{selected.partsUsed.length?selected.partsUsed.map((p,i)=><div className="lrow" key={i}><span style={{flex:1}}>{p.part}</span><b>{p.qty} × {c4Money(p.cost)}</b></div>):<NoData/>}</SimpleModal>}</div>}
function Spares(){const issue=x=>openForm({title:`Issue spare · ${x.id}`,fields:[{key:'qty',label:'Quantity',type:'number'},{key:'jobCard',label:'Job card',type:'select',options:DB.jobCards.map(j=>({value:j.id,label:`${j.id} — ${j.type}`}))}],done:'Spare issued',onSave:v=>{const qty=Number(v.qty);x.qtyOnHand=Math.max(0,x.qtyOnHand-qty);x.status=x.qtyOnHand<x.minLevel?'Below minimum':'Adequate';const jc=DB.jobCards.find(j=>j.id===v.jobCard);if(jc){jc.partsUsed.push({part:x.description,qty,cost:x.unitCost});jc.cost=jc.partsUsed.reduce((a,p)=>a+p.qty*p.cost,0)+jc.labourHours*8}window.__bump()}});return <div className="page page-wide rise"><PageHead title="Spare Parts" sub="Plant spares stock, minimum levels and controlled issue" icon="box"/><C4Kpis items={[["Stock lines",DB.spareParts.length],['Below minimum',DB.spareParts.filter(x=>x.qtyOnHand<x.minLevel).length,null,'alert','bad'],['Stock value',c4Money(DB.spareParts.reduce((a,x)=>a+x.qtyOnHand*x.unitCost,0))],['Stores',new Set(DB.spareParts.map(x=>x.store)).size]]}/><C2Table rows={DB.spareParts} columns={[{label:'Part',render:x=><><b>{x.description}</b><div className="mono faint">{x.id} · {x.partNo}</div></>},{label:'Equipment type',key:'equipmentType'},{label:'Store',key:'store'},{label:'On hand',render:x=><b style={{color:x.qtyOnHand<x.minLevel?'var(--bad)':undefined}}>{x.qtyOnHand}</b>},{label:'Minimum',key:'minLevel'},{label:'Unit cost',render:x=>c4Money(x.unitCost)},{label:'Status',render:x=><Badge kind={x.qtyOnHand<x.minLevel?'bad':'ok'}>{x.status}</Badge>},{label:'Action',render:x=><Btn size="sm" onClick={()=>issue(x)}>Issue</Btn>}]}/></div>}
function AuditTrail(){return <div className="page page-wide rise"><PageHead title="Audit Trail" sub="Immutable user, module and project activity history" icon="history" actions={<Btn icon="download" onClick={()=>printRegister('Audit Trail')}>Export</Btn>}/><C4Kpis items={[["Events",DB.auditLog.length],["Users",DB.systemUsers.length],["Denied",DB.auditLog.filter(x=>x.result==='Denied').length,null,'alert','bad'],["Projects",new Set(DB.auditLog.map(x=>x.project)).size]]}/><C2Table rows={DB.auditLog} columns={[{label:'Timestamp',render:x=>new Date(x.timestamp).toLocaleString('en-GB')},{label:'User',render:x=>(DB.staffById(x.user)||{}).name},{label:'Role',key:'role'},{label:'Module',key:'module'},{label:'Project',key:'project'},{label:'Action',key:'action'},{label:'Record',render:x=><span className="mono">{x.record}</span>},{label:'Result',render:x=><Badge kind={x.result==='Success'?'ok':'bad'}>{x.result}</Badge>},{label:'IP',render:x=><span className="mono">{x.ip}</span>}]}/></div>}
function AccessReview(){const toggle=x=>{x.status=x.status==='Active'?'Suspended':'Active';window.__bump()};return <div className="page page-wide rise"><PageHead title="Users & Access" sub="Role, project scope, MFA and periodic access review" icon="shield"/><C4Kpis items={[["Users",DB.systemUsers.length],["Active",DB.systemUsers.filter(x=>x.status==='Active').length,null,'check','ok'],["MFA enabled",DB.systemUsers.filter(x=>x.mfa).length,null,'shield'],["Review exceptions",DB.systemUsers.filter(x=>!x.mfa||x.status!=='Active').length,null,'alert','warn']]}/><C2Table rows={DB.systemUsers} columns={[{label:'User',render:x=><><b>{x.name}</b><div className="faint">{x.email}</div></>},{label:'Role',key:'role'},{label:'Project scope',render:x=><div className="wrap">{x.projects.map(p=><Badge key={p} kind="neutral">{p}</Badge>)}</div>},{label:'Last login',render:x=>new Date(x.lastLogin).toLocaleString('en-GB')},{label:'MFA',render:x=><Badge kind={x.mfa?'ok':'bad'}>{x.mfa?'Enabled':'Required'}</Badge>},{label:'Status',render:x=>c4Status(x.status)},{label:'Action',render:x=><Btn size="sm" onClick={()=>toggle(x)}>{x.status==='Active'?'Suspend':'Activate'}</Btn>}]}/></div>}
Object.assign(window,{FinanceDashboard,Billing,CostControl,Payables,Receivables,Employees,Attendance,Leave,Payroll,Omanisation,Training,Equipment,PPM,JobCards,Spares,AuditTrail,AccessReview});

/* C6 — writable project and workforce registers. */
const c6Who=()=>DB.staff.find(x=>x.role===window.ROLE)?.id||window.ROLE;
const c6Can=roles=>roles.includes(window.ROLE)||window.ROLE==='System Administrator';
const c6Audit=(module,project,action,record)=>window.auditAction&&window.auditAction(module,project,action,record,c6Who());
function C6RoleNote({children}){return <div className="card" style={{padding:'10px 14px',marginBottom:14,borderLeft:'3px solid var(--warn)'}}><span className="muted" style={{fontSize:12.5}}>{children}</span></div>}
function C6Projects(){const [selected,setSelected]=useState(null),[query,setQuery]=useState(''),[,refresh]=useState(0),canCreate=c6Can(['Executive Management','Project Manager']),canManage=c6Can(['Executive Management','Project Manager']),rows=DB.projects.filter(x=>!query||[x.id,x.name,x.location,(DB.custById(x.client)||{}).name].join(' ').toLowerCase().includes(query.toLowerCase())),pm=DB.employees.filter(x=>/Project Manager/i.test(x.designation)).map(x=>x.id);const add=()=>openForm({title:'New project',sub:'Creates the connected contract, cost budget and current-period man-hours seed',aside:true,fields:[{type:'section',label:'Project'},{key:'name',label:'Project name *'},{key:'client',label:'Client *',type:'select',options:DB.clients.map(x=>({value:x.id,label:x.name}))},{key:'type',label:'Project type',type:'select',options:['Roads & infrastructure','Buildings','Water & utilities','Industrial','Marine'],value:'Buildings'},{key:'location',label:'Location *'},{key:'governorate',label:'Governorate',type:'select',options:['Muscat','Dhofar','Al Batinah North','Al Batinah South','Ad Dakhiliyah','Ash Sharqiyah North','Ash Sharqiyah South','Al Buraimi','Ad Dhahirah','Al Wusta','Musandam']},{type:'section',label:'Commercial'},{key:'contractValue',label:'Contract value * (OMR)',type:'number'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'advancePct',label:'Advance %',type:'number',value:10},{key:'targetMarginPct',label:'Target margin %',type:'number',value:15},{type:'section',label:'Programme'},{key:'startDate',label:'Start date *',type:'date'},{key:'endDate',label:'End date *',type:'date'},{key:'durationMonths',label:'Duration',type:'readonly',value:'Derived from dates'},{type:'section',label:'Team'},{key:'pm',label:'Project manager *',type:'select',options:pm},{key:'plannedManpower',label:'Planned manpower',type:'number'}],derive:v=>{const a=new Date(v.startDate),b=new Date(v.endDate);return {durationMonths:v.startDate&&v.endDate&&b>a?`${Math.max(1,Math.ceil((b-a)/(30.44*DB.day)))} months`:'—'};},live:v=>{const value=Number(v.contractValue||0),a=new Date(v.startDate),b=new Date(v.endDate),months=v.startDate&&v.endDate?Math.ceil((b-a)/(30.44*DB.day)):0,s=window.projectBudgetSuggestion(value,v.type),ret=value*Number(v.retentionPct||0)/100,adv=value*Number(v.advancePct||0)/100,bad=v.startDate&&v.endDate&&b<=a,margin=Number(v.targetMarginPct||0);return {tone:bad||margin<8?'bad':months>0&&months<3?'warn':'info',headline:bad?'BLOCK: end date must be after start date':`Suggested delivery budget ${window.money(s.budget)}`,lines:[{icon:'chart',tone:'info',text:`Type benchmark margin ${s.marginPct}% (${window.money(s.margin)}); entered target ${margin}%.`},{icon:'lock',tone:'info',text:`Retention ${window.money(ret)} · advance ${window.money(adv)}.`},...(months>0&&months<3?[{icon:'alert',tone:'warn',text:'Programme is under three months; validate mobilisation and procurement lead time.'}]:[]),...(margin<8?[{icon:'alert',tone:'bad',text:'Target margin below 8% requires executive commercial review.'}]:[])]};},done:'Project created',onSave:v=>{if(!v.location||!v.pm){toast('Location and project manager are required','bad');return false}const r=window.createProject(v,c6Who());if(!r.ok){toast(r.error,'bad');return false}toast(`Project ${r.id} created with connected records`,'ok');refresh(x=>x+1);}});const progress=p=>openForm({title:`Update progress · ${p.id}`,fields:[{key:'actualPct',label:'Actual % this month',type:'number',value:p.actualPct},{key:'note',label:'Progress note',type:'textarea'}],done:'Progress updated',onSave:v=>{const r=window.updateProjectProgress(p.id,v.actualPct,v.note,c6Who());if(!r.ok){toast(r.error,'bad');return false}toast(`${p.id} updated to ${r.actualPct}%`,'ok');refresh(x=>x+1)}});const edit=p=>openForm({title:`Edit project · ${p.id}`,fields:[{key:'name',label:'Project name',value:p.name},{key:'startDate',label:'Start date',type:'date',value:p.startDate},{key:'endDate',label:'End date',type:'date',value:p.endDate},{key:'pm',label:'Project manager',type:'select',options:pm,value:p.pm},{key:'manpower',label:'Planned manpower',type:'number',value:p.manpower}],done:'Project updated',onSave:v=>{if(new Date(v.endDate)<=new Date(v.startDate)){toast('End date must be after start date','bad');return false}dbUpdate('projects',p.id,{...v,manpower:Number(v.manpower)});c6Audit('Projects',p.id,'Edited project core fields',p.id);refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Projects" sub="Multi-project portfolio, progress, commercial and site performance" icon="building" actions={<><Btn icon="download" onClick={()=>printRegister('Projects')}>Export</Btn>{canCreate&&<Btn variant="primary" icon="plus" onClick={add}>New project</Btn>}</>}/>{!canCreate&&<C6RoleNote>Only Executive Management, Project Managers and System Administrators may create or update projects.</C6RoleNote>}<div className="kpi-grid" style={{gridTemplateColumns:'repeat(4,1fr)',marginBottom:16}}><Kpi label="Portfolio value" value={window.money(DB.projects.reduce((a,x)=>a+x.contractValue,0))} icon="receipt"/><Kpi label="Active" value={DB.projects.filter(x=>x.status==='Active').length} icon="building" tone="ok"/><Kpi label="Behind plan" value={DB.projects.filter(x=>window.projectProgress(x.id).status==='Behind').length} icon="alert" tone="bad"/><Kpi label="Manpower" value={DB.projects.reduce((a,x)=>a+x.manpower,0)} icon="users"/></div><div className="card" style={{padding:12,marginBottom:16}}><div className="searchbox"><Icon name="search" size={15}/><input value={query} onChange={e=>setQuery(e.target.value)} placeholder="Search project, client or location…"/></div></div><div className="card"><div style={{overflowX:'auto'}}><table className="tbl"><thead><tr><th>Project</th><th>Client</th><th>Location</th><th>Value</th><th>Progress</th><th>Status</th><th>Actions</th></tr></thead><tbody>{rows.map(p=>{const pr=window.projectProgress(p.id);return <tr key={p.id} onClick={()=>setSelected(p)} style={{cursor:'pointer'}}><td><b>{p.name}</b><div className="mono faint">{p.id}</div></td><td>{(DB.custById(p.client)||{}).name}</td><td>{p.location}<div className="faint">{p.governorate}</div></td><td>{window.money(p.contractValue)}</td><td style={{minWidth:190}}><div className="row"><Bar pct={p.actualPct} color={pr.status==='Behind'?'var(--bad)':'var(--ok)'}/><b>{p.actualPct}%</b></div><div className="faint">Plan {p.plannedPct}% · {pr.variance}%</div></td><td><StatusBadge status={p.status}/></td><td onClick={e=>e.stopPropagation()}>{canManage?<div className="row"><Btn size="sm" onClick={()=>progress(p)}>Update progress</Btn><Btn size="sm" onClick={()=>edit(p)}>Edit</Btn></div>:<span className="faint">Read only</span>}</td></tr>})}</tbody></table></div></div>{selected&&<ProjectDetail project={selected} onClose={()=>setSelected(null)}/>}</div>}
function C6Attendance(){const [tab,setTab]=useState('Daily attendance'),[project,setProject]=useState(DB.projects.find(x=>x.status==='Active')?.id||DB.projects[0].id),[date,setDate]=useState(new Date(DB.TODAY).toISOString().slice(0,10)),[,refresh]=useState(0),canEdit=c6Can(['Site Engineer','Project Manager','Executive Management','HR Manager']),canApprove=c6Can(['Project Manager','Executive Management','HR Manager']),actor=c6Who(),roster=DB.employees.filter(x=>x.status==='Active'&&x.project===project),attFor=e=>DB.attendance.find(x=>x.employee===e.id&&x.date===date)||{employee:e.id,project,date,status:'Present',hoursWorked:8,overtimeHours:0,shift:'Day'},save=(e,patch)=>{const r=window.markAttendance({...attFor(e),...patch},actor);toast(r.updated?'Attendance updated':'Attendance marked','ok');refresh(x=>x+1)},daily=roster.map(attFor),total=daily.reduce((a,x)=>a+Number(x.hoursWorked||0),0),ot=daily.reduce((a,x)=>a+Number(x.overtimeHours||0),0),sheets=DB.timesheets.filter(x=>x.project===project),act=(fn,x,...args)=>{const r=fn(x.id,actor,...args);if(!r.ok)return toast(r.error,'bad');toast(`${x.id} updated`,'ok');refresh(n=>n+1)};const editSheet=t=>{const end=t.weekEnding,employees=DB.employees.filter(x=>x.status==='Active'&&x.project===t.project);openForm({title:`Edit weekly lines · ${t.id}`,sub:'Enter one line per employee: employee ID | normal hours | overtime hours',fields:[{key:'lines',label:'Employee weekly hours',type:'textarea',rows:12,value:employees.map(e=>`${e.id} | 40 | 0`).join('\n')}],done:'Timesheet lines saved',onSave:v=>{String(v.lines).split('\n').filter(Boolean).forEach(line=>{const [employee,normal,overtime]=line.split('|').map(x=>x.trim());if(DB.empById(employee))window.markAttendance({employee,project:t.project,date:end,status:'Present',hoursWorked:Number(normal),overtimeHours:Number(overtime),shift:'Weekly adjustment'},actor)});window.recalcTimesheet(t.id);c6Audit('Attendance',t.project,'Edited weekly timesheet lines',t.id);refresh(x=>x+1)}})};const reject=t=>openForm({title:`Reject ${t.id}`,fields:[{key:'reason',label:'Reason',type:'textarea'}],submitLabel:'Reject',done:'Timesheet returned to Draft',onSave:v=>{const r=window.rejectTimesheet(t.id,actor,v.reason);if(!r.ok){toast(r.error,'bad');return false}refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Attendance & Timesheets" sub="Daily presence, shifts and weekly project approvals" icon="clock" actions={tab==='Daily attendance'&&canEdit?<Btn variant="primary" icon="check" onClick={()=>{if(window.confirm('Mark every active employee on this project present, except existing Leave rows?')){const r=window.bulkMarkAttendance({project,date,status:'Present',hours:8},actor);toast(`${r.count} employees marked present`,'ok');refresh(x=>x+1)}}}>Mark whole site present</Btn>:null}/>{!canEdit&&<C6RoleNote>Attendance is read-only. Site Engineers, Project Managers, HR Managers, Executive Management and System Administrators may edit; approval is restricted to Project Managers, HR Managers, Executive Management and Administrators.</C6RoleNote>}<div className="row" style={{justifyContent:'space-between',marginBottom:14}}><C2Tabs tabs={['Daily attendance','Timesheets']} active={tab} onChange={setTab}/><div className="row"><select className="input" value={project} onChange={e=>setProject(e.target.value)}>{projOpts().map(x=><option key={x.value} value={x.value}>{x.label}</option>)}</select>{tab==='Daily attendance'&&<input className="input" type="date" value={date} onChange={e=>setDate(e.target.value)}/>}</div></div>{tab==='Daily attendance'?<><C4Kpis items={[["Present",daily.filter(x=>x.status==='Present').length,null,'check','ok'],['Absent',daily.filter(x=>x.status==='Absent').length,null,'x-circle','bad'],['On leave',daily.filter(x=>x.status==='Leave').length,null,'calendar','warn'],['Total hours',total,null,'clock'],['Overtime hours',ot,null,'clock','warn']]}/><div className="card"><table className="tbl"><thead><tr><th>Employee</th><th>Status</th><th>Hours</th><th>Overtime</th><th>Shift</th><th>Last edited</th></tr></thead><tbody>{roster.map(e=>{const a=attFor(e);return <tr key={e.id}><td><b>{e.name}</b><div className="mono faint">{e.id}</div></td><td><select disabled={!canEdit} value={a.status} onChange={x=>save(e,{status:x.target.value,hoursWorked:x.target.value==='Present'?Number(a.hoursWorked||8):0})}>{['Present','Absent','Leave','Rest day'].map(x=><option key={x}>{x}</option>)}</select></td><td><input disabled={!canEdit} style={{width:70}} type="number" value={a.hoursWorked} onChange={x=>save(e,{hoursWorked:Number(x.target.value)})}/></td><td><input disabled={!canEdit} style={{width:70}} type="number" value={a.overtimeHours} onChange={x=>save(e,{overtimeHours:Number(x.target.value)})}/></td><td><select disabled={!canEdit} value={a.shift} onChange={x=>save(e,{shift:x.target.value})}>{['Day','Night','Split'].map(x=><option key={x}>{x}</option>)}</select></td><td>{a.editedBy?<><span>{(DB.staffById(a.editedBy)||{}).name||a.editedBy}</span><div className="faint">{new Date(a.editedAt).toLocaleString('en-GB')}</div></>:'—'}</td></tr>})}</tbody></table></div></>:<><C6RoleNote>Approval refreshes the project/period man-hours row used as the TRIR and LTIFR exposure denominator.</C6RoleNote><C4Kpis items={[["Draft",sheets.filter(x=>x.status==='Draft').length],['Submitted',sheets.filter(x=>x.status==='Submitted').length,null,'clock','warn'],['Approved',sheets.filter(x=>x.status==='Approved').length,null,'check','ok'],['Total hours this period',sheets.reduce((a,x)=>a+Number(x.normalHours)+Number(x.overtimeHours),0),null,'clock']]}/><C2Table rows={sheets} columns={[{label:'Timesheet',key:'id'},{label:'Project',key:'project'},{label:'Week ending',render:x=>c4Date(x.weekEnding)},{label:'Employees',key:'employees'},{label:'Normal hours',key:'normalHours'},{label:'Overtime',key:'overtimeHours'},{label:'Status',render:x=>c4Status(x.status)},{label:'Approved by',render:x=>(DB.staffById(x.approvedBy)||{}).name||'—'},{label:'Actions',render:x=><div className="row" style={{flexWrap:'wrap'}}>{canEdit&&<Btn size="sm" onClick={()=>editSheet(x)}>Edit</Btn>}<Btn size="sm" onClick={()=>{window.recalcTimesheet(x.id);toast(`${x.id} recalculated`,'ok');refresh(n=>n+1)}}>Recalculate</Btn>{canEdit&&x.status==='Draft'&&<Btn size="sm" variant="primary" onClick={()=>act(window.submitTimesheet,x)}>Submit</Btn>}{canApprove&&x.status==='Submitted'&&<><Btn size="sm" variant="primary" onClick={()=>act(window.approveTimesheet,x)}>Approve</Btn><Btn size="sm" onClick={()=>reject(x)}>Reject</Btn></>}</div>}]}/></>}</div>}
window.Projects=C7Projects;window.Attendance=C6Attendance;

/* C6 — primary actions for remaining operational registers. */
const c6Id=(prefix,rows)=>`${prefix}-${String(rows.length+1).padStart(3,'0')}`;
const c6Save=(key,row,module,project,notifyTo)=>{dbAdd(key,row);c6Audit(module,project,`Created ${row.id}`,row.id);if(notifyTo)notify(`${module} handoff`,`${row.id} requires action.`,notifyTo);};
const c6Generic=(title,key,prefix,fields,build,roles)=>({label:title,run:()=>{if(roles&&!c6Can(roles))return toast(`Action restricted to ${roles.join(', ')} or System Administrator`,'bad');openForm({title,fields,done:`${title} saved`,onSave:v=>{const row=build(v,c6Id(prefix,DB[key]));c6Save(key,row,title,row.project);}})}});
window.C6_PRIMARY={
'Clients':c6Generic('Add client','clients','CL', [{key:'name',label:'Name *'},{key:'sector',label:'Sector'},{key:'contact',label:'Contact'},{key:'email',label:'Email'},{key:'phone',label:'Phone'},{key:'crNumber',label:'CR number'},{key:'vatNumber',label:'VAT number'},{key:'paymentTerms',label:'Payment terms'},{key:'creditLimit',label:'Credit limit',type:'number'}],(v,id)=>({id,...v,creditLimit:Number(v.creditLimit),rating:0}),['Sales & BD Manager','Executive Management']),
'Contracts':c6Generic('New contract','contracts','CON-26',[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'client',label:'Client *',type:'select',options:DB.clients.map(x=>({value:x.id,label:x.name}))},{key:'value',label:'Value *',type:'number'},{key:'awardDate',label:'Award date *',type:'date'},{key:'completionDate',label:'Completion date *',type:'date'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'advancePct',label:'Advance %',type:'number',value:10}],(v,id)=>({id,...v,value:Number(v.value),retentionPct:Number(v.retentionPct),retentionHeld:0,advancePct:Number(v.advancePct),advanceRecovered:0,status:'Active',milestones:[]}),['Executive Management','Project Manager']),
'Suppliers':c6Generic('Add supplier','suppliers','SUP',[{key:'name',label:'Name *'},{key:'category',label:'Category *'},{key:'contact',label:'Contact'},{key:'crNumber',label:'CR number'},{key:'prequalStatus',label:'Prequalification',type:'select',options:['Approved','Provisional','Expired']},{key:'prequalExpiry',label:'Expiry',type:'date'}],(v,id)=>({id,...v,prequalified:v.prequalStatus==='Approved',rating:0,onTimePct:0,spendYtd:0,hseScore:0,qualityScore:0,deliveryScore:0,overallScore:0}),['Procurement Manager']),
'Subcontractors':c6Generic('Add subcontractor','subcontractors','SUB',[{key:'name',label:'Name *'},{key:'trade',label:'Trade *'},{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'contractValue',label:'Contract value *',type:'number'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'prequalified',label:'Prequalified',type:'radio',options:['Yes','No']}],(v,id)=>({id,...v,contractValue:Number(v.contractValue),retentionPct:Number(v.retentionPct),prequalified:v.prequalified==='Yes',workDone:0,status:'Active',hseScore:0,workOrders:[],paymentsToDate:0}),['Procurement Manager','Project Manager']),
'Controlled QMS Documents':c6Generic('New document / revision','qmsDocuments','QMS-D',[{key:'title',label:'Title *'},{key:'type',label:'Type *'},{key:'revision',label:'Revision *'},{key:'owner',label:'Owner',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))},{key:'issueDate',label:'Issue date',type:'date'},{key:'reviewDue',label:'Review due',type:'date'}],(v,id)=>({id,...v,status:'Published',acknowledgedBy:[]}),['Quality Manager']),
'Training & Competency':c6Generic('Record training','trainingRecords','TRN-26',[{key:'employee',label:'Employee *',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))},{key:'course',label:'Course *'},{key:'provider',label:'Provider'},{key:'completed',label:'Completed',type:'date'},{key:'expiry',label:'Expiry',type:'date'},{key:'certificateNo',label:'Certificate no.'}],(v,id)=>({...v,id,status:(new Date(v.expiry)-DB.TODAY)/DB.day<0?'Expired':(new Date(v.expiry)-DB.TODAY)/DB.day<90?'Expiring':'Valid'}),['HR Manager']),
'Equipment Register':c6Generic('Add equipment','equipment','EQP',[{key:'name',label:'Name *'},{key:'type',label:'Type'},{key:'ownership',label:'Ownership',type:'select',options:['Owned','Rented']},{key:'project',label:'Project',type:'select',options:projOpts()},{key:'status',label:'Status',type:'select',options:['Operational','Under maintenance','Idle','Breakdown']},{key:'hoursRun',label:'Hours run',type:'number'},{key:'nextService',label:'Next service',type:'date'}],(v,id)=>({...v,id,hoursRun:Number(v.hoursRun)}),['Maintenance Manager']),
'Employees':c6Generic('Add employee','employees','EMP',[{key:'name',label:'Name *'},{key:'nationality',label:'Nationality *'},{key:'designation',label:'Designation *'},{key:'department',label:'Department'},{key:'project',label:'Project',type:'select',options:projOpts()},{key:'joinDate',label:'Join date *',type:'date'},{key:'basicSalary',label:'Basic salary *',type:'number'},{key:'allowances',label:'Allowances',type:'number'},{key:'contractType',label:'Contract type'},{key:'civilId',label:'Civil ID'},{key:'passportExpiry',label:'Passport expiry',type:'date'},{key:'visaExpiry',label:'Visa expiry',type:'date'},{key:'labourCardExpiry',label:'Labour-card expiry',type:'date'}],(v,id)=>({...v,id,omani:/omani/i.test(v.nationality),role:v.designation,basicSalary:Number(v.basicSalary),allowances:Number(v.allowances),status:'Active'}),['HR Manager']),
'Environmental Management':{label:'Log environmental incident',run:()=>openForm({title:'Log environmental incident',fields:[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'type',label:'Type *'},{key:'severity',label:'Severity',type:'select',options:['Minor','Moderate','Major']},{key:'description',label:'Description',type:'textarea'},{key:'correctiveAction',label:'Corrective action',type:'textarea'},{key:'reportedTo',label:'Reported to'}],done:'Environmental incident logged',onSave:v=>c6Save('envIncidents',{id:c6Id('ENV-26',DB.envIncidents),...v,date:new Date().toISOString().slice(0,10),status:'Open'},'Environment',v.project,['HSE Manager'])})},
'Quality Audits & Customer Voice':c6Generic('Schedule audit','audits','AUD-26',[{key:'type',label:'Type',type:'select',options:['Internal','Client','Supplier']},{key:'project',label:'Area / project',type:'select',options:projOpts()},{key:'date',label:'Planned date',type:'date'},{key:'leadAuditor',label:'Lead auditor',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))}],(v,id)=>({id,...v,area:v.project,findings:0,status:'Scheduled'}),['Quality Manager']),
'Payroll & WPS':{label:'Run payroll',run:()=>{if(!c6Can(['HR Manager','Executive Management']))return toast('Payroll is restricted to HR, Executive Management or Admin','bad');openForm({title:'Run payroll',fields:[{key:'period',label:'Open period',type:'month'}],done:'Payroll run created as Draft',onSave:v=>{if(DB.payrollRuns.some(x=>x.period===v.period)){toast('Payroll already exists for this period','bad');return false}let basic=0,allowances=0,overtime=0,deductions=0,employer=0,employee=0;DB.employees.filter(x=>x.status!=='Demobilized').forEach(e=>{basic+=e.basicSalary;allowances+=e.allowances;if(e.omani){employer+=e.basicSalary*.115;employee+=e.basicSalary*.07}});const gross=basic+allowances+overtime,net=gross-deductions-employee;c6Save('payrollRuns',{id:`PR-${v.period}`,period:v.period,employees:DB.employees.filter(x=>x.status!=='Demobilized').length,basic,allowances,overtime,deductions,gosi:{employer,employee,total:employer+employee},gross,net,status:'Draft',wpsFile:`WPS_AlHajar_${v.period}.sif`,bankFile:`AHC_PAY_${v.period.replace('-','')}.csv`},'Payroll') }})}},
'Leave':{label:'New leave request',run:()=>{if(!c6Can(['HR Manager','Project Manager','Executive Management']))return toast('Leave requests are managed by HR, Project Management or Admin','bad');openForm({title:'New leave request',fields:[{key:'employee',label:'Employee *',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))},{key:'type',label:'Type',type:'select',options:['Annual','Sick','Emergency','Unpaid','Hajj']},{key:'from',label:'From',type:'date'},{key:'to',label:'To',type:'date'},{key:'reason',label:'Reason',type:'textarea'}],done:'Leave request submitted',onSave:v=>{const days=Math.max(1,Math.floor((new Date(v.to)-new Date(v.from))/DB.day)+1);c6Save('leaveRequests',{id:c6Id('LV-26',DB.leaveRequests),...v,days,balanceBefore:30,status:'Pending',approver:null},'Leave',DB.empById(v.employee)?.project,['HR Manager','Project Manager'])}})}},
'Spare Parts':{label:'Receive stock',run:()=>{if(!c6Can(['Warehouse Keeper','Maintenance Manager']))return toast('Stock receipt is restricted to Warehouse or Maintenance','bad');openForm({title:'Receive spare stock',fields:[{key:'part',label:'Part',type:'select',options:DB.spareParts.map(x=>({value:x.id,label:`${x.id} — ${x.description||x.name||''}`}))},{key:'qty',label:'Quantity',type:'number'},{key:'reference',label:'Receipt reference'}],done:'Spare stock received',onSave:v=>{const p=DB.spareParts.find(x=>x.id===v.part);p.qtyOnHand+=Number(v.qty);p.status=p.qtyOnHand<p.minLevel?'Below minimum':'Adequate';c6Audit('Maintenance',p.store,`Received ${v.qty} units · ${v.reference}`,p.id);window.__bump()}})}},
'Payables':{label:'Record payment',run:()=>{if(!c6Can(['Finance Manager','Executive Management']))return toast('Payments are restricted to Finance, Executive Management or Admin','bad');openForm({title:'Record supplier payment',fields:[{key:'document',label:'Purchase order',type:'select',options:DB.purchaseOrders.filter(x=>x.status!=='Paid').map(x=>x.id)},{key:'amount',label:'Amount',type:'number'},{key:'date',label:'Payment date',type:'date'},{key:'method',label:'Method',type:'select',options:['Bank transfer','Cheque']}],done:'Payment recorded',onSave:v=>{const po=DB.purchaseOrders.find(x=>x.id===v.document);c6Save('payments',{id:c6Id('PAY-26',DB.payments),purchaseOrder:v.document,project:po?.project,amount:Number(v.amount),date:v.date,method:v.method},'Payables',po?.project);dbUpdate('purchaseOrders',v.document,{status:'Paid'})}})}},
'Receivables':{label:'Record receipt',run:()=>{if(!c6Can(['Finance Manager','Executive Management']))return toast('Receipts are restricted to Finance, Executive Management or Admin','bad');openForm({title:'Record client receipt',fields:[{key:'invoice',label:'IPC',type:'select',options:DB.invoices.filter(x=>x.status!=='Paid').map(x=>x.id)},{key:'amount',label:'Amount',type:'number'},{key:'date',label:'Receipt date',type:'date'},{key:'method',label:'Method',type:'select',options:['Bank transfer','Cheque']}],done:'Receipt recorded',onSave:v=>{const ipc=DB.invoices.find(x=>x.id===v.invoice);c6Save('payments',{id:c6Id('PAY-26',DB.payments),invoice:v.invoice,project:ipc?.project,amount:Number(v.amount),date:v.date,method:v.method},'Receivables',ipc?.project);dbUpdate('invoices',v.invoice,{status:'Paid'})}})}},};

/* C7 — role-gated action wiring for the remaining read-only registers. */
const c7Today=()=>new Date(DB.TODAY).toISOString().slice(0,10);
const c7Actor=()=>DB.staff.find(x=>x.role===window.ROLE)?.id||window.ROLE||'System';
const c7Can=roles=>window.ROLE==='System Administrator'||roles.includes(window.ROLE);
const c7Id=(prefix,rows)=>`${prefix}-${String(rows.length+1).padStart(3,'0')}`;
const c7Required=(v,keys)=>{const missing=keys.filter(k=>v[k]===undefined||v[k]===null||String(v[k]).trim()==='');if(missing.length){toast(`Complete required fields: ${missing.map(DB.pretty).join(', ')}`,'bad');return false}return true};
const c7Audit=(module,project,action,record)=>window.auditAction(module,project||'—',action,record,c7Actor());
const c7Add=(key,row,module,notifyTo)=>{dbAdd(key,row);c7Audit(module,row.project,`Created ${row.id}`,row.id);if(notifyTo)window.notify(`${module} handoff`,`${row.id} requires action.`,notifyTo);return row};
const c7Update=(key,id,patch,module,project,action,notifyTo)=>{const row=dbUpdate(key,id,patch);c7Audit(module,project,action||`Updated ${id}`,id);if(notifyTo)window.notify(`${module} handoff`,`${id} requires action.`,notifyTo);return row};
function C7Note({roles}){return <C6RoleNote>This register is read-only for your role. Only {roles.join(', ')}{roles.length>1?', and ':' and '}System Administrator may act.</C6RoleNote>}
const c7PmOpts=()=>DB.employees.filter(x=>/Project Manager/i.test(x.designation)).map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}));
function C7Projects(){const [selected,setSelected]=useState(null),[,refresh]=useState(0),roles=['Executive Management','Project Manager'],can=c7Can(roles),add=()=>openForm({title:'New project',aside:true,fields:[{key:'name',label:'Project name *'},{key:'client',label:'Client *',type:'select',options:DB.clients.map(x=>({value:x.id,label:x.name}))},{key:'type',label:'Type',type:'select',options:['Roads & infrastructure','Buildings','Water & utilities','Industrial','Marine'],value:'Buildings'},{key:'location',label:'Location *'},{key:'governorate',label:'Governorate',type:'select',options:['Muscat','Dhofar','Al Batinah North','Al Batinah South','Ad Dakhiliyah','Ash Sharqiyah North','Ash Sharqiyah South','Al Buraimi','Ad Dhahirah','Al Wusta','Musandam']},{key:'contractValue',label:'Contract value *',type:'number'},{key:'startDate',label:'Start date *',type:'date'},{key:'endDate',label:'End date *',type:'date'},{key:'pm',label:'Project manager *',type:'select',options:c7PmOpts()},{key:'targetMarginPct',label:'Target margin %',type:'number',value:15},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'advancePct',label:'Advance %',type:'number',value:10},{key:'plannedManpower',label:'Planned manpower',type:'number'}],live:v=>{const a=new Date(v.startDate),b=new Date(v.endDate),bad=v.startDate&&v.endDate&&b<=a,months=v.startDate&&v.endDate&&!bad?Math.max(1,Math.ceil((b-a)/(30.44*DB.day))):0,value=Number(v.contractValue||0),s=window.projectBudgetSuggestion(value,v.type);return {tone:bad?'bad':'info',headline:bad?'BLOCK: end date must be after start date':months?`${months} month programme`:'Enter programme dates',lines:[{icon:'chart',tone:'info',text:`Suggested budget ${window.money(s.budget)} at ${s.marginPct}% benchmark margin.`},{icon:'lock',tone:'info',text:`Retention ${window.money(value*Number(v.retentionPct||0)/100)} · advance ${window.money(value*Number(v.advancePct||0)/100)}.`}]};},done:'Project created',onSave:v=>{if(!c7Required(v,['name','client','location','contractValue','startDate','endDate','pm']))return false;const r=window.createProject(v,c7Actor());if(!r.ok){toast(r.error,'bad');return false}toast(`New project ID: ${r.id}`,'ok');refresh(x=>x+1)}}),progress=p=>openForm({title:`Update progress · ${p.id}`,fields:[{key:'actualPct',label:'Actual progress % *',type:'number',value:p.actualPct},{key:'note',label:'Progress note',type:'textarea'}],done:'Progress updated',onSave:v=>{if(!c7Required(v,['actualPct']))return false;const r=window.updateProjectProgress(p.id,v.actualPct,v.note,c7Actor());if(!r.ok){toast(r.error,'bad');return false}toast(`${p.id} updated to ${r.actualPct}%`,'ok');refresh(x=>x+1)}}),edit=p=>openForm({title:`Edit project · ${p.id}`,fields:[{key:'name',label:'Name *',value:p.name},{key:'startDate',label:'Start date *',type:'date',value:p.startDate},{key:'endDate',label:'End date *',type:'date',value:p.endDate},{key:'pm',label:'Project manager *',type:'select',options:c7PmOpts(),value:p.pm},{key:'manpower',label:'Manpower',type:'number',value:p.manpower}],done:'Project updated',onSave:v=>{if(!c7Required(v,['name','startDate','endDate','pm']))return false;if(new Date(v.endDate)<=new Date(v.startDate)){toast('End date must be after start date','bad');return false}c7Update('projects',p.id,{...v,manpower:Number(v.manpower||0)},'Projects',p.id,'Edited project core fields');refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Projects" sub="Multi-project portfolio, progress, commercial and site performance" icon="building" actions={<><Btn icon="download" onClick={()=>printRegister('Projects')}>Export</Btn>{can&&<Btn variant="primary" icon="plus" onClick={add}>New project</Btn>}</>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.projects} onRow={setSelected} columns={[{label:'Project',render:p=><><b>{p.name}</b><div className="mono faint">{p.id}</div></>},{label:'Client',render:p=>(DB.custById(p.client)||{}).name},{label:'Location',key:'location'},{label:'Value',render:p=>window.money(p.contractValue)},{label:'Progress',render:p=><div style={{minWidth:160}}><div className="row"><Bar pct={p.actualPct}/><b>{p.actualPct}%</b></div><div className="faint">Plan {p.plannedPct}%</div></div>},{label:'Status',render:p=><StatusBadge status={p.status}/>},{label:'Actions',render:p=>can?<div className="row"><Btn size="sm" onClick={e=>{e.stopPropagation();progress(p)}}>Update progress</Btn><Btn size="sm" onClick={e=>{e.stopPropagation();edit(p)}}>Edit</Btn></div>:<span className="faint">Read only</span>}]}/>{selected&&<ProjectDetail project={selected} onClose={()=>setSelected(null)}/>}</div>}
function C7Contracts(){const roles=['Executive Management','Project Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'New contract',fields:[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'client',label:'Client *',type:'select',options:DB.clients.map(x=>({value:x.id,label:x.name}))},{key:'value',label:'Value *',type:'number'},{key:'awardDate',label:'Award date *',type:'date'},{key:'completionDate',label:'Completion date *',type:'date'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'advancePct',label:'Advance %',type:'number',value:10}],done:'Contract created',onSave:v=>{if(!c7Required(v,['project','client','value','awardDate','completionDate']))return false;c7Add('contracts',{id:c7Id('CON-26',DB.contracts),...v,value:Number(v.value),retentionPct:Number(v.retentionPct||0),retentionHeld:0,advancePct:Number(v.advancePct||0),advanceRecovered:0,status:'Active',milestones:[]},'Contracts',['Project Manager']);refresh(x=>x+1)}}),milestone=x=>openForm({title:`Update milestone · ${x.id}`,fields:[{key:'milestone',label:'Milestone *',type:'select',options:x.milestones.map(m=>m.name)},{key:'achieved',label:'Mark achieved',type:'radio',options:['Yes','No'],value:'Yes'},{key:'date',label:'Achievement date *',type:'date'}],done:'Milestone updated',onSave:v=>{if(!c7Required(v,['milestone','date']))return false;c7Update('contracts',x.id,{milestones:x.milestones.map(m=>m.name===v.milestone?{...m,date:v.date,status:v.achieved==='Yes'?'Completed':'Planned'}:m)},'Contracts',x.project,`Updated milestone ${v.milestone}`);refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Contracts" sub="Award, retention, advance recovery and milestone control" icon="documents" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>New contract</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.contracts} columns={[{label:'Contract',render:x=><><b>{(DB.projectById(x.project)||{}).name}</b><div className="mono faint">{x.id}</div></>},{label:'Client',render:x=>(DB.custById(x.client)||{}).name},{label:'Value',render:x=>window.money(x.value)},{label:'Award',render:x=>DB.fmt(x.awardDate)},{label:'Completion',render:x=>DB.fmt(x.completionDate)},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=>can?<Btn size="sm" onClick={()=>milestone(x)}>Update milestone</Btn>:<span className="faint">Read only</span>}]}/></div>}
function C7Suppliers(){const roles=['Procurement Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Add supplier',fields:[{key:'name',label:'Name *'},{key:'category',label:'Category *'},{key:'contact',label:'Contact'},{key:'crNumber',label:'CR number'},{key:'prequalStatus',label:'Prequalification',type:'select',options:['Approved','Provisional','Expired']},{key:'prequalExpiry',label:'Expiry',type:'date'}],done:'Supplier added',onSave:v=>{if(!c7Required(v,['name','category']))return false;c7Add('suppliers',{id:c7Id('SUP',DB.suppliers),...v,prequalified:v.prequalStatus==='Approved',rating:0,onTimePct:0,spendYtd:0,hseScore:0,qualityScore:0,deliveryScore:0,overallScore:0},'Suppliers');refresh(x=>x+1)}}),prequal=x=>openForm({title:`Update prequalification · ${x.id}`,fields:[{key:'prequalStatus',label:'Status *',type:'select',options:['Approved','Provisional','Expired'],value:x.prequalStatus},{key:'prequalExpiry',label:'Expiry',type:'date',value:x.prequalExpiry},{key:'hseScore',label:'HSE score',type:'number',value:x.hseScore},{key:'qualityScore',label:'Quality score',type:'number',value:x.qualityScore},{key:'deliveryScore',label:'Delivery score',type:'number',value:x.deliveryScore}],done:'Prequalification updated',onSave:v=>{if(!c7Required(v,['prequalStatus']))return false;const h=Number(v.hseScore||0),q=Number(v.qualityScore||0),d=Number(v.deliveryScore||0);c7Update('suppliers',x.id,{...v,hseScore:h,qualityScore:q,deliveryScore:d,overallScore:Number(((h+q+d)/3).toFixed(1)),prequalified:v.prequalStatus==='Approved'},'Suppliers',null,'Updated supplier prequalification');refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Suppliers" sub="Prequalification, performance scoring and procurement spend" icon="truck" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Add supplier</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.suppliers} columns={[{label:'Supplier',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Category',key:'category'},{label:'Prequalification',render:x=><Badge kind={x.prequalStatus==='Expired'?'bad':'ok'}>{x.prequalStatus||'Provisional'}</Badge>},{label:'Expiry',render:x=>DB.fmt(x.prequalExpiry)},{label:'HSE',key:'hseScore'},{label:'Quality',key:'qualityScore'},{label:'Delivery',key:'deliveryScore'},{label:'Action',render:x=>can?<Btn size="sm" onClick={()=>prequal(x)}>Update prequalification</Btn>:<span className="faint">Read only</span>}]}/></div>}
function C7Subcontractors(){const roles=['Procurement Manager','Project Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Add subcontractor',fields:[{key:'name',label:'Name *'},{key:'trade',label:'Trade *'},{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'contractValue',label:'Contract value *',type:'number'},{key:'retentionPct',label:'Retention %',type:'number',value:10},{key:'prequalified',label:'Prequalified',type:'radio',options:['Yes','No']}],done:'Subcontractor added',onSave:v=>{if(!c7Required(v,['name','trade','project','contractValue']))return false;c7Add('subcontractors',{id:c7Id('SUB',DB.subcontractors),...v,contractValue:Number(v.contractValue),retentionPct:Number(v.retentionPct||10),prequalified:v.prequalified==='Yes',workDone:0,retentionHeld:0,status:'Active',hseScore:0,workOrders:[],paymentsToDate:0},'Subcontractors',['Project Manager']);refresh(x=>x+1)}}),certify=x=>openForm({title:`Certify work done · ${x.id}`,fields:[{key:'periodValue',label:'Value this period *',type:'number'}],done:'Work certified',onSave:v=>{if(!c7Required(v,['periodValue']))return false;const amount=Number(v.periodValue),workDone=Number(x.workDone||0)+amount,retentionHeld=workDone*Number(x.retentionPct||0)/100;c7Update('subcontractors',x.id,{workDone,retentionHeld},'Subcontractors',x.project,`Certified ${window.money(amount)} work done`,['Finance Manager']);refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Subcontractors" sub="Work orders, certification, retention and HSE performance" icon="users" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Add subcontractor</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.subcontractors} columns={[{label:'Subcontractor',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Trade',key:'trade'},{label:'Project',key:'project'},{label:'Contract',render:x=>window.money(x.contractValue)},{label:'Work done',render:x=>window.money(x.workDone)},{label:'Retention',render:x=>window.money(x.retentionHeld||x.workDone*x.retentionPct/100)},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=>can?<Btn size="sm" onClick={()=>certify(x)}>Certify work done</Btn>:<span className="faint">Read only</span>}]}/></div>}
function C7HSEIncidents(){const roles=['HSE Manager'],can=c7Can(roles),rows=DB.hseIncidents.filter(c3Match),add=()=>openForm({title:'Report incident',aside:true,fields:[{type:'section',label:'Notification'},{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'site',label:'Site'},{key:'exactLocation',label:'Exact location *'},{key:'date',label:'Date *',type:'date'},{key:'type',label:'Type *',type:'radio',options:['Near miss','FAC','MTC','LTI','RTA','Property damage','Environmental']},{key:'title',label:'Title *'},{type:'section',label:'People'},{key:'injured',label:'Injured?',type:'radio',options:['Yes','No'],value:'No'},{key:'designation',label:'Designation',show:v=>v.injured==='Yes'},{key:'bodyPart',label:'Body part',type:'select',options:['Head','Eyes','Shoulder','Arm','Hand','Fingers','Back','Leg','Knee','Foot'],show:v=>v.injured==='Yes'},{key:'side',label:'Side',type:'radio',options:['L','R','N/A'],show:v=>v.injured==='Yes'},{key:'lostDays',label:'Lost days',type:'number',show:v=>v.injured==='Yes'},{type:'section',label:'Description'},{key:'description',label:'What happened *',type:'textarea'},{key:'immediateAction',label:'Immediate action',type:'textarea'},{type:'section',label:'Causes'},{key:'immediateCauses',label:'Immediate causes',type:'checks',options:['Unsafe access','PPE not used','Equipment condition','Poor housekeeping','Line of fire','Procedure not followed']},{key:'rootCauses',label:'Root causes',type:'checks',options:['Inadequate planning','Training gap','Supervision gap','Risk assessment incomplete','Maintenance weakness']}],live:v=>{const serious=['LTI','RTA'].includes(v.type),head=/head|eye/i.test(v.bodyPart||''),blocked=v.type==='LTI'&&String(v.lostDays||'').trim()==='';return {tone:blocked||serious||head?'bad':'info',headline:blocked?'BLOCK: lost days required for LTI':`${v.type||'Pending'} severity classification`,lines:[{icon:'alert',tone:serious?'bad':'info',text:serious?'Statutory-reporting assessment required for LTI/RTA.':'Classify the event and preserve evidence.'},...(head?[{icon:'alert',tone:'bad',text:'Head/eye injury requires urgent medical escalation.'}]:[])]};},done:'Incident reported',onSave:v=>{if(!c7Required(v,['project','exactLocation','date','type','title','description']))return false;if(v.type==='LTI'&&String(v.lostDays||'').trim()===''){toast('LTI submission blocked until lost days is entered','bad');return false}const id=c7Id('INC-26',DB.hseIncidents);c7Add('hseIncidents',{id,...v,lostDays:Number(v.lostDays||0),recordable:['MTC','LTI','RTA'].includes(v.type),severity:['LTI','RTA'].includes(v.type)?'High':v.type==='MTC'?'Medium':'Low',status:'Open',reportedBy:c7Actor()},'HSE', ['HSE Manager','Project Manager']);toast(`${id} reported`,'ok')}});return <div className="page page-wide rise"><PageHead title="HSE Incident Centre" sub="Incident reporting, lagging indicators and toolbox participation" icon="helmet" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Report incident</Btn>}/>{!can&&<C7Note roles={roles}/>}<C3Register rows={rows} columns={[{label:'Incident',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Type',render:x=><Badge kind={c3Kind(x.type)}>{x.type}</Badge>},{label:'Severity',key:'severity'},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/></div>}
function C7NCR(){const roles=['Quality Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Raise NCR',fields:[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'discipline',label:'Discipline *'},{key:'source',label:'Source *'},{key:'title',label:'Title *'},{key:'description',label:'Description *',type:'textarea'},{key:'severity',label:'Severity *',type:'radio',options:['Major','Minor']},{key:'dueDate',label:'Due date *',type:'date'}],done:'NCR raised',onSave:v=>{if(!c7Required(v,['project','discipline','source','title','description','severity','dueDate']))return false;const id=c7Id('NCR-26',DB.ncrs);c7Add('ncrs',{id,...v,date:c7Today(),status:'Open',raisedBy:c7Actor(),closeDate:null},'NCR',['Quality Manager']);toast(`${id} raised`,'ok');refresh(x=>x+1)}}),close=x=>openForm({title:`Close NCR · ${x.id}`,fields:[{key:'closureNote',label:'Closure comments *',type:'textarea'},{key:'verification',label:'Verification *',type:'textarea'}],done:'NCR closed',onSave:v=>{if(!c7Required(v,['closureNote','verification']))return false;c7Update('ncrs',x.id,{...v,status:'Closed',closeDate:c7Today()},'NCR',x.project,'Closed NCR',['Project Manager']);refresh(n=>n+1)}}),capa=x=>openForm({title:`Raise CAPA · ${x.id}`,fields:[{key:'title',label:'CAPA title *',value:x.title},{key:'rootCause',label:'Root cause *',type:'textarea'},{key:'owner',label:'Owner *',type:'select',options:DB.employees.map(e=>({value:e.id,label:`${e.name} — ${e.designation}`}))},{key:'dueDate',label:'Due date *',type:'date'}],done:'CAPA raised',onSave:v=>{if(!c7Required(v,['title','rootCause','owner','dueDate']))return false;c7Add('capas',{id:c7Id('CAPA-26',DB.capas),source:'NCR',sourceRef:x.id,project:x.project,title:v.title,rootCause:v.rootCause,rootCauseMethod:'5 Why',actions:[{desc:v.title,owner:v.owner,due:v.dueDate,status:'Open'}],verification:'Pending effectiveness review.',effectiveness:'Pending',status:'Open',dueDate:v.dueDate},'CAPA',['Quality Manager']);refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Nonconformance & CAPA" sub="Quality deviation, root cause and verified corrective action" icon="flag" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Raise NCR</Btn>}/>{!can&&<C7Note roles={roles}/>}<C3Register rows={DB.ncrs.filter(c3Match)} columns={[{label:'NCR',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Severity',key:'severity'},{label:'Due',render:x=>DB.fmt(x.dueDate)},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Actions',render:x=>can?<div className="row"><Btn size="sm" disabled={x.status==='Closed'} onClick={()=>close(x)}>Close NCR</Btn><Btn size="sm" onClick={()=>capa(x)}>Raise CAPA</Btn></div>:<span className="faint">Read only</span>}]}/></div>}
function C7Environment(){const roles=['HSE Manager'],can=c7Can(roles),[,refresh]=useState(0),incident=()=>openForm({title:'Log environmental incident',fields:[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'type',label:'Type *'},{key:'severity',label:'Severity',type:'select',options:['Minor','Moderate','Major']},{key:'description',label:'Description',type:'textarea'},{key:'correctiveAction',label:'Corrective action',type:'textarea'},{key:'reportedTo',label:'Reported to'}],done:'Environmental incident logged',onSave:v=>{if(!c7Required(v,['project','type']))return false;c7Add('envIncidents',{id:c7Id('ENV-26',DB.envIncidents),...v,date:c7Today(),status:'Open'},'Environment',['HSE Manager']);refresh(x=>x+1)}}),manifest=()=>openForm({title:'Add waste manifest',fields:[{key:'project',label:'Project *',type:'select',options:projOpts()},{key:'wasteType',label:'Waste type *'},{key:'qtyTonnes',label:'Quantity tonnes *',type:'number'},{key:'carrier',label:'Carrier'},{key:'disposalSite',label:'Disposal site'},{key:'manifestNo',label:'Manifest number *'}],done:'Waste manifest added',onSave:v=>{if(!c7Required(v,['project','wasteType','qtyTonnes','manifestNo']))return false;c7Add('wasteManifests',{id:c7Id('WM-26',DB.wasteManifests),...v,qtyTonnes:Number(v.qtyTonnes),date:c7Today(),status:'Disposed'},'Environment');refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Environmental Management" sub="Environmental incidents and licensed waste disposal" icon="droplet" actions={can&&<><Btn onClick={manifest}>Add waste manifest</Btn><Btn variant="primary" icon="plus" onClick={incident}>Log environmental incident</Btn></>}/>{!can&&<C7Note roles={roles}/>}<SectionHead title="Environmental incidents"/><C3Register rows={DB.envIncidents} columns={[{label:'Incident',render:x=><><b>{x.type}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Severity',key:'severity'},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/><div style={{height:16}}/><SectionHead title="Waste manifests"/><C3Register rows={DB.wasteManifests} columns={[{label:'Manifest',render:x=><><b>{x.wasteType}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Quantity',render:x=>`${x.qtyTonnes} t`},{label:'Carrier',key:'carrier'},{label:'Disposal site',key:'disposalSite'},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/></div>}
function C7Documents(){const roles=['Quality Manager'],can=c7Can(roles),[,refresh]=useState(0),user=DB.staff.find(x=>x.role===window.ROLE)?.id||DB.employees[39].id,add=()=>openForm({title:'New document / revision',fields:[{key:'title',label:'Title *'},{key:'type',label:'Type *'},{key:'revision',label:'Revision *'},{key:'owner',label:'Owner',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))},{key:'issueDate',label:'Issue date',type:'date'},{key:'reviewDue',label:'Review due',type:'date'}],done:'Document published',onSave:v=>{if(!c7Required(v,['title','type','revision']))return false;c7Add('qmsDocuments',{id:c7Id('QMS-D',DB.qmsDocuments),...v,status:'Published',acknowledgedBy:[]},'Documents',['Quality Manager']);refresh(x=>x+1)}}),ack=x=>{if(!x.acknowledgedBy.includes(user))c7Update('qmsDocuments',x.id,{acknowledgedBy:[...x.acknowledgedBy,user]},'Documents',null,'Acknowledged controlled document');toast(`${x.id} acknowledged`,'ok');refresh(n=>n+1)};return <div className="page page-wide rise"><PageHead title="Controlled QMS Documents" sub="Revision, review and workforce acknowledgement control" icon="documents" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>New document / revision</Btn>}/>{!can&&<C7Note roles={roles}/>}<C3Register rows={DB.qmsDocuments} columns={[{label:'Document',render:x=><><b>{x.title}</b><div className="mono faint">{x.id}</div></>},{label:'Type',key:'type'},{label:'Revision',key:'revision'},{label:'Review due',render:x=>DB.fmt(x.reviewDue)},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=><Btn size="sm" disabled={x.acknowledgedBy.includes(user)||x.status!=='Published'} onClick={()=>ack(x)}>Acknowledge</Btn>}]}/></div>}
function C7Training(){const roles=['HR Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Record training',aside:true,fields:[{key:'employee',label:'Employee *',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))},{key:'course',label:'Course *'},{key:'provider',label:'Provider'},{key:'completed',label:'Completed *',type:'date'},{key:'expiry',label:'Expiry *',type:'date'},{key:'certificateNo',label:'Certificate number'}],live:v=>{const days=v.expiry?Math.ceil((new Date(v.expiry)-DB.TODAY)/DB.day):null;return {tone:days!=null&&days<=90?'warn':'info',headline:days==null?'Enter certificate expiry':days<0?'Certificate already expired':days<=90?`Warning: expires in ${days} days`:'Certificate validity exceeds 90 days',lines:[{icon:'calendar',tone:days!=null&&days<=90?'warn':'info',text:'Training expiry drives the competency compliance register.'}]};},done:'Training recorded',onSave:v=>{if(!c7Required(v,['employee','course','completed','expiry']))return false;const days=(new Date(v.expiry)-DB.TODAY)/DB.day;c7Add('trainingRecords',{id:c7Id('TRN-26',DB.trainingRecords),...v,status:days<0?'Expired':days<=90?'Expiring':'Valid'},'Training',['HR Manager']);refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Training & Competency" sub="Certificates, expiry and trade competency coverage" icon="certificate" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Record training</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.trainingRecords} columns={[{label:'Course',render:x=><><b>{x.course}</b><div className="mono faint">{x.id}</div></>},{label:'Employee',render:x=>(DB.empById(x.employee)||{}).name},{label:'Provider',key:'provider'},{label:'Completed',render:x=>DB.fmt(x.completed)},{label:'Expiry',render:x=>DB.fmt(x.expiry)},{label:'Status',render:x=><StatusBadge status={x.status}/>}]}/></div>}
function C7QualityAudits(){const roles=['Quality Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Schedule audit',fields:[{key:'type',label:'Type',type:'select',options:['Internal','Client','Supplier']},{key:'project',label:'Area / project *',type:'select',options:projOpts()},{key:'plannedDate',label:'Planned date *',type:'date'},{key:'leadAuditor',label:'Lead auditor',type:'select',options:DB.employees.map(x=>({value:x.id,label:`${x.name} — ${x.designation}`}))}],done:'Audit scheduled',onSave:v=>{if(!c7Required(v,['project','plannedDate']))return false;c7Add('audits',{id:c7Id('AUD-26',DB.audits),project:v.project,area:v.project,date:v.plannedDate,type:v.type,leadAuditor:v.leadAuditor,status:'Scheduled',findings:0,findingRows:[]},'Quality Audits',['Quality Manager']);refresh(x=>x+1)}}),finding=x=>openForm({title:`Log finding · ${x.id}`,fields:[{key:'description',label:'Description *',type:'textarea'},{key:'category',label:'Category *',type:'radio',options:['Major','Minor','Observation']},{key:'owner',label:'Owner',type:'select',options:DB.employees.map(e=>({value:e.id,label:`${e.name} — ${e.designation}`}))},{key:'due',label:'Due',type:'date'},{key:'escalate',label:'Escalate into NCR?',type:'radio',options:['Yes','No'],value:'No'}],done:'Finding logged',onSave:v=>{if(!c7Required(v,['description','category']))return false;const findingRows=[...(x.findingRows||[]),{...v,id:`${x.id}-F${Number(x.findings||0)+1}`}];c7Update('audits',x.id,{findings:Number(x.findings||0)+1,findingRows,status:'Actions open'},'Quality Audits',x.project,'Logged audit finding');if(v.escalate==='Yes')c7Add('ncrs',{id:c7Id('NCR-26',DB.ncrs),project:x.project,date:c7Today(),source:`Audit ${x.id}`,discipline:'Quality',title:v.description,description:v.description,severity:v.category==='Major'?'Major':'Minor',status:'Open',raisedBy:c7Actor(),dueDate:v.due,closeDate:null},'NCR',['Quality Manager']);refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Quality Audits & Customer Voice" sub="Audit programme, findings and client satisfaction" icon="certificate" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Schedule audit</Btn>}/>{!can&&<C7Note roles={roles}/>}<C3Register rows={DB.audits} columns={[{label:'Audit',render:x=><><b>{x.area}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Type',key:'type'},{label:'Date',render:x=>DB.fmt(x.date)},{label:'Findings',key:'findings'},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Action',render:x=>can?<Btn size="sm" onClick={()=>finding(x)}>Log finding</Btn>:<span className="faint">Read only</span>}]}/></div>}
function C7Employees(){const roles=['HR Manager'],can=c7Can(roles),[,refresh]=useState(0),fields=e=>[{key:'name',label:'Name *',value:e?.name},{key:'nationality',label:'Nationality *',value:e?.nationality},{key:'designation',label:'Designation *',value:e?.designation},{key:'department',label:'Department',value:e?.department},{key:'project',label:'Project',type:'select',options:projOpts(),value:e?.project},{key:'joinDate',label:'Join date *',type:'date',value:e?.joinDate},{key:'basicSalary',label:'Basic salary *',type:'number',value:e?.basicSalary},{key:'allowances',label:'Allowances',type:'number',value:e?.allowances},{key:'contractType',label:'Contract type',value:e?.contractType},{key:'civilId',label:'Civil ID',value:e?.civilId},{key:'passportExpiry',label:'Passport expiry',type:'date',value:e?.passportExpiry},{key:'visaExpiry',label:'Visa expiry',type:'date',value:e?.visaExpiry},{key:'labourCardExpiry',label:'Labour card expiry',type:'date',value:e?.labourCardExpiry}],save=(v,e)=>{if(!c7Required(v,['name','nationality','designation','joinDate','basicSalary']))return false;const row={...v,omani:v.nationality==='Omani',role:v.designation,basicSalary:Number(v.basicSalary),allowances:Number(v.allowances||0)};if(e)c7Update('employees',e.id,row,'Employees',v.project,'Edited employee');else c7Add('employees',{id:c7Id('EMP',DB.employees),...row,status:'Active'},'Employees',['HR Manager']);refresh(x=>x+1)},add=()=>openForm({title:'Add employee',fields:fields(),done:'Employee added',onSave:v=>save(v)}),edit=e=>openForm({title:`Edit employee · ${e.id}`,fields:fields(e),done:'Employee updated',onSave:v=>save(v,e)}),assign=e=>openForm({title:`Assign to project · ${e.id}`,fields:[{key:'project',label:'Project *',type:'select',options:projOpts(),value:e.project}],done:'Employee assigned',onSave:v=>{if(!c7Required(v,['project']))return false;c7Update('employees',e.id,{project:v.project},'Employees',v.project,'Assigned employee to project',['Project Manager']);refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Employees" sub="Oman workforce master, remuneration and document compliance" icon="users" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Add employee</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.employees} columns={[{label:'Employee',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Designation',key:'designation'},{label:'Nationality',render:x=><>{x.nationality} {x.omani&&<Badge kind="ok">OMANI</Badge>}</>},{label:'Project',key:'project'},{label:'Salary',render:x=>window.money(x.basicSalary)},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Actions',render:x=>can?<div className="row"><Btn size="sm" onClick={()=>edit(x)}>Edit</Btn><Btn size="sm" onClick={()=>assign(x)}>Assign to project</Btn></div>:<span className="faint">Read only</span>}]}/></div>}
function C7Equipment(){const roles=['Maintenance Manager'],can=c7Can(roles),[,refresh]=useState(0),add=()=>openForm({title:'Add equipment',fields:[{key:'name',label:'Name *'},{key:'type',label:'Type'},{key:'ownership',label:'Ownership',type:'select',options:['Owned','Rented']},{key:'project',label:'Project',type:'select',options:projOpts()},{key:'status',label:'Status',type:'select',options:['Operational','Under maintenance','Idle','Breakdown']},{key:'hoursRun',label:'Hours run',type:'number'},{key:'nextService',label:'Next service',type:'date'}],done:'Equipment added',onSave:v=>{if(!c7Required(v,['name']))return false;c7Add('equipment',{id:c7Id('EQP',DB.equipment),...v,hoursRun:Number(v.hoursRun||0)},'Equipment',['Maintenance Manager']);refresh(x=>x+1)}}),status=e=>openForm({title:`Change status · ${e.id}`,fields:[{key:'status',label:'Status *',type:'select',options:['Operational','Under maintenance','Idle','Breakdown'],value:e.status},{key:'reason',label:'Reason *',type:'textarea'}],done:'Equipment status updated',onSave:v=>{if(!c7Required(v,['status','reason']))return false;c7Update('equipment',e.id,{status:v.status,statusReason:v.reason},'Equipment',e.project,`Changed status to ${v.status}`);if(v.status==='Breakdown'){const id=c7Id('JC-26',DB.jobCards);c7Add('jobCards',{id,equipment:e.id,project:e.project,type:'Breakdown',reportedDate:c7Today(),description:v.reason,priority:'High',assignedTo:DB.employees[28].id,partsUsed:[],labourHours:0,downtimeHours:0,cost:0,status:'Open',completedDate:null},'Maintenance',['Maintenance Manager']);toast(`${id} breakdown job card created`,'ok')}refresh(x=>x+1)}});return <div className="page page-wide rise"><PageHead title="Equipment Register" sub="Plant status, utilisation and service dates" icon="truck" actions={can&&<Btn variant="primary" icon="plus" onClick={add}>Add equipment</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.equipment} columns={[{label:'Equipment',render:x=><><b>{x.name}</b><div className="mono faint">{x.id}</div></>},{label:'Project',key:'project'},{label:'Ownership',key:'ownership'},{label:'Status',render:x=><StatusBadge status={x.status}/>},{label:'Hours',key:'hoursRun'},{label:'Next service',render:x=>DB.fmt(x.nextService)},{label:'Action',render:x=>can?<Btn size="sm" onClick={()=>status(x)}>Change status</Btn>:<span className="faint">Read only</span>}]}/></div>}
window.HSEIncidents=C7HSEIncidents;
window.NCR=C7NCR;
window.Employees=C7Employees;
window.Equipment=C7Equipment;
function C7Receivables(){const roles=['Finance Manager','Executive Management'],can=c7Can(roles),[,refresh]=useState(0),receipt=x=>openForm({title:`Record receipt · ${x.id}`,fields:[{key:'amount',label:'Amount *',type:'number'},{key:'date',label:'Date *',type:'date'},{key:'method',label:'Method *',type:'select',options:['Bank transfer','Cheque','Cash']}],done:'Receipt recorded',onSave:v=>{if(!c7Required(v,['amount','date','method']))return false;const amount=Number(v.amount),paid=DB.payments.filter(p=>p.invoice===x.id).reduce((a,p)=>a+Number(p.amount||0),0)+amount;c7Add('payments',{id:c7Id('PAY-26',DB.payments),invoice:x.id,project:x.project,amount,date:v.date,method:v.method},'Receivables');c7Update('invoices',x.id,{status:paid>=Number(x.total)?'Paid':x.status},'Receivables',x.project,'Recorded client receipt');toast(`${window.money(amount)} received against ${x.id}`,'ok');refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Receivables" sub="Client aging and IPC-to-cash traceability" icon="trend"/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.invoices} columns={[{label:'IPC',key:'id'},{label:'Project',key:'project'},{label:'Total',render:x=>c4Money(x.total)},{label:'Received',render:x=>c4Money(DB.payments.filter(p=>p.invoice===x.id).reduce((a,p)=>a+Number(p.amount||0),0))},{label:'Status',render:x=>c4Status(x.status)},{label:'Action',render:x=>can?<Btn size="sm" disabled={x.status==='Paid'} onClick={()=>receipt(x)}>Record receipt</Btn>:<span className="faint">Read only</span>}]}/></div>}
window.Receivables=C7Receivables;
function C7Payables(){const roles=['Finance Manager','Executive Management'],can=c7Can(roles),[,refresh]=useState(0),pay=x=>openForm({title:`Record payment · ${x.id}`,fields:[{key:'amount',label:'Amount *',type:'number',value:x.value},{key:'date',label:'Date *',type:'date'},{key:'method',label:'Method *',type:'select',options:['Bank transfer','Cheque']}],done:'Payment recorded',onSave:v=>{if(!c7Required(v,['amount','date','method']))return false;c7Add('payments',{id:c7Id('PAY-26',DB.payments),purchaseOrder:x.id,project:x.project,amount:Number(v.amount),date:v.date,method:v.method},'Payables');c7Update('purchaseOrders',x.id,{status:'Paid'},'Payables',x.project,'Recorded supplier payment');toast(`${x.id} marked Paid`,'ok');refresh(n=>n+1)}});return <div className="page page-wide rise"><PageHead title="Payables" sub="Supplier and subcontractor obligations with aging" icon="receipt"/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.purchaseOrders} columns={[{label:'Supplier',render:x=>(DB.suppliers.find(s=>s.id===x.supplier)||{}).name},{label:'Reference',key:'id'},{label:'Project',key:'project'},{label:'Amount',render:x=>c4Money(x.value)},{label:'Due',render:x=>c4Date(x.requiredDate)},{label:'Status',render:x=>c4Status(x.status)},{label:'Action',render:x=>can?<Btn size="sm" disabled={x.status==='Paid'} onClick={()=>pay(x)}>Record payment</Btn>:<span className="faint">Read only</span>}]}/></div>}
window.Payables=C7Payables;
function C7Payroll(){const roles=['HR Manager','Executive Management'],can=c7Can(roles),[wps,setWps]=useState(''),[,refresh]=useState(0),run=()=>openForm({title:'Run payroll',fields:[{key:'period',label:'Open period *',type:'month'}],done:'Payroll run created as Draft',onSave:v=>{if(!c7Required(v,['period']))return false;if(DB.payrollRuns.some(x=>x.period===v.period)){toast('Payroll already exists for this period','bad');return false}let basic=0,allowances=0,overtime=0,deductions=0,employer=0,employee=0;const active=DB.employees.filter(x=>x.status!=='Demobilized');active.forEach(e=>{basic+=Number(e.basicSalary||0);allowances+=Number(e.allowances||0);if(e.omani){employer+=Number(e.basicSalary||0)*.115;employee+=Number(e.basicSalary||0)*.07}});const gross=basic+allowances+overtime,net=gross-deductions-employee,id=`PR-${v.period}`;c7Add('payrollRuns',{id,period:v.period,employees:active.length,basic,allowances,overtime,deductions,gosi:{employer,employee,total:employer+employee},gross,net,status:'Draft',wpsGenerated:false},'Payroll');toast(`${id} created as Draft`,'ok');refresh(n=>n+1)}}),generate=x=>{const text=window.wpsFile(x.id);c7Update('payrollRuns',x.id,{wpsGenerated:true},'Payroll',null,'Generated WPS SIF file');toast(`WPS file generated for ${x.id}`,'ok');setWps(text);refresh(n=>n+1)},paid=x=>{c7Update('payrollRuns',x.id,{status:'Paid',paidDate:c7Today()},'Payroll',null,'Marked payroll paid');toast(`${x.id} marked paid`,'ok');refresh(n=>n+1)};return <div className="page page-wide rise"><PageHead title="Payroll & WPS" sub="Monthly payroll, Omani-only GOSI and EOSB accrual" icon="receipt" actions={can&&<Btn variant="primary" icon="plus" onClick={run}>Run payroll</Btn>}/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.payrollRuns} columns={[{label:'Run',key:'id'},{label:'Period',key:'period'},{label:'Employees',key:'employees'},{label:'Gross',render:x=>c4Money(x.gross)},{label:'GOSI',render:x=>c4Money(x.gosi.total)},{label:'Net',render:x=>c4Money(x.net)},{label:'Status',render:x=>c4Status(x.status)},{label:'Actions',render:x=>can?<div className="row"><Btn size="sm" onClick={()=>generate(x)}>Generate WPS file</Btn><Btn size="sm" disabled={x.status==='Paid'} onClick={()=>paid(x)}>Mark as paid</Btn></div>:<span className="faint">Read only</span>}]}/>{wps&&<SimpleModal title="WPS SIF preview" onClose={()=>setWps('')}><pre className="mono" style={{whiteSpace:'pre-wrap'}}>{wps}</pre></SimpleModal>}</div>}
window.Payroll=C7Payroll;
window.Suppliers=C7Suppliers;
window.Subcontractors=C7Subcontractors;
window.Contracts=C7Contracts;
window.Documents=C7Documents;
window.Training=C7Training;
window.QualityAudits=C7QualityAudits;
window.Environment=C7Environment;
function C7AccessReview(){const roles=['System Administrator'],can=c7Can(roles),[,refresh]=useState(0),setStatus=(x,status)=>openForm({title:`${status} access · ${x.name}`,fields:[{key:'reason',label:'Reason *',type:'textarea'}],done:`User ${status.toLowerCase()}`,onSave:v=>{if(!c7Required(v,['reason']))return false;c7Update('systemUsers',x.id,{status,statusReason:v.reason},'Access Review',null,`${status} user access`);toast(`${x.name} ${status.toLowerCase()}`,'ok');refresh(n=>n+1)}}),review=x=>{c7Update('systemUsers',x.id,{reviewDate:c7Today()},'Access Review',null,'Completed access review');toast(`${x.name} reviewed today`,'ok');refresh(n=>n+1)};return <div className="page page-wide rise"><PageHead title="Users & Access" sub="Role, project scope, MFA and periodic access review" icon="shield"/>{!can&&<C7Note roles={roles}/>}<C2Table rows={DB.systemUsers} columns={[{label:'User',render:x=><><b>{x.name}</b><div className="faint">{x.email}</div></>},{label:'Role',key:'role'},{label:'Last login',render:x=>new Date(x.lastLogin).toLocaleString('en-GB')},{label:'Review date',render:x=>x.reviewDate?DB.fmt(x.reviewDate):'Not reviewed'},{label:'MFA',render:x=><Badge kind={x.mfa?'ok':'bad'}>{x.mfa?'Enabled':'Required'}</Badge>},{label:'Status',render:x=>c4Status(x.status)},{label:'Actions',render:x=>can?<div className="row"><Btn size="sm" onClick={()=>setStatus(x,x.status==='Active'?'Suspended':'Active')}>{x.status==='Active'?'Suspend':'Activate'}</Btn><Btn size="sm" onClick={()=>review(x)}>Review now</Btn></div>:<span className="faint">Read only</span>}]}/></div>}
window.AccessReview=C7AccessReview;

