fmcg-orderflow-pro / order-system.html
emeraj24's picture
import React, { useState, useRef, useEffect } from 'react';
e35c4d4 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FMCG OrderFlow Pro - Modern Order Management</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
body {
font-family: 'Inter', sans-serif;
}
.glass-effect {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass-effect {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.dark .gradient-bg {
background: linear-gradient(135deg, #4c51bf 0%, #553c9a 100%);
}
.hover-scale {
transition: transform 0.2s ease-in-out;
}
.hover-scale:hover {
transform: scale(1.02);
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 3px;
}
.dark .custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.customer-dropdown {
max-height: 300px;
overflow-y: auto;
}
.product-card {
transition: all 0.3s ease;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.dark .product-card:hover {
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
}
.order-item {
transition: all 0.2s ease;
}
.order-item:hover {
background-color: rgba(59, 130, 246, 0.1);
}
.dark .order-item:hover {
background-color: rgba(59, 130, 246, 0.2);
}
.tab-active {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
color: white;
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}
.dark .tab-active {
background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
}
.print-header {
font-size: 14px;
font-weight: bold;
text-align: center;
}
.print-body {
width: 80mm;
font-family: 'Courier New', monospace;
font-size: 11px;
margin: 5mm;
}
.print-line {
border-top: 1px dashed #000;
margin: 5px 0;
}
.print-table {
width: 100%;
border-collapse: collapse;
}
.print-table td {
padding: 2px 0;
}
.print-right {
text-align: right;
}
.print-center {
text-align: center;
}
.print-bold {
font-weight: bold;
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 transition-colors duration-300">
<div id="app">
<!-- Loading Screen -->
<div class="fixed inset-0 bg-gradient-to-br from-blue-600 to-purple-700 flex items-center justify-center z-50" id="loading-screen">
<div class="text-center text-white">
<div class="animate-float mb-4">
<i data-feather="shopping-cart" class="w-16 h-16 mx-auto"></i>
</div>
<h1 class="text-3xl font-bold mb-2">FMCG OrderFlow Pro</h1>
<p class="text-blue-100">Loading your order management system...</p>
<div class="mt-4 flex justify-center">
<div class="w-2 h-2 bg-white rounded-full mx-1 pulse-animation"></div>
<div class="w-2 h-2 bg-white rounded-full mx-1 pulse-animation" style="animation-delay: 0.2s;"></div>
<div class="w-2 h-2 bg-white rounded-full mx-1 pulse-animation" style="animation-delay: 0.4s;"></div>
</div>
</div>
</div>
<!-- Main App Container -->
<div class="min-h-screen transition-colors duration-300" id="main-app" style="display: none;">
<!-- Header -->
<header class="bg-white/80 dark:bg-gray-800/80 backdrop-blur-md shadow-lg border-b border-white/20 dark:border-gray-700/20 sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-4">
<div class="flex items-center space-x-4">
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg hover-scale">
<i data-feather="shopping-bag" class="w-6 h-6 text-white"></i>
</div>
<div>
<h1 class="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">FMCG OrderFlow Pro</h1>
<p class="text-sm text-gray-500 dark:text-gray-400">Modern Order Management</p>
</div>
</div>
<div class="flex items-center space-x-4">
<div class="hidden sm:flex items-center text-sm text-gray-600 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 px-3 py-1 rounded-full">
<i data-feather="user" class="w-4 h-4 mr-2"></i>
<span id="owner-name">Rajesh Kumar</span>
</div>
<button id="theme-toggle" class="p-2 rounded-lg bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
<i data-feather="sun" class="w-5 h-5 text-yellow-500 dark:hidden"></i>
<i data-feather="moon" class="w-5 h-5 text-blue-400 hidden dark:block"></i>
</button>
</div>
</div>
</div>
</header>
<!-- Navigation Tabs -->
<nav class="bg-white/70 dark:bg-gray-800/70 backdrop-blur-md shadow-sm border-b border-white/30 dark:border-gray-700/30 sticky top-16 z-30">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex space-x-1 sm:space-x-4 overflow-x-auto py-2" id="nav-tabs">
<!-- Navigation tabs will be generated by JavaScript -->
</div>
</div>
</nav>
<!-- Main Content -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div id="content-area">
<!-- Content will be dynamically loaded here -->
</div>
</main>
<!-- Modals -->
<div id="modal-container"></div>
</div>
</div>
<script>
// Global state management
class AppState {
constructor() {
this.state = {
activeTab: 'order',
theme: 'light',
products: [],
customers: [],
currentOrder: [],
selectedCustomer: null,
searchTerm: '',
customerSearch: '',
showCustomerDropdown: false,
orders: [],
startDate: '',
endDate: '',
invoiceNumber: 1001,
shopProfile: {
shopName: 'YOUR COMPANY NAME',
ownerName: 'Rajesh Kumar',
address: 'Address Line 1, City - PIN',
phone: '+91-XXXXXXXXXX',
email: 'info@yourcompany.com',
gstin: '27AABCCDDEEFFG'
}
};
this.listeners = [];
}
getState() {
return this.state;
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notifyListeners();
}
subscribe(listener) {
this.listeners.push(listener);
}
notifyListeners() {
this.listeners.forEach(listener => listener(this.state));
}
}
// Initialize app state
const appState = new AppState();
// UI Components
class UIComponents {
static createProductCard(product, onAddToCart) {
return `
<div class="product-card border border-gray-200 dark:border-gray-700 rounded-xl p-4 hover:shadow-lg transition-all duration-300 cursor-pointer bg-white dark:bg-gray-800 hover:bg-blue-50 dark:hover:bg-blue-900/20 hover-scale" onclick="${onAddToCart}">
<div class="flex justify-between items-start mb-2">
<h3 class="font-semibold text-gray-800 dark:text-white text-sm">${product.name}</h3>
<span class="text-xs bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 px-2 py-1 rounded-full">${product.category}</span>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-2">Code: ${product.code}</p>
<div class="flex justify-between items-center">
<span class="text-lg font-bold text-blue-600 dark:text-blue-400">₹${product.price.toFixed(2)}</span>
<span class="text-xs text-green-600 dark:text-green-400 bg-green-100 dark:bg-green-900 px-2 py-1 rounded-full">${product.gst}% GST</span>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">Stock: ${product.stock}</p>
</div>
`;
}
static createCartItem(item, isPurchase = false) {
const updateFunction = isPurchase ? `updatePurchaseQuantity(${item.id}, ${item.quantity - 1})` : `updateQuantity(${item.id}, ${item.quantity - 1})`;
const removeFunction = isPurchase ? `removeFromPurchaseCart(${item.id})` : `removeFromCart(${item.id})`;
const price = isPurchase ? (item.purchasePrice || item.price) : item.price;
return `
<div class="order-item flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg slide-in">
<div class="flex-1">
<h4 class="font-medium text-sm text-gray-800 dark:text-white">${item.name}</h4>
<p class="text-xs text-gray-600 dark:text-gray-300">₹${price.toFixed(2)} × ${item.qty}</p>
${isPurchase ? `<p class="text-xs text-blue-600 dark:text-blue-400">Purchase: ₹${(item.purchasePrice || item.price).toFixed(2)}</p>` : ''}
</div>
<div class="flex items-center space-x-2">
<input type="number" min="0" value="${item.qty}" onchange="${isPurchase ? `updatePurchaseQuantity(${item.id}, this.value)` : `updateQuantity(${item.id}, this.value)`}" class="w-16 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-center text-sm bg-white dark:bg-gray-800">
<button onclick="${removeFunction}" class="text-red-600 hover:text-red-700 transition-colors">
<i data-feather="x" class="w-4 h-4"></i>
</button>
</div>
</div>
`;
}
static createCustomerCard(customer, onSelect) {
return `
<div class="p-4 hover:bg-blue-50 dark:hover:bg-blue-900/20 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors" onclick="${onSelect}">
<div class="flex items-start justify-between">
<div class="flex-1">
<p class="font-semibold text-gray-900 dark:text-white">${customer.name}</p>
<p class="text-sm text-gray-600 dark:text-gray-300 mt-1 flex items-center gap-1">
<i data-feather="package" class="w-3 h-3"></i>
${customer.shop}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">📱 ${customer.mobile}</p>
</div>
<span class="text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 px-2 py-1 rounded">
${customer.code}
</span>
</div>
</div>
`;
}
static createOrderRow(order) {
return `
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<td class="px-4 py-3 text-sm font-medium text-blue-600 dark:text-blue-400">${order.orderNo}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">${order.date}</td>
<td class="px-4 py-3 text-sm font-medium text-gray-800 dark:text-white">${order.customer.name}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">${order.customer.shop}</td>
<td class="px-4 py-3 text-sm text-right text-gray-600 dark:text-gray-300">${order.items.reduce((sum, item) => sum + item.qty, 0)}</td>
<td class="px-4 py-3 text-sm text-right text-gray-600 dark:text-gray-300">₹${order.subtotal}</td>
<td class="px-4 py-3 text-sm text-right text-gray-600 dark:text-gray-300">₹${order.gst}</td>
<td class="px-4 py-3 text-sm text-right font-bold text-green-600 dark:text-green-400">₹${order.total}</td>
<td class="px-4 py-3 text-center">
<button onclick="printOrderFromReport(${order.id})" class="inline-flex items-center gap-1 bg-indigo-600 hover:bg-indigo-700 text-white px-3 py-1.5 rounded-lg transition-colors text-sm">
<i data-feather="printer" class="w-4 h-4"></i>
Print
</button>
</td>
</tr>
`;
}
static createProductSummaryRow(product) {
return `
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<td class="px-4 py-3 text-sm font-medium text-gray-800 dark:text-white">${product.name}</td>
<td class="px-4 py-3 text-sm text-right text-gray-600 dark:text-gray-300">${product.qty}</td>
<td class="px-4 py-3 text-sm text-right font-semibold text-green-600 dark:text-green-400">₹${product.amount.toFixed(2)}</td>
</tr>
`;
}
}
// Navigation Component
class Navigation {
static tabs = [
{ id: 'order', name: 'New Order', icon: 'shopping-cart' },
{ id: 'products', name: 'Products', icon: 'package' },
{ id: 'customers', name: 'Customers', icon: 'users' },
{ id: 'reports', name: 'Reports', icon: 'file-text' }
];
static render() {
const navContainer = document.getElementById('nav-tabs');
const state = appState.getState();
navContainer.innerHTML = this.tabs.map(tab => `
<button onclick="Navigation.switchTab('${tab.id}')"
class="flex items-center space-x-2 px-4 py-2 rounded-lg font-medium transition-all duration-300 whitespace-nowrap ${state.activeTab === tab.id ? 'tab-active' : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'}">
<i data-feather="${tab.icon}" class="w-4 h-4"></i>
<span>${tab.name}</span>
${tab.id === 'products' && state.products.length > 0 ? `<span class="ml-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 px-2 py-0.5 rounded-full">${state.products.length}</span>` : ''}
${tab.id === 'customers' && state.customers.length > 0 ? `<span class="ml-1 text-xs bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400 px-2 py-0.5 rounded-full">${state.customers.length}</span>` : ''}
${tab.id === 'reports' && state.orders.length > 0 ? `<span class="ml-1 text-xs bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-400 px-2 py-0.5 rounded-full">${state.orders.length}</span>` : ''}
</button>
`).join('');
feather.replace();
}
static switchTab(tabId) {
appState.setState({ activeTab: tabId });
}
}
// Content Renderer
class ContentRenderer {
static render() {
const state = appState.getState();
const contentArea = document.getElementById('content-area');
switch(state.activeTab) {
case 'order':
this.renderOrderTab(contentArea);
break;
case 'products':
this.renderProductsTab(contentArea);
break;
case 'customers':
this.renderCustomersTab(contentArea);
break;
case 'reports':
this.renderReportsTab(contentArea);
break;
}
feather.replace();
}
static renderOrderTab(container) {
const state = appState.getState();
const filteredProducts = state.products.filter(p =>
(p.name && p.name.toLowerCase().includes(state.searchTerm.toLowerCase())) ||
(p.code && p.code.toLowerCase().includes(state.searchTerm.toLowerCase()))
);
const filteredCustomers = state.customers.filter(c =>
(c.name && c.name.toLowerCase().includes(state.customerSearch.toLowerCase())) ||
(c.shop && c.shop.toLowerCase().includes(state.customerSearch.toLowerCase())) ||
(c.mobile && c.mobile.includes(state.customerSearch)) ||
(c.code && c.code.toLowerCase().includes(state.customerSearch.toLowerCase()))
);
const totals = this.calculateTotals(state.currentOrder);
container.innerHTML = `
<div class="grid lg:grid-cols-3 gap-6">
<!-- Left: Product Selection -->
<div class="lg:col-span-2 space-y-4">
<!-- Customer Selection -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4 flex items-center gap-2">
<i data-feather="users" class="w-5 h-5 text-blue-600"></i>
Customer Information
</h3>
${!state.selectedCustomer ? `
<div class="relative customer-search-container">
<div class="relative">
<i data-feather="search" class="absolute left-3 top-3.5 w-5 h-5 text-gray-400"></i>
<input
type="text"
placeholder="Search customer by name, shop, mobile or code..."
value="${state.customerSearch}"
oninput="OrderSystem.updateCustomerSearch(this.value)"
onfocus="OrderSystem.showCustomerDropdown(true)"
class="w-full pl-10 pr-4 py-3 border-2 border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
/>
</div>
${state.showCustomerDropdown && state.customerSearch && filteredCustomers.length > 0 ? `
<div class="absolute z-10 w-full mt-2 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-xl customer-dropdown">
${filteredCustomers.map(customer => UIComponents.createCustomerCard(customer, `OrderSystem.selectCustomer(${customer.id})`)).join('')}
</div>
` : ''}
${state.showCustomerDropdown && state.customerSearch && filteredCustomers.length === 0 ? `
<div class="absolute z-10 w-full mt-2 bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg shadow-xl p-4">
<p class="text-center text-gray-500 dark:text-gray-400">No customers found</p>
</div>
` : ''}
</div>
` : `
<div class="relative">
<div class="p-5 bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-lg border-2 border-blue-200 dark:border-blue-700">
<button onclick="OrderSystem.selectCustomer(null)" class="absolute top-3 right-3 text-gray-400 hover:text-red-600 transition-colors">
<i data-feather="x" class="w-5 h-5"></i>
</button>
<div class="flex items-start gap-3">
<div class="bg-blue-600 rounded-full p-2">
<i data-feather="users" class="w-5 h-5 text-white"></i>
</div>
<div class="flex-1">
<p class="font-bold text-lg text-blue-900 dark:text-blue-100">${state.selectedCustomer.name}</p>
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1 flex items-center gap-1">
<i data-feather="package" class="w-4 h-4"></i>
${state.selectedCustomer.shop}
</p>
<div class="flex gap-4 mt-2 text-sm text-blue-600 dark:text-blue-400">
<span>📱 ${state.selectedCustomer.mobile}</span>
${state.selectedCustomer.code ? `<span>🔖 ${state.selectedCustomer.code}</span>` : ''}
</div>
</div>
</div>
</div>
</div>
`}
</div>
<!-- Product Search and List -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<div class="mb-4">
<div class="relative">
<i data-feather="search" class="absolute left-3 top-3 w-5 h-5 text-gray-400"></i>
<input
type="text"
placeholder="Search products..."
value="${state.searchTerm}"
oninput="OrderSystem.updateSearchTerm(this.value)"
class="w-full pl-10 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
/>
</div>
</div>
<div class="grid gap-3 max-h-96 overflow-y-auto custom-scrollbar">
${filteredProducts.map(product => UIComponents.createProductCard(product, `OrderSystem.addToOrder(${product.id})`)).join('')}
</div>
${filteredProducts.length === 0 && state.searchTerm ? `
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
<i data-feather="package" class="w-12 h-12 mx-auto mb-2 opacity-50"></i>
<p>No products found matching "${state.searchTerm}"</p>
</div>
` : ''}
</div>
</div>
<!-- Right: Current Order -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 lg:sticky lg:top-24 h-fit">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4">Current Order</h3>
<div class="space-y-3 mb-4 max-h-64 overflow-y-auto custom-scrollbar">
${state.currentOrder.map(item => UIComponents.createCartItem(item)).join('')}
</div>
${state.currentOrder.length > 0 ? `
<div class="border-t border-gray-200 dark:border-gray-600 pt-4 space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600 dark:text-gray-400">Subtotal:</span>
<span class="font-semibold text-gray-800 dark:text-white">₹${totals.subtotal}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600 dark:text-gray-400">GST:</span>
<span class="font-semibold text-gray-800 dark:text-white">₹${totals.gst}</span>
</div>
<div class="flex justify-between text-lg font-bold border-t border-gray-200 dark:border-gray-600 pt-2">
<span class="text-gray-800 dark:text-white">Total:</span>
<span class="text-blue-600 dark:text-blue-400">₹${totals.total}</span>
</div>
</div>
<div class="mt-6 space-y-2">
<button onclick="OrderSystem.printOrder()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 rounded-lg transition-colors font-semibold flex items-center justify-center gap-2">
<i data-feather="printer" class="w-5 h-5"></i>
Print Order
</button>
<button onclick="OrderSystem.saveOrder()" class="w-full bg-green-600 hover:bg-green-700 text-white py-3 rounded-lg transition-colors font-semibold">
Save Order
</button>
<button onclick="OrderSystem.exportToExcel()" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-3 rounded-lg transition-colors font-semibold flex items-center justify-center gap-2">
<i data-feather="download" class="w-5 h-5"></i>
Export All Orders
</button>
</div>
` : `
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
<i data-feather="shopping-cart" class="w-12 h-12 mx-auto mb-2 opacity-50"></i>
<p>No items in order</p>
</div>
`}
</div>
</div>
`;
}
static renderProductsTab(container) {
const state = appState.getState();
container.innerHTML = `
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<h2 class="text-xl font-bold text-gray-800 dark:text-white">Product Master</h2>
<div class="flex gap-2">
<button onclick="document.getElementById('product-file-input').click()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2">
<i data-feather="upload" class="w-4 h-4"></i>
Upload Excel
</button>
<button onclick="OrderSystem.downloadProductTemplate()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2">
<i data-feather="download" class="w-4 h-4"></i>
Template
</button>
</div>
</div>
<input type="file" id="product-file-input" accept=".xlsx,.xls" class="hidden" onchange="OrderSystem.handleProductUpload(event)">
${state.products.length > 0 ? `
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Code</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Product Name</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Category</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Price</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">GST %</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Stock</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-600">
${state.products.map(product => `
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<td class="px-4 py-3 text-sm text-gray-800 dark:text-white">${product.code}</td>
<td class="px-4 py-3 text-sm font-medium text-gray-800 dark:text-white">${product.name}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">${product.category}</td>
<td class="px-4 py-3 text-sm text-right text-gray-800 dark:text-white">₹${product.price.toFixed(2)}</td>
<td class="px-4 py-3 text-sm text-right text-gray-600 dark:text-gray-300">${product.gst}%</td>
<td class="px-4 py-3 text-sm text-right ${product.stock < 10 ? 'text-red-600 dark:text-red-400 font-semibold' : 'text-gray-600 dark:text-gray-300'}">${product.stock}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
` : `
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
<i data-feather="package" class="w-16 h-16 mx-auto mb-4 opacity-50"></i>
<h3 class="text-lg font-semibold mb-2">No Products Found</h3>
<p class="mb-4">Upload your product data using Excel file</p>
<button onclick="document.getElementById('product-file-input').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg transition-colors">
Upload Products
</button>
</div>
`}
</div>
`;
}
static renderCustomersTab(container) {
const state = appState.getState();
container.innerHTML = `
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<h2 class="text-xl font-bold text-gray-800 dark:text-white">Customer Master</h2>
<div class="flex gap-2">
<button onclick="document.getElementById('customer-file-input').click()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2">
<i data-feather="upload" class="w-4 h-4"></i>
Upload Excel
</button>
<button onclick="OrderSystem.downloadCustomerTemplate()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2">
<i data-feather="download" class="w-4 h-4"></i>
Template
</button>
</div>
</div>
<input type="file" id="customer-file-input" accept=".xlsx,.xls" class="hidden" onchange="OrderSystem.handleCustomerUpload(event)">
${state.customers.length > 0 ? `
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Code</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Name</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Mobile</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Address</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Balance</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-600">
${state.customers.map(customer => `
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<td class="px-4 py-3 text-sm text-gray-800 dark:text-white">${customer.code}</td>
<td class="px-4 py-3 text-sm font-medium text-gray-800 dark:text-white">${customer.name}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">${customer.mobile}</td>
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">${customer.address}</td>
<td class="px-4 py-3 text-sm text-right font-semibold ${customer.balance > 0 ? 'text-red-600 dark:text-red-400' : 'text-green-600 dark:text-green-400'}">₹${customer.balance.toFixed(2)}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
` : `
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
<i data-feather="users" class="w-16 h-16 mx-auto mb-4 opacity-50"></i>
<h3 class="text-lg font-semibold mb-2">No Customers Found</h3>
<p class="mb-4">Upload your customer data using Excel file</p>
<button onclick="document.getElementById('customer-file-input').click()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg transition-colors">
Upload Customers
</button>
</div>
`}
</div>
`;
}
static renderReportsTab(container) {
const state = appState.getState();
const filteredOrders = this.getFilteredOrders(state.orders, state.startDate, state.endDate);
const summary = this.getReportSummary(filteredOrders);
const productSales = this.getProductSalesSummary(filteredOrders);
container.innerHTML = `
<div class="space-y-6">
<!-- Date Filters -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4 flex items-center gap-2">
<i data-feather="calendar" class="w-5 h-5"></i>
Date Filter
</h3>
<div class="grid md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">From Date</label>
<input
type="date"
value="${state.startDate}"
onchange="OrderSystem.updateDateFilter('startDate', this.value)"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">To Date</label>
<input
type="date"
value="${state.endDate}"
onchange="OrderSystem.updateDateFilter('endDate', this.value)"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
/>
</div>
<div class="flex items-end gap-2">
<button onclick="OrderSystem.clearDateFilters()" class="flex-1 bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors">
Clear
</button>
<button onclick="OrderSystem.exportFilteredOrders()" class="flex-1 bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center justify-center gap-2">
<i data-feather="download" class="w-4 h-4"></i>
Export
</button>
</div>
</div>
</div>
<!-- Summary Cards -->
<div class="grid md:grid-cols-4 gap-4">
<div class="bg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-xl p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-blue-100 text-sm">Total Orders</p>
<p class="text-2xl font-bold">${summary.totalOrders}</p>
</div>
<i data-feather="file-text" class="w-8 h-8 text-blue-200"></i>
</div>
</div>
<div class="bg-gradient-to-br from-green-500 to-green-600 text-white rounded-xl p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-green-100 text-sm">Total Amount</p>
<p class="text-2xl font-bold">₹${summary.totalAmount}</p>
</div>
<i data-feather="dollar-sign" class="w-8 h-8 text-green-200"></i>
</div>
</div>
<div class="bg-gradient-to-br from-purple-500 to-purple-600 text-white rounded-xl p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-purple-100 text-sm">Total GST</p>
<p class="text-2xl font-bold">₹${summary.totalGST}</p>
</div>
<i data-feather="percent" class="w-8 h-8 text-purple-200"></i>
</div>
</div>
<div class="bg-gradient-to-br from-orange-500 to-orange-600 text-white rounded-xl p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-orange-100 text-sm">Total Items</p>
<p class="text-2xl font-bold">${summary.totalItems}</p>
</div>
<i data-feather="package" class="w-8 h-8 text-orange-200"></i>
</div>
</div>
</div>
<!-- Orders Table -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4">Order Details</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Order No</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Date</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Customer</th>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Shop</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Items</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Subtotal</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">GST</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Total</th>
<th class="px-4 py-3 text-center text-sm font-semibold text-gray-700 dark:text-gray-300">Action</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-600">
${filteredOrders.length === 0 ? `
<tr>
<td colspan="9" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
No orders found for selected date range
</td>
</tr>
` : filteredOrders.map(order => UIComponents.createOrderRow(order)).join('')}
</tbody>
</table>
</div>
</div>
<!-- Product-wise Summary -->
${filteredOrders.length > 0 ? `
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4">Product-wise Sales Summary</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">Product</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Quantity Sold</th>
<th class="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">Total Amount</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-600">
${productSales.map(product => UIComponents.createProductSummaryRow(product)).join('')}
</tbody>
</table>
</div>
</div>
` : ''}
</div>
`;
}
static calculateTotals(orderItems) {
let subtotal = 0;
let totalGst = 0;
orderItems.forEach(item => {
const itemTotal = item.price * item.qty;
subtotal += itemTotal;
totalGst += (itemTotal * item.gst) / 100;
});
return {
subtotal: subtotal.toFixed(2),
gst: totalGst.toFixed(2),
total: (subtotal + totalGst).toFixed(2)
};
}
static getFilteredOrders(orders, startDate, endDate) {
if (!startDate && !endDate) return orders;
return orders.filter(order => {
const orderDate = new Date(order.dateObj);
const start = startDate ? new Date(startDate) : null;
const end = endDate ? new Date(endDate) : null;
if (start && end) {
end.setHours(23, 59, 59, 999);
return orderDate >= start && orderDate <= end;
} else if (start) {
return orderDate >= start;
} else if (end) {
end.setHours(23, 59, 59, 999);
return orderDate <= end;
}
return true;
});
}
static getReportSummary(orders) {
const totalOrders = orders.length;
const totalAmount = orders.reduce((sum, order) => sum + parseFloat(order.total), 0);
const totalGST = orders.reduce((sum, order) => sum + parseFloat(order.gst), 0);
const totalItems = orders.reduce((sum, order) =>
sum + order.items.reduce((itemSum, item) => itemSum + item.qty, 0), 0
);
return {
totalOrders,
totalAmount: totalAmount.toFixed(2),
totalGST: totalGST.toFixed(2),
totalItems
};
}
static getProductSalesSummary(orders) {
const productSales = {};
orders.forEach(order => {
order.items.forEach(item => {
if (!productSales[item.id]) {
productSales[item.id] = {
name: item.name,
qty: 0,
amount: 0
};
}
productSales[item.id].qty += item.qty;
productSales[item.id].amount += item.price * item.qty;
});
});
return Object.values(productSales)
.sort((a, b) => b.amount - a.amount);
}
}
// Order System
class OrderSystem {
static updateSearchTerm(term) {
appState.setState({ searchTerm: term });
}
static updateCustomerSearch(term) {
appState.setState({
customerSearch: term,
showCustomerDropdown: term.length > 0
});
}
static showCustomerDropdown(show) {
appState.setState({ showCustomerDropdown: show });
}
static selectCustomer(customerId) {
const state = appState.getState();
const customer = state.customers.find(c => c.id === customerId);
appState.setState({
selectedCustomer: customer,
customerSearch: '',
showCustomerDropdown: false
});
}
static addToOrder(productId) {
const state = appState.getState();
const product = state.products.find(p => p.id === productId);
const existingItem = state.currentOrder.find(item => item.id === productId);
if (existingItem) {
const updatedOrder = state.currentOrder.map(item =>
item.id === productId ? { ...item, qty: item.qty + 1 } : item
);
appState.setState({ currentOrder: updatedOrder });
} else {
appState.setState({
currentOrder: [...state.currentOrder, { ...product, qty: 1 }]
});
}
}
static updateQuantity(productId, newQuantity) {
const state = appState.getState();
const quantity = parseInt(newQuantity);
if (quantity <= 0) {
const updatedOrder = state.currentOrder.filter(item => item.id !== productId);
appState.setState({ currentOrder: updatedOrder });
} else {
const updatedOrder = state.currentOrder.map(item =>
item.id === productId ? { ...item, qty: quantity } : item
);
appState.setState({ currentOrder: updatedOrder });
}
}
static calculateTotals(orderItems) {
let subtotal = 0;
let totalGst = 0;
orderItems.forEach(item => {
const itemTotal = item.price * item.qty;
subtotal += itemTotal;
totalGst += (itemTotal * item.gst) / 100;
});
return {
subtotal: subtotal.toFixed(2),
gst: totalGst.toFixed(2),
total: (subtotal + totalGst).toFixed(2)
};
}
static saveOrder() {
const state = appState.getState();
if (!state.selectedCustomer || state.currentOrder.length === 0) {
this.showNotification('Please select customer and add items to order', 'error');
return;
}
const totals = this.calculateTotals(state.currentOrder);
const newOrder = {
id: state.orders.length + 1,
orderNo: `ORD${String(state.orders.length + 1).padStart(5, '0')}`,
date: new Date().toLocaleString('en-IN'),
dateObj: new Date(),
customer: state.selectedCustomer,
items: [...state.currentOrder],
...totals
};
appState.setState({
orders: [...state.orders, newOrder],
currentOrder: [],
selectedCustomer: null
});
this.showNotification('Order saved successfully!', 'success');
}
static printOrder() {
const state = appState.getState();
if (!state.selectedCustomer || state.currentOrder.length === 0) {
this.showNotification('Please select customer and add items to order', 'error');
return;
}
const totals = this.calculateTotals(state.currentOrder);
const orderNo = `ORD${String(state.orders.length + 1).padStart(5, '0')}`;
const printContent = `
<html>
<head>
<title>Order Slip - ${orderNo}</title>
<style>
@media print {
@page { size: 80mm auto; margin: 0; }
body { margin: 0; }
}
body {
width: 80mm;
font-family: 'Courier New', monospace;
font-size: 11px;
margin: 5mm;
}
.center { text-align: center; }
.bold { font-weight: bold; }
.line { border-top: 1px dashed #000; margin: 5px 0; }
table { width: 100%; border-collapse: collapse; }
td { padding: 2px 0; }
.right { text-align: right; }
</style>
</head>
<body>
<div class="center bold" style="font-size: 14px;">${state.shopProfile.shopName}</div>
<div class="center" style="font-size: 10px;">${state.shopProfile.address}</div>
<div class="center" style="font-size: 10px;">Ph: ${state.shopProfile.phone}</div>
<div class="line"></div>
<div class="bold">Order No: ${orderNo}</div>
<div>Date: ${new Date().toLocaleString('en-IN')}</div>
<div class="line"></div>
<div class="bold">Customer Details:</div>
<div>${state.selectedCustomer.name}</div>
<div>${state.selectedCustomer.shop}</div>
<div>Ph: ${state.selectedCustomer.mobile}</div>
<div class="line"></div>
<table>
<tr class="bold">
<td>Item</td>
<td class="right">Qty</td>
<td class="right">Rate</td>
<td class="right">Amt</td>
</tr>
<tr><td colspan="4"><div class="line"></div></td></tr>
${state.currentOrder.map(item => `
<tr>
<td>${item.name}</td>
<td class="right">${item.qty}</td>
<td class="right">${item.price.toFixed(2)}</td>
<td class="right">${(item.price * item.qty).toFixed(2)}</td>
</tr>
`).join('')}
</table>
<div class="line"></div>
<table>
<tr>
<td>Subtotal:</td>
<td class="right">₹${totals.subtotal}</td>
</tr>
<tr>
<td>GST:</td>
<td class="right">₹${totals.gst}</td>
</tr>
<tr class="bold">
<td>Total:</td>
<td class="right">₹${totals.total}</td>
</tr>
</table>
<div class="line"></div>
<div class="center" style="margin-top: 10px;">Thank You!</div>
<div class="center" style="font-size: 9px;">Visit Again</div>
<script>
window.onload = function() {
window.print();
setTimeout(function() {
window.close();
}, 100);
};
</script>
</body>
</html>
`;
const printWindow = window.open('', '_blank', 'width=300,height=600');
if (printWindow) {
printWindow.document.open();
printWindow.document.write(printContent);
printWindow.document.close();
} else {
this.showNotification('Please allow pop-ups to print orders', 'error');
}
}
static printOrderFromReport(orderId) {
const state = appState.getState();
const order = state.orders.find(o => o.id === orderId);
if (!order) return;
const printContent = `
<html>
<head>
<title>Order Slip - ${order.orderNo}</title>
<style>
@media print {
@page { size: 80mm auto; margin: 0; }
body { margin: 0; }
}
body {
width: 80mm;
font-family: 'Courier New', monospace;
font-size: 11px;
margin: 5mm;
}
.center { text-align: center; }
.bold { font-weight: bold; }
.line { border-top: 1px dashed #000; margin: 5px 0; }
table { width: 100%; border-collapse: collapse; }
td { padding: 2px 0; }
.right { text-align: right; }
</style>
</head>
<body>
<div class="center bold" style="font-size: 14px;">${state.shopProfile.shopName}</div>
<div class="center" style="font-size: 10px;">${state.shopProfile.address}</div>
<div class="center" style="font-size: 10px;">Ph: ${state.shopProfile.phone}</div>
<div class="line"></div>
<div class="bold">Order No: ${order.orderNo}</div>
<div>Date: ${order.date}</div>
<div class="line"></div>
<div class="bold">Customer Details:</div>
<div>${order.customer.name}</div>
<div>${order.customer.shop}</div>
<div>Ph: ${order.customer.mobile}</div>
<div class="line"></div>
<table>
<tr class="bold">
<td>Item</td>
<td class="right">Qty</td>
<td class="right">Rate</td>
<td class="right">Amt</td>
</tr>
<tr><td colspan="4"><div class="line"></div></td></tr>
${order.items.map(item => `
<tr>
<td>${item.name}</td>
<td class="right">${item.qty}</td>
<td class="right">${item.price.toFixed(2)}</td>
<td class="right">${(item.price * item.qty).toFixed(2)}</td>
</tr>
`).join('')}
</table>
<div class="line"></div>
<table>
<tr>
<td>Subtotal:</td>
<td class="right">₹${order.subtotal}</td>
</tr>
<tr>
<td>GST:</td>
<td class="right">₹${order.gst}</td>
</tr>
<tr class="bold">
<td>Total:</td>
<td class="right">₹${order.total}</td>
</tr>
</table>
<div class="line"></div>
<div class="center" style="margin-top: 10px;">Thank You!</div>
<div class="center" style="font-size: 9px;">Visit Again</div>
<script>
window.onload = function() {
window.print();
setTimeout(function() {
window.close();
}, 100);
};
</script>
</body>
</html>
`;
const printWindow = window.open('', '_blank', 'width=300,height=600');
if (printWindow) {
printWindow.document.open();
printWindow.document.write(printContent);
printWindow.document.close();
} else {
this.showNotification('Please allow pop-ups to print orders', 'error');
}
}
static handleProductUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
const formattedProducts = jsonData.map((row, idx) => ({
id: idx + 1,
code: String(row.Code || row.code || ''),
name: String(row.Name || row.name || row.Product || ''),
category: String(row.Category || row.category || ''),
price: parseFloat(row.Price || row.price || 0),
mrp: parseFloat(row.MRP || row.mrp || 0),
gst: parseFloat(row.GST || row.gst || 0),
stock: parseInt(row.Stock || row.stock || 0)
}));
appState.setState({ products: formattedProducts });
this.showNotification('Products uploaded successfully!', 'success');
} catch (error) {
this.showNotification('Error uploading products. Please check file format.', 'error');
}
};
reader.readAsArrayBuffer(file);
}
static handleCustomerUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
const formattedCustomers = jsonData.map((row, idx) => ({
id: idx + 1,
code: String(row.Code || row.code || ''),
name: String(row.Name || row.name || ''),
shop: String(row.Shop || row.shop || row['Shop Name'] || ''),
mobile: String(row.Mobile || row.mobile || row.Phone || ''),
address: String(row.Address || row.address || ''),
gstin: String(row.GSTIN || row.gstin || ''),
balance: parseFloat(row.Balance || row.balance || 0)
}));
appState.setState({ customers: formattedCustomers });
this.showNotification('Customers uploaded successfully!', 'success');
} catch (error) {
this.showNotification('Error uploading customers. Please check file format.', 'error');
}
};
reader.readAsArrayBuffer(file);
}
static exportToExcel() {
const state = appState.getState();
if (state.orders.length === 0) {
this.showNotification('No orders to export', 'error');
return;
}
const exportData = [];
state.orders.forEach(order => {
order.items.forEach(item => {
exportData.push({
'Order No': order.orderNo,
'Date': order.date,
'Customer Name': order.customer.name,
'Customer Shop': order.customer.shop,
'Customer Mobile': order.customer.mobile,
'Product Code': item.code,
'Product Name': item.name,
'Category': item.category,
'Quantity': item.qty,
'Rate': item.price,
'Amount': (item.price * item.qty).toFixed(2),
'GST %': item.gst,
'Order Subtotal': order.subtotal,
'Order GST': order.gst,
'Order Total': order.total
});
});
});
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Orders');
XLSX.writeFile(wb, `Orders_${new Date().toISOString().split('T')[0]}.xlsx`);
}
static exportFilteredOrders() {
const state = appState.getState();
const filteredOrders = ContentRenderer.getFilteredOrders(state.orders, state.startDate, state.endDate);
if (filteredOrders.length === 0) {
this.showNotification('No orders to export for selected date range', 'error');
return;
}
const exportData = [];
filteredOrders.forEach(order => {
order.items.forEach(item => {
exportData.push({
'Order No': order.orderNo,
'Date': order.date,
'Customer Name': order.customer.name,
'Customer Shop': order.customer.shop,
'Customer Mobile': order.customer.mobile,
'Product Code': item.code,
'Product Name': item.name,
'Category': item.category,
'Quantity': item.qty,
'Rate': item.price,
'Amount': (item.price * item.qty).toFixed(2),
'GST %': item.gst,
'Order Subtotal': order.subtotal,
'Order GST': order.gst,
'Order Total': order.total
});
});
});
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Orders');
const filename = state.startDate && state.endDate
? `Orders_${state.startDate}_to_${state.endDate}.xlsx`
: `Orders_${new Date().toISOString().split('T')[0]}.xlsx`;
XLSX.writeFile(wb, filename);
}
static updateDateFilter(type, value) {
const state = appState.getState();
if (type === 'startDate') {
appState.setState({ startDate: value });
} else {
appState.setState({ endDate: value });
}
}
static clearDateFilters() {
appState.setState({ startDate: '', endDate: '' });
}
static downloadProductTemplate() {
const template = [
{ Code: 'P001', Name: 'Sample Product', Category: 'Category A', Price: 100, MRP: 120, GST: 18, Stock: 50 },
{ Code: 'P002', Name: 'Another Product', Category: 'Category B', Price: 200, MRP: 250, GST: 12, Stock: 30 }
];
const ws = XLSX.utils.json_to_sheet(template);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Products');
XLSX.writeFile(wb, 'Product_Template.xlsx');
}
static downloadCustomerTemplate() {
const template = [
{ Code: 'C001', Name: 'John Doe', Shop: 'ABC Store', Mobile: '9876543210', Address: '123 Main St', GSTIN: '27AABCCDDEEFFG', Balance: 0 },
{ Code: 'C002', Name: 'Jane Smith', Shop: 'XYZ Mart', Mobile: '9123456789', Address: '456 Market Rd', GSTIN: '27XYZPQRST1234Z', Balance: 500 }
];
const ws = XLSX.utils.json_to_sheet(template);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Customers');
XLSX.writeFile(wb, 'Customer_Template.xlsx');
}
static showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg transition-all duration-300 ${
type === 'success' ? 'bg-green-500 text-white' : 'bg-red-500 text-white'
}`;
notification.innerHTML = `
<div class="flex items-center gap-2">
<i data-feather="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-5 h-5"></i>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification);
feather.replace();
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
}
// Theme Manager
class ThemeManager {
static init() {
const savedTheme = localStorage.getItem('theme') || 'light';
this.setTheme(savedTheme);
document.getElementById('theme-toggle').addEventListener('click', () => {
const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
this.setTheme(newTheme);
});
}
static setTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
appState.setState({ theme });
}
}
// Initialize App
function initializeApp() {
// Subscribe to state changes
appState.subscribe((state) => {
Navigation.render();
ContentRenderer.render();
});
// Initialize theme
ThemeManager.init();
// Hide loading screen and show main app
setTimeout(() => {
document.getElementById('loading-screen').style.display = 'none';
document.getElementById('main-app').style.display = 'block';
// Initial render
Navigation.render();
ContentRenderer.render();
// Add some sample data for demo
if (appState.getState().products.length === 0) {
appState.setState({
products: [
{ id: 1, code: 'P001', name: 'Parle-G Biscuits', category: 'Snacks', price: 10, mrp: 12, gst: 18, stock: 100 },
{ id: 2, code: 'P002', name: 'Dairy Milk Chocolate', category: 'Chocolates', price: 20, mrp: 25, gst: 12, stock: 50 },
{ id: 3, code: 'P003', name: 'Nescafe Coffee', category: 'Beverages', price: 180, mrp: 200, gst: 18, stock: 30 },
{ id: 4, code: 'P004', name: 'Amul Butter', category: 'Dairy', price: 45, mrp: 50, gst: 5, stock: 40 },
{ id: 5, code: 'P005', name: 'Colgate Toothpaste', category: 'Personal Care', price: 85, mrp: 95, gst: 18, stock: 60 }
],
customers: [
{ id: 1, code: 'C001', name: 'Rajesh Kumar', shop: 'Shree Ganesh Traders', mobile: '9876543210', address: '123 Main Street, Mumbai', gstin: '27AABCCDDEEFFG', balance: 0 },
{ id: 2, code: 'C002', name: 'Suresh Patel', shop: 'Patel General Store', mobile: '9123456789', address: '456 Market Road, Delhi', gstin: '07XYZPQRST1234Z', balance: 500 }
]
});
}
}, 2000);
}
// Close dropdown when clicking outside
document.addEventListener('click', (event) => {
const state = appState.getState();
if (state.showCustomerDropdown && !event.target.closest('.customer-search-container')) {
appState.setState({ showCustomerDropdown: false });
}
});
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', initializeApp);
</script>
</body>
</html>