Since 1984 | Top Bag Manufacturer & Wholesale Supplier in Bangalore
NS BAGS
SINCE 1984
Loading...
Featured Models
NS BAGS - Top Bag Manufacturer in Bangalore
THANKS FOR VISITING NS BAGS.
#tags
#Since1984Bags
#TopBagManufacturerBangalore
#BangaloreWholesale
#BagFactoryBangalore
#CorporateGiftsBangalore
#SchoolBagsWholesale
#LaptopBagsManufacturer
#CustomBagsIndia
#TravelBagsWholesale
Get Our App!
Download the official NS BAGS App for instant wholesale quoting, secure checkout, and priority support no Ads no hiden permissions.
Added to Quote Deck!
Configuration saved successfully.
Subtotal (Incl GST)₹0
50% Advance Due₹0
Complete B2B Details
NS AI Assistant
Ask about products, MOQ, or tracking
Hello! I'm the Since 1984 AI assistant. How can I help you source the perfect bags today?
Live Analytics Engine
Click any bar to drill down into order breakdown
Live
| Image | Details | Category | Base Price | Actions |
|---|
Filter by Last Booking:
Advanced CSV Reports Engine
Add New Banner Image
Active Sliders & Settings
App Download Links
Order Dossier
`).join('');
if(g.length>1) ind.innerHTML=g.map((_,i)=>``).join(''); else ind.innerHTML='';
document.getElementById('fullscreen-viewer').classList.remove('hidden'); document.getElementById('fullscreen-viewer').classList.add('flex'); window.location.hash='fullscreen';
setTimeout(()=>{const td=s.children[idx];if(td)s.scrollTo({left:td.offsetLeft,behavior:'instant'});},50);
},
closeFullScreenImg: () => { if(window.location.hash==='#fullscreen') history.back(); else { document.getElementById('fullscreen-viewer').classList.add('hidden'); document.getElementById('fullscreen-viewer').classList.remove('flex'); } },
closeInfoPopup: () => { if(window.location.hash.startsWith('#info=')) history.back(); else { document.getElementById('info-popup').classList.add('hidden'); document.getElementById('info-popup').classList.remove('flex'); } },
openWriteReview: () => { window.location.hash='review'; }, closeWriteReview: () => { if(window.location.hash==='#review') history.back(); else { document.getElementById('review-modal').classList.add('hidden'); document.getElementById('review-modal').classList.remove('flex'); } },
closePostAddModal: () => { if(window.location.hash==='#postadd') history.back(); else { document.getElementById('post-add-modal').classList.add('hidden'); document.getElementById('post-add-modal').classList.remove('flex'); } },
openAppDownload: () => { window.location.hash='appdl'; }, closeAppDownload: () => { if(window.location.hash==='#appdl') history.back(); else { document.getElementById('app-download-modal').classList.add('hidden'); document.getElementById('app-download-modal').classList.remove('flex'); } },
checkAppLinks: () => {
const links = AppData.settings.appLinks || {};
const hasAny = (links.android && links.android.length>3) || (links.ios && links.ios.length>3) || (links.win && links.win.length>3);
const btn = document.getElementById('floating-app-dl-btn');
if(btn) { if(hasAny) btn.classList.remove('hidden'); else btn.classList.add('hidden'); }
const modal = document.getElementById('app-download-buttons');
if(modal) {
let html = '';
if(links.android && links.android.length>3) html += ``;
if(links.ios && links.ios.length>3) html += ``;
if(links.win && links.win.length>3) html += ``;
modal.innerHTML = html;
}
},
openCartFromPostAdd: () => { window.location.hash='cart'; },
scrollToReviews: () => { document.getElementById('pd-reviews-section').scrollIntoView({behavior:'smooth'}); },
toggleSortDropdown: (e) => { if(e)e.stopPropagation(); const m=document.getElementById('sort-dropdown'); m.classList.toggle('hidden'); if(!m.classList.contains('hidden')){m.classList.add('dropdown-enter');m.classList.remove('dropdown-exit');}else{m.classList.remove('dropdown-enter');m.classList.add('dropdown-exit');} }
};document.addEventListener('click', (e) => {
const sd=document.getElementById('sort-dropdown'); const sp=document.getElementById('support-popover');
if(sd&&!sd.classList.contains('hidden')&&!e.target.closest('#sort-dropdown')&&!e.target.closest('[onclick="UI.toggleSortDropdown(event)"]')){sd.classList.add('hidden');sd.classList.remove('dropdown-enter');sd.classList.add('dropdown-exit');}
if(sp&&!sp.classList.contains('hidden')&&!e.target.closest('#support-popover')&&!e.target.closest('[onclick="UI.toggleSupportMenu(event)"]')){sp.classList.add('hidden');sp.classList.remove('dropdown-enter');sp.classList.add('dropdown-exit');}
});window.Utils = {
resizeImage: (file, mxW, cb) => { const r=new FileReader(); r.onload=(e)=>{const img=new Image();img.onload=()=>{const c=document.createElement('canvas');let w=img.width,h=img.height;if(w>mxW){h=Math.round((h*mxW)/w);w=mxW;}c.width=w;c.height=h;const ctx=c.getContext('2d');ctx.drawImage(img,0,0,w,h);cb(c.toDataURL('image/jpeg',0.5));};img.src=e.target.result;};r.readAsDataURL(file); },
formatPrice: (n) => "₹"+(n||0).toLocaleString('en-IN')
};window.Store = {
handleSearchInput: (val) => {
AppData.searchQuery = String(val||'').trim(); AppData.currentPage=1; const cb=document.getElementById('clear-search'); const sg=document.getElementById('search-suggestions');
if(AppData.searchQuery.length>0) {
cb.classList.remove('hidden'); sg.classList.remove('hidden'); sg.classList.add('flex','dropdown-enter'); sg.classList.remove('dropdown-exit');
const q=AppData.searchQuery.toLowerCase();
const m=(AppData.products||[]).filter(p=>String(p?.title||'').toLowerCase().includes(q)||String(p?.id||'').toLowerCase().includes(q)||String(p?.category||'').toLowerCase().includes(q)||String(p?.subcategory||'').toLowerCase().includes(q)||(Array.isArray(p?.tags)?p.tags:[]).some(t=>String(t||'').toLowerCase().includes(q))).slice(0,5);
if(m.length===0) sg.innerHTML=`No matching products found.
`;
else sg.innerHTML=m.map(x=>`
${x.title}${x.category} > ${x.subcategory}
₹${x.basePrice} `).join('');
} else { cb.classList.add('hidden'); sg.classList.remove('dropdown-enter'); sg.classList.add('dropdown-exit'); setTimeout(()=>{sg.classList.add('hidden');sg.classList.remove('flex');},200); }
Render.products();
},
clearSearch: () => { document.getElementById('search-input').value=''; Store.handleSearchInput(''); },
filterByCategory: (cId) => {
AppData.activeCat=String(cId||'all').trim(); AppData.activeSubs=[]; AppData.currentPage=1;
// Clear active search properly when changing categories to prevent "No products found" clash
AppData.searchQuery=''; const sI=document.getElementById('search-input'); if(sI) sI.value='';
document.getElementById('clear-search').classList.add('hidden'); document.getElementById('search-suggestions').classList.add('hidden');
UI.closeSubcatPopup(); Render.categories(); Render.products();
},
openSubcategories: (cId) => {
const tId=String(cId||'').trim(); Store.filterByCategory(tId); const cat=(AppData.categories||[]).find(c=>String(c.id||'').trim()===tId||String(c.name||'').trim()===tId);
const sItems=cat?.subItems||[]; const subs=cat?.subs||[];
if(cat && (sItems.length>0||subs.length>0)) {
document.getElementById('subcat-popup-title').innerText=`${cat.name} Types`; const lst=document.getElementById('subcat-popup-list');
const rndr=sItems.length>0?sItems:subs.map(s=>({name:s,image:cat.cover}));
lst.innerHTML=rndr.map(s=>`
${s.name}${AppData.activeSubs.includes(String(s.name).trim())?'
':''}
`).join('');
window.location.hash='subcat';
}
},
toggleSubcat: (sub) => { const s=String(sub||'').trim(); if(AppData.activeSubs.includes(s)) AppData.activeSubs=AppData.activeSubs.filter(x=>x!==s); else AppData.activeSubs=[s]; AppData.currentPage=1; UI.closeSubcatPopup(); Render.categories(); Render.products(); },
clearSubcategoryFilter: () => { AppData.activeSubs=[]; AppData.currentPage=1; Render.categories(); Render.products(); },
sortProducts: (v, lbl) => { AppData.sortBy=v; AppData.currentPage=1; document.getElementById('active-sort-label').innerText=`Sort: ${lbl}`; document.querySelectorAll('.sort-check').forEach(e=>e.classList.add('hidden')); document.querySelector(`.sort-check[data-sort="${v}"]`)?.classList.remove('hidden'); UI.toggleSortDropdown(); Render.products(); },
openProduct: (pid) => { window.location.hash='product='+encodeURIComponent(pid); },
renderDescription: () => { const p=AppData.viewingProduct; if(!p)return; const d=document.getElementById('pd-desc'); const b=document.getElementById('pd-desc-more-btn'); const f=p.description||""; const m=160; if(f.length>m&&!AppData.isDescExpanded){d.innerText=f.substring(0,m)+"...";b.innerHTML=`Read More `;b.classList.remove('hidden');}else if(f.length>m&&AppData.isDescExpanded){d.innerText=f;b.innerHTML=`Show Less `;b.classList.remove('hidden');}else{d.innerText=f;b.classList.add('hidden');} },
toggleFullDescription: () => { AppData.isDescExpanded=!AppData.isDescExpanded; Store.renderDescription(); },loadMoreSuggestions: () => { AppData.relatedDisplayCount+=10; Store.renderRelatedProducts(); },
loadProductIntoModal: (pid) => {
const p=(AppData.products||[]).find(x=>String(x.id||'').trim()===String(pid||'').trim()); if(!p) return;
AppData.searchQuery=''; const sI=document.getElementById('search-input'); if(sI) sI.value=''; document.getElementById('search-suggestions').classList.add('hidden');
Render.products();
AppData.viewingProduct=p; AppData.currentQty=100; AppData.selectedCustoms={}; AppData.isDescExpanded=false;
(p.customizations||[]).forEach(g=>{if(g.options&&g.options.length>0)AppData.selectedCustoms[g.id]=g.options[0].label;});
document.getElementById('pd-cat-badge').innerText=`${p.category||''} / ${p.subcategory||''}`; document.getElementById('pd-id').innerText=`${p.id}`; document.getElementById('pd-title').innerText=p.title||'';
Store.renderDescription();
const aI=[{url:p.images?.main||'',label:'Main'},...(p.images?.angles||[])]; AppData.currentGallery=aI.map(i=>i.url).filter(u=>u);
document.getElementById('pd-image-slider').innerHTML=aI.filter(i=>i.url).map((img,i)=>`
`).join('');
const vC=document.getElementById('pd-video-container'); const vB=document.getElementById('pd-video-btn'); if(p.images&&p.images.video){vC.classList.remove('hidden');vB.onclick=()=>window.open(p.images.video,'_blank');}else vC.classList.add('hidden');
const tC=document.getElementById('pd-thumbnails'); tC.innerHTML=aI.filter(i=>i.url).map((img,i)=>``).join('');
if(aI.length>1) tC.classList.remove('hidden'); else tC.classList.add('hidden');
const aR=p.reviews?p.reviews.filter(r=>r.status==='approved'):[]; const sD=document.getElementById('pd-stars');
if(aR.length>0){const avg=aR.reduce((s,r)=>s+r.rating,0)/aR.length;sD.innerHTML=Array.from({length:5}).map((_,i)=>``).join('');document.getElementById('pd-rating-text').innerText=`${avg.toFixed(1)} (${aR.length} Verified Reviews)`;}else{sD.innerHTML='';document.getElementById('pd-rating-text').innerText='No reviews yet. Be the first!';}
const o=(AppData.products||[]).filter(x=>x.id!==p.id); const sC=o.filter(x=>String(x.category||'').trim()===String(p.category||'').trim()).sort(()=>Math.random()-.5); const dC=o.filter(x=>String(x.category||'').trim()!==String(p.category||'').trim()).sort(()=>Math.random()-.5);
AppData.currentRelatedList=[...sC,...dC]; AppData.relatedDisplayCount=10; Store.renderRelatedProducts();
const fC=document.getElementById('pd-additional-fees-container'); if(p.additionalCharges&&p.additionalCharges.length>0)fC.classList.remove('hidden'); else fC.classList.add('hidden');
document.getElementById('pd-qty-input').value=AppData.currentQty; Store.updateTierHighlights(); Store.renderCustomizations(); Store.updatePriceCalculation();document.getElementById('product-modal').classList.remove('hidden'); document.getElementById('product-modal').classList.add('flex');
const mC=document.getElementById('pd-modal-scroll-area'); if(mC) mC.scrollTop=0;
},
updateTierHighlights: () => {
const p=AppData.viewingProduct; if(!p)return;},
renderCustomizations: () => {
const p=AppData.viewingProduct; if(!p)return;},
updateQty: (diff) => { AppData.currentQty=Math.max(1,parseInt(AppData.currentQty||0)+diff); document.getElementById('pd-qty-input').value=AppData.currentQty; Store.updateTierHighlights(); Store.updatePriceCalculation(); },
setDirectQty: (val) => { if(val===''){AppData.currentQty=0;Store.updateTierHighlights();Store.updatePriceCalculation();return;} let pd=parseInt(val); if(isNaN(pd)||pd<0)pd=1; AppData.currentQty=pd; Store.updateTierHighlights(); Store.updatePriceCalculation(); },
setCustom: (gId, optLbl) => { AppData.selectedCustoms[gId]=optLbl; Store.renderCustomizations(); Store.updatePriceCalculation(); },
showInfoPopupInternal: (eStr) => {
const opt=JSON.parse(decodeURIComponent(eStr)); document.getElementById('info-popup-title').innerText=opt.label; document.getElementById('info-popup-desc').innerText=opt.infoDesc||"Custom specification tailored for manufacturing.";
const iC=document.getElementById('info-popup-img-container'); const img=document.getElementById('info-popup-img');
if(opt.infoImg){img.src=opt.infoImg;iC.classList.remove('hidden');AppData.currentGallery=[opt.infoImg];}else{iC.classList.add('hidden');AppData.currentGallery=[];}
const prc=document.getElementById('info-popup-price'); if(opt.price>0){prc.innerText=`Impact: +₹${opt.price} / unit`;prc.classList.remove('hidden');}else prc.classList.add('hidden');
document.getElementById('info-popup').classList.remove('hidden'); document.getElementById('info-popup').classList.add('flex');
},
updatePriceCalculation: () => {
const p=AppData.viewingProduct; if(!p)return;
let bs=p.basePrice||0; if(p.quantityTiers){const m=p.quantityTiers.filter(t=>AppData.currentQty>=t.minQty).sort((a,b)=>b.minQty-a.minQty)[0];if(m)bs=m.pricePerUnit;}
let ad=0; (p.customizations||[]).forEach(g=>{const opt=(g.options||[]).find(o=>o.label===AppData.selectedCustoms[g.id]);if(opt)ad+=Number(opt.price||0);});
const uP=bs+ad; let aF=0;
(p.additionalCharges||[]).forEach(c=>{const mn=c.minQty?Number(c.minQty):0;const mx=(c.maxQty!==null&&c.maxQty!==undefined&&c.maxQty!=='')?Number(c.maxQty):Infinity;if(AppData.currentQty>=mn&&AppData.currentQty<=mx)aF+=Number(c.amount||0);});
const tot=(uP*AppData.currentQty)+aF;
document.getElementById('pd-unit-price').innerText=`${Utils.formatPrice(uP)} / unit`; document.getElementById('pd-total-price').innerText=Utils.formatPrice(tot);
},
addToCart: (isBuyNow=false) => {
const p=AppData.viewingProduct; if(!p)return; if(AppData.currentQty<1){UI.showToast("Please enter a valid quantity.");return;}
let bs=p.basePrice||0; if(p.quantityTiers){const m=p.quantityTiers.filter(t=>AppData.currentQty>=t.minQty).sort((a,b)=>b.minQty-a.minQty)[0];if(m)bs=m.pricePerUnit;}
let ad=0; (p.customizations||[]).forEach(g=>{const opt=(g.options||[]).find(o=>o.label===AppData.selectedCustoms[g.id]);if(opt)ad+=Number(opt.price||0);}); const uP=bs+ad; let aF=0;
(p.additionalCharges||[]).forEach(c=>{const mn=c.minQty?Number(c.minQty):0;const mx=(c.maxQty!==null&&c.maxQty!==undefined&&c.maxQty!=='')?Number(c.maxQty):Infinity;if(AppData.currentQty>=mn&&AppData.currentQty<=mx)aF+=Number(c.amount||0);});
const tot=(uP*AppData.currentQty)+aF;
const itm={cartId:'C_'+Date.now(),productId:p.id,title:p.title,mainImage:p.images?.main||'',quantity:AppData.currentQty,unitPrice:uP,additionalFeesTotal:aF,totalPrice:tot,specs:{...AppData.selectedCustoms}};
AppData.cart.push(itm); Render.cartBadge();
if(isBuyNow) UI.openCheckout(); else { document.getElementById('pam-title').innerText=`Added ${p.title} to deck.`; window.location.hash='postadd'; }
},
removeFromCart: (cid) => { AppData.cart=AppData.cart.filter(c=>c.cartId!==cid); Render.cartBadge(); Render.cart(); },
copyProductId: () => { if(!AppData.viewingProduct)return; navigator.clipboard.writeText(AppData.viewingProduct.id).then(()=>UI.showToast("Product ID Copied!")); },
shareProduct: () => { if(!AppData.viewingProduct)return; const u=window.location.origin+window.location.pathname+'#product='+encodeURIComponent(AppData.viewingProduct.id); if(navigator.share)navigator.share({title:AppData.viewingProduct.title,url:u,text:`Check out this product!`}); else navigator.clipboard.writeText(u).then(()=>UI.showToast("Product Link Copied!")); },
submitReview: async () => {
const n=document.getElementById('rev-name').value; const o=document.getElementById('rev-order').value; const t=document.getElementById('rev-text').value; let rt=5; document.querySelectorAll('#rev-stars-select i').forEach(e=>{if(e.classList.contains('text-amber-500')&&!e.classList.contains('fa-regular'))rt=parseInt(e.getAttribute('data-val'));});
if(!n||!t) { UI.showToast("Name and Review text required!"); return; }
const p=AppData.viewingProduct; const rv={id:'r_'+Date.now(),customerName:n,orderId:o,rating:rt,text:t,status:'pending',date:getISODate().split('T')[0]};
p.reviews=p.reviews||[]; p.reviews.unshift(rv); await setDoc(doc(AppData.db, "products", p.id), p);
UI.closeWriteReview(); document.getElementById('rev-name').value=''; document.getElementById('rev-text').value=''; UI.showToast("Review submitted! Pending Admin Approval.");
},
// Integrated Formsubmit.co implementation directly inside processCheckout
processCheckout: async () => {
const c={name:document.getElementById('chk-name').value,company:document.getElementById('chk-company').value,phone:document.getElementById('chk-phone').value,email:document.getElementById('chk-email').value,gst:document.getElementById('chk-gst').value};
const addr=document.getElementById('chk-address').value; const cty=document.getElementById('chk-city').value; const st=document.getElementById('chk-state').value; const pin=document.getElementById('chk-pin').value; const nts=document.getElementById('chk-notes').value;
if(!c.name||!c.company||!c.phone||!c.email||!addr||!cty||!pin||!st) { UI.showToast("Please fill all mandatory fields (*)"); return; }
const sub=AppData.cart.reduce((s,i)=>s+i.totalPrice,0);
const ord={orderId:'ORD-'+Date.now().toString().slice(-6),status:'Pending',client:c,deliveryAddress:addr,city:cty,state:st,deliveryPinCode:pin,notes:nts,items:[...AppData.cart],financials:{total:sub,advance:sub/2,additionalChargesTotal:AppData.cart.reduce((s,i)=>s+i.additionalFeesTotal,0),paymentStatus:'Unpaid',amountPaid:0,balance:sub},date:getISODate()};
await setDoc(doc(AppData.db, "orders", ord.orderId), ord);
// FormSubmit.co Integration via Fetch API directly (No Redirects)
try {
await fetch("https://formsubmit.co/ajax/orders@nsbags.in", {
method: "POST",
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
_subject: `New B2B Order Generated: ${ord.orderId}`,
orderId: ord.orderId,
customerName: c.name,
company: c.company,
email: c.email,
phone: c.phone,
state: st,
totalValue: Utils.formatPrice(sub),
itemsOrdered: ord.items.map(i=>`${i.quantity}x ${i.title}`).join(' | '),
_template: 'table'
})
});
} catch(e) { console.warn("Email alert failed, but order saved in DB.", e); }AppData.cart=[]; Render.cartBadge(); UI.closeCheckout();
UI.showToast(`Order ${ord.orderId} Placed! Factory notified.`);
}
};window.Render={
products found.`;if(pg)pg.innerHTML=Array.from({length:Math.ceil(t/AppData.itemsPerPage)}).map((_,i)=>`
`).join('');},marketingBar:()=>{const c=document.getElementById('announcement-bar-container'),b=document.getElementById('announcement-bar');if(!c||!b)return;const ann=AppData.settings.announcement;if(ann&&ann.isEnabled){c.classList.remove('hidden');b.innerText=ann.text;if(ann.isMarquee)b.classList.add('animate-marquee');else b.classList.remove('animate-marquee');}else c.classList.add('hidden');},
sliders:()=>{
const sc=document.getElementById('banner-slider'),idc=document.getElementById('slider-indicators');
if(!sc||!idc)return;
const sls=AppData.settings.sliders||[];
if(sls.length===0){sc.parentElement.classList.add('hidden');return;}
sc.parentElement.classList.remove('hidden');
sc.className=`w-full flex overflow-x-auto snap-x-mandatory no-scrollbar rounded-[20px] border border-slate-200 shadow-sm gap-2 pb-2 scroll-smooth ${AppData.settings.sliderConfig?.ratio||'h-44 md:h-64'}`;
sc.innerHTML=sls.map(s=>{
let mH='';
if(s.type==='video'){
if(s.url.includes('youtube')||s.url.includes('youtu.be')){
let yId='';const m=s.url.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})/);
if(m&&m[1])yId=m[1];
// Embedded Invisible Autoplaying Looping YouTube Player
mH=`
`;
}else{
mH=`
`;
}
}else{
mH=`

`;
}
return `
${s.link?`
`:''}
${mH}
${s.title||''}
${s.desc||''}
${s.btnText?`
`:''}
`;
}).join('');
if(sls.length>1){
idc.innerHTML=sls.map((_,i)=>`
`).join('');
idc.children[0]?.classList.add('!bg-primary','!w-3');
clearInterval(window.sliderInterval);
let c=0;
window.sliderInterval=setInterval(()=>{
c=(c+1)%sls.length;
const e=sc.children[c];
if(e)sc.scrollTo({left:e.offsetLeft,behavior:'smooth'});
Array.from(idc.children).forEach((d,i)=>{
if(i===c)d.classList.add('!bg-primary','!w-3');else d.classList.remove('!bg-primary','!w-3');
});
},AppData.settings.sliderConfig?.speed||4000);
sc.onscroll=()=>{
const idx=Math.round(sc.scrollLeft/sc.clientWidth);
Array.from(idc.children).forEach((d,i)=>{
if(i===idx)d.classList.add('!bg-primary','!w-3');else d.classList.remove('!bg-primary','!w-3');
});
};
}else{
idc.innerHTML='';clearInterval(window.sliderInterval);
}
},cartBadge:()=>{const b=document.getElementById('cart-badge');if(!b)return;if(AppData.cart.length>0){b.innerText=AppData.cart.length;b.classList.remove('hidden');}else b.classList.add('hidden');}
};window.AI={
handleSend:()=>{const i=document.getElementById('ai-input'),v=i.value.trim();if(!v)return;const cb=document.getElementById('ai-chat-box');cb.innerHTML+=`
`;i.value='';cb.scrollTop=cb.scrollHeight;setTimeout(()=>{cb.innerHTML+=`
I'll notify our factory team about "${v}". They'll respond shortly!
`;cb.scrollTop=cb.scrollHeight;},1000);}
};document.querySelectorAll('#rev-stars-select i').forEach(st => { st.addEventListener('click', (e) => { const v=parseInt(e.target.getAttribute('data-val')); document.querySelectorAll('#rev-stars-select i').forEach(s=>{const sv=parseInt(s.getAttribute('data-val')); if(sv<=v){s.classList.add('text-amber-500');s.classList.remove('text-slate-300');}else{s.classList.remove('text-amber-500');s.classList.add('text-slate-300');} }); }); });window.Admin = {
failedAttempts:0, lockUntil:0, secretTaps:0, tapTimeout:null, productSearchQuery:'', catSearchQuery:'', orderSearchQuery:'', reviewSearchQuery:'', chartMode:'7days', chartYear:new Date().getFullYear(),
handleProductSearch: (v) => { Admin.productSearchQuery=String(v||'').trim().toLowerCase(); Admin.renderProductsTable(); },
handleCatSearch: (v) => { Admin.catSearchQuery=String(v||'').trim().toLowerCase(); Admin.renderCategories(); },
handleOrderSearch: (v) => { Admin.orderSearchQuery=String(v||'').trim().toLowerCase(); Admin.renderOrders(); },
handleReviewSearch: (v) => { Admin.reviewSearchQuery=String(v||'').trim().toLowerCase(); Admin.renderReviews(); },
toggleExportDateInputs: (v) => { const c=document.getElementById('exp-date-inputs'); if(v==='range')c.classList.remove('hidden');else c.classList.add('hidden'); },
downloadCSVReport: () => {
const t=document.getElementById('exp-type').value; const pm=document.getElementById('exp-period-mode').value; const fv=document.getElementById('exp-date-from').value; const tv=document.getElementById('exp-date-to').value;
let sD=new Date(0); let eD=new Date(); eD.setHours(23,59,59,999);
if(pm==='range'){if(fv){sD=new Date(fv);sD.setHours(0,0,0,0);}if(tv){eD=new Date(tv);eD.setHours(23,59,59,999);}}
let csv=""; let pl=pm==='entire'?'Entire_Report':`${fv||'Start'}_to_${tv||'End'}`; let fn=`NSBAGS_${t.toUpperCase()}_${pl}.csv`; const ec=(s)=>`"${String(s||'').replace(/"/g,'""')}"`;else if(t==='products'){csv="Product ID,Title,Category,Subcategory,Base Price,Tags,Video Link,Rating Count,Updated At\n";(AppData.products||[]).filter(p=>{if(pm==='entire'||!p.updatedAt)return true;const d=new Date(p.updatedAt);return d>=sD&&d<=eD;}).forEach(p=>{const ts=Array.isArray(p?.tags)?p.tags.join(', '):String(p?.tags||'');csv+=`${ec(p?.id)},${ec(p?.title)},${ec(p?.category)},${ec(p?.subcategory)},${p?.basePrice||0},${ec(ts)},${ec(p?.images?.video)},${(p?.reviews||[]).length},${ec(p?.updatedAt||'')}\n`;});}
else if(t==='reviews'){csv="Product ID,Product Title,Customer Name,Order ID,Rating,Review Text,Status,Date\n";(AppData.products||[]).forEach(p=>{(p.reviews||[]).forEach(r=>{const d=new Date(r.date);if(pm==='entire'||(d>=sD&&d<=eD)){csv+=`${ec(p?.id)},${ec(p?.title)},${ec(r?.customerName)},${ec(r?.orderId)},${r?.rating||0},${ec(r?.text)},${ec(r?.status)},${ec(r?.date)}\n`;}});});}
else if(t==='categories'){csv="Category ID,Category Name,Subcategory Name,Subcategory Image\n";(AppData.categories||[]).forEach(c=>{if((c?.subItems||[]).length>0){c.subItems.forEach(s=>{csv+=`${ec(c?.id)},${ec(c?.name)},${ec(s?.name)},${ec(s?.image)}\n`;});}else if((c?.subs||[]).length>0){c.subs.forEach(s=>{csv+=`${ec(c?.id)},${ec(c?.name)},${ec(s)},""\n`;});}else{csv+=`${ec(c?.id)},${ec(c?.name)},"",""\n`;}});}const blb=new Blob([csv],{type:'text/csv;charset=utf-8;'}); const u=window.URL.createObjectURL(blb); const a=document.createElement('a'); a.setAttribute('href',u); a.setAttribute('download',fn); a.click(); UI.showToast(`Exported ${t.toUpperCase()} CSV report!`);
},
handleSecretTap: () => { Admin.secretTaps++; clearTimeout(Admin.tapTimeout); if(Admin.secretTaps>=5){Admin.secretTaps=0;window.location.hash='admin-login';}else{Admin.tapTimeout=setTimeout(()=>{Admin.secretTaps=0;},600);} },
login: () => { const now=Date.now(); if(Admin.lockUntil>now){UI.showToast(`Security Lockout! Wait ${Math.ceil((Admin.lockUntil-now)/1000)}s.`);return;} const id=document.getElementById('admin-id').value.trim(); const pass=document.getElementById('admin-pass').value; if((id.toLowerCase()===AppData.settings.adminId.toLowerCase()||id.toLowerCase()==='admin')&&pass===AppData.settings.passcode){Admin.failedAttempts=0;window.location.hash='admin';Admin.switchTab('dashboard');}else{Admin.failedAttempts++;if(Admin.failedAttempts>=3){Admin.lockUntil=now+60000;UI.showToast("Gateway locked for 1 minute.");}else UI.showToast(`Invalid Credentials. ${3-Admin.failedAttempts} attempts left.`);} },
logout: () => { if(confirm("Log out of Command Center?")) { window.location.hash=''; document.getElementById('admin-id').value=''; document.getElementById('admin-pass').value=''; UI.showToast("Logged out securely."); } },currentFilteredCustomers: [],
renderCustomers: () => {
const fm = document.getElementById('crm-filter-months')?.value || 'all';
const q = (document.getElementById('crm-search')?.value || '').toLowerCase();
const cm = {}; const now = new Date();
(AppData.orders||[]).forEach(o => {
if(o.status === 'Cancelled') return;
const key = String(o.client?.email || o.client?.phone || o.orderId).toLowerCase();
if(!cm[key]) { cm[key] = { name: o.client?.name||'N/A', company: o.client?.company||'', phone: o.client?.phone||'', email: o.client?.email||'', orderCount: 0, totalSpend: 0, lastDate: new Date(0) }; }
cm[key].orderCount += 1;
cm[key].totalSpend += (o.financials?.total || 0);
const oD = new Date(o.date);
if(oD > cm[key].lastDate) cm[key].lastDate = oD;
});let list = Object.values(cm).map(c => {
const diffMonths = Math.floor(Math.abs(now - c.lastDate) / (1000 * 60 * 60 * 24 * 30));
c.monthsAgo = diffMonths; return c;
});if(fm !== 'all') {
if(fm === '1') list = list.filter(c => c.monthsAgo <= 1);
else if(fm === '2') list = list.filter(c => c.monthsAgo === 1 || c.monthsAgo === 2);
else if(fm === '3') list = list.filter(c => c.monthsAgo === 2 || c.monthsAgo === 3);
else if(fm === '4') list = list.filter(c => c.monthsAgo === 3 || c.monthsAgo === 4);
else if(fm === '5+') list = list.filter(c => c.monthsAgo >= 4);
}if(q) list = list.filter(c => String(c.name).toLowerCase().includes(q) || String(c.company).toLowerCase().includes(q) || String(c.email).toLowerCase().includes(q) || String(c.phone).toLowerCase().includes(q));
Admin.currentFilteredCustomers = list;document.getElementById('admin-customers-list').innerHTML = list.sort((a,b)=>b.lastDate-a.lastDate).map(c=>`
${c.company || c.name} ${c.orderCount} Bookings
${c.name} • ${c.email || 'No Email'} • ${c.phone}
Last Booked: ${c.lastDate.toLocaleDateString()} ${c.monthsAgo === 0 ? 'Recently (This Month)' : c.monthsAgo + ' Months Ago'}
Lifetime Spend
${Utils.formatPrice(c.totalSpend)}
`).join('') || '
No customers match this filter.
';
},
sendBulkEmail: () => {
const list = Admin.currentFilteredCustomers || [];
const emails = list.map(c => c.email).filter(e => e && e.includes('@')).join(',');
if(!emails) { UI.showToast("No valid email addresses found in current list."); return; }
const subject = encodeURIComponent("Exclusive Update from Since 1984");
window.location.href = `mailto:?bcc=${emails}&subject=${subject}`;
UI.showToast(`Preparing email for ${list.length} customers!`);
},
dashFilter: 'lifetime',
renderDashboard: () => {
const fE=document.getElementById('dash-date-from'); const tE=document.getElementById('dash-date-to'); let sD=new Date(0); let eD=new Date(); eD.setHours(23,59,59,999); let lbl="Custom Range";
if(fE&&fE.value){sD=new Date(fE.value);sD.setHours(0,0,0,0);} if(tE&&tE.value){eD=new Date(tE.value);eD.setHours(23,59,59,999);}
if(!fE.value&&!tE.value)lbl="Lifetime"; else if(fE.value&&tE.value)lbl=`${fE.value} to ${tE.value}`; else if(fE.value)lbl=`From ${fE.value}`; else if(tE.value)lbl=`Until ${tE.value}`;
const fO=(AppData.orders||[]).filter(o=>o.status!=='Cancelled'&&new Date(o.date)>=sD&&new Date(o.date)<=eD); const tR=fO.reduce((s,o)=>s+(o.financials?.total||0),0);
document.getElementById('dash-lbl-rev').innerText=lbl; document.getElementById('dash-lbl-ord').innerText=lbl; document.getElementById('dash-rev').innerText=Utils.formatPrice(tR); document.getElementById('dash-ord').innerText=fO.length; document.getElementById('dash-pend').innerText=(AppData.orders||[]).filter(o=>o.status==='Pending').length; document.getElementById('dash-prod').innerText=(AppData.products||[]).length;
const yrS=document.getElementById('chart-year-select'); if(yrS){const cY=new Date().getFullYear();const aY=Array.from(new Set((AppData.orders||[]).map(o=>new Date(o.date).getFullYear()))).sort((a,b)=>b-a);if(!aY.includes(cY))aY.unshift(cY);yrS.innerHTML=aY.map(y=>`
`).join('');}
Admin.renderChart();
},
renderChart: () => {
const cEl=document.getElementById('dash-chart'); const yrS=document.getElementById('chart-year-select'); const sL=document.getElementById('chart-sub-label'); if(!cEl)return;
if(Admin.chartMode==='monthly'){if(yrS)yrS.classList.remove('hidden');}else{if(yrS)yrS.classList.add('hidden');}
let prds=[]; const now=new Date();
if(Admin.chartMode==='7days'){
if(sL)sL.innerText="Showing daily sales for the past 7 days. Click any bar for details."; const dNs=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
for(let i=6;i>=0;i--){const d=new Date();d.setDate(d.getDate()-i);d.setHours(0,0,0,0);const nD=new Date(d);nD.setDate(d.getDate()+1);const dOs=(AppData.orders||[]).filter(o=>{const oD=new Date(o.date);return o.status!=='Cancelled'&&oD>=d&&oD
s+(o.financials?.total||0),0);prds.push({label:`${dNs[d.getDay()]}`,sub:`${d.getDate()}/${d.getMonth()+1}`,startDateStr:d.toISOString(),endDateStr:nD.toISOString(),count:dOs.length,revenue:dR,title:`Daily Breakdown: ${d.toLocaleDateString()}`});}
}else if(Admin.chartMode==='weekly'){
if(sL)sL.innerText="Showing weekly sales performance over the last 8 weeks.";
for(let i=7;i>=0;i--){const d=new Date();d.setDate(d.getDate()-(i*7));d.setHours(0,0,0,0);const nD=new Date(d);nD.setDate(d.getDate()+7);const wOs=(AppData.orders||[]).filter(o=>{const oD=new Date(o.date);return o.status!=='Cancelled'&&oD>=d&&oDs+(o.financials?.total||0),0);prds.push({label:`Wk ${8-i}`,sub:`${d.getDate()}/${d.getMonth()+1}`,startDateStr:d.toISOString(),endDateStr:nD.toISOString(),count:wOs.length,revenue:wR,title:`Weekly Breakdown (${d.toLocaleDateString()} - ${nD.toLocaleDateString()})`});}
}else if(Admin.chartMode==='monthly'){
if(sL)sL.innerText=`Monthly revenue for ${Admin.chartYear}. Highlighted bar is your peak month!`; const mNs=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
for(let m=0;m<12;m++){const sD=new Date(Admin.chartYear,m,1,0,0,0,0);const eD=new Date(Admin.chartYear,m+1,0,23,59,59,999);const mOs=(AppData.orders||[]).filter(o=>{const oD=new Date(o.date);return o.status!=='Cancelled'&&oD>=sD&&oD<=eD;});const mR=mOs.reduce((s,o)=>s+(o.financials?.total||0),0);prds.push({label:mNs[m],sub:`${Admin.chartYear}`,startDateStr:sD.toISOString(),endDateStr:eD.toISOString(),count:mOs.length,revenue:mR,title:`Monthly Breakdown: ${mNs[m]} ${Admin.chartYear}`});}
}else if(Admin.chartMode==='yearly'){
if(sL)sL.innerText="Yearly growth & revenue totals across all stored periods."; const cY=new Date().getFullYear();
for(let y=cY-4;y<=cY;y++){const sD=new Date(y,0,1,0,0,0,0);const eD=new Date(y,11,31,23,59,59,999);const yOs=(AppData.orders||[]).filter(o=>{const oD=new Date(o.date);return o.status!=='Cancelled'&&oD>=sD&&oD<=eD;});const yR=yOs.reduce((s,o)=>s+(o.financials?.total||0),0);prds.push({label:`${y}`,sub:'Year',startDateStr:sD.toISOString(),endDateStr:eD.toISOString(),count:yOs.length,revenue:yR,title:`Yearly Breakdown: ${y}`});}
}
const pkR=Math.max(...prds.map(p=>p.revenue)); const mxR=pkR>0?pkR:1;},
openPeriodBreakdown: (sIs, eIs, eTit) => {
const sD=new Date(sIs); const eD=new Date(eIs); const t=decodeURIComponent(eTit); const pOs=(AppData.orders||[]).filter(o=>{const oD=new Date(o.date);return oD>=sD&&oD<=eD;}); document.getElementById('db-modal-title').innerText=t;
const tR=pOs.filter(o=>o.status!=='Cancelled').reduce((s,o)=>s+(o.financials?.total||0),0);
let h=`Period Revenue Total${Utils.formatPrice(tR)}
`;
if(pOs.length===0) h+=`No orders logged for this selected period.
`;document.getElementById('db-modal-content').innerHTML=h; document.getElementById('day-breakdown-modal').classList.remove('hidden'); document.getElementById('day-breakdown-modal').classList.add('flex');
},
closeDayBreakdown: () => { document.getElementById('day-breakdown-modal').classList.add('hidden'); document.getElementById('day-breakdown-modal').classList.remove('flex'); },
anglesArr:[], tiersArr:[], customsArr:[], additionalFeesArr:[],
handleImageUpload: (el, t) => { if(el.files&&el.files[0])Utils.resizeImage(el.files[0],800,(b64)=>{if(t==='main'){document.getElementById('addP-imgMain').value=b64;document.getElementById('addP-imgMain-preview').src=b64;document.getElementById('addP-imgMain-preview').classList.remove('hidden');}else if(t==='cat')document.getElementById('cat-img').value=b64;else if(t==='subcat')document.getElementById('subcat-img').value=b64;else if(t==='cust')document.getElementById('cust-img').value=b64;}); },
handleMediaUpload: (el, t) => {
if(el.files&&el.files[0]){
const f=el.files[0];
if(f.type.startsWith('video/')){
if(f.size > 100 * 1024 * 1024) { UI.showToast("Video exceeds 100MB! Use YouTube Link instead."); return; }
const r=new FileReader();r.onload=(e)=>{if(t==='slider')document.getElementById('mkt-slider-url').value=e.target.result;};r.readAsDataURL(f);
}else{
Utils.resizeImage(f,1200,(b64)=>{if(t==='slider')document.getElementById('mkt-slider-url').value=b64;});
}
}
},addTier: () => { const mn=parseInt(document.getElementById('tier-min').value);const pr=parseFloat(document.getElementById('tier-price').value);if(mn>0&&pr>0){Admin.tiersArr.push({minQty:mn,pricePerUnit:pr});document.getElementById('tier-min').value='';document.getElementById('tier-price').value='';Admin.renderTiers();} },
renderTiers: () => { document.getElementById('addP-tiers-container').innerHTML=Admin.tiersArr.sort((a,b)=>a.minQty-b.minQty).map((t,i)=>`${t.minQty}+ Units -> ₹${t.pricePerUnit}
`).join(''); },
addCustomization: () => { const g=document.getElementById('cust-group').value.trim();const o=document.getElementById('cust-opt').value.trim();const p=parseFloat(document.getElementById('cust-price').value)||0;const d=document.getElementById('cust-desc').value.trim();const i=document.getElementById('cust-img').value.trim();if(g&&o){let ex=Admin.customsArr.find(x=>x.label===g);if(!ex){ex={id:g.replace(/\s+/g,'_').toLowerCase(),label:g,options:[]};Admin.customsArr.push(ex);}ex.options.push({label:o,value:o,price:p,infoDesc:d,infoImg:i});document.getElementById('cust-opt').value='';document.getElementById('cust-price').value='';document.getElementById('cust-desc').value='';document.getElementById('cust-img').value='';Admin.renderCustoms();} },addFee: () => { const n=document.getElementById('fee-name').value.trim();const a=parseFloat(document.getElementById('fee-amt').value);const mn=parseInt(document.getElementById('fee-min').value)||null;const mx=parseInt(document.getElementById('fee-max').value)||null;if(n&&a>0){Admin.additionalFeesArr.push({id:'ac_'+Date.now(),name:n,amount:a,minQty:mn,maxQty:mx});document.getElementById('fee-name').value='';document.getElementById('fee-amt').value='';document.getElementById('fee-min').value='';document.getElementById('fee-max').value='';Admin.renderFees();}else UI.showToast("Fee Name and Amount required."); },updateSubcatDropdown: () => { const catEl=document.getElementById('addP-cat'); if(!catEl) return; const ci=catEl.value;const c=(AppData.categories||[]).find(x=>x.id===ci);const sS=document.getElementById('addP-subcat'); if(!sS) return; if(c&&c.subItems&&c.subItems.length>0)sS.innerHTML=c.subItems.map(s=>``).join('');else if(c&&c.subs&&c.subs.length>0)sS.innerHTML=c.subs.map(s=>``).join('');else sS.innerHTML=``; },
clearProductForm: () => {
const ids = ['addP-id','addP-title','addP-price','addP-tags','addP-desc','addP-imgMain','addP-videoLink'];
ids.forEach(id=>{ const el = document.getElementById(id); if(el) el.value = ''; });
const idEl = document.getElementById('addP-id');
if(idEl) { idEl.readOnly=false; idEl.classList.remove('opacity-50'); }
const prv = document.getElementById('addP-imgMain-preview');
if(prv) { prv.src=''; prv.classList.add('hidden'); }
const angC = document.getElementById('addP-angles-container');
if(angC) angC.innerHTML='';
Admin.tiersArr=[];Admin.customsArr=[];Admin.additionalFeesArr=[];
const tc = document.getElementById('addP-tiers-container'); if(tc) Admin.renderTiers();
const cc = document.getElementById('addP-customs-container'); if(cc) Admin.renderCustoms();
const fc = document.getElementById('addP-fees-container'); if(fc) Admin.renderFees();
UI.showToast("Form Cleared");
},renderProductsTable: () => {
const cS=document.getElementById('addP-cat'); const pS=document.getElementById('subcat-parent');
if(cS&&pS&&(AppData.categories||[]).length>0){const cH=AppData.categories.map(c=>``).join('');if(!cS.options.length)cS.innerHTML=cH;if(!pS.options.length)pS.innerHTML=cH;}
let l=[...(AppData.products||[])]; const sI=document.getElementById('admin-product-search'); if(sI&&sI.value)Admin.productSearchQuery=String(sI.value||'').trim().toLowerCase();
if(Admin.productSearchQuery){const q=Admin.productSearchQuery;l=l.filter(p=>String(p?.title||'').toLowerCase().includes(q)||String(p?.id||'').toLowerCase().includes(q)||String(p?.category||'').toLowerCase().includes(q)||String(p?.subcategory||'').toLowerCase().includes(q)||(Array.isArray(p?.tags)?p.tags:[]).some(t=>String(t||'').toLowerCase().includes(q)));}},
editProduct: (id) => {Admin.tiersArr=[...(p.quantityTiers||[])]; Admin.customsArr=[...(p.customizations||[])]; Admin.additionalFeesArr=[...(p.additionalCharges||[])]; Admin.renderTiers(); Admin.renderCustoms(); Admin.renderFees();
document.getElementById('product-form-modal').classList.remove('hidden'); document.getElementById('product-form-modal').classList.add('flex'); const mS=document.querySelector('#product-form-modal .overflow-y-auto'); if(mS)mS.scrollTo({top:0,behavior:'smooth'}); UI.showToast("Editing: "+p.title);
},
delProduct: async (id) => { if(confirm("Are you sure? This cannot be undone.")){UI.showToast("Deleting...");await deleteDoc(doc(AppData.db,"products",id));} },
editCategory: (id) => { const c=(AppData.categories||[]).find(x=>x.id===id); if(!c)return; document.getElementById('cat-id').value=c.id; document.getElementById('cat-id').readOnly=true; document.getElementById('cat-id').classList.add('opacity-50'); document.getElementById('cat-name').value=c.name; document.getElementById('cat-img').value=c.cover; window.scrollTo({top:0,behavior:'smooth'}); UI.showToast("Editing Category: "+c.name); },
addCategory: async () => { const id=document.getElementById('cat-id').value.trim();const nm=document.getElementById('cat-name').value.trim();const cv=document.getElementById('cat-img').value.trim(); if(id&&nm&&cv){const ex=(AppData.categories||[]).find(c=>c.id===id);const c={id:id,name:nm,cover:cv,subs:ex?(ex.subs||[]):[],subItems:ex?(ex.subItems||[]):[]};await setDoc(doc(AppData.db,"categories",id),c);document.getElementById('cat-id').value='';document.getElementById('cat-id').readOnly=false;document.getElementById('cat-id').classList.remove('opacity-50');document.getElementById('cat-name').value='';document.getElementById('cat-img').value='';UI.showToast("Category Saved!");} },
editingSubcat: null,
editSubcat: (cid, sidx) => { const c=(AppData.categories||[]).find(x=>x.id===cid); if(!c||!c.subItems)return; const s=c.subItems[sidx]; document.getElementById('subcat-parent').value=cid; document.getElementById('subcat-name').value=s.name; document.getElementById('subcat-img').value=s.image; Admin.editingSubcat={cid,sidx}; window.scrollTo({top:0,behavior:'smooth'}); UI.showToast("Editing Subcategory"); },
addSubcategory: async () => { const p=document.getElementById('subcat-parent').value;const n=document.getElementById('subcat-name').value.trim();const i=document.getElementById('subcat-img').value.trim(); if(p&&n){const pc=(AppData.categories||[]).find(c=>c.id===p);if(pc){if(Admin.editingSubcat&&Admin.editingSubcat.cid===p){pc.subItems[Admin.editingSubcat.sidx]={name:n,image:i};pc.subs[Admin.editingSubcat.sidx]=n;Admin.editingSubcat=null;}else{if(!pc.subItems)pc.subItems=[];if(!pc.subs)pc.subs=[];pc.subItems.push({name:n,image:i});pc.subs.push(n);}await setDoc(doc(AppData.db,"categories",p),pc);document.getElementById('subcat-name').value='';document.getElementById('subcat-img').value='';UI.showToast("Subcategory Saved!");}} },
renderCategories: () => {
let l=AppData.categories||[]; if(Admin.catSearchQuery)l=l.filter(c=>String(c?.name||'').toLowerCase().includes(Admin.catSearchQuery)||String(c?.id||'').toLowerCase().includes(Admin.catSearchQuery)||(Array.isArray(c?.subItems)?c.subItems:[]).some(s=>String(s?.name||'').toLowerCase().includes(Admin.catSearchQuery)));document.getElementById('subcat-parent').innerHTML=(AppData.categories||[]).map(c=>``).join('');
},
delCategory: async (id) => { if(confirm("Delete ENTIRE category?")){await deleteDoc(doc(AppData.db,"categories",id));UI.showToast("Category Deleted.");} },
delSubcat: async (cId, idx) => { if(confirm("Remove subcategory?")){const c=(AppData.categories||[]).find(x=>x.id===cId);c.subItems.splice(idx,1);c.subs=c.subItems.map(s=>s.name);await setDoc(doc(AppData.db,"categories",cId),c);UI.showToast("Subcategory Deleted.");} },
activeOrderFilter: 'All',
filterOrders: (st, el) => { Admin.activeOrderFilter=st; document.querySelectorAll('.filter-btn').forEach(b=>{b.classList.remove('bg-slate-900','text-white');b.classList.add('bg-white','text-slate-600')}); if(el){el.classList.remove('bg-white','text-slate-600');el.classList.add('bg-slate-900','text-white');} Admin.renderOrders(); },
quickUpdateOrder: async (oid, st) => {
if(!confirm(`Mark order ${oid} as ${st}?`)) return;
const o = (AppData.orders||[]).find(x=>x.orderId===oid);
if(o) {
o.status = st;
await setDoc(doc(AppData.db, "orders", oid), o);
UI.showToast(`Order correctly marked as ${st}`);
Admin.renderOrders(); Admin.renderDashboard();
}
},renderOrders: () => {
let l=Admin.activeOrderFilter==='All'?[...(AppData.orders||[])]:(AppData.orders||[]).filter(o=>o.status===Admin.activeOrderFilter);
if(Admin.orderSearchQuery) l=l.filter(o=>String(o?.orderId||'').toLowerCase().includes(Admin.orderSearchQuery)||String(o?.client?.company||'').toLowerCase().includes(Admin.orderSearchQuery)||String(o?.client?.name||'').toLowerCase().includes(Admin.orderSearchQuery)||String(o?.client?.phone||'').toLowerCase().includes(Admin.orderSearchQuery));
document.getElementById('admin-orders-list').innerHTML=l.sort((a,b)=>new Date(b.date)-new Date(a.date)).map(o=>`${o.orderId} ${o.status}
${o.client?.company||'N/A'} (${o.client?.name||''})
${o.city||''}, ${o.state||''} • ${new Date(o.date).toLocaleDateString()}
${Utils.formatPrice(o.financials?.total)}
${(o.items||[]).length} Configured Items
`).join('')||'No orders found.
';
const pc=(AppData.orders||[]).filter(o=>o.status==='Pending').length; const bd=document.getElementById('admin-pending-badge'); if(pc>0){bd.innerText=pc;bd.classList.remove('hidden');}else bd.classList.add('hidden');
},
openOrderDossier: (oid) => {
const o=(AppData.orders||[]).find(x=>x.orderId===oid); if(!o)return; document.getElementById('aom-title').innerText=`Dossier: ${o.orderId}`;
object-cover border border-slate-200">${i.title}
ID: ${i.productId}
${i.quantity} Pcs @ ₹${i.unitPrice} = ${Utils.formatPrice(i.totalPrice)}
${Object.entries(i.specs||{}).map(([k,v])=>`[${k}: ${v}]`).join('')}
`).join('')}`;
document.getElementById('aom-status-select').value=o.status;document.getElementById('aom-btn-cancel').onclick=async()=>{if(confirm("Cancel this order?")){o.status="Cancelled";await setDoc(doc(AppData.db,"orders",o.orderId),o);UI.showToast(`Order Cancelled`);Admin.renderOrders();Admin.renderDashboard();document.getElementById('admin-order-modal').classList.add('hidden');}};
document.getElementById('admin-order-modal').classList.remove('hidden');document.getElementById('admin-order-modal').classList.add('flex');
},
editOrderPrice: async (oid) => {
const o=(AppData.orders||[]).find(x=>x.orderId===oid); if(!o)return;
const nt=prompt(`Edit Total Price for ${oid}:\n\nEnter new amount (₹):`, o.financials?.total||0);
if(nt!==null) { const pf=parseFloat(nt); if(!isNaN(pf)&&pf>=0){if(!o.financials)o.financials={};o.financials.total=pf;o.financials.balance=pf-(o.financials.amountPaid||0);await setDoc(doc(AppData.db,"orders",o.orderId),o);UI.showToast("Price updated.");Admin.openOrderDossier(oid);Admin.renderOrders();Admin.renderDashboard();}else UI.showToast("Invalid price."); }
},
saveFinancials: async (oid) => {
const o=(AppData.orders||[]).find(x=>x.orderId===oid); if(!o)return;
const st=document.getElementById('aom-pay-status').value; const pd=parseFloat(document.getElementById('aom-amt-paid').value)||0;
if(!o.financials)o.financials={}; o.financials.paymentStatus=st; o.financials.amountPaid=pd; o.financials.balance=(o.financials.total||0)-pd;
await setDoc(doc(AppData.db,"orders",o.orderId),o); UI.showToast("Financials saved.");
const bd=`Dear ${o.client?.name||'Customer'},\n\nUpdate regarding order (${o.orderId}):\n\n- Order Total: ${Utils.formatPrice(o.financials.total)}\n- Payment Status: ${st}\n- Amount Received: ${Utils.formatPrice(pd)}\n- Balance Due: ${Utils.formatPrice(o.financials.balance)}\n\nThank you for your business.`;
window.location.href=`mailto:${o.client?.email}?subject=Invoice / Payment Update: Order ${o.orderId}&body=${encodeURIComponent(bd)}`;
Admin.openOrderDossier(oid);
},activeReviewFilter:'pending',
setReviewFilter:(s)=>{Admin.activeReviewFilter=s;document.querySelectorAll('[id^="rev-filter-"]').forEach(e=>{e.className="flex-1 lg:flex-none px-4 py-2.5 rounded-xl text-xs font-bold bg-slate-100 text-slate-600 transition whitespace-nowrap";});document.getElementById(`rev-filter-${s}`).className=`flex-1 lg:flex-none px-4 py-2.5 rounded-xl text-xs font-bold text-white transition whitespace-nowrap ${s==='pending'?'bg-amber-500':s==='approved'?'bg-emerald-500':'bg-red-500'}`;Admin.renderReviews();},
renderReviews:()=>{
const r=(AppData.products||[]).flatMap(p=>(p.reviews||[]).map(r=>({p,r})));let l=r.filter(x=>x.r.status===Admin.activeReviewFilter);
if(Admin.reviewSearchQuery){const q=Admin.reviewSearchQuery;l=l.filter(x=>String(x?.r?.customerName||'').toLowerCase().includes(q)||String(x?.r?.text||'').toLowerCase().includes(q)||String(x?.p?.title||'').toLowerCase().includes(q)||String(x?.r?.orderId||'').toLowerCase().includes(q));}
document.getElementById('admin-reviews-list').innerHTML=l.map(({p,r})=>`
${r.customerName} on ${p.title}
${Array.from({length:r.rating}).map(()=>'').join('')}
"${r.text}"
`).join('')||`
No reviews found.
`;
},
modReview:async(pId,rId,act)=>{const p=AppData.products.find(x=>x.id===pId);p.reviews.find(x=>x.id===rId).status=act;await setDoc(doc(AppData.db,"products",pId),p);UI.showToast(`Review ${act}.`);Admin.renderReviews();},
saveSliderConfig:async()=>{
AppData.settings.sliderConfig={
speed:parseInt(document.getElementById('mkt-slider-speed').value)||4000,
ratio:document.getElementById('mkt-slider-ratio').value||'h-44 md:h-64'
};
await setDoc(doc(AppData.db,"settings","main"),AppData.settings);
UI.showToast("Settings saved!"); Render.sliders();
},
renderMarketing:()=>{
document.getElementById('mkt-ann-enable').checked=AppData.settings.announcement.isEnabled;
document.getElementById('mkt-ann-marquee').checked=AppData.settings.announcement.isMarquee;
document.getElementById('mkt-ann-text').value=AppData.settings.announcement.text;
document.getElementById('mkt-slider-speed').value=AppData.settings.sliderConfig?.speed||4000;
document.getElementById('mkt-slider-ratio').value=AppData.settings.sliderConfig?.ratio||'h-44 md:h-64';
document.getElementById('mkt-sliders-list').innerHTML=(AppData.settings.sliders||[]).map((s,i)=>{
let mp = s.type==='video' ?
(s.url.includes('youtube')||s.url.includes('youtu.be') ?
`
`
: `
`)
: `

`;
return `
${mp}
${s.title||'No Title'} ${s.type||'img'}
${s.desc||'No desc'}
`;
}).join('');
},
saveAnnouncement:async()=>{AppData.settings.announcement.isEnabled=document.getElementById('mkt-ann-enable').checked;AppData.settings.announcement.isMarquee=document.getElementById('mkt-ann-marquee').checked;AppData.settings.announcement.text=document.getElementById('mkt-ann-text').value;await setDoc(doc(AppData.db,"settings","main"),AppData.settings);UI.showToast("Saved!");},delSlider:async(i)=>{if(confirm("Delete slider?")){AppData.settings.sliders.splice(i,1);await setDoc(doc(AppData.db,"settings","main"),AppData.settings);UI.showToast("Removed.");}},
saveAppLinks:async()=>{
AppData.settings.appLinks={
android: document.getElementById('set-link-android').value.trim(),
ios: document.getElementById('set-link-ios').value.trim(),
win: document.getElementById('set-link-win').value.trim()
};
await setDoc(doc(AppData.db,"settings","main"),AppData.settings); UI.checkAppLinks(); UI.showToast("App Links Published!");
},
saveTheme:async()=>{AppData.settings.themePrimary=document.getElementById('set-color').value;await setDoc(doc(AppData.db,"settings","main"),AppData.settings);UI.applyTheme();UI.showToast("Updated Theme!");},
saveSecurity:async()=>{const u=document.getElementById('set-userid').value.trim();const p=document.getElementById('set-passcode').value.trim();if(u&&p){AppData.settings.adminId=u;AppData.settings.passcode=p;await setDoc(doc(AppData.db,"settings","main"),AppData.settings);UI.showToast("Updated!");}else UI.showToast("ID and Passcode required.");},};window.onload = () => { initCloud(); };