Review provider documents before approving. Approved providers appear to customers immediately.
Provider Approval Queue
ID
Name
Mobile
Services
Location
ID Proof
Date
Status
Actions
All Providers
ID
Name
Mobile
Services
Location
Rating
Bookings
Earned
Commission
Net Earned
Pending WD
Available
Status
Actions
💳 Payment Flow
Customer Pays Full Amount→Platform Deducts Commission→Net Credited to Provider Wallet→Provider Requests Withdrawal→Admin Approves Here 👇→Bank Transfer Done
💰
₹0
Total GMV
All customer payments
✂
₹0
Platform Commission
Our revenue
💼
₹0
Provider Wallets Total
GMV minus commission
✅
₹0
Paid Out
Approved & transferred
⌛
₹0
Pending Payouts
Needs your approval
🏦
₹0
Platform Balance
Total commission earned
💸 Withdrawal Requests 0
⚠ Once approved, amount is marked paid. You must manually transfer to the provider account shown.
Request ID
Provider
Amount
Method
Account / UPI
Requested
Status
Action
📊 Commission Ledger — Every Booking
Order ID
Provider
Service
Customer Paid
Comm%
Platform Gets
Provider Gets
Date
Penalties are deducted from the provider next payout. Repeated violations may lead to suspension.
Use this before going live to clear all test data. This will delete all providers (freeing their emails for re-registration), all orders, all customer profiles, penalties, and payouts. Admin credentials and service catalog will be preserved.
Provider Details
Services Offered
Booking History
⚠️ Data Management
Permanently delete data from Firebase. This cannot be undone. Services and prices are preserved.
All Bookings & Orders
Clears bookings/ and active_bookings/ from Firebase
All Customers
Clears customers/ from Firebase
All Providers
Clears providers/ from Firebase (cannot be recovered)
OTP Records
Clears job_otp/ from Firebase
Payments & Withdrawals
Clears payouts/ and payout_requests/ from Firebase
Ratings & Reviews
Clears reviews/ and provider_reviews/ from Firebase
Balance Sheet
Clears hs_balance_sheet/ from Firebase
Service Catalog
Clears hs_catalog/ from Firebase
Service Prices
Clears hs_service_prices/ from Firebase
Tracking Data
Clears tracking/ from Firebase
⚡ Erase Everything
Clears ALL data — bookings, payments, reviews, catalog, providers, customers
Add New Service
This service will be available for providers to offer
Provider Details
Services Offered
Booking History
Add New Service
This service will be available for providers to offer
= 1) return c;
} catch(e){}
// Nothing in storage — use and store defaults
lsSet('hs_catalog', DEFAULT_CATALOG);
return DEFAULT_CATALOG;
}
function saveCatalog(c){
lsSet('hs_catalog',c);
showToast('💾 Saving...');
// Write to catalog.json via PHP
var x=new XMLHttpRequest();
x.open('POST','save-catalog.php',true);
x.setRequestHeader('Content-Type','application/json');
x.timeout=8000;
x.onload=function(){
try{
var r=JSON.parse(x.responseText);
showToast(r.ok?'✅ Saved! All pages updated.':'❌ '+(r.error||'Save failed'));
}catch(e){showToast('❌ Server error');}
};
x.onerror=x.ontimeout=function(){showToast('❌ Network error');};
x.send(JSON.stringify({catalog:c}));
}
function loadCatalogFromServer(){
var x=new XMLHttpRequest();
x.open('GET','catalog.json?_='+Date.now(),true);
x.timeout=5000;
x.onload=function(){
try{
var d=JSON.parse(x.responseText);
if(d&&Array.isArray(d)&&d.length>=1){
// Server has data — use it
lsSet('hs_catalog',d);
loadCatalog();
loadCommission();
} else {
// catalog.json is empty or missing — push DEFAULT_CATALOG to server
saveCatalog(DEFAULT_CATALOG);
}
}catch(e){
// Parse error — push defaults
saveCatalog(DEFAULT_CATALOG);
}
};
x.onerror=x.ontimeout=function(){
// Server unreachable — already showing from DEFAULT_CATALOG, nothing to do
};
x.send();
}
// getAdminCreds: defined below
function getCustomers(){return lsGet('hs_customers',[]);}
function saveCustomers(c){lsSet('hs_customers',c);}
function getPenalties(){return lsGet('hs_penalties',[]);}
function savePenalties(p){lsSet('hs_penalties',p);}
function getPayouts(){return lsGet('hs_payouts',[]);}
function savePayouts(p){lsSet('hs_payouts',p);}
function getAllOrders(){
// Read from localStorage cache (populated by syncFromFirebase)
var bks = lsGet('hs_all_bookings', []);
return bks;
}
function syncAllOrders(cb) {
// Load ALL bookings from Firebase directly
var all = [];
var done = 0;
var total = 2;
function finish() {
done++;
if (done < total) return;
// Dedup by id
var seen = new Set();
all = all.filter(function(b) {
var k = b.id; if (!k || seen.has(k)) return false; seen.add(k); return true;
});
all.sort(function(a,b){ return new Date(b.createdAt||0) - new Date(a.createdAt||0); });
lsSet('hs_all_bookings', all);
if (cb) cb(all);
}
// bookings/
fbGet('bookings', function(err, data) {
if (!err && data && !data.error) {
toArr(data).filter(function(b){ return b && b.id; }).forEach(function(b){ all.push(b); });
}
finish();
});
// active_bookings/
fbGet('active_bookings', function(err, data) {
if (!err && data && !data.error) {
toArr(data).filter(function(b){ return b && b.id; }).forEach(function(b){
var ei = all.findIndex(function(x){ return x.id === b.id; });
if (ei === -1) all.push(b);
else all[ei] = Object.assign({}, all[ei], b);
});
}
finish();
});
}
// ── Sync from Firebase (background) ─────────────────────────────────────────
// ── Always read providers from Firebase (never localStorage) ─────────────────
function fbGetProviders(cb){
_fbTok = ''; // always fresh token
fbGet('providers', function(err, data){
if(err || !data || data.error){
console.warn('fbGetProviders error:', err||data);
cb(lsGet('hs_providers',[])); // fallback to cache
return;
}
var arr = toArr(data).filter(function(p){ return p && p.id; });
cb(arr);
});
}
// ── syncFromFirebase: now just calls fbGetProviders ───────────────────────────
function syncFromFirebase(){
var banner = document.getElementById('syncBanner');
if(banner){ banner.style.display='block'; banner.innerHTML='⏳ Loading from Firebase...'; }
// Providers — always from Firebase
fbGetProviders(function(arr){
if(banner) banner.style.display='none';
lsSet('hs_providers', arr);
loadApprovals(currentApprovalFilter||'pending');
loadProviders(null,null);
loadDashboard();
showToast(arr.length ? '✅ '+arr.length+' providers loaded' : 'ℹ️ No providers yet');
});
// Customers
fbGet('customers', function(err, data){
if(!err && data && !data.error){
var arr=toArr(data).filter(function(c){ return c&&(c.id||c.uid); });
if(arr.length){ lsSet('hs_customers',arr); loadCustomers(null); }
}
});
// Bookings — load from both paths
syncAllOrders(function(orders){
loadOrders(null, null);
});
// Payouts
fbGet('payouts', function(err, data){
if(!err && data && !data.error){
var arr=toArr(data).filter(function(p){ return p&&p.id; });
if(arr.length){ lsSet('hs_payouts',arr); loadPayments('pending'); }
}
});
}
// ── Utilities ────────────────────────────────────────────────────────────────
function cap(s){return s?s.charAt(0).toUpperCase()+s.slice(1):'';}
function fmt(n){return Number(n||0).toLocaleString('en-IN');}
function showToast(msg){
var t=document.getElementById('toast');
if(!t)return;
t.textContent=msg;t.style.display='block';
clearTimeout(t._t);t._t=setTimeout(function(){t.style.display='none';},3500);
}
function closeModal(id){var m=document.getElementById(id);if(m)m.classList.remove('show');}
// ── Login ────────────────────────────────────────────────────────────────────
// doAdminLogin: defined below
// ── Navigation ───────────────────────────────────────────────────────────────
var PAGE_TITLES={dashboard:'Dashboard',analytics:'Analytics',approvals:'Provider Approvals',providers:'All Providers',payments:'Payments & Payouts',penalties:'Penalties',services:'Service Catalog',commission:'Commission Rules',orders:'All Orders',customers:'Customers',settings:'Settings'};
function showPage(name){
document.querySelectorAll('.page').forEach(function(p){p.classList.remove('active');});
document.querySelectorAll('.nav-item').forEach(function(n){n.classList.remove('active');});
var pg=document.getElementById('page-'+name);if(pg)pg.classList.add('active');
var nav=document.getElementById('nav-'+name);if(nav)nav.classList.add('active');
var pt=document.getElementById('pageTitle');if(pt)pt.textContent=PAGE_TITLES[name]||name;
var sb=document.getElementById('sidebar');if(sb)sb.classList.remove('open');
// Render pricing cards when services page opens
if(name==='services') { setTimeout(prRenderCards, 50); }
}
// ── INIT ─────────────────────────────────────────────────────────────────────
function initAdmin(){
var dt = document.getElementById('topbarDate');
if(dt) dt.textContent = new Date().toLocaleDateString('en-IN',{weekday:'short',day:'numeric',month:'short',year:'numeric'});
var sc = document.getElementById('set_user');
if(sc) sc.value = getAdminCreds().user;
// Load from localStorage first (instant)
loadCatalog();
loadCommission();
loadDashboard();
loadAnalytics();
loadApprovals('pending');
loadProviders(null, null);
loadOrders(null, null);
loadPayments('pending');
loadPenalties();
loadCustomers(null);
showToast('⏳ Loading latest data from Firebase…');
// Sync from Firebase (async — re-renders when done)
loadCatalogFromServer();
syncFromFirebase();
// Init pricing editor
setTimeout(prRenderCards, 100);
// Auto-refresh every 30 seconds — Firebase is source of truth
setInterval(function(){
fbGetProviders(function(arr){
if(arr.length){ lsSet('hs_providers',arr); loadApprovals(currentApprovalFilter||'pending'); loadProviders(null,null); }
});
}, 30000);
}
// ── DASHBOARD ────────────────────────────────────────────────────────────────
function loadDashboard(){
var providers=getProviders();
var customers=getCustomers();
var orders=getAllOrders();
var catalog=getCatalog();
var penalties=getPenalties();
var pending=providers.filter(function(p){return !p.status||p.status==='pending';});
var approved=providers.filter(function(p){return p.status==='approved';});
var completed=orders.filter(function(o){return o.status==='completed';});
var totalGMV=completed.reduce(function(s,o){return s+(o.amount||0);},0);
var totalComm=completed.reduce(function(s,o){return s+(o.commissionAmt||0);},0);
function ds(id,v){var e=document.getElementById(id);if(e)e.textContent=v;}
ds('ds-providers',providers.length);
ds('ds-pending',pending.length);
ds('ds-customers',customers.length);
ds('ds-services',catalog.filter(function(s){return s.status==='active';}).length);
ds('ds-orders',orders.length);
ds('ds-completed',completed.length);
ds('ds-gmv','₹'+fmt(totalGMV));
ds('ds-commission','₹'+fmt(totalComm));
// Recent bookings table
var rb=document.getElementById('recentBookings');
if(rb){
var recent=orders.slice().sort(function(a,b){return (b.createdAt||0)-(a.createdAt||0);}).slice(0,10);
rb.innerHTML=recent.length?recent.map(function(o){
return '
'+o.id+'
'+(o.customerName||o.service||'—')+'
'+(o.providerName||'—')+'
₹'+(o.amount||0)+'
'+cap(o.status)+'
';
}).join(''):'
No bookings yet
';
}
}
// ── ANALYTICS ────────────────────────────────────────────────────────────────
function loadAnalytics(){
var orders=getAllOrders();
var completed=orders.filter(function(o){return o.status==='completed';});
var providers=getProviders();
var customers=getCustomers();
function ds(id,v){var e=document.getElementById(id);if(e)e.textContent=v;}
ds('an-total-orders',orders.length);
ds('an-completed',completed.length);
ds('an-providers',providers.length);
ds('an-customers',customers.length);
}
// ── CATALOG ───────────────────────────────────────────────────────────────────
var editingSvcId=null;
function loadCatalog(){
var catalog=getCatalog();
var body=document.getElementById('catalogBody');
if(!body)return;
body.innerHTML=catalog.map(function(s){
return '
'
+'
'+s.icon+'
'
+'
'+s.name+' '+s.description+'
'
+'
'+s.cat+'
'
+'
₹'+fmt(s.userPrice)+'
'
+'
₹'+fmt(s.minPrice)+'
'
+'
₹'+fmt(s.maxPrice)+'
'
+'
'+s.commission+'%
'
+'
'+cap(s.status)+'
'
+'
'
+' '
+' '
+''
+'
'
+'
';
}).join('');
}
function openAddService(){
editingSvcId=null;
var t=document.getElementById('svcModalTitle');if(t)t.textContent='Add New Service';
['svc_icon','svc_name','svc_min','svc_max','svc_default','svc_desc'].forEach(function(id){var e=document.getElementById(id);if(e)e.value='';});
var sc=document.getElementById('svc_comm');if(sc)sc.value='15';
var ss=document.getElementById('svc_status');if(ss)ss.value='active';
var sm=document.getElementById('svcModal');if(sm)sm.classList.add('show');
}
function editService(id){
var s=getCatalog().find(function(x){return x.id===id;});if(!s)return;
editingSvcId=id;
var t=document.getElementById('svcModalTitle');if(t)t.textContent='Edit Service';
var set=function(eid,val){var e=document.getElementById(eid);if(e)e.value=val||'';};
set('svc_icon',s.icon);set('svc_name',s.name);set('svc_min',s.minPrice);
set('svc_max',s.maxPrice);set('svc_default',s.defaultPrice||s.userPrice);
set('svc_comm',s.commission);set('svc_desc',s.description);
set('svc_status',s.status);set('svc_cat',s.cat);
var sm=document.getElementById('svcModal');if(sm)sm.classList.add('show');
}
function toggleService(id){
var c=getCatalog();
var idx=c.findIndex(function(x){return x.id===id;});
if(idx>-1){c[idx].status=c[idx].status==='active'?'inactive':'active';saveCatalog(c);}
loadCatalog();
}
function deleteService(id){
if(!confirm('Delete this service?'))return;
saveCatalog(getCatalog().filter(function(x){return x.id!==id;}));
loadCatalog();loadCommission();
}
// ── COMMISSION ────────────────────────────────────────────────────────────────
function loadCommission(){
var catalog=getCatalog();
var body=document.getElementById('commBody');
if(!body)return;
body.innerHTML=catalog.map(function(s){
return '
';
}).join('');
}
// ── APPROVALS ────────────────────────────────────────────────────────────────
var currentApprovalFilter='pending';
function loadApprovals(filter){
currentApprovalFilter=filter||currentApprovalFilter;
var providers=getProviders();
var filtered=currentApprovalFilter==='all'?providers:providers.filter(function(p){return p.status===currentApprovalFilter||(currentApprovalFilter==='pending'&&!p.status);});
var body=document.getElementById('approvalsBody');
if(!body)return;
body.innerHTML=filtered.length?filtered.map(function(p){
return '
";
}function viewProvider(id){
var p=getProviders().find(function(x){return x.id===id;});if(!p)return;
var body=document.getElementById('providerModalBody');
if(body)body.innerHTML='
'
+'
Name
'+p.name+'
'
+'
Phone
'+(p.phone||'—')+'
'
+'
Email
'+(p.email||'—')+'
'
+'
Service
'+(p.service||'—')+'
'
+'
City
'+(p.city||'—')+'
'
+'
Status
'+cap(p.status||'pending')+'
'
+'
';
var m=document.getElementById('providerModal');if(m)m.classList.add('show');
}
// ── PAYMENTS ──────────────────────────────────────────────────────────────────
var payFilterState='pending';
function loadPayments(filter){
payFilterState=filter||payFilterState;
var payouts=getPayouts();
var filtered=payFilterState==='all'?payouts:payouts.filter(function(p){return p.status===payFilterState;});
var body=document.getElementById('payoutsBody');
if(!body)return;
body.innerHTML=filtered.length?filtered.map(function(p){
return '
';
}
function deductPenalty(id){
var p=getPenalties();var idx=p.findIndex(function(x){return x.id===id;});
if(idx>-1){p[idx].status='deducted';savePenalties(p);loadPenalties();}
showToast('Penalty deducted from next payout');
}
function cancelPenalty(id){savePenalties(getPenalties().filter(function(p){return p.id!==id;}));loadPenalties();}
// ── ORDERS ───────────────────────────────────────────────────────────────────
function loadOrders(filter,query){
var body=document.getElementById('ordersBody');
if(!body)return;
body.innerHTML='
⏳ Loading from Firebase…
';
syncAllOrders(function(orders){
var filtered=filter&&filter!=='all'?orders.filter(function(o){return o.status===filter;}):orders;
if(query)filtered=filtered.filter(function(o){return (o.customerName||o.customer||o.service||'').toLowerCase().includes(query);});
var st=function(s){return s==='completed'||s==='paid'?'approved':s==='cancelled'?'inactive':'pending';};
body.innerHTML=filtered.length?filtered.map(function(o){
return '
';
});
}
// ── CUSTOMERS ────────────────────────────────────────────────────────────────
function loadCustomers(query){
var customers=getCustomers();
var filtered=query?customers.filter(function(c){return (c.name||c.email||'').toLowerCase().includes(query);}):customers;
var body=document.getElementById('customersBody');
if(!body)return;
body.innerHTML=filtered.length?filtered.map(function(c){
return '