Since 1984 | Top Bag Manufacturer & Wholesale Supplier in Bangalore
NS BAGS

SINCE 1984

Loading...

SINCE 1984

Categories

Featured Models

NS BAGS - Top Bag Manufacturer in Bangalore

THANKS FOR VISITING NS BAGS.

#tags

#Since1984Bags #TopBagManufacturerBangalore #BangaloreWholesale #BagFactoryBangalore #CorporateGiftsBangalore #SchoolBagsWholesale #LaptopBagsManufacturer #CustomBagsIndia #TravelBagsWholesale
`).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+=`
${v}
`;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&&oDs+(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(); };
Scroll to Top