const API='./api/index.php'; const ctx={csrf:'',user:null,data:null,loading:false,loaded:false}; const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const active=r=>Number(r?.active)!==0; const page=()=>document.querySelector('.breadcrumb strong')?.textContent.trim()||''; async function api(action,data){const r=await fetch(`${API}?action=${action}`,{method:data?'POST':'GET',credentials:'same-origin',headers:data?{'Content-Type':'application/json','X-CSRF-Token':ctx.csrf}:undefined,body:data?JSON.stringify(data):undefined});const j=await r.json().catch(()=>({error:'Server returned an invalid response.'}));if(j.csrf)ctx.csrf=j.csrf;if(!r.ok)throw Error(j.error||'Request failed');return j;} async function load(){try{const s=await api('session');ctx.user=s.user;if(!ctx.user||ctx.user.role==='office')return false;ctx.data=(await api('data')).data;ctx.loaded=true;return true}catch{return false}} const canPermission=permission=>ctx.user?.role==='admin'||(ctx.user?.permissions?.actions||[]).includes(permission); function field(label,name,value='',type='text',extra=''){return ``} function select(label,name,value,options,required=true){return ``} function formFor(entity,r){ if(entity==='office')return field('Office name','name',r.name)+field('Office code','code',r.code)+field('City','city',r.city)+field('Contact person','contact',r.contact)+field('Phone','phone',r.phone); if(entity==='vendor')return field('Vendor name','name',r.name)+field('Contact person','contact',r.contact)+field('Phone','phone',r.phone)+field('NTN','ntn',r.ntn)+field('STRN','strn',r.strn); if(entity==='asset')return field('Asset name','name',r.name)+field('Category','category',r.category)+field('Serial number','serial',r.serial)+select('Condition','condition',r.condition,['New','Good','Fair','Needs repair'].map(x=>[x,x]))+field('Warranty expiry','warranty',r.warranty||'','date'); if(entity==='stock')return field('Item name','name',r.name)+field('Unit','unit',r.unit)+field('Reorder level','minimum',r.minimum,'number','min="0"'); const offices=(ctx.data?.offices||[]).filter(active).map(o=>[o.id,o.name]); return field('Full name','name',r.name)+field('Email','email',r.email,'email')+select('Role','role',r.role,['admin','store','office'].map(x=>[x,x]))+select('Assigned office','office_id',r.office_id,offices,false); } function openEditor(entity,r){ const title={office:'Edit office',vendor:'Edit vendor',asset:'Edit asset',stock:'Edit stock item',user:'Edit user'}[entity]; const back=document.createElement('div');back.className='crud-backdrop';back.innerHTML=`
ASSETDESK RECORD

${title}

Changes are saved immediately and recorded in the audit log.

${formFor(entity,r)}

`; document.body.append(back);const form=back.querySelector('form');const close=()=>back.remove();back.querySelector('.crud-close').onclick=close;back.querySelector('.crud-cancel').onclick=close;back.addEventListener('click',e=>{if(e.target===back)close()}); form.onsubmit=async e=>{e.preventDefault();const button=form.querySelector('button[type=submit]');button.disabled=true;const d=Object.fromEntries(new FormData(form));d.id=r.id;d.active=active(r)?'1':'0';if(entity==='user'&&d.role!=='office')d.office_id='';try{await api(`${entity}_update`,d);location.reload()}catch(err){form.querySelector('.crud-error').textContent=err.message;button.disabled=false}}; } async function toggle(entity,r){const next=!active(r);const label=next?'activate':'deactivate';if(!confirm(`Are you sure you want to ${label} this record?`))return;try{await api('set_active',{entity,id:r.id,active:next?'1':'0'});location.reload()}catch(e){alert(e.message)}} async function removeRecord(entity,r){if(!confirm('Delete this record permanently? Linked records will block deletion.'))return;try{await api('delete_record',{entity,id:r.id});location.reload()}catch(e){alert(e.message)}} function controls(entity,r){const bar=document.createElement('div');bar.className='crud-action-bar';const state=document.createElement('span');state.className=`crud-state ${active(r)?'is-active':'is-inactive'}`;state.textContent=active(r)?'Active':'Inactive';bar.append(state);const editPermission={office:'office_edit',vendor:'vendor_edit',asset:'asset_edit',stock:'stock_edit',user:'user_edit'}[entity];if(canPermission(editPermission)){const edit=document.createElement('button');edit.className='crud-button edit';edit.textContent='Edit';edit.onclick=e=>{e.stopPropagation();openEditor(entity,r)};bar.append(edit)}if(canPermission('record_status')&&entity!=='user'||ctx.user?.role==='admin'){const change=document.createElement('button');change.className='crud-button toggle';change.textContent=active(r)?'Deactivate':'Activate';change.onclick=e=>{e.stopPropagation();toggle(entity,r)};bar.append(change)}if(canPermission('record_delete')&&(entity!=='user'||ctx.user?.role==='admin')){const del=document.createElement('button');del.className='crud-button delete';del.textContent='Delete';del.onclick=e=>{e.stopPropagation();removeRecord(entity,r)};bar.append(del)}return bar} function enhanceCards(selector,entity,rows){document.querySelectorAll(selector).forEach(card=>{if(card.dataset.crudEnhanced)return;const heading=card.querySelector('h2')?.textContent.trim();const r=rows.find(x=>x.name===heading);if(!r)return;card.dataset.crudEnhanced='1';card.append(controls(entity,r))})} function enhance(){if(!ctx.user||!ctx.data)return;const p=page();if(p==='Offices')enhanceCards('.office-grid .office-card','office',ctx.data.offices||[]);if(p==='Inventory')enhanceCards('.office-grid .office-card','stock',ctx.data.stock||[]);if(p==='Vendors')document.querySelectorAll('.table-wrap tbody tr').forEach(row=>{if(row.dataset.crudEnhanced)return;const name=row.cells[0]?.textContent.trim();const r=(ctx.data.vendors||[]).find(x=>x.name===name);if(!r)return;row.dataset.crudEnhanced='1';const cell=document.createElement('td');cell.append(controls('vendor',r));row.append(cell)});if(p==='Assets')document.querySelectorAll('.table-wrap tbody tr').forEach(row=>{if(row.dataset.crudEnhanced)return;const tag=row.querySelector('.asset-cell small')?.textContent.split('·')[0].trim();const r=(ctx.data.assets||[]).find(x=>x.tag===tag);if(!r)return;row.dataset.crudEnhanced='1';const cell=document.createElement('td');cell.append(controls('asset',r));row.append(cell)});if(p==='Users & access'&&ctx.user.role==='admin')document.querySelectorAll('.table-wrap tbody tr').forEach(row=>{if(row.dataset.crudEnhanced)return;const email=row.cells[1]?.textContent.trim();const r=(ctx.data.users||[]).find(x=>x.email===email);if(!r)return;row.dataset.crudEnhanced='1';row.lastElementChild?.append(controls('user',r))})} const observer=new MutationObserver(()=>requestAnimationFrame(enhance));observer.observe(document.body,{childList:true,subtree:true}); setInterval(async()=>{if(!ctx.user||!ctx.loaded){if(await load())enhance()}},1500);load().then(enhance);