Enhanced Indian Invoice System
Complete GST-Compliant Invoicing Solution with Stock Management & Customer Database
Managing invoices for Indian businesses just got easier with this comprehensive web-based invoice system. This tool combines GST compliance, inventory management, customer database, and professional invoice generation into one powerful platform that works entirely in your browser.
📋 GST-Compliant Invoicing
Generate professional invoices with HSN/SAC codes, SGST, CGST calculations, and proper Indian tax compliance. Perfect for small to medium businesses.
📦 Stock Management
Track inventory levels, manage product codes, prices, and quantities. Get low-stock alerts and automatic inventory updates with each sale.
👥 Customer Database
Store customer information with auto-complete functionality. Never retype customer details again - just start typing and watch the magic happen.
📊 Sales Dashboard
Monitor your business performance with real-time analytics, top-selling products tracking, and revenue insights.
📱 WhatsApp Integration
Share invoices directly via WhatsApp with customizable messages. Generate PDF invoices and share them instantly.
💾 Data Management
Backup and restore your data with ease. Export/import stock and customer data separately or together.
How to Use the Invoice System
Getting Started
- Company Setup: Fill in your company name, address, and contact information. Upload your company logo if desired - you can drag it around and resize it on the invoice.
- Add Your First Customer: Go to the "Customer Database" section and add customer details. Include contact number, name, email, and address.
- Set Up Your Inventory: Navigate to "Stock Management" and add your products with HSN/SAC codes, descriptions, prices, and quantities.
Try Our GST-Compliant Invoice Creation Tool
Experience our GST-compliant invoice system right here! Create professional invoices with automated GST calculations, logo positioning, and inventory management.
Creating an Invoice
- Select Customer: In the "Invoice Generator" section, start typing a customer's contact number. The system will auto-complete their details.
- Add Items: Type product codes in the items table, and watch as descriptions, prices, and tax rates auto-populate. Use the "Browse Items" button to select from your inventory.
- Review & Generate: Check quantities, apply discounts if needed, and click "Generate Invoice" to see the preview with automatic tax calculations.
- Save & Share: Click "Save Invoice" to record the sale and update inventory. Use WhatsApp integration or PDF export to share with customers.
Smart Features That Save Time
- Auto-Complete: Start typing customer contact numbers or product codes - the system suggests matches from your database
- Inventory Tracking: Quantities automatically reduce when you save invoices, with warnings for insufficient stock
- Tax Calculations: SGST and CGST are calculated automatically based on your product settings
- Professional Branding: Upload your logo and position it anywhere on the invoice with drag-and-drop functionality
Advanced Features
Stock Management
- Adding Products: Include item codes, HSN/SAC codes, descriptions, prices, units of measurement, and tax rates. The system supports various units (PCS, KG, LTR, MTR, etc.).
- Low Stock Alerts: Items with 5 or fewer units remaining are highlighted in red to help you restock in time.
- Editing Items: Click "Edit" on any inventory item to modify details, or "Delete" to remove items you no longer sell.
Sales Dashboard
- Analytics Overview: View total revenue, order count, active customers, and products sold for different time periods.
- Top Products: See which items are selling best to help with inventory planning and marketing decisions.
- Recent Orders: Quick access to your latest invoices for customer service and order tracking.
Technical Specifications
Data Backup & Security
Your business data is precious. This system stores everything locally in your browser and provides comprehensive backup options:
- Full Backup: Export all invoices, inventory, and customer data in one file
- Selective Backup: Export just stock data or customer information separately
- Easy Restore: Import your data back from JSON files with validation checks
- Privacy First: No data is sent to external servers - everything stays on your device
WhatsApp Integration Guide
- Set Up WhatsApp Number: Enter the customer's WhatsApp number in international format (e.g., +919037393709).
- Customize Message: Adjust the description length and choose whether to include GST details in the WhatsApp message.
- Preview Before Sending: Click "Preview WhatsApp Message" to see exactly what will be sent to your customer.
- Send Invoice: Use "Send via WhatsApp" for text messages or "Share PDF via WhatsApp" for document sharing.
Perfect for These Businesses
- Retail Stores: Electronics, clothing, general merchandise
- Service Providers: Consultants, repair services, freelancers
- Small Manufacturers: Custom products, handicrafts, food items
- Distributors: Product resellers, wholesale businesses
- Online Sellers: E-commerce businesses, social media sellers
Start Using the Enhanced Indian Invoice System Today
Transform your invoicing process with this comprehensive, GST-compliant solution. No downloads, no subscriptions - just professional invoicing at your fingertips.
Try the System Now Download HTML FileHere is the code
Click or tap to cooy it
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=yes">
<title>Invoice Generator</title>
<!-- Include html2canvas and jsPDF libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
/* Your provided CSS remains unchanged */
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
touch-action: manipulation;
}
.container { max-width: 1400px; margin: auto; }
/* Navigation Menu Styles */
.nav-menu {
background: linear-gradient(135deg, #007bff, #0056b3);
padding: 15px 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.nav-menu h2 {
color: white;
margin: 0 0 15px 0;
text-align: center;
}
.nav-buttons {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.nav-btn {
background: rgba(255,255,255,0.2);
color: white;
border: 2px solid rgba(255,255,255,0.3);
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
backdrop-filter: blur(10px);
}
.nav-btn:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
}
.nav-btn.active {
background: white;
color: #007bff;
border-color: white;
}
.section { display: none; }
.section.active { display: block; }
.input-section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.invoice-preview {
background: white;
padding: 0;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h3 { margin-top: 0; color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }
.form-row { display: flex; gap: 15px; margin-bottom: 15px; flex-wrap: wrap; }
.form-group { flex: 1; min-width: 200px; position: relative; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #555; }
.form-group input, .form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group textarea { resize: vertical; height: 80px; }
.auto-populated {
background-color: #e8f5e8 !important;
border-color: #28a745 !important;
transition: background-color 0.5s ease;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-top: none;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.dropdown-item {
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.dropdown-item:hover {
background: #f5f5f5;
}
.items-section { margin-top: 20px; }
.items-table {
width: 100%;
border-collapse: collapse;
background: #f8f9fa;
border-radius: 5px;
overflow: hidden;
}
.items-table th, .items-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: left;
font-size: 14px;
}
.items-table th {
background: #e9ecef;
font-weight: bold;
color: #333;
}
.items-table input {
width: 100%;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.items-table .code-input { min-width: 100px; }
.items-table .desc-input { min-width: 200px; }
.items-table .qty-input, .items-table .rate-input, .items-table .disc-input, .items-table .sgst-input, .items-table .cgst-input, .items-table .total-input { min-width: 80px; }
.items-table .total-input {
background: #f0f0f0;
text-align: right;
pointer-events: none;
}
.items-table .remove-btn {
background: #dc3545;
color: white;
border: none;
padding: 6px 10px;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
.items-table .remove-btn:hover { background: #c82333; }
.add-item-btn, .generate-btn, .browse-items-btn, .add-stock-btn, .add-customer-btn, .backup-btn, .restore-btn, .save-btn {
background: #28a745;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
margin-top: 10px;
}
.add-item-btn:hover, .add-stock-btn:hover, .add-customer-btn:hover, .backup-btn:hover, .restore-btn:hover, .save-btn:hover { background: #218838; }
.browse-items-btn { background: #17a2b8; }
.browse-items-btn:hover { background: #138496; }
.generate-btn { background: #007bff; }
.generate-btn:hover { background: #0056b3; }
.save-btn { background: #ffc107; color: #333; }
.save-btn:hover { background: #e0a800; }
.stock-table, .customer-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.stock-table th, .stock-table td, .customer-table th, .customer-table td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
.stock-table th, .customer-table th {
background: #f8f9fa;
font-weight: bold;
}
.stock-table tr:hover, .customer-table tr:hover {
background: #f5f5f5;
}
.edit-btn, .delete-btn {
padding: 5px 10px;
margin: 2px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.edit-btn { background: #ffc107; color: #333; }
.edit-btn:hover { background: #e0a800; }
.delete-btn { background: #dc3545; color: white; }
.delete-btn:hover { background: #c82333; }
.low-stock {
color: #dc3545;
font-weight: bold;
}
.quantity-error {
border: 2px solid #dc3545 !important;
background-color: #fff3f3 !important;
}
/* Invoice Styles */
.invoice-box {
max-width: 700px;
margin: auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
position: relative;
}
.company-header {
text-align: center;
margin-bottom: 20px;
}
.company-header h2 { margin: 0; color: #333; }
.company-header p { margin: 2px 0; font-size: 13px; color: #666; }
.header { display: flex; justify-content: space-between; margin-bottom: 20px; }
.bill-to { font-size: 14px; }
.invoice-details { text-align: right; font-size: 14px; }
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.invoice-table, .invoice-table th, .invoice-table td { border: 1px solid #ccc; }
.invoice-table th, .invoice-table td { padding: 8px; text-align: left; }
.invoice-table th { background: #f8f9fa; }
.total { text-align: right; font-weight: bold; margin-top: 10px; font-size: 16px; }
.print-btn {
margin-top: 20px;
padding: 8px 16px;
background: #007bff;
color: #fff;
border: none;
cursor: pointer;
border-radius: 5px;
}
.print-btn:hover { background: #0056b3; }
/* Logo Styles */
.invoice-logo {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
cursor: move;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
.invoice-logo img {
width: 100%;
height: 100%;
object-fit: contain;
}
.invoice-logo.active {
border: 2px dashed #007bff;
}
.resize-handle {
position: absolute;
width: 12px;
height: 12px;
background: #007bff;
border-radius: 50%;
display: none;
}
.invoice-logo.active .resize-handle {
display: block;
}
.resize-handle.top-left {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.resize-handle.top-right {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.resize-handle.bottom-left {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.resize-handle.bottom-right {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
.logo-controls {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.logo-controls input[type="number"] {
width: 80px;
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
}
.logo-controls button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.reset-logo-btn {
background: #6c757d;
color: white;
}
.reset-logo-btn:hover {
background: #5a6268;
}
.remove-logo-btn {
background: #dc3545;
color: white;
}
.remove-logo-btn:hover {
background: #c82333;
}
/* Floating Dialog Styles */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: none;
align-items: center;
justify-content: center;
}
.dialog {
background: white;
border-radius: 12px;
padding: 0;
width: 90%;
max-width: 600px;
min-width: 300px;
max-height: 80vh;
min-height: 200px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
position: relative;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px 10px 20px;
border-bottom: 2px solid #e9ecef;
background: #f8f9fa;
-webkit-user-select: none;
user-select: none;
touch-action: none;
}
.dialog-controls {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.size-inputs {
display: flex;
gap: 5px;
align-items: center;
margin-right: 10px;
}
.size-input {
width: 60px;
padding: 2px 5px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 0.8rem;
text-align: center;
}
.resize-btn, .apply-btn {
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
}
.resize-btn {
background: #6c757d;
color: white;
font-weight: 600;
}
.resize-btn:hover { background: #5a6268; }
.apply-btn { background: #007bff; color: white; }
.apply-btn:hover { background: #0056b3; }
.dialog-header h3 { color: #495057; margin: 0; border: none; }
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #6c757d;
padding: 5px;
border-radius: 50%;
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover { background: #f8f9fa; color: #495057; }
.search-section { margin-bottom: 20px; padding: 0 20px; }
.search-input {
width: 100%;
padding: 12px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 1rem;
}
.search-input:focus {
outline: none;
border-color: #4facfe;
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
}
.items-container {
max-height: calc(100% - 150px);
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 8px;
margin: 0 20px 20px 20px;
}
.item-table { width: 100%; border-collapse: collapse; }
.item-table th, .item-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
.item-table th {
background: #f8f9fa;
position: sticky;
top: 0;
font-weight: 600;
color: #495057;
}
.item-table tbody tr {
cursor: pointer;
transition: background-color 0.2s ease;
}
.item-table tbody tr:hover { background-color: #e3f2fd; }
.status-message {
padding: 10px;
margin: 10px 20px;
border-radius: 6px;
font-size: 0.9rem;
display: none;
}
.status-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.status-error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
@media print {
body { background: white; }
.input-section, .nav-menu { display: none; }
.invoice-preview { box-shadow: none; }
.dialog-overlay { display: none !important; }
.invoice-logo.active { border: none !important; }
.resize-handle { display: none !important; }
}
@media (max-width: 768px) {
.form-row { flex-direction: column; }
.items-section, .stock-table, .customer-table {
overflow-x: auto;
display: block;
}
.items-table th, .items-table td { min-width: 60px; }
.items-table .desc-input { min-width: 120px; }
.items-table .remove-btn { min-width: 80px; }
.dialog { width: 90%; min-width: 250px; max-width: 95vw; }
.dialog-header { padding: 10px 15px; }
.size-input { width: 50px; font-size: 0.7rem; }
.resize-btn, .apply-btn { padding: 3px 6px; font-size: 0.7rem; }
.nav-buttons { flex-direction: column; align-items: center; }
.nav-btn { width: 200px; }
.logo-controls input[type="number"] { width: 60px; }
}
@media (max-width: 480px) {
.dialog { min-height: 150px; }
.items-container { max-height: calc(100% - 120px); }
.items-table th, .items-table td, .stock-table th, .stock-table td, .customer-table th, .customer-table td {
font-size: 12px;
padding: 6px;
}
.items-table input { font-size: 12px; padding: 4px; }
.item-table th, .item-table td { font-size: 12px; padding: 6px; }
.resize-handle { width: 10px; height: 10px; }
}
.hidden { display: none; }
.whatsapp-section {
background: #e8f5e8;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
border-left: 4px solid #25d366;
}
.whatsapp-section h3 {
color: #333;
border-bottom: 2px solid #25d366;
}
.btn-whatsapp {
background: #25d366;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
}
.btn-whatsapp:hover {
background: #20c360;
transform: translateY(-2px);
}
.whatsapp-preview {
max-height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
margin-top: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
white-space: pre-wrap;
font-family: monospace;
display: none;
}
.pdf-btn {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
margin-top: 10px;
}
.pdf-btn:hover {
background: #0056b3;
}
.pdf-btn.share {
background: linear-gradient(135deg, #17a2b8, #20c997);
}
/* Dashboard Styles */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.dashboard-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.dashboard-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.dashboard-card h4 {
margin: 0 0 10px;
color: #333;
}
.dashboard-card p {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
color: white;
}
.top-products-list, .recent-orders-list {
max-height: 200px;
overflow-y: auto;
margin-top: 10px;
}
.top-products-list div, .recent-orders-list div {
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: background 0.2s;
}
.top-products-list div:hover, .recent-orders-list div:hover {
background: #f5f5f5;
}
.refresh-btn {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
.refresh-btn:hover {
background: #0056b3;
}
@media (max-width: 768px) {
.stats-grid, .dashboard-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Navigation Menu -->
<div class="nav-menu">
<h2>🇮🇳 Enhanced Indian Invoice System</h2>
<div class="nav-buttons">
<button class="nav-btn active" data-section="invoice">📋 Invoice Generator</button>
<button class="nav-btn" data-section="dashboard">📊 Sales Dashboard</button>
<button class="nav-btn" data-section="stock">📦 Stock Management</button>
<button class="nav-btn" data-section="customers">👥 Customer Database</button>
<button class="nav-btn" data-section="backup-restore">💾 Backup & Restore</button>
</div>
</div>
<!-- Sales Dashboard Section -->
<div class="section" id="dashboard">
<div class="input-section">
<h3>📊 Sales Dashboard</h3>
<div class="form-row">
<div class="form-group">
<label>Date Range</label>
<select id="dateRangeFilter" onchange="toggleCustomDateRange()">
<option value="7">Last 7 days</option>
<option value="30">Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last 365 days</option>
<option value="custom">Custom Range</option>
</select>
</div>
<div class="form-group hidden" id="customDateRange">
<label>From</label>
<input type="date" id="fromDate">
<label>To</label>
<input type="date" id="toDate">
</div>
<div class="form-group">
<button class="refresh-btn" onclick="updateDashboard()">Refresh</button>
<button class="refresh-btn" onclick="clearInvoices()">Clear Invoices</button>
</div>
</div>
<div class="stats-grid">
<div class="dashboard-card" style="background: linear-gradient(135deg, #28a745, #218838);">
<h4>Total Revenue</h4>
<p id="totalRevenue">₹0.00</p>
</div>
<div class="dashboard-card" style="background: linear-gradient(135deg, #007bff, #0056b3);">
<h4>Total Orders</h4>
<p id="totalOrders">0</p>
</div>
<div class="dashboard-card" style="background: linear-gradient(135deg, #ff4d4f, #cf1322);">
<h4>Active Customers</h4>
<p id="totalCustomers">0</p>
</div>
<div class="dashboard-card" style="background: linear-gradient(135deg, #17a2b8, #117a8b);">
<h4>Products Sold</h4>
<p id="totalProducts">0</p>
</div>
</div>
<div class="dashboard-grid">
<div class="dashboard-card">
<h4>Top Selling Products</h4>
<div class="top-products-list" id="topProductsList"></div>
</div>
<div class="dashboard-card">
<h4>Recent Orders</h4>
<div class="recent-orders-list" id="recentOrdersList"></div>
</div>
</div>
</div>
</div>
<!-- Invoice Generator Section -->
<div class="section active" id="invoice">
<div class="input-section">
<h3>📝 Invoice Generator</h3>
<!-- Company Info -->
<div class="form-row">
<div class="form-group">
<label>Company Name</label>
<input type="text" id="companyName" value="Your Company Name">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Company Address</label>
<textarea id="companyAddress">123 Business Street, City, State - ZIP</textarea>
</div>
<div class="form-group">
<label>Contact Info</label>
<textarea id="companyContact">Email: info@company.com
Phone: +91 9037393709</textarea>
</div>
</div>
<!-- Logo Upload -->
<h3>🖼️ Logo</h3>
<div class="form-row">
<div class="form-group">
<label>Upload Logo</label>
<input type="file" id="logoUpload" accept="image/*">
</div>
<div class="form-group logo-controls">
<label>Width (px)</label>
<input type="number" id="logoWidth" min="20" max="300" placeholder="Width">
<label>Height (px)</label>
<input type="number" id="logoHeight" min="20" max="300" placeholder="Height">
<label><input type="checkbox" id="lockAspectRatio" checked> Lock Aspect Ratio</label>
<button class="reset-logo-btn" onclick="resetLogoPosition()">Reset Position</button>
<button class="remove-logo-btn" onclick="removeLogo()">Remove Logo</button>
</div>
</div>
<p style="font-size: 12px; color: #666;">Tip: Tap logo to show handles, drag to move, or tap handles to resize. Double-tap logo to hide handles.</p>
<!-- Bill To -->
<h3>👤 Bill To</h3>
<div class="form-row">
<div class="form-group">
<label>Client Contact Number</label>
<input type="text" id="clientContact" placeholder="Enter contact number">
<div class="dropdown" id="contactDropdown"></div>
</div>
<div class="form-group">
<label>Client Name</label>
<input type="text" id="clientName" placeholder="Name will auto-populate">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Client Email</label>
<input type="email" id="clientEmail" placeholder="Email will auto-populate">
</div>
<div class="form-group">
<label>Client Address</label>
<textarea id="clientAddress" placeholder="Address will auto-populate"></textarea>
</div>
</div>
<!-- Invoice Details -->
<h3>📋 Invoice Details</h3>
<div class="form-row">
<div class="form-group">
<label>Invoice Number</label>
<input type="text" id="invoiceNumber" value="INV-004">
</div>
<div class="form-group">
<label>Invoice Date</label>
<input type="date" id="invoiceDate">
</div>
<div class="form-group">
<label>Due Date</label>
<input type="date" id="dueDate">
</div>
</div>
<!-- Items Section -->
<h3>📦 Items</h3>
<div class="items-section">
<table class="items-table" id="itemsList">
<thead>
<tr>
<th>Item Code</th>
<th>Description</th>
<th>Qty</th>
<th>Rate (₹)</th>
<th>Disc %</th>
<th>SGST %</th>
<th>CGST %</th>
<th>Total (₹)</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr class="item-row">
<td><input type="text" class="item-code code-input" placeholder="Enter item code..."><div class="dropdown item-dropdown"></div></td>
<td><input type="text" class="item-desc desc-input" placeholder="Service or product description"></td>
<td><input type="number" class="item-qty qty-input" value="1" min="1"></td>
<td><input type="number" class="item-rate rate-input" value="0" step="0.01" min="0"></td>
<td><input type="number" class="item-disc disc-input" value="0" step="0.01" min="0" max="100"></td>
<td><input type="number" class="item-sgst sgst-input" value="0" step="0.01" min="0"></td>
<td><input type="number" class="item-cgst cgst-input" value="0" step="0.01" min="0"></td>
<td><input type="text" class="item-total total-input" value="0.00" readonly></td>
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
</tr>
</tbody>
</table>
<button class="add-item-btn" onclick="addItem()">+ Add Item</button>
<button class="browse-items-btn" onclick="openItemDialog()">📋 Browse Items</button>
<button class="generate-btn" onclick="updateItemTotals()">Generate Invoice</button>
<button class="save-btn" onclick="saveInvoice()">Save Invoice</button>
</div>
<!-- WhatsApp Section -->
<div class="whatsapp-section">
<h3>Send Invoice via WhatsApp</h3>
<div class="form-row">
<div class="form-group">
<label>WhatsApp Number</label>
<input type="text" id="whatsappNumber" placeholder="Enter phone number (e.g., +919037393709)">
</div>
<div class="form-group">
<label>Max Description Length</label>
<input type="number" id="whatsappDescLength" value="20" min="10" max="50">
</div>
<div class="form-group">
<label><input type="checkbox" id="gstEnabled" checked> Include GST in Message</label>
</div>
</div>
<button class="btn-whatsapp" onclick="generateWhatsAppMessage()">Preview WhatsApp Message</button>
<div class="whatsapp-preview" id="whatsappPreview" style="display: none;"></div>
<button class="btn-whatsapp" style="margin-top: 10px;" onclick="sendWhatsApp()">Send via WhatsApp</button>
</div>
<div class="pdf-buttons">
<button class="pdf-btn download" onclick="generatePDF()">Download PDF</button>
<button class="pdf-btn share" onclick="sharePDF()">Share PDF via WhatsApp</button>
<button class="pdf-btn print" onclick="printInvoice()">Print Invoice</button>
</div>
</div>
<!-- Invoice Preview -->
<div class="invoice-preview" id="invoicePreview">
<div class="invoice-box">
<div class="company-header">
<h2 id="previewCompanyName">Your Company Name</h2>
<p id="previewCompanyAddress">123 Business Street, City, State - ZIP</p>
<p id="previewCompanyContact">Email: info@company.com | Phone: +91 9037393709</p>
</div>
<div class="header">
<div class="bill-to">
<b>Bill To</b><br>
<span id="previewClientName">Customer Name</span><br>
<span id="previewClientAddress">Customer Address</span><br>
Contact: <span id="previewClientContact">Customer Contact</span><br>
Email: <span id="previewClientEmail">Customer Email</span>
</div>
<div class="invoice-details">
<b style="color:red;">INVOICE</b><br>
Invoice #: <span id="previewInvoiceNumber">INV-004</span><br>
Invoice Date: <span id="previewInvoiceDate">27 August 2025</span><br>
Due Date: <span id="previewDueDate">26 September 2025</span>
</div>
</div>
<table class="invoice-table">
<thead>
<tr>
<th>HSN/SAC</th>
<th>Description</th>
<th>Qty</th>
<th>Rate</th>
<th>Disc %</th>
<th>SGST %</th>
<th>CGST %</th>
<th>Total</th>
</tr>
</thead>
<tbody id="previewItems">
<tr>
<td></td><td>Sample Item</td><td>1</td><td>₹0.00</td><td>0</td><td>0</td><td>0</td><td>₹0.00</td>
</tr>
</tbody>
</table>
<p class="total">Grand Total: <span id="previewTotal">₹0.00</span></p>
<button class="print-btn" onclick="window.print()">Print / Save as PDF</button>
</div>
</div>
</div>
<!-- Stock Management Section -->
<div class="section" id="stock">
<div class="input-section">
<h3>📦 Stock Management</h3>
<div class="form-row">
<div class="form-group">
<label>Item Code</label>
<input type="text" id="stockCode" placeholder="e.g., ITM001">
</div>
<div class="form-group">
<label>HSN/SAC Code</label>
<input type="text" id="stockHSN" placeholder="e.g., 8471">
</div>
<div class="form-group">
<label>Description</label>
<input type="text" id="stockDescription" placeholder="e.g., Ergonomic wireless mouse">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Rate (₹)</label>
<input type="number" id="stockPrice" placeholder="e.g., 25.99" step="0.01" min="0">
</div>
<div class="form-group">
<label>Unit of Measurement</label>
<select id="stockUnit">
<option value="PCS">PCS - Pieces</option>
<option value="KG">KG - Kilogram</option>
<option value="LTR">LTR - Liter</option>
<option value="MTR">MTR - Meter</option>
<option value="BOX">BOX - Box</option>
<option value="SET">SET - Set</option>
</select>
</div>
<div class="form-group">
<label>Quantity</label>
<input type="number" id="stockQuantity" min="0" placeholder="Enter quantity">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>SGST Rate (%)</label>
<input type="number" id="stockSGST" value="9" step="0.01" min="0">
</div>
<div class="form-group">
<label>CGST Rate (%)</label>
<input type="number" id="stockCGST" value="9" step="0.01" min="0">
</div>
</div>
<button class="add-stock-btn" onclick="addStockItem()">Add to Inventory</button>
<table class="stock-table" id="stockTable">
<thead>
<tr>
<th>Item Code</th>
<th>HSN/SAC</th>
<th>Description</th>
<th>Rate (₹)</th>
<th>Unit</th>
<th>SGST %</th>
<th>CGST %</th>
<th>Quantity</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="stockTableBody">
</tbody>
</table>
</div>
</div>
<!-- Customer Database Section -->
<div class="section" id="customers">
<div class="input-section">
<h3>👥 Customer Database</h3>
<div class="form-row">
<div class="form-group">
<label>Contact Number</label>
<input type="text" id="newCustomerContact" placeholder="Enter contact number">
</div>
<div class="form-group">
<label>Customer Name</label>
<input type="text" id="newCustomerName" placeholder="Enter customer name">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" id="newCustomerEmail" placeholder="Enter email">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Address</label>
<textarea id="newCustomerAddress" placeholder="Enter address"></textarea>
</div>
</div>
<button class="add-customer-btn" onclick="addCustomer()">+ Add Customer</button>
<table class="customer-table" id="customerTable">
<thead>
<tr>
<th>Contact</th>
<th>Name</th>
<th>Email</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="customerTableBody">
</tbody>
</table>
</div>
</div>
<!-- Backup & Restore Section -->
<div class="section" id="backup-restore">
<div class="input-section">
<h3>💾 Backup & Restore</h3>
<div class="form-row">
<div class="form-group">
<label>Backup All Data</label>
<button class="backup-btn" onclick="backupData()">Backup Data to JSON</button>
</div>
<div class="form-group">
<label>Restore All Data</label>
<input type="file" id="restoreFileInput" accept=".json">
<button class="restore-btn" onclick="restoreData()">Restore Data</button>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Backup Stock Data Only</label>
<button class="backup-btn" onclick="backupStockData()">Backup Stock to JSON</button>
</div>
<div class="form-group">
<label>Restore Stock Data Only</label>
<input type="file" id="restoreStockFileInput" accept=".json">
<button class="restore-btn" onclick="restoreStockData()">Restore Stock Data</button>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Backup Customer Data Only</label>
<button class="backup-btn" onclick="backupCustomerData()">Backup Customers to JSON</button>
</div>
<div class="form-group">
<label>Restore Customer Data Only</label>
<input type="file" id="restoreCustomerFileInput" accept=".json">
<button class="restore-btn" onclick="restoreCustomerData()">Restore Customer Data</button>
</div>
</div>
<div class="status-message" id="backupStatusMessage"></div>
</div>
</div>
</div>
<!-- Item Selection Dialog -->
<div class="dialog-overlay" id="dialogOverlay" onclick="closeDialog(event, 'dialogOverlay')">
<div class="dialog" id="itemDialog" onclick="event.stopPropagation()">
<div class="dialog-header">
<h3>Select Items</h3>
<div class="dialog-controls">
<div class="size-inputs">
<span style="font-size: 0.8rem;">W:</span>
<input type="number" class="size-input" id="itemWidthInput" value="600" min="300" max="1200">
<span style="font-size: 0.8rem;">H:</span>
<input type="number" class="size-input" id="itemHeightInput" value="450" min="200" max="800">
<button class="apply-btn" onclick="applyCustomSize('itemDialog', 'itemWidthInput', 'itemHeightInput')">Apply</button>
</div>
<div class="resize-controls">
<button class="resize-btn" onclick="quickResize(400, 250, 'itemDialog', 'itemWidthInput', 'itemHeightInput')" title="Compact">XS</button>
<button class="resize-btn" onclick="quickResize(500, 350, 'itemDialog', 'itemWidthInput', 'itemHeightInput')" title="Small">S</button>
<button class="resize-btn" onclick="quickResize(700, 500, 'itemDialog', 'itemWidthInput', 'itemHeightInput')" title="Large">L</button>
</div>
<button class="close-btn" onclick="closeDialog(null, 'dialogOverlay')">×</button>
</div>
</div>
<div class="search-section">
<input type="text" class="search-input" id="searchInput" placeholder="Search items by code, name, or description..." oninput="searchItems()">
</div>
<div class="status-message" id="statusMessage"></div>
<div class="items-container">
<table class="item-table">
<thead>
<tr>
<th>Item Code</th>
<th>HSN/SAC</th>
<th>Description</th>
<th>Price</th>
<th>SGST %</th>
<th>CGST %</th>
</tr>
</thead>
<tbody id="itemsList2">
</tbody>
</table>
</div>
</div>
</div>
<!-- Edit Customer Dialog -->
<div class="dialog-overlay" id="editCustomerDialogOverlay" onclick="closeDialog(event, 'editCustomerDialogOverlay')">
<div class="dialog" id="editCustomerDialog" onclick="event.stopPropagation()">
<div class="dialog-header">
<h3>Edit Customer</h3>
<button class="close-btn" onclick="closeDialog(null, 'editCustomerDialogOverlay')">×</button>
</div>
<div class="form-row" style="padding: 20px;">
<div class="form-group">
<label>Contact Number</label>
<input type="text" id="editCustomerContact" placeholder="Enter contact number">
</div>
<div class="form-group">
<label>Customer Name</label>
<input type="text" id="editCustomerName" placeholder="Enter customer name">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" id="editCustomerEmail" placeholder="Enter email">
</div>
<div class="form-group">
<label>Address</label>
<textarea id="editCustomerAddress" placeholder="Enter address"></textarea>
</div>
</div>
<div style="padding: 0 20px 20px;">
<button class="add-customer-btn" onclick="saveEditedCustomer()">Save Changes</button>
</div>
</div>
</div>
<script>
const inventory = [
{ code: 'ITM001', hsn: '8471', name: 'Wireless Mouse', description: 'Ergonomic wireless mouse with USB receiver', price: 25.99, unit: 'PCS', quantity: 50, sgst: 9, cgst: 9 },
{ code: 'ITM002', hsn: '8471', name: 'Keyboard', description: 'Mechanical gaming keyboard with RGB lighting', price: 89.99, unit: 'PCS', quantity: 30, sgst: 9, cgst: 9 },
{ code: 'ITM003', hsn: '9403', name: 'Monitor Stand', description: 'Adjustable aluminum monitor stand', price: 45.00, unit: 'PCS', quantity: 20, sgst: 9, cgst: 9 },
{ code: 'ITM004', hsn: '8544', name: 'USB Cable', description: '6ft USB-C to USB-A cable', price: 12.50, unit: 'PCS', quantity: 100, sgst: 9, cgst: 9 },
{ code: 'ITM005', hsn: '8525', name: 'Webcam', description: '1080p HD webcam with built-in microphone', price: 67.99, unit: 'PCS', quantity: 15, sgst: 9, cgst: 9 },
{ code: 'ITM006', hsn: '9405', name: 'Desk Lamp', description: 'LED desk lamp with touch control', price: 34.99, unit: 'PCS', quantity: 25, sgst: 9, cgst: 9 },
{ code: 'ITM007', hsn: '8504', name: 'Phone Charger', description: 'Fast charging wireless phone charger', price: 28.99, unit: 'PCS', quantity: 40, sgst: 9, cgst: 9 },
{ code: 'ITM008', hsn: '8518', name: 'Speakers', description: 'Bluetooth desktop speakers', price: 55.00, unit: 'PCS', quantity: 10, sgst: 9, cgst: 9 },
{ code: 'ITM009', hsn: '4016', name: 'Mouse Pad', description: 'Extra large gaming mouse pad', price: 19.99, unit: 'PCS', quantity: 60, sgst: 9, cgst: 9 },
{ code: 'ITM010', hsn: '8518', name: 'Headphones', description: 'Noise-canceling over-ear headphones', price: 149.99, unit: 'PCS', quantity: 5, sgst: 9, cgst: 9 },
{ code: 'ITM011', hsn: '8473', name: 'External Drive', description: '1TB portable external hard drive', price: 79.99, unit: 'PCS', quantity: 12, sgst: 9, cgst: 9 },
{ code: 'ITM012', hsn: '3926', name: 'Cable Organizer', description: 'Desktop cable management solution', price: 15.99, unit: 'PCS', quantity: 80, sgst: 9, cgst: 9 }
];
let customerDatabase = [
{ contact: "+919037393709", name: "John Doe", email: "john@example.com", address: "123 Main St, City" },
{ contact: "+919876543210", name: "Jane Smith", email: "jane@example.com", address: "456 Park Ave, City" }
];
let filteredInventory = [...inventory];
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
let activeDialog = null;
let editingCustomerIndex = null;
let invoices = JSON.parse(localStorage.getItem('invoices')) || [];
// Logo variables
let logoElement = null;
let isLogoDragging = false;
let isResizing = false;
let resizeHandle = null;
let logoStartPos = { x: 0, y: 0 };
let logoStartSize = { width: 0, height: 0 };
let originalAspectRatio = 1;
let lastTapTime = 0;
// Input sanitization function
function sanitizeInput(input) {
return input.replace(/[<>"'&]/g, '');
}
// Debounce function for autocomplete
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Generate unique invoice number
function generateInvoiceNumber() {
const existingNumbers = invoices.map(inv => parseInt(inv.invoiceNumber.replace('INV-', '')));
const nextNumber = existingNumbers.length ? Math.max(...existingNumbers) + 1 : 1;
return `INV-${String(nextNumber).padStart(3, '0')}`;
}
// Core Functions
function addItem() {
const itemsList = document.getElementById('itemsList').querySelector('tbody');
const newRow = document.createElement('tr');
newRow.className = 'item-row';
newRow.innerHTML = `
<td><input type="text" class="item-code code-input" placeholder="Enter item code..."><div class="dropdown item-dropdown"></div></td>
<td><input type="text" class="item-desc desc-input" placeholder="Service or product description"></td>
<td><input type="number" class="item-qty qty-input" value="1" min="1"></td>
<td><input type="number" class="item-rate rate-input" value="0" step="0.01" min="0"></td>
<td><input type="number" class="item-disc disc-input" value="0" step="0.01" min="0" max="100"></td>
<td><input type="number" class="item-sgst sgst-input" value="0" step="0.01" min="0"></td>
<td><input type="number" class="item-cgst cgst-input" value="0" step="0.01" min="0"></td>
<td><input type="text" class="item-total total-input" value="0.00" readonly></td>
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
`;
itemsList.appendChild(newRow);
attachInputListeners(newRow);
setupItemAutocomplete(newRow);
updateItemTotals();
}
function removeItem(button) {
const itemsList = document.getElementById('itemsList').querySelector('tbody');
if (itemsList.children.length > 1) {
button.closest('tr').remove();
updateItemTotals();
} else {
alert('You must have at least one item.');
}
}
function attachInputListeners(row) {
row.querySelectorAll('.item-qty, .item-rate, .item-disc, .item-sgst, .item-cgst').forEach(input => {
input.addEventListener('input', () => {
const code = row.querySelector('.item-code').value;
const qty = parseInt(row.querySelector('.item-qty').value) || 0;
if (input.classList.contains('item-qty') && code) {
const item = inventory.find(i => i.code === code);
if (item && qty > item.quantity) {
input.classList.add('quantity-error');
showStatus(`Insufficient stock for ${item.description}. Available: ${item.quantity} units.`, 'error');
input.value = item.quantity;
} else {
input.classList.remove('quantity-error');
}
}
updateItemTotal(row);
updateItemTotals();
});
});
}
function updateItemTotal(row) {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const rate = parseFloat(row.querySelector('.item-rate').value) || 0;
const disc = parseFloat(row.querySelector('.item-disc').value) || 0;
const sgst = parseFloat(row.querySelector('.item-sgst').value) || 0;
const cgst = parseFloat(row.querySelector('.item-cgst').value) || 0;
const subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const afterDisc = subtotal - discAmount;
const sgstAmount = afterDisc * (sgst / 100);
const cgstAmount = afterDisc * (cgst / 100);
const lineTotal = afterDisc + sgstAmount + cgstAmount;
row.querySelector('.item-total').value = lineTotal.toFixed(2);
}
function updateItemTotals() {
const rows = document.querySelectorAll('.item-row');
let subtotal = 0;
let totalDiscount = 0;
let totalSGST = 0;
let totalCGST = 0;
let grandTotal = 0;
let canGenerate = true;
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
const qty = parseInt(row.querySelector('.item-qty').value) || 0;
const item = inventory.find(i => i.code === code);
if (item && qty > item.quantity) {
canGenerate = false;
row.querySelector('.item-qty').classList.add('quantity-error');
showStatus(`Insufficient stock for ${item.description}. Available: ${item.quantity} units.`, 'error');
} else {
row.querySelector('.item-qty').classList.remove('quantity-error');
}
});
if (!canGenerate) {
return;
}
rows.forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const rate = parseFloat(row.querySelector('.item-rate').value) || 0;
const disc = parseFloat(row.querySelector('.item-disc').value) || 0;
const sgst = parseFloat(row.querySelector('.item-sgst').value) || 0;
const cgst = parseFloat(row.querySelector('.item-cgst').value) || 0;
const subtotalAmount = qty * rate;
const discAmount = subtotalAmount * (disc / 100);
const afterDisc = subtotalAmount - discAmount;
const sgstAmount = afterDisc * (sgst / 100);
const cgstAmount = afterDisc * (cgst / 100);
const lineTotal = afterDisc + sgstAmount + cgstAmount;
row.querySelector('.item-total').value = lineTotal.toFixed(2);
subtotal += subtotalAmount;
totalDiscount += discAmount;
totalSGST += sgstAmount;
totalCGST += cgstAmount;
grandTotal += lineTotal;
});
const totalsRow = `
<tr>
<td colspan="4"></td>
<td colspan="3">Subtotal</td>
<td>₹${subtotal.toFixed(2)}</td>
</tr>
<tr>
<td colspan="4"></td>
<td colspan="3">Total Discount</td>
<td>₹${totalDiscount.toFixed(2)}</td>
</tr>
<tr>
<td colspan="4"></td>
<td colspan="3">Total SGST</td>
<td>₹${totalSGST.toFixed(2)}</td>
</tr>
<tr>
<td colspan="4"></td>
<td colspan="3">Total CGST</td>
<td>₹${totalCGST.toFixed(2)}</td>
</tr>
<tr>
<td colspan="4"></td>
<td colspan="3"><strong>Grand Total</strong></td>
<td><strong>₹${grandTotal.toFixed(2)}</strong></td>
</tr>
`;
generateInvoice(totalsRow);
}
function saveInvoice() {
const invoice = {
invoiceNumber: document.getElementById('invoiceNumber').value || generateInvoiceNumber(),
invoiceDate: document.getElementById('invoiceDate').value,
dueDate: document.getElementById('dueDate').value,
clientContact: sanitizeInput(document.getElementById('clientContact').value),
clientName: sanitizeInput(document.getElementById('clientName').value),
clientEmail: sanitizeInput(document.getElementById('clientEmail').value),
clientAddress: sanitizeInput(document.getElementById('clientAddress').value),
items: [],
subtotal: 0,
totalDiscount: 0,
totalSGST: 0,
totalCGST: 0,
grandTotal: 0,
timestamp: new Date().toISOString()
};
const rows = document.querySelectorAll('.item-row');
let canSave = true;
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
const qty = parseInt(row.querySelector('.item-qty').value) || 0;
const item = inventory.find(i => i.code === code);
if (item && qty > item.quantity) {
canSave = false;
row.querySelector('.item-qty').classList.add('quantity-error');
showStatus(`Insufficient stock for ${item.description}. Available: ${item.quantity} units.`, 'error');
} else {
row.querySelector('.item-qty').classList.remove('quantity-error');
}
});
if (!canSave) {
return;
}
if (!invoice.clientContact || !invoice.clientName || !invoice.invoiceDate || !invoice.dueDate) {
showStatus('Please fill in all invoice details (client contact, name, invoice date, due date).', 'error');
return;
}
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
const description = row.querySelector('.item-desc').value;
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const rate = parseFloat(row.querySelector('.item-rate').value) || 0;
const disc = parseFloat(row.querySelector('.item-disc').value) || 0;
const sgst = parseFloat(row.querySelector('.item-sgst').value) || 0;
const cgst = parseFloat(row.querySelector('.item-cgst').value) || 0;
const subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const afterDisc = subtotal - discAmount;
const sgstAmount = afterDisc * (sgst / 100);
const cgstAmount = afterDisc * (cgst / 100);
const lineTotal = afterDisc + sgstAmount + cgstAmount;
const item = inventory.find(i => i.code === code);
invoice.items.push({
code: sanitizeInput(code),
hsn: item ? item.hsn : '',
description: sanitizeInput(description),
quantity: qty,
rate: rate,
discount: disc,
sgst: sgst,
cgst: cgst,
total: lineTotal
});
invoice.subtotal += subtotal;
invoice.totalDiscount += discAmount;
invoice.totalSGST += sgstAmount;
invoice.totalCGST += cgstAmount;
invoice.grandTotal += lineTotal;
// Update inventory quantity
if (item) {
item.quantity -= qty;
}
});
if (invoice.items.length === 0) {
showStatus('Please add at least one item to the invoice.', 'error');
return;
}
invoices.push(invoice);
localStorage.setItem('invoices', JSON.stringify(invoices));
loadStockTable();
updateDashboard();
showStatus('Invoice saved successfully!', 'success');
}
function generateInvoice(totalsRow) {
const companyName = document.getElementById('companyName').value;
const companyAddress = document.getElementById('companyAddress').value;
const companyContact = document.getElementById('companyContact').value;
const clientName = document.getElementById('clientName').value;
const clientAddress = document.getElementById('clientAddress').value;
const clientContact = document.getElementById('clientContact').value;
const clientEmail = document.getElementById('clientEmail').value;
const invoiceNumber = document.getElementById('invoiceNumber').value;
const invoiceDate = document.getElementById('invoiceDate').value;
const dueDate = document.getElementById('dueDate').value;
document.getElementById('previewCompanyName').textContent = companyName;
document.getElementById('previewCompanyAddress').textContent = companyAddress;
document.getElementById('previewCompanyContact').textContent = companyContact;
document.getElementById('previewClientName').textContent = clientName;
document.getElementById('previewClientAddress').textContent = clientAddress;
document.getElementById('previewClientContact').textContent = clientContact;
document.getElementById('previewClientEmail').textContent = clientEmail;
document.getElementById('previewInvoiceNumber').textContent = invoiceNumber;
document.getElementById('previewInvoiceDate').textContent = formatDate(invoiceDate);
document.getElementById('previewDueDate').textContent = formatDate(dueDate);
const previewItems = document.getElementById('previewItems');
previewItems.innerHTML = '';
const rows = document.querySelectorAll('.item-row');
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
const item = inventory.find(i => i.code === code);
const hsn = item ? item.hsn : '';
const description = row.querySelector('.item-desc').value;
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const rate = parseFloat(row.querySelector('.item-rate').value) || 0;
const disc = parseFloat(row.querySelector('.item-disc').value) || 0;
const sgst = parseFloat(row.querySelector('.item-sgst').value) || 0;
const cgst = parseFloat(row.querySelector('.item-cgst').value) || 0;
const subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const afterDisc = subtotal - discAmount;
const sgstAmount = afterDisc * (sgst / 100);
const cgstAmount = afterDisc * (cgst / 100);
const lineTotal = afterDisc + sgstAmount + cgstAmount;
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${hsn}</td>
<td>${sanitizeInput(description)}</td>
<td>${qty}</td>
<td>₹${rate.toFixed(2)}</td>
<td>${disc}%</td>
<td>${sgst}%</td>
<td>${cgst}%</td>
<td>₹${lineTotal.toFixed(2)}</td>
`;
previewItems.appendChild(tr);
});
previewItems.innerHTML += totalsRow;
document.getElementById('previewTotal').textContent = `₹${document.querySelector('#itemsList tbody tr:last-child td:last-child').textContent}`;
}
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric' });
}
function generateWhatsAppMessage() {
const companyName = document.getElementById('companyName').value;
const invoiceNumber = document.getElementById('invoiceNumber').value;
const invoiceDate = document.getElementById('invoiceDate').value;
const clientName = document.getElementById('clientName').value;
const descLength = parseInt(document.getElementById('whatsappDescLength').value) || 20;
const includeGST = document.getElementById('gstEnabled').checked;
const rows = document.querySelectorAll('.item-row');
let grandTotal = 0;
let message = `*Invoice from ${sanitizeInput(companyName)}*\n`;
message += `Invoice #: ${sanitizeInput(invoiceNumber)}\n`;
message += `Date: ${formatDate(invoiceDate)}\n`;
message += `To: ${sanitizeInput(clientName)}\n\n`;
message += `*Items:*\n`;
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
const item = inventory.find(i => i.code === code);
const hsn = item ? item.hsn : '';
let description = row.querySelector('.item-desc').value;
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const rate = parseFloat(row.querySelector('.item-rate').value) || 0;
const disc = parseFloat(row.querySelector('.item-disc').value) || 0;
const sgst = parseFloat(row.querySelector('.item-sgst').value) || 0;
const cgst = parseFloat(row.querySelector('.item-cgst').value) || 0;
const subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const afterDisc = subtotal - discAmount;
const sgstAmount = afterDisc * (sgst / 100);
const cgstAmount = afterDisc * (cgst / 100);
const lineTotal = afterDisc + sgstAmount + cgstAmount;
description = description.length > descLength ? description.substring(0, descLength) + '...' : description;
message += `- ${sanitizeInput(description)} (HSN/SAC: ${hsn}) x${qty} @ ₹${rate.toFixed(2)}`;
if (disc > 0) message += `, Disc: ${disc}%`;
if (includeGST) {
message += `, SGST: ${sgst}%, CGST: ${cgst}%`;
}
message += ` = ₹${lineTotal.toFixed(2)}\n`;
grandTotal += lineTotal;
});
message += `\n*Grand Total: ₹${grandTotal.toFixed(2)}*\n`;
message += `Thank you for your business!`;
const preview = document.getElementById('whatsappPreview');
preview.textContent = message;
preview.style.display = 'block';
}
function sendWhatsApp() {
const phone = document.getElementById('whatsappNumber').value.replace(/\D/g, '');
const message = document.getElementById('whatsappPreview').textContent;
if (!phone) {
showStatus('Please enter a valid WhatsApp number.', 'error');
return;
}
const url = `https://wa.me/${phone}?text=${encodeURIComponent(message)}`;
window.open(url, '_blank');
}
function generatePDF() {
console.log('Starting PDF generation...');
if (!window.jspdf || !window.jspdf.jsPDF) {
alert('jsPDF library not loaded. Please refresh the page.');
return;
}
if (!window.html2canvas) {
alert('html2canvas library not loaded. Please refresh the page.');
return;
}
const invoiceBox = document.querySelector('.invoice-box');
if (!invoiceBox) {
alert('Invoice preview not found. Please generate the invoice first.');
return;
}
try {
html2canvas(invoiceBox, {
scale: 2,
useCORS: true,
allowTaint: true,
logging: true
}).then(canvas => {
try {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
const invoiceNumber = document.getElementById('invoiceNumber').value || 'Untitled';
const timestamp = new Date().toISOString().replace(/[-:.]/g, ''); // e.g., 20250910T185100
doc.save(`invoice_${invoiceNumber}_${timestamp}.pdf`);
console.log('PDF download triggered successfully.');
} catch (innerErr) {
console.error('Error in html2canvas callback:', innerErr);
alert('Failed to generate PDF: ' + innerErr.message);
}
}).catch(err => {
console.error('html2canvas error:', err);
alert('html2canvas failed: ' + err.message);
});
} catch (err) {
console.error('jsPDF error:', err);
alert('PDF generation error: ' + err.message);
}
}
function sharePDF() {
const { jsPDF } = window.jspdf;
const invoiceBox = document.querySelector('.invoice-box');
const companyName = document.getElementById('companyName').value || 'Your Company Name';
const whatsappNumber = document.getElementById('whatsappNumber').value;
if (!whatsappNumber) {
showStatus('Please enter a WhatsApp number.', 'error');
return;
}
html2canvas(invoiceBox, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
});
const imgProps = pdf.getImageProperties(imgData);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
const pdfBlob = pdf.output('blob');
const file = new File([pdfBlob], `${companyName.replace(/\s+/g, '_')}_Invoice_${document.getElementById('invoiceNumber').value}.pdf`, { type: 'application/pdf' });
const shareData = {
files: [file],
title: `${companyName} Invoice`,
text: `Here is your invoice from ${companyName}.`
};
if (navigator.canShare && navigator.canShare({ files: [file] })) {
navigator.share(shareData).catch(err => {
showStatus('Error sharing PDF: ' + err.message, 'error');
});
} else {
const url = `https://api.whatsapp.com/send?phone=${encodeURIComponent(whatsappNumber)}&text=${encodeURIComponent(shareData.text)}`;
window.open(url, '_blank');
showStatus('PDF sharing not supported on this device. Opened WhatsApp with message.', 'error');
}
});
}
function printInvoice() {
window.print();
}
function showStatus(message, type) {
const statusMessage = document.getElementById('statusMessage');
statusMessage.textContent = message;
statusMessage.className = `status-message status-${type}`;
statusMessage.style.display = 'block';
setTimeout(() => {
statusMessage.style.display = 'none';
}, 3000);
}
function setupItemAutocomplete(row) {
const codeInput = row.querySelector('.item-code');
const dropdown = row.querySelector('.item-dropdown');
const updateDropdown = debounce(() => {
const value = codeInput.value.toLowerCase();
dropdown.innerHTML = '';
if (!value) {
dropdown.style.display = 'none';
return;
}
const matches = inventory.filter(item =>
item.code.toLowerCase().includes(value) ||
item.name.toLowerCase().includes(value) ||
item.description.toLowerCase().includes(value)
);
matches.forEach(item => {
const div = document.createElement('div');
div.className = 'dropdown-item';
div.textContent = `${item.code} - ${item.name} (${item.description})`;
div.addEventListener('click', () => {
codeInput.value = item.code;
row.querySelector('.item-desc').value = item.description;
row.querySelector('.item-rate').value = item.price.toFixed(2);
row.querySelector('.item-sgst').value = item.sgst.toFixed(2);
row.querySelector('.item-cgst').value = item.cgst.toFixed(2);
row.querySelector('.item-qty').classList.remove('quantity-error');
updateItemTotal(row);
updateItemTotals();
dropdown.style.display = 'none';
});
dropdown.appendChild(div);
});
dropdown.style.display = matches.length ? 'block' : 'none';
}, 300);
codeInput.addEventListener('input', updateDropdown);
codeInput.addEventListener('focus', () => updateDropdown());
codeInput.addEventListener('blur', () => {
setTimeout(() => {
dropdown.style.display = 'none';
}, 200);
});
}
function setupCustomerAutocomplete() {
const contactInput = document.getElementById('clientContact');
const dropdown = document.getElementById('contactDropdown');
const updateDropdown = debounce(() => {
const value = contactInput.value.toLowerCase();
dropdown.innerHTML = '';
if (!value) {
dropdown.style.display = 'none';
return;
}
const matches = customerDatabase.filter(customer =>
customer.contact.toLowerCase().includes(value) ||
customer.name.toLowerCase().includes(value)
);
matches.forEach(customer => {
const div = document.createElement('div');
div.className = 'dropdown-item';
div.textContent = `${customer.contact} - ${customer.name}`;
div.addEventListener('click', () => {
contactInput.value = customer.contact;
document.getElementById('clientName').value = customer.name;
document.getElementById('clientEmail').value = customer.email;
document.getElementById('clientAddress').value = customer.address;
document.getElementById('whatsappNumber').value = customer.contact;
['clientName', 'clientEmail', 'clientAddress'].forEach(id => {
document.getElementById(id).classList.add('auto-populated');
setTimeout(() => document.getElementById(id).classList.remove('auto-populated'), 1000);
});
dropdown.style.display = 'none';
});
dropdown.appendChild(div);
});
dropdown.style.display = matches.length ? 'block' : 'none';
}, 300);
contactInput.addEventListener('input', updateDropdown);
contactInput.addEventListener('focus', () => updateDropdown());
contactInput.addEventListener('blur', () => {
setTimeout(() => {
dropdown.style.display = 'none';
}, 200);
});
}
function openItemDialog() {
const dialog = document.getElementById('dialogOverlay');
activeDialog = dialog;
dialog.style.display = 'flex';
searchItems();
}
function closeDialog(event, overlayId) {
if (event && event.target.id !== overlayId) return;
const dialog = document.getElementById(overlayId);
dialog.style.display = 'none';
activeDialog = null;
document.getElementById('searchInput').value = '';
searchItems();
}
function searchItems() {
const searchValue = document.getElementById('searchInput').value.toLowerCase();
filteredInventory = inventory.filter(item =>
item.code.toLowerCase().includes(searchValue) ||
item.name.toLowerCase().includes(searchValue) ||
item.description.toLowerCase().includes(searchValue) ||
item.hsn.toLowerCase().includes(searchValue)
);
loadItemDialog();
}
function loadItemDialog() {
const tbody = document.getElementById('itemsList2');
tbody.innerHTML = '';
filteredInventory.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.code}</td>
<td>${item.hsn}</td>
<td>${item.description}</td>
<td>₹${item.price.toFixed(2)}</td>
<td>${item.sgst}%</td>
<td>${item.cgst}%</td>
`;
tr.addEventListener('click', () => {
const row = document.querySelector('.item-row:last-child');
row.querySelector('.item-code').value = item.code;
row.querySelector('.item-desc').value = item.description;
row.querySelector('.item-rate').value = item.price.toFixed(2);
row.querySelector('.item-sgst').value = item.sgst.toFixed(2);
row.querySelector('.item-cgst').value = item.cgst.toFixed(2);
updateItemTotal(row);
updateItemTotals();
closeDialog(null, 'dialogOverlay');
});
tbody.appendChild(tr);
});
}
function applyCustomSize(dialogId, widthInputId, heightInputId) {
const dialog = document.getElementById(dialogId);
const width = parseInt(document.getElementById(widthInputId).value);
const height = parseInt(document.getElementById(heightInputId).value);
if (width >= 300 && width <= 1200 && height >= 200 && height <= 800) {
dialog.style.width = `${width}px`;
dialog.style.height = `${height}px`;
}
}
function quickResize(width, height, dialogId, widthInputId, heightInputId) {
document.getElementById(widthInputId).value = width;
document.getElementById(heightInputId).value = height;
applyCustomSize(dialogId, widthInputId, heightInputId);
}
function addStockItem() {
const code = sanitizeInput(document.getElementById('stockCode').value);
const hsn = sanitizeInput(document.getElementById('stockHSN').value);
const description = sanitizeInput(document.getElementById('stockDescription').value);
const price = parseFloat(document.getElementById('stockPrice').value) || 0;
const unit = document.getElementById('stockUnit').value;
const quantity = parseInt(document.getElementById('stockQuantity').value) || 0;
const sgst = parseFloat(document.getElementById('stockSGST').value) || 0;
const cgst = parseFloat(document.getElementById('stockCGST').value) || 0;
if (!code || !description || price <= 0 || quantity < 0) {
showStatus('Please fill in all stock item details correctly.', 'error');
return;
}
if (inventory.find(item => item.code === code)) {
showStatus('Item code already exists.', 'error');
return;
}
inventory.push({ code, hsn, name: description.split(' ')[0], description, price, unit, quantity, sgst, cgst });
loadStockTable();
['stockCode', 'stockHSN', 'stockDescription', 'stockPrice', 'stockQuantity', 'stockSGST', 'stockCGST'].forEach(id => {
document.getElementById(id).value = id.includes('GST') ? '9' : '';
});
showStatus('Stock item added successfully!', 'success');
}
function loadStockTable() {
const tbody = document.getElementById('stockTableBody');
tbody.innerHTML = '';
inventory.forEach((item, index) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.code}</td>
<td>${item.hsn}</td>
<td>${item.description}</td>
<td>₹${item.price.toFixed(2)}</td>
<td>${item.unit}</td>
<td>${item.sgst}%</td>
<td>${item.cgst}%</td>
<td class="${item.quantity < 10 ? 'low-stock' : ''}">${item.quantity}</td>
<td>
<button class="edit-btn" onclick="editStockItem(${index})">Edit</button>
<button class="delete-btn" onclick="deleteStockItem(${index})">Delete</button>
</td>
`;
tbody.appendChild(tr);
});
}
function editStockItem(index) {
const item = inventory[index];
document.getElementById('stockCode').value = item.code;
document.getElementById('stockHSN').value = item.hsn;
document.getElementById('stockDescription').value = item.description;
document.getElementById('stockPrice').value = item.price;
document.getElementById('stockUnit').value = item.unit;
document.getElementById('stockQuantity').value = item.quantity;
document.getElementById('stockSGST').value = item.sgst;
document.getElementById('stockCGST').value = item.cgst;
inventory.splice(index, 1);
loadStockTable();
showStatus('Edit the item details and click Add to save.', 'success');
}
function deleteStockItem(index) {
if (confirm('Are you sure you want to delete this item?')) {
inventory.splice(index, 1);
loadStockTable();
showStatus('Stock item deleted successfully!', 'success');
}
}
function addCustomer() {
const contact = sanitizeInput(document.getElementById('newCustomerContact').value);
const name = sanitizeInput(document.getElementById('newCustomerName').value);
const email = sanitizeInput(document.getElementById('newCustomerEmail').value);
const address = sanitizeInput(document.getElementById('newCustomerAddress').value);
if (!contact || !name) {
showStatus('Please fill in contact number and customer name.', 'error');
return;
}
if (customerDatabase.find(customer => customer.contact === contact)) {
showStatus('Customer with this contact number already exists.', 'error');
return;
}
customerDatabase.push({ contact, name, email, address });
loadCustomerTable();
['newCustomerContact', 'newCustomerName', 'newCustomerEmail', 'newCustomerAddress'].forEach(id => {
document.getElementById(id).value = '';
});
showStatus('Customer added successfully!', 'success');
}
function loadCustomerTable() {
const tbody = document.getElementById('customerTableBody');
tbody.innerHTML = '';
customerDatabase.forEach((customer, index) => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${customer.contact}</td>
<td>${customer.name}</td>
<td>${customer.email || '-'}</td>
<td>${customer.address || '-'}</td>
<td>
<button class="edit-btn" onclick="editCustomer(${index})">Edit</button>
<button class="delete-btn" onclick="deleteCustomer(${index})">Delete</button>
</td>
`;
tbody.appendChild(tr);
});
}
function editCustomer(index) {
editingCustomerIndex = index;
const customer = customerDatabase[index];
document.getElementById('editCustomerContact').value = customer.contact;
document.getElementById('editCustomerName').value = customer.name;
document.getElementById('editCustomerEmail').value = customer.email;
document.getElementById('editCustomerAddress').value = customer.address;
document.getElementById('editCustomerDialogOverlay').style.display = 'flex';
}
function saveEditedCustomer() {
const contact = sanitizeInput(document.getElementById('editCustomerContact').value);
const name = sanitizeInput(document.getElementById('editCustomerName').value);
const email = sanitizeInput(document.getElementById('editCustomerEmail').value);
const address = sanitizeInput(document.getElementById('editCustomerAddress').value);
if (!contact || !name) {
showStatus('Please fill in contact number and customer name.', 'error');
return;
}
if (customerDatabase.find((customer, idx) => idx !== editingCustomerIndex && customer.contact === contact)) {
showStatus('Customer with this contact number already exists.', 'error');
return;
}
customerDatabase[editingCustomerIndex] = { contact, name, email, address };
loadCustomerTable();
closeDialog(null, 'editCustomerDialogOverlay');
showStatus('Customer updated successfully!', 'success');
editingCustomerIndex = null;
}
function deleteCustomer(index) {
if (confirm('Are you sure you want to delete this customer?')) {
customerDatabase.splice(index, 1);
loadCustomerTable();
showStatus('Customer deleted successfully!', 'success');
}
}
function backupData() {
const data = {
inventory: inventory,
customers: customerDatabase,
invoices: invoices
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `backup_${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
showStatus('Backup created successfully!', 'success');
}
function restoreData() {
const fileInput = document.getElementById('restoreFileInput');
const file = fileInput.files[0];
if (!file) {
showStatus('Please select a file to restore.', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
inventory.length = 0;
inventory.push(...data.inventory);
customerDatabase.length = 0;
customerDatabase.push(...data.customers);
invoices.length = 0;
invoices.push(...data.invoices);
loadStockTable();
loadCustomerTable();
updateDashboard();
showStatus('Data restored successfully!', 'success');
} catch (err) {
showStatus('Invalid backup file.', 'error');
}
};
reader.readAsText(file);
}
function backupStockData() {
const blob = new Blob([JSON.stringify(inventory, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `stock_backup_${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
showStatus('Stock data backed up successfully!', 'success');
}
function restoreStockData() {
const fileInput = document.getElementById('restoreStockFileInput');
const file = fileInput.files[0];
if (!file) {
showStatus('Please select a file to restore.', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
inventory.length = 0;
inventory.push(...JSON.parse(e.target.result));
loadStockTable();
showStatus('Stock data restored successfully!', 'success');
} catch (err) {
showStatus('Invalid stock backup file.', 'error');
}
};
reader.readAsText(file);
}
function backupCustomerData() {
const blob = new Blob([JSON.stringify(customerDatabase, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `customer_backup_${new Date().toISOString().split('T')[0]}.json`;
link.click();
URL.revokeObjectURL(url);
showStatus('Customer data backed up successfully!', 'success');
}
function restoreCustomerData() {
const fileInput = document.getElementById('restoreCustomerFileInput');
const file = fileInput.files[0];
if (!file) {
showStatus('Please select a file to restore.', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
customerDatabase.length = 0;
customerDatabase.push(...JSON.parse(e.target.result));
loadCustomerTable();
showStatus('Customer data restored successfully!', 'success');
} catch (err) {
showStatus('Invalid customer backup file.', 'error');
}
};
reader.readAsText(file);
}
function toggleCustomDateRange() {
const select = document.getElementById('dateRangeFilter');
const customRange = document.getElementById('customDateRange');
customRange.classList.toggle('hidden', select.value !== 'custom');
}
function updateDashboard() {
const dateRange = document.getElementById('dateRangeFilter').value;
let fromDate, toDate = new Date();
if (dateRange === 'custom') {
fromDate = new Date(document.getElementById('fromDate').value);
toDate = new Date(document.getElementById('toDate').value);
if (!fromDate || !toDate || fromDate > toDate) {
showStatus('Please select a valid date range.', 'error');
return;
}
} else {
fromDate = new Date();
fromDate.setDate(toDate.getDate() - parseInt(dateRange));
}
const filteredInvoices = invoices.filter(inv => {
const invDate = new Date(inv.timestamp);
return invDate >= fromDate && invDate <= toDate;
});
let totalRevenue = 0;
let totalOrders = filteredInvoices.length;
let totalProducts = 0;
const customerSet = new Set();
const productSales = {};
filteredInvoices.forEach(inv => {
totalRevenue += inv.grandTotal;
inv.items.forEach(item => {
totalProducts += item.quantity;
productSales[item.code] = productSales[item.code] || { code: item.code, description: item.description, quantity: 0 };
productSales[item.code].quantity += item.quantity;
});
customerSet.add(inv.clientContact);
});
document.getElementById('totalRevenue').textContent = `₹${totalRevenue.toFixed(2)}`;
document.getElementById('totalOrders').textContent = totalOrders;
document.getElementById('totalCustomers').textContent = customerSet.size;
document.getElementById('totalProducts').textContent = totalProducts;
const topProductsList = document.getElementById('topProductsList');
topProductsList.innerHTML = '';
Object.values(productSales)
.sort((a, b) => b.quantity - a.quantity)
.slice(0, 5)
.forEach(product => {
const div = document.createElement('div');
div.textContent = `${product.code} - ${product.description} (${product.quantity} sold)`;
topProductsList.appendChild(div);
});
const recentOrdersList = document.getElementById('recentOrdersList');
recentOrdersList.innerHTML = '';
filteredInvoices
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.slice(0, 5)
.forEach(inv => {
const div = document.createElement('div');
div.textContent = `${inv.invoiceNumber} - ${inv.clientName} (₹${inv.grandTotal.toFixed(2)})`;
div.addEventListener('click', () => loadInvoiceToForm(inv));
recentOrdersList.appendChild(div);
});
}
function loadInvoiceToForm(invoice) {
document.getElementById('invoiceNumber').value = invoice.invoiceNumber;
document.getElementById('invoiceDate').value = invoice.invoiceDate;
document.getElementById('dueDate').value = invoice.dueDate;
document.getElementById('clientContact').value = invoice.clientContact;
document.getElementById('clientName').value = invoice.clientName;
document.getElementById('clientEmail').value = invoice.clientEmail;
document.getElementById('clientAddress').value = invoice.clientAddress;
document.getElementById('whatsappNumber').value = invoice.clientContact;
const itemsList = document.getElementById('itemsList').querySelector('tbody');
itemsList.innerHTML = '';
invoice.items.forEach(item => {
const row = document.createElement('tr');
row.className = 'item-row';
row.innerHTML = `
<td><input type="text" class="item-code code-input" value="${item.code}" placeholder="Enter item code..."><div class="dropdown item-dropdown"></div></td>
<td><input type="text" class="item-desc desc-input" value="${item.description}" placeholder="Service or product description"></td>
<td><input type="number" class="item-qty qty-input" value="${item.quantity}" min="1"></td>
<td><input type="number" class="item-rate rate-input" value="${item.rate.toFixed(2)}" step="0.01" min="0"></td>
<td><input type="number" class="item-disc disc-input" value="${item.discount}" step="0.01" min="0" max="100"></td>
<td><input type="number" class="item-sgst sgst-input" value="${item.sgst}" step="0.01" min="0"></td>
<td><input type="number" class="item-cgst cgst-input" value="${item.cgst}" step="0.01" min="0"></td>
<td><input type="text" class="item-total total-input" value="${item.total.toFixed(2)}" readonly></td>
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
`;
itemsList.appendChild(row);
attachInputListeners(row);
setupItemAutocomplete(row);
});
updateItemTotals();
showStatus('Invoice loaded successfully!', 'success');
}
function clearInvoices() {
if (confirm('Are you sure you want to clear all invoices? This cannot be undone.')) {
invoices = [];
localStorage.setItem('invoices', JSON.stringify(invoices));
updateDashboard();
showStatus('All invoices cleared.', 'success');
}
}
function handleLogoUpload() {
const fileInput = document.getElementById('logoUpload');
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
if (logoElement) logoElement.remove();
logoElement = document.createElement('div');
logoElement.className = 'invoice-logo';
logoElement.innerHTML = `
<img src="${event.target.result}" alt="Logo">
<div class="resize-handle top-left"></div>
<div class="resize-handle top-right"></div>
<div class="resize-handle bottom-left"></div>
<div class="resize-handle bottom-right"></div>
`;
document.querySelector('.invoice-box').prepend(logoElement);
const img = logoElement.querySelector('img');
img.onload = () => {
originalAspectRatio = img.naturalWidth / img.naturalHeight;
logoElement.style.width = '100px';
logoElement.style.height = '100px';
document.getElementById('logoWidth').value = 100;
document.getElementById('logoHeight').value = 100;
setupLogoInteractions();
};
};
reader.readAsDataURL(file);
});
}
function setupLogoInteractions() {
if (!logoElement) return;
logoElement.addEventListener('mousedown', startLogoDrag);
logoElement.addEventListener('touchstart', startLogoDrag);
logoElement.addEventListener('dblclick', toggleLogoHandles);
const handles = logoElement.querySelectorAll('.resize-handle');
handles.forEach(handle => {
handle.addEventListener('mousedown', (e) => startResize(e, handle));
handle.addEventListener('touchstart', (e) => startResize(e, handle));
});
document.getElementById('logoWidth').addEventListener('input', updateLogoSize);
document.getElementById('logoHeight').addEventListener('input', updateLogoSize);
}
function startLogoDrag(e) {
e.preventDefault();
if (isResizing) return;
isLogoDragging = true;
const rect = logoElement.getBoundingClientRect();
const clientX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
const clientY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY;
dragOffset.x = clientX - rect.left;
dragOffset.y = clientY - rect.top;
document.addEventListener('mousemove', dragLogo);
document.addEventListener('touchmove', dragLogo);
document.addEventListener('mouseup', stopLogoDrag);
document.addEventListener('touchend', stopLogoDrag);
}
function dragLogo(e) {
if (!isLogoDragging) return;
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
const invoiceBox = document.querySelector('.invoice-box').getBoundingClientRect();
let newLeft = clientX - dragOffset.x - invoiceBox.left;
let newTop = clientY - dragOffset.y - invoiceBox.top;
newLeft = Math.max(0, Math.min(newLeft, invoiceBox.width - logoElement.offsetWidth));
newTop = Math.max(0, Math.min(newTop, invoiceBox.height - logoElement.offsetHeight));
logoElement.style.left = `${newLeft}px`;
logoElement.style.top = `${newTop}px`;
}
function stopLogoDrag() {
isLogoDragging = false;
document.removeEventListener('mousemove', dragLogo);
document.removeEventListener('touchmove', dragLogo);
document.removeEventListener('mouseup', stopLogoDrag);
document.removeEventListener('touchend', stopLogoDrag);
}
function startResize(e, handle) {
e.preventDefault();
isResizing = true;
resizeHandle = handle;
const rect = logoElement.getBoundingClientRect();
logoStartPos = { x: rect.left, y: rect.top };
logoStartSize = { width: rect.width, height: rect.height };
document.addEventListener('mousemove', resizeLogo);
document.addEventListener('touchmove', resizeLogo);
document.addEventListener('mouseup', stopResize);
document.addEventListener('touchend', stopResize);
}
function resizeLogo(e) {
if (!isResizing) return;
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY;
const invoiceBox = document.querySelector('.invoice-box').getBoundingClientRect();
let newWidth, newHeight, newLeft, newTop;
if (resizeHandle.classList.contains('top-left')) {
newWidth = logoStartSize.width + (logoStartPos.x - clientX);
newHeight = document.getElementById('lockAspectRatio').checked
? newWidth / originalAspectRatio
: logoStartSize.height + (logoStartPos.y - clientY);
newLeft = logoStartPos.x + logoStartSize.width - newWidth;
newTop = document.getElementById('lockAspectRatio').checked
? logoStartPos.y + logoStartSize.height - newHeight
: logoStartPos.y + logoStartSize.height - newHeight;
} else if (resizeHandle.classList.contains('top-right')) {
newWidth = clientX - logoStartPos.x;
newHeight = document.getElementById('lockAspectRatio').checked
? newWidth / originalAspectRatio
: logoStartSize.height + (logoStartPos.y - clientY);
newLeft = logoStartPos.x;
newTop = document.getElementById('lockAspectRatio').checked
? logoStartPos.y + logoStartSize.height - newHeight
: logoStartPos.y + logoStartSize.height - newHeight;
} else if (resizeHandle.classList.contains('bottom-left')) {
newWidth = logoStartSize.width + (logoStartPos.x - clientX);
newHeight = document.getElementById('lockAspectRatio').checked
? newWidth / originalAspectRatio
: clientY - logoStartPos.y;
newLeft = logoStartPos.x + logoStartSize.width - newWidth;
newTop = logoStartPos.y;
} else {
newWidth = clientX - logoStartPos.x;
newHeight = document.getElementById('lockAspectRatio').checked
? newWidth / originalAspectRatio
: clientY - logoStartPos.y;
newLeft = logoStartPos.x;
newTop = logoStartPos.y;
}
newWidth = Math.max(20, Math.min(newWidth, 300));
newHeight = Math.max(20, Math.min(newHeight, 300));
newLeft = Math.max(0, Math.min(newLeft, invoiceBox.width - newWidth));
newTop = Math.max(0, Math.min(newTop, invoiceBox.height - newHeight));
logoElement.style.width = `${newWidth}px`;
logoElement.style.height = `${newHeight}px`;
logoElement.style.left = `${newLeft}px`;
logoElement.style.top = `${newTop}px`;
document.getElementById('logoWidth').value = Math.round(newWidth);
document.getElementById('logoHeight').value = Math.round(newHeight);
}
function stopResize() {
isResizing = false;
resizeHandle = null;
document.removeEventListener('mousemove', resizeLogo);
document.removeEventListener('touchmove', resizeLogo);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener('touchend', stopResize);
}
function updateLogoSize() {
if (!logoElement) return;
let width = parseInt(document.getElementById('logoWidth').value) || 100;
let height = parseInt(document.getElementById('logoHeight').value) || 100;
if (document.getElementById('lockAspectRatio').checked) {
if (document.activeElement.id === 'logoWidth') {
height = width / originalAspectRatio;
} else {
width = height * originalAspectRatio;
}
}
width = Math.max(20, Math.min(width, 300));
height = Math.max(20, Math.min(height, 300));
logoElement.style.width = `${width}px`;
logoElement.style.height = `${height}px`;
document.getElementById('logoWidth').value = Math.round(width);
document.getElementById('logoHeight').value = Math.round(height);
}
function resetLogoPosition() {
if (logoElement) {
logoElement.style.left = '20px';
logoElement.style.top = '20px';
logoElement.style.width = '100px';
logoElement.style.height = '100px';
document.getElementById('logoWidth').value = 100;
document.getElementById('logoHeight').value = 100;
}
}
function removeLogo() {
if (logoElement) {
logoElement.remove();
logoElement = null;
document.getElementById('logoUpload').value = '';
document.getElementById('logoWidth').value = '';
document.getElementById('logoHeight').value = '';
}
}
function toggleLogoHandles() {
if (!logoElement) return;
const currentTime = new Date().getTime();
if (currentTime - lastTapTime < 300) {
logoElement.classList.toggle('active');
}
lastTapTime = currentTime;
}
// Initialize application
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.getElementById(btn.dataset.section).classList.add('active');
});
});
document.querySelectorAll('.item-row').forEach(row => {
attachInputListeners(row);
setupItemAutocomplete(row);
});
setupCustomerAutocomplete();
handleLogoUpload();
loadStockTable();
loadCustomerTable();
updateDashboard();
// Set default dates
const today = new Date().toISOString().split('T')[0];
document.getElementById('invoiceDate').value = today;
document.getElementById('dueDate').value = new Date(new Date().setMonth(new Date().getMonth() + 1)).toISOString().split('T')[0];
});
</script>
</body>
</html>
No comments:
Post a Comment