Logo

Our Society Mart

Freshness Delivered to your doorstep

Add Products

Search and add items to cart

Shopping Cart

0 items

Your cart is empty

Search and add products above

Checkout

Complete your order

Order Summary

Subtotal 0.00

Discount

Discount Amount -₹0.00
Grand Total 0.00
// ============================================ // Data Models & State // ============================================ let products = []; let cart = []; let bills = []; let currentBill = null; let discountType = 'amount'; let selectedSearchProducts = new Set(); let currentFilter = 'all'; let qrCodeImage = null; // Unit conversion rates (base unit = the product's original unit) const unitConversions = { kg: { kg: 1, gms: 1000 }, gms: { gms: 1, kg: 0.001 }, ltr: { ltr: 1, ml: 1000 }, ml: { ml: 1, ltr: 0.001 }, pck: { pck: 1 }, dozen: { dozen: 1, pcs: 12 }, pcs: { pcs: 1, dozen: 1/12 } }; // CSV/Excel export/import utilities function downloadExcelTemplate() { try { const template = [ ['Product Name', 'Category', 'Price (₹)', 'Unit', 'Quantity'], ...defaultProducts.map(p => [p.name, p.category, p.price, p.unit, p.quantity || 0]) ]; let csv = template.map(row => row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',') ).join('\n'); const bom = '\uFEFF'; const blob = new Blob([bom + csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); // Open in new window for user to save manually const newWindow = window.open(url, '_blank'); if (!newWindow) { showToast('Pop-up blocked. Please allow pop-ups for downloads.', 'error'); return; } // Cleanup after a delay setTimeout(() => { URL.revokeObjectURL(url); }, 30000); showToast('Template opened in new window. Use browser Save As to download.'); } catch (error) { console.error('Download template error:', error); showToast('Failed to generate template', 'error'); } } function handleExcelImport(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const csv = e.target.result; const lines = csv.trim().split('\n'); if (lines.length < 2) { showToast('Invalid file format', 'error'); return; } const importedProducts = []; for (let i = 1; i < lines.length; i++) { const cells = lines[i].split(',').map(cell => cell.replace(/^"|"$/g, '').trim()); if (cells.length < 4) continue; const name = cells[0]; const category = cells[1]; const price = parseFloat(cells[2]); const unit = cells[3]; const quantity = cells.length > 4 ? parseInt(cells[4]) || 0 : 0; if (name && category && !isNaN(price) && unit) { importedProducts.push({ id: generateId(), name, category, price, unit, quantity, baseQty: 1 }); } } if (importedProducts.length === 0) { showToast('No valid products found in file', 'error'); return; } products = importedProducts; saveProducts(); renderProductsTable(); showToast(`${importedProducts.length} products imported successfully!`); } catch (error) { console.error('Import error:', error); showToast('Error importing file', 'error'); } }; reader.readAsText(file); } const unitGroups = { weight: ['kg', 'gms'], volume: ['ltr', 'ml'], count: ['pck'], quantity: ['dozen', 'pcs'] }; // ============================================ // Default Products // ============================================ const defaultProducts = [ { id: 'p1', name: 'Basmati Rice', category: 'Grains', price: 120, unit: 'kg', quantity: 50, baseQty: 1 }, { id: 'p2', name: 'Red Onions', category: 'Vegetables', price: 40, unit: 'kg', quantity: 100, baseQty: 1 }, { id: 'p3', name: 'Fresh Tomatoes', category: 'Vegetables', price: 35, unit: 'kg', quantity: 80, baseQty: 1 }, { id: 'p4', name: 'Full Cream Milk', category: 'Dairy', price: 60, unit: 'ltr', quantity: 30, baseQty: 1 }, { id: 'p5', name: 'Farm Fresh Eggs', category: 'Dairy', price: 90, unit: 'dozen', quantity: 40, baseQty: 1 }, { id: 'p6', name: 'Whole Wheat Bread', category: 'Bakery', price: 45, unit: 'pck', quantity: 25, baseQty: 1 }, { id: 'p7', name: 'Sunflower Oil', category: 'Oils', price: 180, unit: 'ltr', quantity: 20, baseQty: 1 }, { id: 'p8', name: 'Toor Dal', category: 'Pulses', price: 140, unit: 'kg', quantity: 45, baseQty: 1 }, { id: 'p9', name: 'Surf Excel', category: 'Household', price: 250, unit: 'pck', quantity: 15, baseQty: 1 }, { id: 'p10', name: 'Dove Soap', category: 'Personal Care', price: 55, unit: 'pcs', quantity: 60, baseQty: 1 } ]; // ============================================ // LocalStorage Functions // ============================================ function loadFromStorage() { try { const storedProducts = localStorage.getItem('societymart_products'); const storedBills = localStorage.getItem('societymart_bills'); const storedQR = localStorage.getItem('societymart_qr'); if (storedProducts) { products = JSON.parse(storedProducts); } else { products = [...defaultProducts]; saveProducts(); } if (storedBills) { bills = JSON.parse(storedBills); } if (storedQR) { qrCodeImage = storedQR; } } catch (e) { console.error('Error loading from storage:', e); products = [...defaultProducts]; } } function saveProducts() { localStorage.setItem('societymart_products', JSON.stringify(products)); } function saveBills() { localStorage.setItem('societymart_bills', JSON.stringify(bills)); } function saveQRCode(base64) { localStorage.setItem('societymart_qr', base64); qrCodeImage = base64; } // ============================================ // Utility Functions // ============================================ function generateId() { return 'id_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } function formatPrice(price) { return parseFloat(price).toFixed(2); } function formatDate(date) { return new Date(date).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } function getCompatibleUnits(unit) { for (const [group, units] of Object.entries(unitGroups)) { if (units.includes(unit)) { return units; } } return [unit]; } function convertUnit(price, fromUnit, toUnit, quantity) { if (fromUnit === toUnit) { return price * quantity; } const conversions = unitConversions[fromUnit]; if (conversions && conversions[toUnit]) { const basePrice = price / conversions[toUnit]; return basePrice * quantity; } return price * quantity; } function showToast(message, type = 'success') { const container = document.getElementById('toast-container'); const toast = document.createElement('div'); const bgColor = type === 'success' ? 'bg-mint-500' : type === 'error' ? 'bg-red-500' : 'bg-brand-500'; toast.className = `toast ${bgColor} text-white px-4 py-3 rounded-lg shadow-lg flex items-center space-x-2`; toast.innerHTML = ` ${type === 'success' ? '' : type === 'error' ? '' : ''} ${message} `; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100px)'; toast.style.transition = 'all 0.3s ease'; setTimeout(() => toast.remove(), 300); }, 3000); } // ============================================ // View Management // ============================================ function switchView(view) { document.querySelectorAll('.view-container').forEach(v => v.classList.add('hidden')); document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); const viewElement = document.getElementById(`view-${view}`); if (viewElement) { viewElement.classList.remove('hidden'); } const navElement = document.getElementById(`nav-${view}`); if (navElement) { navElement.classList.add('active'); } if (view === 'products') { setTimeout(renderProductsTable, 50); } else if (view === 'history') { setTimeout(renderHistory, 50); } } // ============================================ // Product Management // ============================================ function renderProductsTable() { const tbody = document.getElementById('products-table-body'); if (!tbody) return; const filtered = currentFilter === 'all' ? products : products.filter(p => p.category === currentFilter); const categories = [...new Set(products.map(p => p.category))].sort(); const categoryFilters = document.getElementById('category-filters'); if (categoryFilters) { categoryFilters.innerHTML = categories.map(cat => ` `).join(''); } const productCount = document.getElementById('product-count'); if (productCount) { productCount.textContent = filtered.length; } tbody.innerHTML = filtered.map(product => `

${product.name}

${product.id}

${product.category} ₹${formatPrice(product.price)} ${product.unit} `).join(''); } function filterByCategory(category) { currentFilter = category; document.querySelectorAll('#category-filters button, #filter-all').forEach(btn => { btn.classList.remove('bg-brand-500', 'text-white'); btn.classList.add('bg-slate-100', 'text-slate-600'); }); const activeBtn = category === 'all' ? document.getElementById('filter-all') : document.getElementById(`filter-${category}`); if (activeBtn) { activeBtn.classList.remove('bg-slate-100', 'text-slate-600'); activeBtn.classList.add('bg-brand-500', 'text-white'); } renderProductsTable(); } function updateProductQuantity(productId, quantity) { const product = products.find(p => p.id === productId); if (product) { product.quantity = parseInt(quantity) || 0; saveProducts(); showToast('Product updated'); } } function editProduct(productId) { const product = products.find(p => p.id === productId); if (!product) return; document.getElementById('new-product-name').value = product.name; document.getElementById('new-product-category').value = product.category; document.getElementById('new-product-price').value = product.price; document.getElementById('new-product-unit').value = product.unit; document.getElementById('new-product-quantity').value = product.quantity || 0; const form = document.getElementById('add-product-form'); form.onsubmit = function(e) { e.preventDefault(); product.name = document.getElementById('new-product-name').value; product.category = document.getElementById('new-product-category').value; product.price = parseFloat(document.getElementById('new-product-price').value); product.unit = document.getElementById('new-product-unit').value; product.quantity = parseInt(document.getElementById('new-product-quantity').value) || 0; saveProducts(); renderProductsTable(); hideAddProductModal(); showToast('Product updated successfully'); form.onsubmit = handleAddProduct; }; showAddProductModal(); } function deleteProduct(productId) { products = products.filter(p => p.id !== productId); saveProducts(); renderProductsTable(); showToast('Product deleted', 'info'); } function showAddProductModal() { document.getElementById('add-product-modal').classList.remove('hidden'); } function hideAddProductModal() { document.getElementById('add-product-modal').classList.add('hidden'); document.getElementById('add-product-form').reset(); document.getElementById('add-product-form').onsubmit = handleAddProduct; } function handleAddProduct(event) { event.preventDefault(); const name = document.getElementById('new-product-name').value.trim(); const category = document.getElementById('new-product-category').value.trim(); const price = parseFloat(document.getElementById('new-product-price').value); const unit = document.getElementById('new-product-unit').value; const quantity = parseInt(document.getElementById('new-product-quantity').value) || 0; if (!name || !category || isNaN(price) || !unit) { showToast('Please fill all required fields', 'error'); return; } products.push({ id: generateId(), name, category, price, unit, quantity, baseQty: 1 }); saveProducts(); renderProductsTable(); hideAddProductModal(); showToast('Product added successfully!'); } const searchInput = document.getElementById('product-search'); const searchDropdown = document.getElementById('search-dropdown'); const searchResults = document.getElementById('search-results'); const searchActions = document.getElementById('search-actions'); searchInput.addEventListener('input', (e) => { const query = e.target.value.toLowerCase().trim(); selectedSearchProducts.clear(); if (query.length === 0) { searchDropdown.classList.add('hidden'); return; } const filtered = products.filter(p => p.name.toLowerCase().includes(query) || p.category.toLowerCase().includes(query) ); if (filtered.length === 0) { searchResults.innerHTML = '

No products found

'; searchActions.classList.add('hidden'); } else { searchResults.innerHTML = filtered.map(p => `
`).join(''); searchActions.classList.remove('hidden'); } searchDropdown.classList.remove('hidden'); document.querySelectorAll('.search-checkbox').forEach(cb => { cb.addEventListener('change', (e) => { if (e.target.checked) { selectedSearchProducts.add(e.target.dataset.id); } else { selectedSearchProducts.delete(e.target.dataset.id); } }); }); }); searchInput.addEventListener('focus', () => { if (searchInput.value.trim().length > 0) { searchDropdown.classList.remove('hidden'); } }); document.addEventListener('click', (e) => { if (!searchInput.contains(e.target) && !searchDropdown.contains(e.target)) { searchDropdown.classList.add('hidden'); } }); function addSelectedProducts() { selectedSearchProducts.forEach(id => { addToCart(id); }); selectedSearchProducts.clear(); searchInput.value = ''; searchDropdown.classList.add('hidden'); } // ============================================ // History & Bill Management // ============================================ function renderHistory() { const historyList = document.getElementById('history-list'); const historyEmpty = document.getElementById('history-empty'); const historyCount = document.getElementById('history-count'); if (!historyList) return; if (bills.length === 0) { historyList.innerHTML = historyEmpty.outerHTML; historyCount.textContent = '0'; return; } historyCount.textContent = bills.length; const sortedBills = [...bills].reverse(); const billsHtml = sortedBills.map((bill, index) => `

${bill.customerName}

${bill.id} • ${formatDate(bill.date)}

${bill.items.length} items

₹${formatPrice(bill.grandTotal)}

${bill.discountAmount > 0 ? `

Discount: ₹${formatPrice(bill.discountAmount)}

` : ''}
`).join(''); historyList.innerHTML = billsHtml; } function viewBillDetail(billId) { const bill = bills.find(b => b.id === billId); if (!bill) { showToast('Bill not found', 'error'); return; } currentBill = bill; renderReceipt(); switchView('receipt'); } function newBill() { cart = []; currentBill = null; document.getElementById('customer-name').value = ''; document.getElementById('customer-address').value = ''; document.getElementById('discount-value').value = ''; document.getElementById('discount-reason').value = ''; discountType = 'amount'; renderCart(); updateTotals(); switchView('billing'); } // ============================================ // Cart Functions // ============================================ function addToCart(productId) { const product = products.find(p => p.id === productId); if (!product) return; const existingItem = cart.find(item => item.productId === productId); if (existingItem) { existingItem.quantity += 1; existingItem.total = calculateItemTotal(existingItem); } else { cart.push({ id: generateId(), productId: product.id, name: product.name, category: product.category, basePrice: product.price, customPrice: null, baseUnit: product.unit, selectedUnit: product.unit, quantity: 1, total: product.price }); } renderCart(); updateTotals(); showToast(`${product.name} added to cart`); } function calculateItemTotal(item) { const price = item.customPrice !== null ? item.customPrice : item.basePrice; return convertUnit(price, item.baseUnit, item.selectedUnit, item.quantity); } function updateCartItem(itemId, field, value) { const item = cart.find(i => i.id === itemId); if (!item) return; if (field === 'quantity') { item.quantity = Math.max(0.01, parseFloat(value) || 1); } else if (field === 'customPrice') { item.customPrice = value === '' ? null : parseFloat(value); } else if (field === 'selectedUnit') { item.selectedUnit = value; } item.total = calculateItemTotal(item); renderCart(); updateTotals(); } function incrementQuantity(itemId) { const item = cart.find(i => i.id === itemId); if (item) { item.quantity += 1; item.total = calculateItemTotal(item); renderCart(); updateTotals(); } } function decrementQuantity(itemId) { const item = cart.find(i => i.id === itemId); if (item && item.quantity > 0.01) { item.quantity = Math.max(0.01, item.quantity - 1); item.total = calculateItemTotal(item); renderCart(); updateTotals(); } } function moveCartItem(itemId, direction) { const index = cart.findIndex(i => i.id === itemId); if (index === -1) return; const newIndex = direction === 'up' ? index - 1 : index + 1; if (newIndex < 0 || newIndex >= cart.length) return; [cart[index], cart[newIndex]] = [cart[newIndex], cart[index]]; renderCart(); } function removeFromCart(itemId) { cart = cart.filter(i => i.id !== itemId); renderCart(); updateTotals(); showToast('Item removed from cart', 'info'); } function clearCart() { cart = []; renderCart(); updateTotals(); showToast('Cart cleared', 'info'); } function renderCart() { const cartItems = document.getElementById('cart-items'); const cartEmpty = document.getElementById('cart-empty'); const cartCount = document.getElementById('cart-count'); cartCount.textContent = cart.length; if (cart.length === 0) { cartEmpty.classList.remove('hidden'); cartEmpty.nextElementSibling?.remove(); return; } cartEmpty.classList.add('hidden'); const itemsHtml = cart.map((item, index) => { const compatibleUnits = getCompatibleUnits(item.baseUnit); const unitOptions = compatibleUnits.map(u => `` ).join(''); return `

${item.name}

${item.category}
₹${formatPrice(item.total)}
`; }).join(''); cartItems.innerHTML = cartEmpty.outerHTML + itemsHtml; } // ============================================ // Checkout & Totals // ============================================ function setDiscountType(type) { discountType = type; document.getElementById('discount-type-amount').className = type === 'amount' ? 'flex-1 py-2 px-3 text-sm font-medium rounded-lg bg-brand-500 text-white transition-all' : 'flex-1 py-2 px-3 text-sm font-medium rounded-lg bg-white/10 text-slate-300 hover:bg-white/20 transition-all'; document.getElementById('discount-type-percent').className = type === 'percent' ? 'flex-1 py-2 px-3 text-sm font-medium rounded-lg bg-brand-500 text-white transition-all' : 'flex-1 py-2 px-3 text-sm font-medium rounded-lg bg-white/10 text-slate-300 hover:bg-white/20 transition-all'; updateTotals(); } function updateTotals() { const subtotal = cart.reduce((sum, item) => sum + item.total, 0); const discountValue = parseFloat(document.getElementById('discount-value').value) || 0; let discountAmount = 0; if (discountType === 'amount') { discountAmount = Math.min(discountValue, subtotal); } else { discountAmount = (subtotal * Math.min(discountValue, 100)) / 100; } const grandTotal = Math.max(0, subtotal - discountAmount); document.getElementById('subtotal').textContent = formatPrice(subtotal); document.getElementById('discount-amount').textContent = formatPrice(discountAmount); document.getElementById('grand-total').textContent = formatPrice(grandTotal); const generateBtn = document.getElementById('generate-bill-btn'); generateBtn.disabled = cart.length === 0; } document.getElementById('discount-value').addEventListener('input', updateTotals); // ============================================ // Bill Generation // ============================================ function generateBill() { if (cart.length === 0) { showToast('Cart is empty', 'error'); return; } const customerName = document.getElementById('customer-name').value.trim() || 'Walk-in Customer'; const customerAddress = document.getElementById('customer-address').value.trim(); const discountValue = parseFloat(document.getElementById('discount-value').value) || 0; const discountReason = document.getElementById('discount-reason').value.trim(); const subtotal = cart.reduce((sum, item) => sum + item.total, 0); let discountAmount = 0; if (discountType === 'amount') { discountAmount = Math.min(discountValue, subtotal); } else { discountAmount = (subtotal * Math.min(discountValue, 100)) / 100; } const billId = 'INV-' + String(bills.length + 1).padStart(4, '0'); currentBill = { id: billId, customerName, customerAddress, date: new Date().toISOString(), items: [...cart], subtotal, discountAmount, discountReason, discountType, discountValue, grandTotal: subtotal - discountAmount }; bills.push(currentBill); saveBills(); renderReceipt(); switchView('receipt'); document.getElementById('nav-billing').classList.remove('active'); showToast('Bill generated successfully!'); } function renderReceipt() { if (!currentBill) return; document.getElementById('receipt-store-name').textContent = config.store_name; document.getElementById('receipt-tagline').textContent = config.store_tagline; document.getElementById('receipt-customer').textContent = currentBill.customerName; document.getElementById('receipt-address').textContent = currentBill.customerAddress; document.getElementById('receipt-bill-id').textContent = '#' + currentBill.id; document.getElementById('receipt-date').textContent = formatDate(currentBill.date); const itemsHtml = currentBill.items.map(item => `

${item.name}

${item.category}

${item.quantity} ${item.selectedUnit} × ₹${formatPrice(item.customPrice || item.basePrice)}
₹${formatPrice(item.total)}
`).join(''); document.getElementById('receipt-items').innerHTML = itemsHtml; document.getElementById('receipt-subtotal').textContent = formatPrice(currentBill.subtotal); const discountRow = document.getElementById('receipt-discount-row'); if (currentBill.discountAmount > 0) { discountRow.classList.remove('hidden'); document.getElementById('receipt-discount').textContent = formatPrice(currentBill.discountAmount); document.getElementById('receipt-discount-reason').textContent = currentBill.discountReason ? `(${currentBill.discountReason})` : ''; } else { discountRow.classList.add('hidden'); } document.getElementById('receipt-total').textContent = formatPrice(currentBill.grandTotal); document.getElementById('receipt-footer').textContent = config.receipt_footer; // QR Code const qrSection = document.getElementById('qr-section'); if (qrCodeImage) { qrSection.classList.remove('hidden'); document.getElementById('qr-image').src = qrCodeImage; } else { qrSection.classList.add('hidden'); } } function handleQRUpload(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { saveQRCode(e.target.result); const qrSection = document.getElementById('qr-section'); qrSection.classList.remove('hidden'); document.getElementById('qr-image').src = qrCodeImage; showToast('QR Code uploaded successfully!'); }; reader.readAsDataURL(file); } function downloadReceipt() { if (!currentBill) { showToast('No bill to download', 'error'); return; } const receipt = document.getElementById('receipt-content'); if (!receipt) { showToast('Receipt not found', 'error'); return; } const downloadBtn = event.target.closest('button'); const originalText = downloadBtn.innerHTML; downloadBtn.disabled = true; downloadBtn.innerHTML = ' '; setTimeout(() => { try { html2canvas(receipt, { scale: 2, backgroundColor: '#ffffff', logging: false, useCORS: true, allowTaint: true, windowWidth: 400, windowHeight: receipt.offsetHeight, removeContainer: false, imageTimeout: 0 }).then(canvas => { try { const pngData = canvas.toDataURL('image/png'); const date = new Date().toISOString().split('T')[0]; const nameSlug = (currentBill.customerName || 'Customer').replace(/\s+/g, '_').toLowerCase(); const filename = `receipt_${nameSlug}_${currentBill.id}_${date}.png`; const newWindow = window.open(); const htmlContent = ' Receipt - ' + currentBill.id + '

Receipt Details

Bill ID: ' + currentBill.id + '

Customer: ' + currentBill.customerName + '

Date: ' + formatDate(currentBill.date) + '

Receipt

Right-click image and select "Save image as..." to download

'; newWindow.document.write(htmlContent); newWindow.document.close(); showToast('Receipt opened in new window'); } catch (e) { console.error('Error displaying image:', e); showToast('Could not display image', 'error'); } downloadBtn.disabled = false; downloadBtn.innerHTML = originalText; }).catch(err => { console.error('Canvas error:', err); showToast('Could not generate image', 'error'); downloadBtn.disabled = false; downloadBtn.innerHTML = originalText; }); } catch (e) { console.error('Error:', e); showToast('Error processing receipt', 'error'); downloadBtn.disabled = false; downloadBtn.innerHTML = originalText; }