Enhanced Indian Invoice System
Revolutionize your business with our comprehensive, tax-free invoice management solution
Transform Your Business Operations
Managing invoices shouldn't be a hassle. Our Enhanced Indian Invoice System is designed specifically for Indian businesses, offering a complete solution that handles everything from invoice generation to customer management, all while maintaining compliance with tax-free operations.
Comprehensive Feature Set
๐ Smart Invoice Generator
Create professional invoices with automatic calculations, HSN/SAC code support, and intelligent item suggestions. The system handles quantity validation and ensures accuracy in every transaction.
๐ Real-Time Sales Dashboard
Monitor your business performance with comprehensive analytics, revenue tracking, and customer insights. Get actionable data to drive your business decisions.
๐ฆ Advanced Stock Management
Track inventory levels, manage product catalogs with HSN codes, and receive low-stock alerts. Keep your business running smoothly with automated inventory control.
๐ฅ Customer Database
Maintain detailed customer records with auto-population features. Build stronger relationships with comprehensive customer management and history tracking.
๐พ Backup & Restore
Protect your business data with comprehensive backup solutions. Export and import your complete business data with just a few clicks.
๐ผ️ Custom Branding
Upload and position your company logo with precise control. Maintain brand consistency across all your invoices with drag-and-drop logo positioning.
Tender Cash Management
Streamlined Cash Handling
Our system includes sophisticated tender cash management that automatically calculates change due when cash payments exceed the invoice total. This feature is essential for retail operations, small businesses, and service providers who handle cash transactions regularly.
Advanced Communication Features
๐ฑ WhatsApp Integration
Send invoice details directly through WhatsApp with customizable message formatting. Perfect for instant communication with customers and maintaining professional relationships.
๐ PDF Generation
Create high-quality PDF invoices with professional formatting. Share digitally or print for physical records with consistent branding and layout.
๐จ️ Print-Ready Output
Optimized printing functionality ensures your invoices look professional on paper. Perfect formatting for standard paper sizes and printer configurations.
Business Intelligence
Make data-driven decisions with our comprehensive analytics suite:
Why Choose Our Solution?
๐ฎ๐ณ India-Specific Design
Built specifically for Indian businesses with HSN/SAC codes, rupee formatting, and local business practices in mind.
๐ซ Tax-Free Compliant
Designed for businesses operating in tax-free environments or specific sectors where tax calculations aren't required.
๐ป Browser-Based
No installation required. Works on any device with a web browser, ensuring accessibility across all your business devices.
๐ฑ Mobile Responsive
Optimized for mobile devices and tablets. Manage your business on-the-go with touch-friendly interfaces.
๐ Data Security
Local storage ensures your sensitive business data stays on your device. Export backups for additional security.
⚡ Performance Optimized
Lightweight and fast, designed to work efficiently even on slower internet connections or older devices.
Perfect For
Our Enhanced Indian Invoice System is ideal for:
Ready to Transform Your Business?
Join thousands of Indian businesses already using our comprehensive invoice system to streamline operations, improve customer relationships, and drive growth.
Start managing your invoices like a pro today!
Try Our Tender Cash Tool
Experience our tender cash management system right here! Calculate change instantly and manage cash transactions with ease for your business.
Here is the code
Click or tap to copy code
<!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 (Tax-Free)</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>
* { 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 .total-input, .items-table .tendered-input, .items-table .change-input { min-width: 80px; }
.items-table .total-input, .items-table .change-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; }
.tendered-change { text-align: right; margin-top: 10px; font-size: 14px; }
.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 (Tax-Free)</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>
<!-- Payment Details -->
<h3>๐ณ Payment Details</h3>
<div class="form-row">
<div class="form-group">
<label>Tendered Cash (₹)</label>
<input type="number" id="tenderedCash" min="0" step="0.01" placeholder="Enter tendered cash">
</div>
<div class="form-group">
<label>Change Due (₹)</label>
<input type="text" id="changeDue" readonly placeholder="Change will be calculated">
</div>
</div>
<!-- Items Section -->
<h3>๐ฆ Items</h3>
<div class="items-section">
<table class="items-table" id="itemsList">
<thead>
<tr>
<th>HSN/SAC Code</th>
<th>Description</th>
<th>Qty</th>
<th>Rate (₹)</th>
<th>Disc %</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 HSN/SAC 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="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>
<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>Code</th>
<th>Description</th>
<th>Qty</th>
<th>Rate</th>
<th>Disc %</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.00</td>
</tr>
</tbody>
</table>
<p class="total">Grand Total: <span id="previewTotal">₹0.00</span></p>
<div class="tendered-change">
<p>Tendered Cash: <span id="previewTenderedCash">₹0.00</span></p>
<p>Change Due: <span id="previewChangeDue">₹0.00</span></p>
</div>
<p style="text-align: right; font-size: 12px; color: #666;">Note: All items are tax-free.</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>
<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>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 Data</label>
<button class="backup-btn" onclick="backupData()">Backup Data to JSON</button>
</div>
<div class="form-group">
<label>Restore Data</label>
<input type="file" id="restoreFileInput" accept=".json">
<button class="restore-btn" onclick="restoreData()">Restore 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>Code</th>
<th>Name</th>
<th>Description</th>
<th>Price</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 },
{ code: 'ITM002', hsn: '8471', name: 'Keyboard', description: 'Mechanical gaming keyboard with RGB lighting', price: 89.99, unit: 'PCS', quantity: 30 },
{ code: 'ITM003', hsn: '9403', name: 'Monitor Stand', description: 'Adjustable aluminum monitor stand', price: 45.00, unit: 'PCS', quantity: 20 },
{ code: 'ITM004', hsn: '8544', name: 'USB Cable', description: '6ft USB-C to USB-A cable', price: 12.50, unit: 'PCS', quantity: 100 },
{ code: 'ITM005', hsn: '8525', name: 'Webcam', description: '1080p HD webcam with built-in microphone', price: 67.99, unit: 'PCS', quantity: 15 },
{ code: 'ITM006', hsn: '9405', name: 'Desk Lamp', description: 'LED desk lamp with touch control', price: 34.99, unit: 'PCS', quantity: 25 },
{ code: 'ITM007', hsn: '8504', name: 'Phone Charger', description: 'Fast charging wireless phone charger', price: 28.99, unit: 'PCS', quantity: 40 },
{ code: 'ITM008', hsn: '8518', name: 'Speakers', description: 'Bluetooth desktop speakers', price: 55.00, unit: 'PCS', quantity: 10 },
{ code: 'ITM009', hsn: '4016', name: 'Mouse Pad', description: 'Extra large gaming mouse pad', price: 19.99, unit: 'PCS', quantity: 60 },
{ code: 'ITM010', hsn: '8518', name: 'Headphones', description: 'Noise-canceling over-ear headphones', price: 149.99, unit: 'PCS', quantity: 5 },
{ code: 'ITM011', hsn: '8473', name: 'External Drive', description: '1TB portable external hard drive', price: 79.99, unit: 'PCS', quantity: 12 },
{ code: 'ITM012', hsn: '3926', name: 'Cable Organizer', description: 'Desktop cable management solution', price: 15.99, unit: 'PCS', quantity: 80 }
];
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')) || [];
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;
function sanitizeInput(input) {
return input.replace(/[<>"'&]/g, '');
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
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')}`;
}
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 HSN/SAC 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="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').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 subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const lineTotal = subtotal - discAmount;
row.querySelector('.item-total').value = lineTotal.toFixed(2);
}
function updateItemTotals() {
const rows = document.querySelectorAll('.item-row');
let subtotal = 0;
let totalDiscount = 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 subtotalAmount = qty * rate;
const discAmount = subtotalAmount * (disc / 100);
const lineTotal = subtotalAmount - discAmount;
row.querySelector('.item-total').value = lineTotal.toFixed(2);
subtotal += subtotalAmount;
totalDiscount += discAmount;
grandTotal += lineTotal;
});
const tenderedCash = parseFloat(document.getElementById('tenderedCash').value) || 0;
const changeDue = tenderedCash >= grandTotal ? (tenderedCash - grandTotal).toFixed(2) : '0.00';
document.getElementById('changeDue').value = changeDue;
const totalsRow = `
<tr>
<td colspan="3"></td>
<td colspan="2">Subtotal</td>
<td>₹${subtotal.toFixed(2)}</td>
</tr>
<tr>
<td colspan="3"></td>
<td colspan="2">Total Discount</td>
<td>₹${totalDiscount.toFixed(2)}</td>
</tr>
<tr>
<td colspan="3"></td>
<td colspan="2"><strong>Grand Total</strong></td>
<td><strong>₹${grandTotal.toFixed(2)}</strong></td>
</tr>
<tr>
<td colspan="3"></td>
<td colspan="2">Tendered Cash</td>
<td>₹${tenderedCash.toFixed(2)}</td>
</tr>
<tr>
<td colspan="3"></td>
<td colspan="2">Change Due</td>
<td>₹${changeDue}</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),
tenderedCash: parseFloat(document.getElementById('tenderedCash').value) || 0,
changeDue: parseFloat(document.getElementById('changeDue').value) || 0,
items: [],
subtotal: 0,
totalDiscount: 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');
}
});
if (!canSave) {
return;
}
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) {
item.quantity -= qty;
}
});
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 subtotalAmount = qty * rate;
const discAmount = subtotalAmount * (disc / 100);
const lineTotal = subtotalAmount - discAmount;
invoice.items.push({
code: sanitizeInput(row.querySelector('.item-code').value),
description: sanitizeInput(row.querySelector('.item-desc').value),
quantity: qty,
rate: rate,
discount: disc,
total: lineTotal
});
invoice.subtotal += subtotalAmount;
invoice.totalDiscount += discAmount;
invoice.grandTotal += lineTotal;
});
if (!invoice.clientContact || !invoice.clientName || invoice.items.length === 0) {
showStatus('Please fill in client details and add at least one item.', 'error');
return;
}
invoices.push(invoice);
localStorage.setItem('invoices', JSON.stringify(invoices));
showStatus('Invoice saved successfully!', 'success');
updateDashboard();
document.getElementById('invoiceNumber').value = generateInvoiceNumber();
document.getElementById('clientContact').value = '';
document.getElementById('clientName').value = '';
document.getElementById('clientEmail').value = '';
document.getElementById('clientAddress').value = '';
document.getElementById('tenderedCash').value = '';
document.getElementById('changeDue').value = '';
document.getElementById('itemsList').querySelector('tbody').innerHTML = `
<tr class="item-row">
<td><input type="text" class="item-code code-input" placeholder="Enter HSN/SAC 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="text" class="item-total total-input" value="0.00" readonly></td>
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
</tr>
`;
document.querySelectorAll('.item-row').forEach(row => {
attachInputListeners(row);
setupItemAutocomplete(row);
});
loadStockTable();
}
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
const options = { day: 'numeric', month: 'long', year: 'numeric' };
return date.toLocaleDateString('en-GB', options);
}
function generateInvoice(totalsRow = '') {
document.getElementById('previewCompanyName').textContent = sanitizeInput(document.getElementById('companyName').value);
document.getElementById('previewCompanyAddress').textContent = sanitizeInput(document.getElementById('companyAddress').value);
document.getElementById('previewCompanyContact').textContent = sanitizeInput(document.getElementById('companyContact').value.replace('\n', ' | '));
document.getElementById('previewClientName').textContent = sanitizeInput(document.getElementById('clientName').value) || 'Customer Name';
document.getElementById('previewClientContact').textContent = sanitizeInput(document.getElementById('clientContact').value) || 'Customer Contact';
document.getElementById('previewClientEmail').textContent = sanitizeInput(document.getElementById('clientEmail').value) || 'Customer Email';
document.getElementById('previewClientAddress').textContent = sanitizeInput(document.getElementById('clientAddress').value) || 'Customer Address';
document.getElementById('previewInvoiceNumber').textContent = document.getElementById('invoiceNumber').value;
document.getElementById('previewInvoiceDate').textContent = formatDate(document.getElementById('invoiceDate').value);
document.getElementById('previewDueDate').textContent = formatDate(document.getElementById('dueDate').value);
document.getElementById('previewTenderedCash').textContent = `₹${(parseFloat(document.getElementById('tenderedCash').value) || 0).toFixed(2)}`;
document.getElementById('previewChangeDue').textContent = `₹${(parseFloat(document.getElementById('changeDue').value) || 0).toFixed(2)}`;
const previewItems = document.getElementById('previewItems');
previewItems.innerHTML = '';
const itemRows = document.querySelectorAll('.item-row');
itemRows.forEach(row => {
const code = sanitizeInput(row.querySelector('.item-code').value) || '';
const desc = sanitizeInput(row.querySelector('.item-desc').value) || 'Item';
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 subtotal = qty * rate;
const discAmount = subtotal * (disc / 100);
const lineTotal = subtotal - discAmount;
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${code}</td>
<td>${desc}</td>
<td>${qty}</td>
<td>₹${rate.toFixed(2)}</td>
<td>${disc}</td>
<td>₹${lineTotal.toFixed(2)}</td>
`;
previewItems.appendChild(tr);
});
previewItems.insertAdjacentHTML('beforeend', totalsRow);
document.getElementById('previewTotal').textContent = '₹' + (parseFloat(document.querySelector('#previewItems tr:nth-last-child(3) td:last-child').textContent.replace('₹', '')) || 0).toFixed(2);
}
function createLogoElement(imageSrc, width = 100, height = 100) {
const invoiceBox = document.querySelector('.invoice-box');
if (logoElement) {
logoElement.remove();
}
logoElement = document.createElement('div');
logoElement.className = 'invoice-logo';
logoElement.style.width = `${width}px`;
logoElement.style.height = `${height}px`;
logoElement.style.top = '20px';
logoElement.style.left = '20px';
logoElement.innerHTML = `
<img src="${imageSrc}" 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>
`;
invoiceBox.appendChild(logoElement);
document.getElementById('logoWidth').value = width;
document.getElementById('logoHeight').value = height;
setupLogoDragging();
setupLogoResizing();
setupLogoDoubleTap();
}
function setupLogoUpload() {
const logoUpload = document.getElementById('logoUpload');
logoUpload.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file && file.type.startsWith('image/')) {
if (file.size > 5 * 1024 * 1024) {
showStatus('Logo file size must be less than 5MB.', 'error');
logoUpload.value = '';
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
originalAspectRatio = img.width / img.height;
createLogoElement(e.target.result);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
} else {
showStatus('Please upload a valid image file.', 'error');
logoUpload.value = '';
}
});
}
function setupLogoDragging() {
if (!logoElement) return;
const invoiceBox = document.querySelector('.invoice-box');
const moveLogo = (e) => {
if (!isLogoDragging || !logoElement) return;
e.preventDefault();
let clientX, clientY;
if (e.type.includes('touch')) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const rect = invoiceBox.getBoundingClientRect();
let newX = clientX - dragOffset.x - rect.left;
let newY = clientY - dragOffset.y - rect.top;
newX = Math.max(0, Math.min(newX, rect.width - logoElement.offsetWidth));
newY = Math.max(0, Math.min(newY, rect.height - logoElement.offsetHeight));
logoElement.style.left = `${newX}px`;
logoElement.style.top = `${newY}px`;
};
logoElement.addEventListener('mousedown', (e) => {
if (e.target.classList.contains('resize-handle')) return;
isLogoDragging = true;
const rect = logoElement.getBoundingClientRect();
dragOffset.x = e.clientX - rect.left;
dragOffset.y = e.clientY - rect.top;
});
logoElement.addEventListener('touchstart', (e) => {
if (e.target.classList.contains('resize-handle')) return;
e.preventDefault();
isLogoDragging = true;
const rect = logoElement.getBoundingClientRect();
dragOffset.x = e.touches[0].clientX - rect.left;
dragOffset.y = e.touches[0].clientY - rect.top;
}, { passive: false });
document.addEventListener('mousemove', moveLogo);
document.addEventListener('touchmove', moveLogo, { passive: false });
document.addEventListener('mouseup', () => isLogoDragging = false);
document.addEventListener('touchend', () => isLogoDragging = false);
}
function setupLogoResizing() {
if (!logoElement) return;
const handles = logoElement.querySelectorAll('.resize-handle');
const resizeLogoElement = (e) => {
if (!logoElement || !isResizing || !resizeHandle) return;
e.preventDefault();
let clientX, clientY;
if (e.type.includes('touch')) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
const rect = logoElement.getBoundingClientRect();
const invoiceBox = document.querySelector('.invoice-box').getBoundingClientRect();
let newWidth = logoStartSize.width;
let newHeight = logoStartSize.height;
let newX = parseFloat(logoElement.style.left) || 0;
let newY = parseFloat(logoElement.style.top) || 0;
const lockAspectRatio = document.getElementById('lockAspectRatio').checked;
if (resizeHandle.classList.contains('bottom-right')) {
newWidth = clientX - rect.left;
newHeight = lockAspectRatio ? newWidth / originalAspectRatio : clientY - rect.top;
} else if (resizeHandle.classList.contains('top-left')) {
newWidth = logoStartSize.width - (clientX - logoStartPos.x);
newHeight = lockAspectRatio ? newWidth / originalAspectRatio : logoStartSize.height - (clientY - logoStartPos.y);
newX = clientX - logoStartPos.x + logoStartSize.width - newWidth;
newY = lockAspectRatio ? newY : clientY - logoStartPos.y + logoStartSize.height - newHeight;
} else if (resizeHandle.classList.contains('top-right')) {
newWidth = clientX - rect.left;
newHeight = lockAspectRatio ? newWidth / originalAspectRatio : logoStartSize.height - (clientY - logoStartPos.y);
newY = lockAspectRatio ? newY : clientY - logoStartPos.y + logoStartSize.height - newHeight;
} else if (resizeHandle.classList.contains('bottom-left')) {
newWidth = logoStartSize.width - (clientX - logoStartPos.x);
newHeight = lockAspectRatio ? newWidth / originalAspectRatio : clientY - rect.top;
newX = clientX - logoStartPos.x + logoStartSize.width - newWidth;
}
newWidth = Math.max(20, Math.min(300, newWidth));
newHeight = Math.max(20, Math.min(300, newHeight));
newX = Math.max(0, Math.min(newX, invoiceBox.width - newWidth));
newY = Math.max(0, Math.min(newY, invoiceBox.height - newHeight));
logoElement.style.width = `${newWidth}px`;
logoElement.style.height = `${newHeight}px`;
logoElement.style.left = `${newX}px`;
logoElement.style.top = `${newY}px`;
document.getElementById('logoWidth').value = Math.round(newWidth);
document.getElementById('logoHeight').value = Math.round(newHeight);
};
handles.forEach(handle => {
handle.addEventListener('mousedown', (e) => {
isResizing = true;
resizeHandle = handle;
const rect = logoElement.getBoundingClientRect();
logoStartPos = { x: e.clientX, y: e.clientY };
logoStartSize = { width: rect.width, height: rect.height };
});
handle.addEventListener('touchstart', (e) => {
e.preventDefault();
isResizing = true;
resizeHandle = handle;
const rect = logoElement.getBoundingClientRect();
logoStartPos = { x: e.touches[0].clientX, y: e.touches[0].clientY };
logoStartSize = { width: rect.width, height: rect.height };
}, { passive: false });
});
document.addEventListener('mousemove', resizeLogoElement);
document.addEventListener('touchmove', resizeLogoElement, { passive: false });
document.addEventListener('mouseup', () => { isResizing = false; resizeHandle = null; });
document.addEventListener('touchend', () => { isResizing = false; resizeHandle = null; });
}
function setupLogoDoubleTap() {
if (!logoElement) return;
logoElement.addEventListener('click', () => {
const currentTime = new Date().getTime();
const tapInterval = currentTime - lastTapTime;
if (tapInterval < 300 && tapInterval > 0) {
logoElement.classList.toggle('active');
} else {
logoElement.classList.add('active');
}
lastTapTime = currentTime;
});
logoElement.addEventListener('touchstart', (e) => {
e.preventDefault();
const currentTime = new Date().getTime();
const tapInterval = currentTime - lastTapTime;
if (tapInterval < 300 && tapInterval > 0) {
logoElement.classList.toggle('active');
} else {
logoElement.classList.add('active');
}
lastTapTime = currentTime;
}, { passive: false });
}
function applyLogoSize() {
if (!logoElement) return;
const widthInput = document.getElementById('logoWidth');
const heightInput = document.getElementById('logoHeight');
const lockAspectRatio = document.getElementById('lockAspectRatio').checked;
let newWidth = parseInt(widthInput.value) || 100;
let newHeight = parseInt(heightInput.value) || 100;
newWidth = Math.max(20, Math.min(300, newWidth));
newHeight = Math.max(20, Math.min(300, newHeight));
if (lockAspectRatio) {
if (widthInput === document.activeElement) {
newHeight = newWidth / originalAspectRatio;
} else {
newWidth = newHeight * originalAspectRatio;
}
newWidth = Math.max(20, Math.min(300, newWidth));
newHeight = Math.max(20, Math.min(300, newHeight));
}
logoElement.style.width = `${newWidth}px`;
logoElement.style.height = `${newHeight}px`;
widthInput.value = Math.round(newWidth);
heightInput.value = Math.round(newHeight);
const invoiceBox = document.querySelector('.invoice-box').getBoundingClientRect();
let newX = parseFloat(logoElement.style.left) || 0;
let newY = parseFloat(logoElement.style.top) || 0;
newX = Math.max(0, Math.min(newX, invoiceBox.width - newWidth));
newY = Math.max(0, Math.min(newY, invoiceBox.height - newHeight));
logoElement.style.left = `${newX}px`;
logoElement.style.top = `${newY}px`;
}
function resetLogoPosition() {
if (!logoElement) return;
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;
logoElement.classList.remove('active');
}
function removeLogo() {
if (logoElement) {
logoElement.remove();
logoElement = null;
document.getElementById('logoUpload').value = '';
document.getElementById('logoWidth').value = '';
document.getElementById('logoHeight').value = '';
showStatus('Logo removed successfully.', 'success');
}
}
function setupItemAutocomplete(row) {
const codeInput = row.querySelector('.item-code');
const dropdown = row.querySelector('.item-dropdown');
codeInput.addEventListener('input', debounce(() => {
const query = codeInput.value.toLowerCase();
dropdown.innerHTML = '';
if (query) {
const filteredItems = inventory.filter(item =>
item.code.toLowerCase().includes(query) ||
item.description.toLowerCase().includes(query)
);
filteredItems.forEach(item => {
const div = document.createElement('div');
div.className = 'dropdown-item';
div.textContent = `${item.code} - ${item.description} (₹${item.price.toFixed(2)})`;
div.addEventListener('click', () => {
codeInput.value = item.code;
row.querySelector('.item-desc').value = item.description;
row.querySelector('.item-rate').value = item.price.toFixed(2);
dropdown.style.display = 'none';
updateItemTotal(row);
updateItemTotals();
});
dropdown.appendChild(div);
});
dropdown.style.display = filteredItems.length ? 'block' : 'none';
} else {
dropdown.style.display = 'none';
}
}, 300));
codeInput.addEventListener('focus', () => {
if (codeInput.value) {
codeInput.dispatchEvent(new Event('input'));
}
});
codeInput.addEventListener('blur', () => {
setTimeout(() => {
dropdown.style.display = 'none';
}, 200);
});
}
function setupCustomerAutocomplete() {
const contactInput = document.getElementById('clientContact');
const dropdown = document.getElementById('contactDropdown');
contactInput.addEventListener('input', debounce(() => {
const query = contactInput.value.toLowerCase();
dropdown.innerHTML = '';
if (query) {
const filteredCustomers = customerDatabase.filter(customer =>
customer.contact.toLowerCase().includes(query) ||
customer.name.toLowerCase().includes(query)
);
filteredCustomers.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;
document.getElementById('clientName').classList.add('auto-populated');
document.getElementById('clientEmail').classList.add('auto-populated');
document.getElementById('clientAddress').classList.add('auto-populated');
setTimeout(() => {
document.getElementById('clientName').classList.remove('auto-populated');
document.getElementById('clientEmail').classList.remove('auto-populated');
document.getElementById('clientAddress').classList.remove('auto-populated');
}, 1000);
dropdown.style.display = 'none';
});
dropdown.appendChild(div);
});
dropdown.style.display = filteredCustomers.length ? 'block' : 'none';
} else {
dropdown.style.display = 'none';
}
}, 300));
contactInput.addEventListener('focus', () => {
if (contactInput.value) {
contactInput.dispatchEvent(new Event('input'));
}
});
contactInput.addEventListener('blur', () => {
setTimeout(() => {
dropdown.style.display = 'none';
}, 200);
});
}
function openItemDialog() {
activeDialog = 'itemDialog';
const dialogOverlay = document.getElementById('dialogOverlay');
dialogOverlay.style.display = 'flex';
searchItems();
}
function searchItems() {
const searchInput = document.getElementById('searchInput').value.toLowerCase();
filteredInventory = inventory.filter(item =>
item.code.toLowerCase().includes(searchInput) ||
item.name.toLowerCase().includes(searchInput) ||
item.description.toLowerCase().includes(searchInput)
);
renderItemsList();
}
function renderItemsList() {
const itemsList = document.getElementById('itemsList2');
itemsList.innerHTML = '';
filteredInventory.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.code}</td>
<td>${item.name}</td>
<td>${item.description}</td>
<td>₹${item.price.toFixed(2)}</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);
updateItemTotal(row);
updateItemTotals();
document.getElementById('dialogOverlay').style.display = 'none';
});
itemsList.appendChild(tr);
});
}
function closeDialog(event, overlayId) {
if (event && event.target.id === overlayId) {
document.getElementById(overlayId).style.display = 'none';
activeDialog = null;
} else if (!event) {
document.getElementById(overlayId).style.display = 'none';
activeDialog = null;
}
}
function applyCustomSize(dialogId, widthInputId, heightInputId) {
const dialog = document.getElementById(dialogId);
const widthInput = document.getElementById(widthInputId);
const heightInput = document.getElementById(heightInputId);
let width = parseInt(widthInput.value) || 600;
let height = parseInt(heightInput.value) || 400;
width = Math.max(300, Math.min(1200, width));
height = Math.max(200, Math.min(800, height));
dialog.style.width = `${width}px`;
dialog.style.height = `${height}px`;
widthInput.value = width;
heightInput.value = height;
}
function quickResize(width, height, dialogId, widthInputId, heightInputId) {
const dialog = document.getElementById(dialogId);
dialog.style.width = `${width}px`;
dialog.style.height = `${height}px`;
document.getElementById(widthInputId).value = width;
document.getElementById(heightInputId).value = height;
}
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;
if (!code || !description || !hsn || price <= 0 || quantity < 0) {
showStatus('Please fill in all stock details correctly.', 'error');
return;
}
const existingItem = inventory.find(item => item.code === code);
if (existingItem) {
existingItem.hsn = hsn;
existingItem.description = description;
existingItem.price = price;
existingItem.unit = unit;
existingItem.quantity = quantity;
showStatus('Stock item updated successfully!', 'success');
} else {
inventory.push({ code, hsn, description, price, unit, quantity });
showStatus('Stock item added successfully!', 'success');
}
document.getElementById('stockCode').value = '';
document.getElementById('stockHSN').value = '';
document.getElementById('stockDescription').value = '';
document.getElementById('stockPrice').value = '';
document.getElementById('stockQuantity').value = '';
loadStockTable();
}
function loadStockTable() {
const stockTableBody = document.getElementById('stockTableBody');
stockTableBody.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 class="${item.quantity <= 5 ? '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>
`;
stockTableBody.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;
inventory.splice(index, 1);
loadStockTable();
}
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 name.', 'error');
return;
}
const existingCustomer = customerDatabase.find(c => c.contact === contact);
if (existingCustomer) {
existingCustomer.name = name;
existingCustomer.email = email;
existingCustomer.address = address;
showStatus('Customer updated successfully!', 'success');
} else {
customerDatabase.push({ contact, name, email, address });
showStatus('Customer added successfully!', 'success');
}
document.getElementById('newCustomerContact').value = '';
document.getElementById('newCustomerName').value = '';
document.getElementById('newCustomerEmail').value = '';
document.getElementById('newCustomerAddress').value = '';
loadCustomerTable();
}
function loadCustomerTable() {
const customerTableBody = document.getElementById('customerTableBody');
customerTableBody.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>
`;
customerTableBody.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 name.', 'error');
return;
}
customerDatabase[editingCustomerIndex] = { contact, name, email, address };
document.getElementById('editCustomerDialogOverlay').style.display = 'none';
loadCustomerTable();
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 showStatus(message, type) {
const statusMessage = document.getElementById('statusMessage') || document.createElement('div');
statusMessage.className = `status-message status-${type}`;
statusMessage.textContent = message;
statusMessage.style.display = 'block';
if (!statusMessage.parentElement) {
const dialog = document.querySelector('.dialog');
dialog.insertBefore(statusMessage, dialog.querySelector('.items-container'));
}
setTimeout(() => {
statusMessage.style.display = 'none';
}, 3000);
}
function backupData() {
const data = {
inventory: inventory,
customerDatabase: customerDatabase,
invoices: invoices
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `invoice_backup_${new Date().toISOString().slice(0,10)}.json`;
a.click();
URL.revokeObjectURL(url);
showBackupStatus('Backup created successfully!', 'success');
}
function restoreData() {
const fileInput = document.getElementById('restoreFileInput');
const file = fileInput.files[0];
if (!file) {
showBackupStatus('Please select a backup file.', 'error');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (data.inventory && data.customerDatabase && data.invoices) {
inventory.length = 0;
inventory.push(...data.inventory);
customerDatabase.length = 0;
customerDatabase.push(...data.customerDatabase);
invoices.length = 0;
invoices.push(...data.invoices);
localStorage.setItem('invoices', JSON.stringify(invoices));
loadStockTable();
loadCustomerTable();
updateDashboard();
showBackupStatus('Data restored successfully!', 'success');
} else {
showBackupStatus('Invalid backup file format.', 'error');
}
} catch (error) {
showBackupStatus('Error restoring data: ' + error.message, 'error');
}
};
reader.readAsText(file);
}
function showBackupStatus(message, type) {
const statusMessage = document.getElementById('backupStatusMessage');
statusMessage.className = `status-message status-${type}`;
statusMessage.textContent = message;
statusMessage.style.display = 'block';
setTimeout(() => {
statusMessage.style.display = 'none';
}, 3000);
}
function generateWhatsAppMessage() {
const whatsappNumber = document.getElementById('whatsappNumber').value;
const descLength = parseInt(document.getElementById('whatsappDescLength').value) || 20;
if (!whatsappNumber) {
showStatus('Please enter a WhatsApp number.', 'error');
return;
}
let message = `๐งพ *Invoice Details*\n`;
message += `Invoice #: ${document.getElementById('invoiceNumber').value}\n`;
message += `Date: ${formatDate(document.getElementById('invoiceDate').value)}\n`;
message += `Customer: ${document.getElementById('clientName').value}\n\n`;
message += `*Items:*\n`;
const rows = document.querySelectorAll('.item-row');
rows.forEach(row => {
const code = row.querySelector('.item-code').value;
let desc = row.querySelector('.item-desc').value;
desc = desc.length > descLength ? desc.substring(0, descLength) + '...' : desc;
const qty = row.querySelector('.item-qty').value;
const rate = parseFloat(row.querySelector('.item-rate').value).toFixed(2);
const disc = parseFloat(row.querySelector('.item-disc').value).toFixed(2);
const total = parseFloat(row.querySelector('.item-total').value).toFixed(2);
message += `- ${code}: ${desc}\n Qty: ${qty}, Rate: ₹${rate}, Disc: ${disc}%, Total: ₹${total}\n`;
});
const subtotal = parseFloat(document.querySelector('#previewItems tr:nth-last-child(3) td:last-child').textContent.replace('₹', '')) || 0;
const tenderedCash = parseFloat(document.getElementById('tenderedCash').value) || 0;
const changeDue = parseFloat(document.getElementById('changeDue').value) || 0;
message += `\n*Subtotal:* ₹${subtotal.toFixed(2)}\n`;
message += `*Tendered Cash:* ₹${tenderedCash.toFixed(2)}\n`;
message += `*Change Due:* ₹${changeDue.toFixed(2)}\n`;
message += `*Grand Total:* ₹${subtotal.toFixed(2)}\n`;
message += `Note: All items are tax-free.\n`;
const preview = document.getElementById('whatsappPreview');
preview.textContent = message;
preview.style.display = 'block';
}
function sendWhatsApp() {
const whatsappNumber = document.getElementById('whatsappNumber').value.replace(/[^0-9+]/g, '');
if (!whatsappNumber) {
showStatus('Please enter a valid WhatsApp number.', 'error');
return;
}
generateWhatsAppMessage();
const message = encodeURIComponent(document.getElementById('whatsappPreview').textContent);
const url = `https://wa.me/${whatsappNumber}?text=${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 whatsappNumber = document.getElementById('whatsappNumber').value.replace(/[^0-9+]/g, '');
if (!whatsappNumber) {
showStatus('Please enter a valid WhatsApp number.', 'error');
return;
}
const invoiceBox = document.querySelector('.invoice-box');
html2canvas(invoiceBox, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = imgData;
link.download = `invoice_${document.getElementById('invoiceNumber').value}.png`;
link.click();
showStatus('Download the image and share it manually via WhatsApp.', 'success');
});
}
function printInvoice() {
window.print();
}
function toggleCustomDateRange() {
const dateRangeFilter = document.getElementById('dateRangeFilter').value;
const customDateRange = document.getElementById('customDateRange');
customDateRange.classList.toggle('hidden', dateRangeFilter !== 'custom');
}
function updateDashboard() {
const dateRangeFilter = document.getElementById('dateRangeFilter').value;
let startDate, endDate = new Date();
if (dateRangeFilter === 'custom') {
startDate = new Date(document.getElementById('fromDate').value);
endDate = new Date(document.getElementById('toDate').value);
if (!startDate || !endDate || startDate > endDate) {
showStatus('Please select a valid date range.', 'error');
return;
}
} else {
const days = parseInt(dateRangeFilter);
startDate = new Date();
startDate.setDate(endDate.getDate() - days);
}
const filteredInvoices = invoices.filter(invoice => {
const invoiceDate = new Date(invoice.timestamp);
return invoiceDate >= startDate && invoiceDate <= endDate;
});
let totalRevenue = 0;
let totalProducts = 0;
const productCounts = {};
const uniqueCustomers = new Set();
filteredInvoices.forEach(invoice => {
totalRevenue += invoice.grandTotal;
invoice.items.forEach(item => {
totalProducts += item.quantity;
productCounts[item.code] = (productCounts[item.code] || 0) + item.quantity;
});
uniqueCustomers.add(invoice.clientContact);
});
document.getElementById('totalRevenue').textContent = `₹${totalRevenue.toFixed(2)}`;
document.getElementById('totalOrders').textContent = filteredInvoices.length;
document.getElementById('totalCustomers').textContent = uniqueCustomers.size;
document.getElementById('totalProducts').textContent = totalProducts;
const topProductsList = document.getElementById('topProductsList');
topProductsList.innerHTML = '';
const sortedProducts = Object.entries(productCounts)
.map(([code, qty]) => ({ code, qty, ...inventory.find(item => item.code === code) }))
.sort((a, b) => b.qty - a.qty)
.slice(0, 5);
sortedProducts.forEach(product => {
const div = document.createElement('div');
div.innerHTML = `<span>${product.description}</span><span>${product.qty} units</span>`;
div.addEventListener('click', () => {
document.getElementById('stockCode').value = product.code;
document.getElementById('stockHSN').value = product.hsn;
document.getElementById('stockDescription').value = product.description;
document.getElementById('stockPrice').value = product.price;
document.getElementById('stockQuantity').value = product.quantity;
document.querySelector('.nav-btn[data-section="stock"]').click();
});
topProductsList.appendChild(div);
});
const recentOrdersList = document.getElementById('recentOrdersList');
recentOrdersList.innerHTML = '';
filteredInvoices.slice(-5).reverse().forEach(invoice => {
const div = document.createElement('div');
div.innerHTML = `<span>${invoice.invoiceNumber} - ${invoice.clientName}</span><span>₹${invoice.grandTotal.toFixed(2)}</span>`;
div.addEventListener('click', () => {
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('tenderedCash').value = invoice.tenderedCash.toFixed(2);
document.getElementById('changeDue').value = invoice.changeDue.toFixed(2);
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 HSN/SAC 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="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();
document.querySelector('.nav-btn[data-section="invoice"]').click();
});
recentOrdersList.appendChild(div);
});
}
function clearInvoices() {
if (confirm('Are you sure you want to clear all invoices? This action cannot be undone.')) {
invoices.length = 0;
localStorage.setItem('invoices', JSON.stringify(invoices));
updateDashboard();
showStatus('All invoices cleared successfully!', 'success');
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('invoiceNumber').value = generateInvoiceNumber();
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];
document.querySelectorAll('.item-row').forEach(row => {
attachInputListeners(row);
setupItemAutocomplete(row);
});
setupCustomerAutocomplete();
setupLogoUpload();
loadStockTable();
loadCustomerTable();
updateDashboard();
// Navigation
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(section => section.classList.remove('active'));
document.getElementById(btn.dataset.section).classList.add('active');
});
});
// Tendered Cash Input Listener
document.getElementById('tenderedCash').addEventListener('input', () => {
const tenderedCash = parseFloat(document.getElementById('tenderedCash').value) || 0;
if (tenderedCash < 0) {
document.getElementById('tenderedCash').value = '';
showStatus('Tendered cash cannot be negative.', 'error');
return;
}
updateItemTotals();
});
// Logo Size Inputs
document.getElementById('logoWidth').addEventListener('input', applyLogoSize);
document.getElementById('logoHeight').addEventListener('input', applyLogoSize);
document.getElementById('lockAspectRatio').addEventListener('change', applyLogoSize);
});
</script>
</body>
</html>
No comments:
Post a Comment