Fix My Block — Bengaluru | Prital Printing
BENGALURU • COMMUNITY CIVIC ACTION • PRITAL PRINTING INITIATIVE

See it.
Report it.
Track it.

Turn neighbourhood problems into clear, trackable civic reports. Add a photo, landmark and location, get a unique Fix My Block ID, and follow the progress from report to resolution.

🔐 Private phone data📍 GPS-ready🆔 Unique tracking ID📱 Admin-controlled sharing
OPEN INSTAGRAM
Transparent by design: Fix My Block is a community reporting initiative by Prital Printing and is not a government department unless explicitly stated otherwise.

Live community pulse

Reports
Resolved
Today
Urgent open
Supported by Prital Printing

Your report is stored with a private phone hash. Public visitors see the civic issue, not your phone number.

Report a problem

It takes about a minute.

One clear photo is enough. It is resized before upload.
Location coordinates not captured yet.
Please do not submit private, defamatory or emergency information. For immediate danger, contact the appropriate emergency service.

What happens next?

1 • REPORTED — Your issue gets a Fix My Block ID.
2 • VERIFIED — Useful reports can be checked.
3 • ACTION — Staff/community follow-up can be recorded.
4 • RESOLVED — A resolution can be recorded.
5 • CITIZEN CHECK — Residents can confirm whether it is still present.
Privacy: phone numbers are kept in the private backend and are not included in the public report feed.

Community map

Explore reported issues across Bengaluru.

Loading reports…

Track a report

Enter your Fix My Block ID.

Community

Small confirmations help make reports more useful.

How to make a strong report

📍 Give a specific landmark
📷 Show the problem clearly
📝 Describe what is happening
⚠️ Use severity honestly
🔄 Check the status later
✅ Confirm an issue if it is still present

Community impact

Total reports
Resolved
Open
Reported today

Support Fix My Block

Optional support for hosting, coding, maintenance and future platform improvements.

A voluntary contribution

Help keep the platform running

Fix My Block is being developed and maintained by Prital Printing. You never need to pay to report an issue. If you find the platform useful, you may optionally contribute toward development and maintenance.

No contribution is required.
Reporting civic issues and tracking reports remain available without payment.
Choose an amount
Scan to pay
UPI QR code for Fix My Block support contribution
Preparing QR…
Scan with any supported UPI app
8197255861@fam
The QR includes the selected amount. Check the payment screen before confirming.
UPI ID: 8197255861@fam • Payments open your UPI app when supported by your device.
Simple & transparent

How the contribution works

1 • Choose any amount you like.
2 • Use the UPI button to pay.
3 • Tap “I've completed payment”.
4 • Prital Printing confirms the payment.
5 • A clean acknowledgement / receipt can be printed.
Important: the website does not independently verify a UPI transfer. Your contribution stays pending until it is confirmed by an authorised Owner/Admin.

Owner / Admin & Staff Command Centre

Private operations dashboard. Owner/Admin can manage staff accounts, permissions, reward codes and system sharing settings.

PRIVATE OPERATIONS
Authorised access only
Checking backend connection…
Backend: https://script.google.com/macros/s/AKfycbx5_zqfkilNOBlUg02xu_3kkA-5-KsxUAdGtJu4Lr1m0qwqVrLWpAmK7-nUGMkbkX5Z/exec
Fix My Block • Bengaluru • A Prital Printing initiative
Community reporting platform — not a government department unless explicitly stated. • Voluntary support is never required to report an issue.
`); w.document.close(); setTimeout(()=>w.print(),250); } async function createContribution(){ const amount=tipAmount(); if(!amount||amount<1||amount>1000000){toast('Enter a contribution between ₹1 and ₹10,00,000.',false);return} const btn=$('tipStartBtn');btn.disabled=true;btn.textContent='CREATING…'; try{ const contributionId='FMB-TIP-'+new Date().getFullYear()+String(Date.now()).slice(-8)+Math.floor(Math.random()*90+10); await postJSON({action:'submitContribution',contributionId,amount,name:$('tipName').value.trim(),phone:cleanPhone($('tipPhone').value),note:$('tipNote').value.trim(),upiId:SUPPORT_CONFIG.upi}); const saved=await waitFor(async()=>{const d=await jsonp({action:'getContribution',id:contributionId});return d?.contribution||null},12,700); if(!saved)throw new Error('contribution_not_verified'); currentContribution=saved; $('tipResult').classList.remove('hidden'); $('tipResult').innerHTML=`Contribution ID created: ${esc(saved.contributionId)}
${money(saved.amount)} • ${esc(saved.status)}
After paying, tap “I've completed payment”. Your acknowledgement remains pending until an authorised Owner/Admin confirms the transfer.
`; $('tipTrackId').value=saved.contributionId;updateQr(Number(saved.amount)); toast('Contribution ID created.'); }catch(e){toast('Could not create the contribution record. Check the live backend.',false)} finally{btn.disabled=false;btn.textContent='CREATE CONTRIBUTION ID'} } async function markContributionPaid(id){ const ref=prompt('Optional UPI transaction/reference number:',''); try{await postJSON({action:'markContributionPaid',contributionId:id,paymentRef:(ref||'').trim()});const saved=await waitFor(async()=>{const d=await jsonp({action:'getContribution',id});return d?.contribution||null},10,700);if(!saved)throw new Error('not_found');currentContribution=saved;renderContributionPublic(saved);toast('Payment marked for Owner/Admin confirmation.')}catch(e){toast('Could not update the contribution yet.',false)} } async function trackContribution(id){ id=String(id||'').trim();if(!id){toast('Enter a contribution ID.',false);return} $('tipTrackResult').innerHTML='
Checking…
'; try{const d=await jsonp({action:'getContribution',id});if(!d.contribution){$('tipTrackResult').innerHTML='
No contribution found with that ID.
';return}currentContribution=d.contribution;renderContributionPublic(d.contribution)}catch(e){$('tipTrackResult').innerHTML='
Unable to check this contribution.
'} } function renderContributionPublic(c){$('tipTrackResult').innerHTML=`
${esc(c.contributionId)}
${money(c.amount)}
${esc(c.status)}
${esc(fmt(c.createdAt))} • UPI ${esc(SUPPORT_CONFIG.upi)}
${c.status!=='Confirmed'&&c.status!=='Rejected'?``:''}
${c.status==='Confirmed'?'
Confirmed by Prital Printing. You can print/save this acknowledgement as PDF.
':c.status==='Rejected'?'
Not confirmed. Please contact Prital Printing if you believe a payment was made.
':'
Awaiting payment confirmation by an authorised Owner/Admin.
'}
`} function copyContributionId(id){navigator.clipboard?.writeText(id).then(()=>toast('Contribution ID copied.')).catch(()=>prompt('Copy this contribution ID:',id))} async function loadContributions(){ if(!(staffRole==='owner'||hasPerm('manageContributions')))return; try{const data=await jsonp({action:'ownerContributions',token:staffToken});if(!data.ok)throw new Error(data.error);window.__contributions=data.contributions||[];renderContributionAdmin();}catch(e){$('contributionAdminList').innerHTML='
Could not load contributions. Check your permission and deployment.
'} } function printContributionById(id){const c=(window.__contributions||[]).find(x=>x.contributionId===id);if(c)printContributionReceipt(c)} function renderContributionAdmin(){ const list=window.__contributions||[]; $('contributionAdminList').innerHTML=list.length?list.map(c=>`
${esc(c.contributionId)}
${money(c.amount)} • ${esc(c.name||'Anonymous')}
${esc(c.status)}
${esc(fmt(c.createdAt))}${c.phone?' • '+esc(c.phone):''}${c.paymentRef?' • Ref: '+esc(c.paymentRef):''}
${esc(c.note||'No note')}
${c.status!=='Confirmed'&&c.status!=='Rejected'?``:''}${c.phone&&hasPerm('sendWhatsApp')?``:''}
`).join(''):'
No contribution records yet.
'; } async function setContributionStatus(id,status){ if(!(staffRole==='owner'||hasPerm('manageContributions'))){toast('Contribution-management permission is disabled.',false);return} const ref=status==='Confirmed'?prompt('UPI transaction/reference number (optional):',''):''; try{await postJSON({action:'setContributionStatus',token:staffToken,contributionId:id,status,paymentRef:(ref||'').trim()});const saved=await waitFor(async()=>{const d=await jsonp({action:'ownerContributions',token:staffToken});return (d.contributions||[]).find(x=>x.contributionId===id&&x.status===status)||null},12,700);if(!saved)throw new Error('not_verified');await loadContributions();toast(status==='Confirmed'?'Contribution confirmed.':'Contribution rejected.')}catch(e){toast('Could not verify the contribution change.',false)} } function contributionWhatsApp(id){ const c=(window.__contributions||[]).find(x=>x.contributionId===id);if(!c||!c.phone)return; if(!hasPerm('sendWhatsApp')){toast('WhatsApp permission is disabled.',false);return} const text=encodeURIComponent(contributionWaText(c));window.open('https://wa.me/91'+cleanPhone(c.phone)+'?text='+text,'_blank','noopener'); } async function staffLogin(){ const role=$('staffRole').value; const password=$('staffPin').value.trim(); const username=$('staffUsername').value.trim(); if(!password){toast('Enter the password.',false);return;} if(role==='staff'&&!username){toast('Enter the staff username.',false);return;} const button=$('staffLoginBtn'); button.disabled=true; button.textContent='LOGIN…'; $('staffLoginStatus').textContent='Connecting to the secure command centre…'; try{ const key=await sha256(password); const data=await jsonp({action:'staffLogin',role,key,username}); if(!data.ok||!data.token){ const message=data.error==='staff_disabled'?'This staff account is disabled.':'Login failed. Check the username, password and deployment.'; throw new Error(message); } staffToken=data.token; staffRole=data.role||role; window.staffPerms=data.permissions||[]; window.staffName=data.name||''; sessionStorage.setItem('fmbStaffToken',staffToken); sessionStorage.setItem('fmbStaffRole',staffRole); sessionStorage.setItem('fmbStaffPerms',JSON.stringify(window.staffPerms)); $('staffPin').value=''; $('staffLogin').classList.add('hidden'); $('staffPanel').classList.remove('hidden'); $('staffRoleLabel').textContent=staffRole==='owner'?'Owner / Admin':`Staff • ${esc(window.staffName||username)}`; $('staffWelcome').textContent=staffRole==='owner'?'OWNER / ADMIN COMMAND CENTRE':'STAFF COMMAND CENTRE'; $('createCode').disabled=staffRole!=='owner'; $('staffExport').disabled=!hasPerm('exportCsv'); $('staffManagement').style.display=staffRole==='owner'?'block':'none'; $('sharingSettings').style.display=staffRole==='owner'?'block':'none'; $('contributionManagement').style.display=(staffRole==='owner'||hasPerm('manageContributions'))?'block':'none'; $('staffLoginStatus').textContent=''; await loadStaff(); await loadContributions(); toast('Secure login successful.'); }catch(error){ $('staffLoginStatus').textContent=error.message||'Login failed. Check the username, password and deployment.'; toast('Login failed.',false); }finally{ button.disabled=false; button.textContent='LOGIN SECURELY'; } } function logoutStaff(){ staffToken=''; staffRole=''; window.staffPerms=[]; window.__staffList=[]; sessionStorage.removeItem('fmbStaffToken'); sessionStorage.removeItem('fmbStaffRole'); sessionStorage.removeItem('fmbStaffPerms'); $('staffPanel').classList.add('hidden'); $('staffLogin').classList.remove('hidden'); $('sharingSettings').style.display='none'; $('staffLoginStatus').textContent='Logged out.'; } function hasPerm(permission){ return staffRole==='owner'||(window.staffPerms||[]).includes(permission); } async function loadStaff(){ if(!staffToken)return; try{ if(hasPerm('viewReports')){ const data=await jsonp({action:'staffReports',token:staffToken}); if(!data.ok)throw new Error(data.error||'staff_reports_failed'); staffReports=data.reports||[]; window.staffPerms=data.permissions||window.staffPerms||[]; renderStaff(); const stats=await jsonp({action:'staffStats',token:staffToken}); if(stats.ok){ [['aTotal','total'],['aNew','newCount'],['aProgress','inProgress'],['aResolved','resolved'],['aUrgent','urgent'],['aToday','today']] .forEach(([id,key])=>$(id).textContent=stats.stats[key]??0); } const audit=await jsonp({action:'audit',token:staffToken}); $('auditList').innerHTML=(audit.events||[]) .map(item=>`
${fmt(item.timestamp)} • ${esc(item.actor)} • ${esc(item.action)}
${esc(item.details)}
`) .join('')||'
No recent activity.
'; }else{ staffReports=[]; renderStaffNoPermission(); } if(staffRole==='owner'){ await loadStaffAccounts(); await loadCodes(); } }catch(error){ if(String(error.message||'').includes('unauthorized')){ logoutStaff(); toast('Your session expired. Please log in again.',false); }else{ toast('Staff data could not be loaded. Check the Apps Script deployment.',false); } } } function renderStaffNoPermission(){ $('staffList').innerHTML='
This staff account does not have permission to view reports. Your enabled tools remain available below.
'; ['aTotal','aNew','aProgress','aResolved','aUrgent','aToday'].forEach(id=>$(id).textContent='—'); $('auditList').innerHTML='
Report viewing permission is disabled for this account.
'; } function renderStaff(){ const q=$('staffSearch').value.toLowerCase(); const status=$('staffStatus').value; const severity=$('staffSeverity').value; const filtered=staffReports.filter(report=> (!q||[report.id,report.publicId,report.type,report.location,report.description,report.name,report.phone].join(' ').toLowerCase().includes(q))&& (!status||report.status===status)&& (!severity||report.severity===severity) ); $('staffList').innerHTML=filtered.length?filtered.map(report=>`
${esc(report.publicId||report.id)}
${esc(report.type)}
${esc(report.location)}${report.phone?' • '+esc(report.phone):''}
${esc(report.status)}

${esc(report.description)}

${esc(report.severity)} • ${fmt(report.createdAt)}
${report.name?`
Reporter: ${esc(report.name)}
`:''} ${report.department?`
Assignment: ${esc(report.department)}${report.ward?' • '+esc(report.ward):''}${report.zone?' • '+esc(report.zone):''}
`:''} ${report.hasPhoto?'
📷 Photo attached
':''} ${report.latestUpdate?`
Latest update: ${esc(report.latestUpdate.message||'')}
`:''}
${hasPerm('changeStatus')?``:''} ${hasPerm('addUpdate')?``:''} ${hasPerm('assign')?``:''} ${hasPerm('instagram')?``:''} ${hasPerm('sendWhatsApp')?``:''}
`).join(''):'
No reports match these filters.
'; } async function reportWhatsApp(id){ if(!hasPerm('sendWhatsApp')){toast('WhatsApp permission is disabled for this account.',false);return;} const report=staffReports.find(item=>item.id===id); if(!report)return; try{ await postJSON({action:'socialAction',token:staffToken,kind:'whatsapp',reportId:report.id}); }catch(_){ } openWhatsApp(report); } async function staffWrite(body){ await postJSON({...body,token:staffToken}); await new Promise(resolve=>setTimeout(resolve,750)); await loadStaff(); await loadReports(); } function staffStatus(id,status){ if(!hasPerm('changeStatus')){toast('Status permission is disabled.',false);return;} if(status)staffWrite({action:'setReportStatus',id,status}); } function staffUpdate(id){ if(!hasPerm('addUpdate')){toast('Update permission is disabled.',false);return;} const report=staffReports.find(item=>item.id===id); const message=prompt('Add the public update for this report:',report?.latestUpdate?.message||''); if(message&&message.trim()){ staffWrite({action:'addReportUpdate',reportId:id,status:report.status,message:message.trim()}); } } function staffAssign(id){ if(!hasPerm('assign')){toast('Assignment permission is disabled.',false);return;} const report=staffReports.find(item=>item.id===id); const department=prompt('Department / team:',report?.department||''); if(department!==null){ staffWrite({action:'setReportAssignment',id,department:department||'',ward:report?.ward||'',zone:report?.zone||''}); } } async function instagram(id){ if(!hasPerm('instagram')){toast('Instagram permission is disabled for this account.',false);return;} const report=staffReports.find(item=>item.id===id); if(!report)return; const caption=`🚨 FIX MY BLOCK — COMMUNITY ISSUE\n\n🆔 Report ID: ${report.publicId||report.id}\n🛠 Issue: ${report.type||''}\n⚠️ Severity: ${report.severity||''}\n📍 Location: ${report.location||''}\n📌 Status: ${report.status||'Reported'}\n\n${report.description||''}\n\nPlease report local civic issues through Fix My Block.\n📷 ${APP_CONFIG.instagram}\n\n#FixMyBlock #Bengaluru #CivicAction #PritalPrinting`; try{await postJSON({action:'socialAction',token:staffToken,kind:'instagram',reportId:report.id});}catch(_){ } try{await navigator.clipboard.writeText(caption);}catch(_){ } window.open(APP_CONFIG.instagram,'_blank','noopener'); toast('Instagram caption copied. Instagram opened — review and publish manually.'); } function exportCSV(){ if(!hasPerm('exportCsv')){toast('Export permission is disabled for this account.',false);return;} const columns=['publicId','type','severity','location','description','name','phone','status','createdAt']; const quote=value=>`"${String(value??'').replace(/"/g,'""')}"`; const csv=[columns.join(','),...staffReports.map(report=>columns.map(column=>quote(report[column])).join(','))].join('\\n'); const link=document.createElement('a'); link.href=URL.createObjectURL(new Blob([csv],{type:'text/csv;charset=utf-8'})); link.download='fix-my-block-reports.csv'; link.click(); setTimeout(()=>URL.revokeObjectURL(link.href),1000); } /* ----------------------------- STAFF ACCOUNTS ---------------------------- */ async function loadStaffAccounts(){ if(staffRole!=='owner')return; try{ const data=await jsonp({action:'staffList',token:staffToken}); if(!data.ok)throw new Error(data.error||'staff_list_failed'); window.__staffList=data.staff||[]; $('staffAccounts').innerHTML=window.__staffList.length?window.__staffList.map(staff=>`
${esc(staff.username)}
${esc(staff.name||'No name')}${staff.phone?' • '+esc(staff.phone):''}
${(staff.permissions||[]).map(permission=>`${esc(permission)}`).join(' ')||'No permissions'}
${staff.active?'ACTIVE':'DISABLED'}
`).join(''):'
No individual staff accounts created yet.
'; }catch(error){ $('staffAccounts').innerHTML='
Could not load staff accounts. Check the Apps Script deployment.
'; } } function createStaffEditorModal(){ if($('fmbStaffEditor'))return $('fmbStaffEditor'); const modal=document.createElement('div'); modal.id='fmbStaffEditor'; modal.className='modalWrap hidden'; modal.innerHTML=`
EDIT STAFF ACCOUNT
Permissions
`; document.body.appendChild(modal); return modal; } function editStaffAccount(staffId){ if(staffRole!=='owner')return; const staff=(window.__staffList||[]).find(item=>item.staffId===staffId); if(!staff)return; const modal=createStaffEditorModal(); const permissions=[ ['viewReports','View reports'],['changeStatus','Change status'],['addUpdate','Add updates'],['assign','Assign teams'], ['instagram','Instagram post tools'],['sendWhatsApp','Send WhatsApp'],['exportCsv','Export CSV'],['redeemCodes','Redeem codes'],['manageContributions','Manage contributions'] ]; $('editStaffSubtitle').textContent=`@${staff.username}`; $('editStaffName').value=staff.name||''; $('editStaffPhone').value=staff.phone||''; $('editStaffPassword').value=''; $('editStaffActive').checked=staff.active; $('editStaffPerms').innerHTML=permissions.map(([key,label])=>``).join(''); $('saveStaffEdit').onclick=async()=>{ const selected=[...document.querySelectorAll('[data-edit-perm]:checked')].map(input=>input.dataset.editPerm); const payload={ action:'updateStaff', token:staffToken, staffId, name:$('editStaffName').value.trim(), phone:cleanPhone($('editStaffPhone').value), active:$('editStaffActive').checked, permissions:JSON.stringify(selected) }; const password=$('editStaffPassword').value; try{ if(password)payload.passwordHash=await sha256(password); await postJSON(payload); const updated=await waitFor(async()=>{ const data=await jsonp({action:'staffList',token:staffToken}); const found=(data.staff||[]).find(item=>item.staffId===staffId); return found&&found.active===payload.active&&JSON.stringify(found.permissions||[])===JSON.stringify(selected)?found:null; },10,700); if(!updated)throw new Error('staff_update_not_verified'); closeStaffEditor(); await loadStaffAccounts(); toast('Staff controls updated successfully.'); }catch(error){ toast('Staff changes could not be verified. Check the deployment.',false); } }; modal.classList.remove('hidden'); } function closeStaffEditor(){ const modal=$('fmbStaffEditor'); if(modal)modal.classList.add('hidden'); } async function createStaffAccount(){ if(staffRole!=='owner'){toast('Owner/Admin access is required.',false);return;} const username=$('newStaffUsername').value.trim().toLowerCase(); const name=$('newStaffName').value.trim(); const phone=cleanPhone($('newStaffPhone').value); const password=$('newStaffPassword').value; const permissions=[...document.querySelectorAll('[data-perm]:checked')].map(input=>input.dataset.perm); if(!username||!password){toast('Enter a username and temporary password.',false);return;} try{ const passwordHash=await sha256(password); await postJSON({action:'createStaff',token:staffToken,username,name,phone,passwordHash,permissions:JSON.stringify(permissions)}); const created=await waitFor(async()=>{ const data=await jsonp({action:'staffList',token:staffToken}); return (data.staff||[]).find(item=>item.username===username)||null; },12,750); if(!created)throw new Error('staff_create_not_verified'); await loadStaffAccounts(); $('staffCreateResult').textContent=`${created.name||created.username} was created successfully. Social permissions remain ${created.permissions.includes('instagram')||created.permissions.includes('sendWhatsApp')?'enabled as selected':'OFF'} for this account.`; $('newStaffUsername').value=''; $('newStaffName').value=''; $('newStaffPhone').value=''; $('newStaffPassword').value=''; toast('Staff account created successfully.'); }catch(error){ toast('Could not verify staff account creation. Check the Apps Script deployment.',false); } } /* ------------------------------- REWARDS -------------------------------- */ async function loadCodes(){ if(staffRole!=='owner')return; try{ const data=await jsonp({action:'staffCodes',token:staffToken}); window.__codes=data.codes||[]; }catch(_){ window.__codes=[]; } } async function createReward(){ if(staffRole!=='owner'){toast('Owner/Admin access is required.',false);return;} const code=$('rewardCode').value.trim().toUpperCase(); const label=$('rewardLabel').value.trim(); const phone=cleanPhone($('rewardPhone').value); if(!code||!label){toast('Enter a reward code and label.',false);return;} try{ await postJSON({action:'createCode',token:staffToken,code,label,phone}); const created=await waitFor(async()=>{ const data=await jsonp({action:'staffCodes',token:staffToken}); return (data.codes||[]).find(item=>item.code===code)||null; },10,700); if(!created)throw new Error('code_create_not_verified'); $('codeResult').textContent=`${created.code} created successfully — ${created.label}.`; $('rewardCode').value=''; $('rewardLabel').value=''; $('rewardPhone').value=''; await loadCodes(); toast('Reward code created successfully.'); }catch(error){ toast('Could not verify reward code creation.',false); } } async function redeemReward(){ if(!hasPerm('redeemCodes')){toast('Redeem-code permission is disabled.',false);return;} const code=$('redeemCode').value.trim().toUpperCase(); if(!code){toast('Enter a reward code.',false);return;} try{ const data=await jsonp({action:'redeemCode',token:staffToken,code}); $('redeemResult').textContent=data.ok?(data.alreadyRedeemed?`Already redeemed — ${data.label||code}.`:`Redeemed successfully — ${data.label||code}.`):(data.error||'Could not redeem the code.'); }catch(error){ toast('Could not redeem the code.',false); } } /* ------------------------------ EVENT WIRING ----------------------------- */ $('staffLoginBtn').onclick=staffLogin; $('staffLogout').onclick=logoutStaff; $('staffRefresh').onclick=async()=>{await loadStaff();await loadContributions();}; $('staffExport').onclick=exportCSV; $('staffSearch').oninput=renderStaff; $('staffStatus').onchange=renderStaff; $('staffSeverity').onchange=renderStaff; $('createCode').onclick=createReward; $('redeemBtn').onclick=redeemReward; $('createStaffBtn').onclick=createStaffAccount; $('tipAmount').oninput=updateTipButtons;document.querySelectorAll('.tipBtn').forEach(btn=>btn.onclick=()=>{$('tipAmount').value=btn.dataset.tip;updateTipButtons()});$('tipPayBtn').onclick=()=>openUpi(tipAmount()||10);$('openUpiFromQr').onclick=()=>openUpi(tipAmount()||10);$('copyUpiBtn').onclick=()=>navigator.clipboard?.writeText(SUPPORT_CONFIG.upi).then(()=>toast('UPI ID copied.')).catch(()=>prompt('Copy this UPI ID:',SUPPORT_CONFIG.upi));$('tipForm').onsubmit=e=>{e.preventDefault();createContribution()};$('tipTrackBtn').onclick=()=>trackContribution($('tipTrackId').value);$('tipTrackId').onkeydown=e=>{if(e.key==='Enter')trackContribution($('tipTrackId').value)};$('refreshContributions').onclick=loadContributions; $('saveSharing').onclick=()=>{ if(staffRole!=='owner'){toast('Owner/Admin access is required.',false);return;} localStorage.setItem('fmb_admin_wa',$('adminWa').value.replace(/\D/g,'')); localStorage.setItem('fmb_public_site_url',$('publicSiteUrl').value.trim()); syncSocialLinks(); $('sharingResult').textContent='Owner social settings saved on this admin device. WhatsApp targets the reporter when their phone is available.'; toast('Owner social settings saved.'); }; if(window.L){ map=L.map('map',{scrollWheelZoom:false}).setView([12.9716,77.5946],11); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19,attribution:'© OpenStreetMap contributors'}).addTo(map); layer=L.layerGroup().addTo(map); }else{ $('map').innerHTML='
Map service is unavailable right now. Reports and tracking still work.
'; map=null;layer=null; } window.staffPerms=JSON.parse(sessionStorage.getItem('fmbStaffPerms')||'[]'); syncSocialLinks(); $('upiIdDisplay').textContent=SUPPORT_CONFIG.upi;updateTipButtons(); async function checkBackend(){ try{ const data=await jsonp({action:'health'}); if(data.ok&&data.version===APP_VERSION){ $('staffLoginStatus').textContent='Backend online • '+data.version; return true; } $('staffLoginStatus').textContent='Backend responded with version '+(data.version||'unknown')+'. Redeploy the FINAL COMPLETE backend.'; return false; }catch(error){ $('staffLoginStatus').textContent='Backend connection unavailable. Check the Apps Script /exec deployment.'; return false; } } if(staffToken){ $('staffLogin').classList.add('hidden'); $('staffPanel').classList.remove('hidden'); $('staffRoleLabel').textContent=staffRole==='owner'?'Owner / Admin':'Staff'; $('staffWelcome').textContent=staffRole==='owner'?'OWNER / ADMIN COMMAND CENTRE':'STAFF COMMAND CENTRE'; $('staffManagement').style.display=staffRole==='owner'?'block':'none'; $('sharingSettings').style.display=staffRole==='owner'?'block':'none'; $('contributionManagement').style.display=(staffRole==='owner'||hasPerm('manageContributions'))?'block':'none'; $('staffExport').disabled=!hasPerm('exportCsv'); loadContributions(); } checkBackend().finally(()=>{ if(staffToken)loadStaff(); loadReports(); });