const REPORT_API='./api/index.php'; const reportCtx={csrf:'',user:null,data:null,type:'asset-trace',selectedAsset:null,filters:{search:'',kind:'All',status:'All',condition:'All',vendor:'All'}}; const reportTypes=[['asset-trace','Asset trace / item history'],['movements','Issues, returns & transfers'],['vendor','Vendor analysis'],['purchases','Purchase & payment report'],['status','Asset status & condition'],['office','Office allocation']]; const reportEsc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const activeReport=r=>Number(r?.active)!==0; const reportPage=()=>document.querySelector('.breadcrumb strong')?.textContent.trim()||''; const reportMoney=v=>new Intl.NumberFormat('en-PK',{style:'currency',currency:'PKR',maximumFractionDigits:2}).format(Number(v||0)); const reportDate=v=>v||'—'; async function reportApi(action){const r=await fetch(`${REPORT_API}?action=${action}`,{credentials:'same-origin'});const j=await r.json().catch(()=>({error:'Server returned an invalid response.'}));if(!r.ok)throw Error(j.error||'Request failed');return j;} async function reportLoad(){const s=await reportApi('session');reportCtx.user=s.user;if(!reportCtx.user||reportCtx.user.role==='office')return false;reportCtx.data=(await reportApi('data')).data;return true;} function officeName(id){return (reportCtx.data?.offices||[]).find(o=>Number(o.id)===Number(id))?.name||'Central store';} function vendorName(id){return (reportCtx.data?.vendors||[]).find(v=>Number(v.id)===Number(id))?.name||'Unknown vendor';} function assetFor(id){return (reportCtx.data?.assets||[]).find(a=>Number(a.id)===Number(id));} function reportShell(){return `
OPERATIONS INTELLIGENCE

Reports & asset trace

Vendor costs, movement history, office allocation aur asset condition ek jagah.

${reportTypes.map(([key,label])=>``).join('')}
`;} function summaryCards(items){return `
${items.map(([label,value,detail])=>`
${label}${value}${detail||''}
`).join('')}
`;} function table(headers,rows,empty='No records found.'){return `
${headers.map(h=>``).join('')}${rows||``}
${h}
${empty}
`;} function filtersFor(){const f=reportCtx.filters;const vendors=reportCtx.data?.vendors||[];if(reportCtx.type==='asset-trace')return ``;if(reportCtx.type==='movements')return ``;if(reportCtx.type==='vendor')return ``;if(reportCtx.type==='status')return ``;return ``;} function assetTrace(){const assets=(reportCtx.data?.assets||[]).filter(activeReport).filter(a=>{const q=reportCtx.filters.search.toLowerCase();return !q||`${a.name} ${a.tag} ${a.serial} ${a.category}`.toLowerCase().includes(q)});const rows=assets.map(a=>`${reportEsc(a.tag)}${reportEsc(a.serial||'No serial')}${reportEsc(a.name)}${reportEsc(a.category)}${reportEsc(a.status)}${reportEsc(officeName(a.office_id))}${reportEsc(a.condition)}${reportDate(a.since)}`).join('');const selected=assetFor(reportCtx.selectedAsset);const movements=(reportCtx.data?.movements||[]).filter(m=>Number(m.asset_id)===Number(reportCtx.selectedAsset)).sort((a,b)=>String(a.date).localeCompare(String(b.date))||Number(a.id)-Number(b.id));let detail='';if(selected){detail=`
SELECTED ASSET

${reportEsc(selected.name)} ${reportEsc(selected.tag)}

${reportEsc(selected.status)}
Current office${reportEsc(officeName(selected.office_id))}
Condition${reportEsc(selected.condition)}
Category${reportEsc(selected.category)}
Since${reportDate(selected.since)}
Serial${reportEsc(selected.serial||'Not recorded')}
Warranty${reportDate(selected.warranty)}

Movement history — kis ko aur kab diya

${table(['DATE','MOVEMENT','FROM','TO','RECEIVED BY','NOTES'],movements.map(m=>`${reportDate(m.date)}${reportEsc(m.kind)}${reportEsc(m.reference)}${reportEsc(officeName(m.from_office_id))}${reportEsc(officeName(m.office_id))}${reportEsc(m.receiver)}${reportEsc(m.notes||'—')}`).join(''),'No issue, transfer ya return history recorded.')}
`;}reportCtx.csv=[['Tag','Item','Category','Status','Current office','Condition','Since'],...assets.map(a=>[a.tag,a.name,a.category,a.status,officeName(a.office_id),a.condition,a.since])];return summaryCards([['Assets found',assets.length,'Matching active asset records'],['Issued',assets.filter(a=>a.status==='Issued').length,'Currently with offices'],['In store',assets.filter(a=>a.status==='In store').length,'Available in central store']])+`

Asset register

${table(['TAG','ITEM','CATEGORY','STATUS','CURRENT OFFICE','CONDITION','SINCE'],rows,'No matching assets.')}

Row select karein taa-ke complete custody trail, receiver aur dates nazar aayen.

${detail}`;} function movementReport(){const q=reportCtx.filters.search.toLowerCase(),kind=reportCtx.filters.kind;const rows=(reportCtx.data?.movements||[]).filter(m=>(kind==='All'||m.kind===kind)&&(!q||`${m.reference} ${m.kind} ${m.receiver} ${m.notes} ${assetFor(m.asset_id)?.name||''}`.toLowerCase().includes(q))).sort((a,b)=>String(b.date).localeCompare(String(a.date))||Number(b.id)-Number(a.id));reportCtx.csv=[['Reference','Date','Movement','Asset','From','To','Received by','Notes'],...rows.map(m=>[m.reference,m.date,m.kind,assetFor(m.asset_id)?.tag||m.notes,officeName(m.from_office_id),officeName(m.office_id),m.receiver,m.notes])];return summaryCards([['Movements',rows.length,'Matching custody events'],['Issues',rows.filter(m=>m.kind==='Issue').length,'Assets issued'],['Returns',rows.filter(m=>m.kind==='Return').length,'Assets returned']])+`

Issues, returns & transfers

${table(['REFERENCE','DATE','MOVEMENT','ASSET','FROM','TO','RECEIVED BY','NOTES'],rows.map(m=>`${reportEsc(m.reference)}${reportDate(m.date)}${reportEsc(m.kind)}${reportEsc(assetFor(m.asset_id)?.tag||m.notes)}${reportEsc(assetFor(m.asset_id)?.name||'Stock movement')}${reportEsc(officeName(m.from_office_id))}${reportEsc(officeName(m.office_id))}${reportEsc(m.receiver)}${reportEsc(m.notes||'—')}`).join(''))}
`;} function vendorReport(){const q=reportCtx.filters.search.toLowerCase(),chosen=reportCtx.filters.vendor;const vendors=(reportCtx.data?.vendors||[]).filter(v=>(chosen==='All'||String(v.id)===String(chosen))&&(!q||v.name.toLowerCase().includes(q)));let total=0,paid=0,units=0;const rows=vendors.map(v=>{const ps=(reportCtx.data?.purchases||[]).filter(p=>Number(p.vendor_id)===Number(v.id));const assets=(reportCtx.data?.assets||[]).filter(a=>Number(a.vendor_id)===Number(v.id));const value=ps.reduce((s,p)=>s+Number(p.total),0),p=ps.reduce((s,x)=>s+Number(x.paid),0),q=ps.reduce((s,x)=>s+Number(x.quantity),0);total+=value;paid+=p;units+=q;return `${reportEsc(v.name)}${reportEsc(v.contact||'')} · ${reportEsc(v.phone||'')}${ps.length}${q}${assets.length}${reportMoney(value)}${reportMoney(p)}${reportMoney(value-p)}`}).join('');reportCtx.csv=[['Vendor','Purchases','Units','Assets','Total','Paid','Balance'],...vendors.map(v=>{const ps=(reportCtx.data?.purchases||[]).filter(p=>Number(p.vendor_id)===Number(v.id));const value=ps.reduce((s,p)=>s+Number(p.total),0),p=ps.reduce((s,x)=>s+Number(x.paid),0);return [v.name,ps.length,ps.reduce((s,x)=>s+Number(x.quantity),0),(reportCtx.data?.assets||[]).filter(a=>Number(a.vendor_id)===Number(v.id)).length,value,p,value-p]})];return summaryCards([['Vendors',vendors.length,'Matching suppliers'],['Purchase value',reportMoney(total),'Invoice totals'],['Amount paid',reportMoney(paid),'Recorded payments'],['Balance',reportMoney(total-paid),'Outstanding amount']])+`

Vendor analysis

${table(['VENDOR','PURCHASES','UNITS','ASSETS','TOTAL','PAID','BALANCE'],rows)}
`;} function purchaseReport(){const q=reportCtx.filters.search.toLowerCase();const rows=(reportCtx.data?.purchases||[]).filter(p=>!q||`${p.invoice} ${p.name} ${vendorName(p.vendor_id)} ${p.reference}`.toLowerCase().includes(q));const total=rows.reduce((s,p)=>s+Number(p.total),0),paid=rows.reduce((s,p)=>s+Number(p.paid),0),tax=rows.reduce((s,p)=>s+Number(p.tax),0);reportCtx.csv=[['Reference','Invoice','Date','Item','Vendor','Quantity','Tax %','Discount','Total','Paid','Balance'],...rows.map(p=>[p.reference,p.invoice,p.date,p.name,vendorName(p.vendor_id),p.quantity,p.tax,p.discount,p.total,p.paid,Number(p.total)-Number(p.paid)])];return summaryCards([['Invoices',rows.length,'Matching purchases'],['Total',reportMoney(total),'Purchase value'],['Paid',reportMoney(paid),'Recorded payments'],['Balance',reportMoney(total-paid),'Outstanding'],['Tax rates',tax.toFixed(2)+'%','Recorded invoice rates']])+`

Purchase & payment report

${table(['REFERENCE','INVOICE','DATE','ITEM','VENDOR','QTY','TOTAL','PAID','BALANCE'],rows.map(p=>`${reportEsc(p.reference)}${reportEsc(p.invoice)}${reportDate(p.date)}${reportEsc(p.name)}${reportEsc(p.category)}${reportEsc(vendorName(p.vendor_id))}${p.quantity}${reportMoney(p.total)}${reportMoney(p.paid)}${reportMoney(Number(p.total)-Number(p.paid))}`).join(''))}
`;} function statusReport(){const q=reportCtx.filters.search.toLowerCase(),status=reportCtx.filters.status,condition=reportCtx.filters.condition;const assets=(reportCtx.data?.assets||[]).filter(a=>activeReport(a)&&(status==='All'||a.status===status)&&(condition==='All'||a.condition===condition)&&(!q||`${a.name} ${a.tag} ${a.category}`.toLowerCase().includes(q)));const counts=['In store','Issued','Maintenance'].map(x=>[x,assets.filter(a=>a.status===x).length,'']);reportCtx.csv=[['Tag','Item','Category','Status','Condition','Office','Cost'],...assets.map(a=>[a.tag,a.name,a.category,a.status,a.condition,officeName(a.office_id),a.cost])];return summaryCards(counts.concat([['Total value',reportMoney(assets.reduce((s,a)=>s+Number(a.cost),0)),'Filtered acquisition value']]))+`

Asset status & condition

${table(['TAG','ITEM','CATEGORY','STATUS','CONDITION','OFFICE','VALUE'],assets.map(a=>`${reportEsc(a.tag)}${reportEsc(a.name)}${reportEsc(a.serial||'No serial')}${reportEsc(a.category)}${reportEsc(a.status)}${reportEsc(a.condition)}${reportEsc(officeName(a.office_id))}${reportMoney(a.cost)}`).join(''))}
`;} function officeReport(){const offices=(reportCtx.data?.offices||[]).filter(activeReport);let total=0;const rows=offices.map(o=>{const assets=(reportCtx.data?.assets||[]).filter(a=>Number(a.office_id)===Number(o.id)&&activeReport(a));const value=assets.reduce((s,a)=>s+Number(a.cost),0);total+=value;return `${reportEsc(o.name)}${reportEsc(o.code)} · ${reportEsc(o.city)}${assets.length}${assets.filter(a=>a.status==='Issued').length}${assets.filter(a=>a.condition==='Needs repair').length}${reportMoney(value)}`});reportCtx.csv=[['Office','Assets','Issued','Needs repair','Value'],...offices.map(o=>{const a=(reportCtx.data?.assets||[]).filter(x=>Number(x.office_id)===Number(o.id)&&activeReport(x));return [o.name,a.length,a.filter(x=>x.status==='Issued').length,a.filter(x=>x.condition==='Needs repair').length,a.reduce((s,x)=>s+Number(x.cost),0)]})];return summaryCards([['Offices',offices.length,'Active registered offices'],['Issued assets',(reportCtx.data?.assets||[]).filter(a=>a.status==='Issued').length,'Across all offices'],['Allocated value',reportMoney(total),'Current office allocation']])+`

Office allocation

${table(['OFFICE','ASSETS','ISSUED','NEEDS REPAIR','VALUE'],rows.join(''))}
`;} function renderReport(panel){panel.querySelectorAll('.report-type-tabs button').forEach(b=>b.classList.toggle('active',b.dataset.reportType===reportCtx.type));panel.querySelector('[data-report-filters]').innerHTML=filtersFor();const renderers={'asset-trace':assetTrace,movements:movementReport,vendor:vendorReport,purchases:purchaseReport,status:statusReport,office:officeReport};panel.querySelector('[data-report-content]').innerHTML=renderers[reportCtx.type]();panel.querySelector('[data-report-summary]').innerHTML='';const summary=panel.querySelector('[data-report-content] .report-summary-grid');if(summary){panel.querySelector('[data-report-summary]').append(summary);summary.closest('.report-content')?.removeChild(summary);}} function exportReport(){if(!reportCtx.csv?.length)return;const csv=reportCtx.csv.map(row=>row.map(v=>'"'+String(v??'').replaceAll('"','""')+'"').join(',')).join('\r\n');const a=document.createElement('a');a.href=URL.createObjectURL(new Blob(['\ufeff'+csv],{type:'text/csv;charset=utf-8'}));a.download=`assetdesk-${reportCtx.type}.csv`;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),500);} function wireReports(panel){panel.querySelector('[data-report-print]').onclick=()=>window.print();panel.querySelector('[data-report-export]').onclick=exportReport;panel.addEventListener('click',e=>{const type=e.target.closest('[data-report-type]');if(type){reportCtx.type=type.dataset.reportType;reportCtx.selectedAsset=null;renderReport(panel);return}const asset=e.target.closest('[data-report-asset]');if(asset){reportCtx.selectedAsset=asset.dataset.reportAsset;renderReport(panel);}});panel.addEventListener('input',e=>{const field=e.target.closest('[data-report-filter]');if(!field)return;reportCtx.filters[field.dataset.reportFilter]=field.value;renderReport(panel);});panel.addEventListener('change',e=>{const field=e.target.closest('[data-report-filter]');if(!field)return;reportCtx.filters[field.dataset.reportFilter]=field.value;renderReport(panel);});renderReport(panel);} function enhanceReports(){if(!reportCtx.user||reportCtx.user.role==='office'||reportPage()!=='Reports')return;let panel=document.querySelector('[data-report-center]');if(panel)return;const old=[...document.querySelectorAll('section.card')].find(s=>s.textContent.includes('Office allocation report'));if(!old)return;document.querySelector('main > .stats')?.remove();const holder=document.createElement('div');holder.innerHTML=reportShell();panel=holder.firstElementChild;old.replaceWith(panel);wireReports(panel);} const reportObserver=new MutationObserver(()=>requestAnimationFrame(enhanceReports));reportObserver.observe(document.body,{childList:true,subtree:true});setInterval(async()=>{if(!reportCtx.user){if(await reportLoad())enhanceReports();}else enhanceReports();},1500);reportLoad().then(enhanceReports).catch(()=>{});