invoicer / index.html
Batfly's picture
Create a Professional Invoice Generator Add-On for Powerhouse Batteries Description / Requirements: Build a standalone invoice generator script for an existing PHP website called Powerhouse Batteries. This script will be run separately from the main site via admin.php but must seamlessly match the website’s modern, professional branding. Functional Requirements: Technology Stack: Backend: PHP 8+ (procedural or OOP with PDO for MySQL) Frontend: HTML5, CSS3 (match Powerhouse Batteries branding), JavaScript (vanilla or minimal libraries) Invoice Management: Create, edit, and delete invoices. Automatically generate unique invoice numbers (e.g., PHB-2025-0001). Store invoices in a MySQL table (invoices) with fields: id, invoice_number, customer_name, customer_email, customer_address, items (JSON), subtotal, vat, total, date_created, status (Paid/Unpaid). Currency & Calculations: Currency: South African Rand (ZAR) with proper formatting (R 1,234.56). Apply 15% VAT calculation automatically (editable). Invoice Builder Interface: Add multiple line items dynamically (description, quantity, price). Show live calculations for subtotal, VAT, and total. Responsive design with print and download (PDF) options. PDF Export & Email: Generate a professional PDF invoice using libraries like Dompdf or TCPDF. Include Powerhouse Batteries logo, company details, and branding colors. Ability to email invoice directly to the customer with PHPMailer. Admin Dashboard Integration: Accessible from admin.php via a new tab or button (e.g., “Manage Invoices”). Display list of all invoices with filters (by date, customer, status). View, edit, resend, or delete invoices. UI/UX Requirements: Clean, modern layout (inspired by SaaS billing dashboards). Use company colors and logo from the existing site. Mobile-friendly and professional look. Security: Protect with existing admin login session. Validate and sanitize all form inputs. CSRF token for invoice creation forms. Extra Features (Optional): Add a “Mark as Paid” button to update invoice status. Option to duplicate previous invoices. Include search and pagination for invoice list. Deliverables: Full working PHP script (separate from main site but accessible from admin.php). SQL file to create required tables. CSS/JS files matching the site’s branding. Fully functional PDF generation and email sending features. Clear setup instructions (e.g., database config, file structure). - Initial Deployment
dfabeb6 verified
Raw
History Blame Contribute Delete
30.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Powerhouse Batteries - Invoice Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
.powerhouse-blue {
background-color: #1e3a8a;
}
.powerhouse-blue-light {
background-color: #3b82f6;
}
.text-powerhouse-blue {
color: #1e3a8a;
}
.border-powerhouse-blue {
border-color: #1e3a8a;
}
.invoice-item:last-child {
border-bottom: none;
}
/* Print-specific styles */
@media print {
body * {
visibility: hidden;
}
#invoice-preview, #invoice-preview * {
visibility: visible;
}
#invoice-preview {
position: absolute;
left: 0;
top: 0;
width: 100%;
margin: 0;
padding: 0;
}
.no-print {
display: none !important;
}
}
</style>
</head>
<body class="bg-gray-100">
<div class="min-h-screen">
<!-- Header -->
<header class="powerhouse-blue text-white shadow-md">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-2">
<img src="https://via.placeholder.com/50" alt="Powerhouse Batteries Logo" class="h-10">
<h1 class="text-xl font-bold">Powerhouse Batteries</h1>
</div>
<nav class="hidden md:flex space-x-6">
<a href="#" class="hover:text-blue-200">Dashboard</a>
<a href="#" class="hover:text-blue-200">Customers</a>
<a href="#" class="font-semibold border-b-2 border-white">Invoices</a>
<a href="#" class="hover:text-blue-200">Products</a>
</nav>
<div class="flex items-center space-x-4">
<button class="powerhouse-blue-light px-4 py-2 rounded-md hover:bg-blue-700 transition">
<i class="fas fa-user mr-2"></i>Admin
</button>
<button class="md:hidden">
<i class="fas fa-bars text-xl"></i>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<div class="flex flex-col lg:flex-row gap-8">
<!-- Invoice Form -->
<div class="w-full lg:w-1/2 bg-white rounded-lg shadow-md p-6 no-print">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-powerhouse-blue">Create New Invoice</h2>
<span id="invoice-number" class="px-3 py-1 bg-gray-100 rounded-md font-mono">PHB-2025-0001</span>
</div>
<!-- Customer Details -->
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3 text-gray-700">Customer Details</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="customer-name" class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input type="text" id="customer-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label for="customer-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="customer-email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="md:col-span-2">
<label for="customer-address" class="block text-sm font-medium text-gray-700 mb-1">Address</label>
<textarea id="customer-address" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
</div>
</div>
</div>
<!-- Invoice Items -->
<div class="mb-6">
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-semibold text-gray-700">Invoice Items</h3>
<button id="add-item" class="px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition flex items-center">
<i class="fas fa-plus mr-1"></i> Add Item
</button>
</div>
<div id="items-container">
<!-- Items will be added here dynamically -->
<div class="item-row grid grid-cols-12 gap-2 mb-3">
<div class="col-span-5">
<input type="text" placeholder="Description" class="item-desc w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<input type="number" min="1" value="1" class="item-qty w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-3">
<input type="number" step="0.01" min="0" placeholder="0.00" class="item-price w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2 flex items-center">
<span class="item-total text-gray-700">R 0.00</span>
<button class="remove-item ml-2 text-red-500 hover:text-red-700">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Invoice Options -->
<div class="mb-6">
<h3 class="text-lg font-semibold mb-3 text-gray-700">Invoice Options</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="invoice-date" class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="invoice-date" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label for="vat-rate" class="block text-sm font-medium text-gray-700 mb-1">VAT Rate (%)</label>
<input type="number" id="vat-rate" value="15" min="0" max="100" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
</div>
<!-- Invoice Summary -->
<div class="bg-gray-50 p-4 rounded-md mb-6">
<div class="flex justify-between py-2 border-b border-gray-200">
<span class="font-medium">Subtotal:</span>
<span id="subtotal" class="font-mono">R 0.00</span>
</div>
<div class="flex justify-between py-2 border-b border-gray-200">
<span class="font-medium">VAT (<span id="vat-rate-display">15</span>%):</span>
<span id="vat-amount" class="font-mono">R 0.00</span>
</div>
<div class="flex justify-between py-2">
<span class="font-bold">Total:</span>
<span id="total" class="font-bold font-mono">R 0.00</span>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-wrap gap-3">
<button id="save-invoice" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition flex items-center">
<i class="fas fa-save mr-2"></i> Save Invoice
</button>
<button id="preview-invoice" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition flex items-center">
<i class="fas fa-eye mr-2"></i> Preview
</button>
<button id="clear-form" class="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition flex items-center">
<i class="fas fa-trash-alt mr-2"></i> Clear
</button>
</div>
</div>
<!-- Invoice Preview -->
<div id="invoice-preview" class="w-full lg:w-1/2 bg-white rounded-lg shadow-md p-6 hidden print:block">
<div class="flex justify-between items-start mb-8">
<div>
<img src="https://via.placeholder.com/150x50" alt="Powerhouse Batteries Logo" class="h-12 mb-2">
<p class="text-sm text-gray-600">123 Battery Street</p>
<p class="text-sm text-gray-600">Johannesburg, 2000</p>
<p class="text-sm text-gray-600">South Africa</p>
</div>
<div class="text-right">
<h1 class="text-2xl font-bold text-powerhouse-blue mb-1">INVOICE</h1>
<p class="text-sm text-gray-600">#<span id="preview-invoice-number">PHB-2025-0001</span></p>
<p class="text-sm text-gray-600">Date: <span id="preview-invoice-date">2023-11-15</span></p>
<p class="text-sm text-gray-600">Status: <span class="px-2 py-1 bg-yellow-100 text-yellow-800 rounded-full text-xs">Unpaid</span></p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div>
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">Bill To</h3>
<p id="preview-customer-name" class="font-medium">John Doe</p>
<p id="preview-customer-email" class="text-sm text-gray-600">john@example.com</p>
<p id="preview-customer-address" class="text-sm text-gray-600 mt-1">123 Customer Street<br>Pretoria, 0001</p>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-500 uppercase mb-2">Payment Details</h3>
<p class="text-sm text-gray-600">Bank: Standard Bank</p>
<p class="text-sm text-gray-600">Account: 1234567890</p>
<p class="text-sm text-gray-600">Branch: 051001</p>
<p class="text-sm text-gray-600 mt-2">VAT No: 4560123789</p>
</div>
</div>
<div class="mb-8">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gray-100">
<th class="py-3 px-4 text-left font-semibold text-gray-700 border-b border-gray-200">Description</th>
<th class="py-3 px-4 text-right font-semibold text-gray-700 border-b border-gray-200">Qty</th>
<th class="py-3 px-4 text-right font-semibold text-gray-700 border-b border-gray-200">Price</th>
<th class="py-3 px-4 text-right font-semibold text-gray-700 border-b border-gray-200">Amount</th>
</tr>
</thead>
<tbody id="preview-items">
<!-- Items will be added here dynamically -->
<tr>
<td class="py-3 px-4 border-b border-gray-200">Sample Item</td>
<td class="py-3 px-4 text-right border-b border-gray-200">1</td>
<td class="py-3 px-4 text-right border-b border-gray-200">R 100.00</td>
<td class="py-3 px-4 text-right border-b border-gray-200">R 100.00</td>
</tr>
</tbody>
</table>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="text-sm text-gray-600">
<p>Thank you for your business!</p>
<p class="mt-2">Terms: Payment due within 30 days</p>
</div>
<div>
<div class="flex justify-between py-2">
<span class="font-medium">Subtotal:</span>
<span id="preview-subtotal" class="font-mono">R 100.00</span>
</div>
<div class="flex justify-between py-2">
<span class="font-medium">VAT (<span id="preview-vat-rate">15</span>%):</span>
<span id="preview-vat-amount" class="font-mono">R 15.00</span>
</div>
<div class="flex justify-between py-2 border-t border-gray-200 mt-2">
<span class="font-bold">Total:</span>
<span id="preview-total" class="font-bold font-mono">R 115.00</span>
</div>
</div>
</div>
<div class="mt-8 pt-8 border-t border-gray-200 text-center text-sm text-gray-500 no-print">
<p>Powerhouse Batteries - Quality Power Solutions Since 2010</p>
<div class="flex justify-center space-x-4 mt-4">
<button id="print-invoice" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition flex items-center">
<i class="fas fa-print mr-2"></i> Print Invoice
</button>
<button id="download-pdf" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition flex items-center">
<i class="fas fa-file-pdf mr-2"></i> Download PDF
</button>
<button id="send-email" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition flex items-center">
<i class="fas fa-paper-plane mr-2"></i> Send Email
</button>
</div>
</div>
</div>
</div>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Set today's date as default
const today = new Date().toISOString().split('T')[0];
document.getElementById('invoice-date').value = today;
document.getElementById('preview-invoice-date').textContent = formatDate(today);
// Generate a sample invoice number (in a real app, this would come from the server)
const invoiceNumber = `PHB-${new Date().getFullYear()}-${Math.floor(1000 + Math.random() * 9000)}`;
document.getElementById('invoice-number').textContent = invoiceNumber;
document.getElementById('preview-invoice-number').textContent = invoiceNumber;
// Add event listeners
document.getElementById('add-item').addEventListener('click', addNewItem);
document.getElementById('preview-invoice').addEventListener('click', previewInvoice);
document.getElementById('clear-form').addEventListener('click', clearForm);
document.getElementById('print-invoice').addEventListener('click', printInvoice);
document.getElementById('download-pdf').addEventListener('click', downloadPDF);
document.getElementById('send-email').addEventListener('click', sendEmail);
document.getElementById('save-invoice').addEventListener('click', saveInvoice);
// Initialize with one item
addNewItem();
// Calculate totals when any input changes
document.addEventListener('input', function(e) {
if (e.target.classList.contains('item-desc') ||
e.target.classList.contains('item-qty') ||
e.target.classList.contains('item-price') ||
e.target.id === 'vat-rate') {
calculateTotals();
}
});
});
function formatDate(dateString) {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
return new Date(dateString).toLocaleDateString('en-ZA', options);
}
function formatCurrency(amount) {
return new Intl.NumberFormat('en-ZA', {
style: 'currency',
currency: 'ZAR',
minimumFractionDigits: 2
}).format(amount);
}
function addNewItem() {
const itemsContainer = document.getElementById('items-container');
const newItem = document.createElement('div');
newItem.className = 'item-row grid grid-cols-12 gap-2 mb-3';
newItem.innerHTML = `
<div class="col-span-5">
<input type="text" placeholder="Description" class="item-desc w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2">
<input type="number" min="1" value="1" class="item-qty w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-3">
<input type="number" step="0.01" min="0" placeholder="0.00" class="item-price w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="col-span-2 flex items-center">
<span class="item-total text-gray-700">R 0.00</span>
<button class="remove-item ml-2 text-red-500 hover:text-red-700">
<i class="fas fa-times"></i>
</button>
</div>
`;
itemsContainer.appendChild(newItem);
// Add event listener to remove button
newItem.querySelector('.remove-item').addEventListener('click', function() {
itemsContainer.removeChild(newItem);
calculateTotals();
// Ensure at least one item remains
if (document.querySelectorAll('.item-row').length === 0) {
addNewItem();
}
});
}
function calculateTotals() {
let subtotal = 0;
const itemRows = document.querySelectorAll('.item-row');
itemRows.forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const total = qty * price;
row.querySelector('.item-total').textContent = formatCurrency(total);
subtotal += total;
});
const vatRate = parseFloat(document.getElementById('vat-rate').value) || 0;
const vatAmount = subtotal * (vatRate / 100);
const total = subtotal + vatAmount;
document.getElementById('subtotal').textContent = formatCurrency(subtotal);
document.getElementById('vat-amount').textContent = formatCurrency(vatAmount);
document.getElementById('total').textContent = formatCurrency(total);
document.getElementById('vat-rate-display').textContent = vatRate;
}
function previewInvoice() {
// Update customer details
document.getElementById('preview-customer-name').textContent = document.getElementById('customer-name').value || 'Customer Name';
document.getElementById('preview-customer-email').textContent = document.getElementById('customer-email').value || 'customer@example.com';
document.getElementById('preview-customer-address').textContent = document.getElementById('customer-address').value || 'Customer Address';
// Update invoice details
document.getElementById('preview-invoice-number').textContent = document.getElementById('invoice-number').textContent;
document.getElementById('preview-invoice-date').textContent = formatDate(document.getElementById('invoice-date').value);
// Update items
const previewItems = document.getElementById('preview-items');
previewItems.innerHTML = '';
const itemRows = document.querySelectorAll('.item-row');
itemRows.forEach(row => {
const desc = row.querySelector('.item-desc').value || 'Item Description';
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const total = qty * price;
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="py-3 px-4 border-b border-gray-200">${desc}</td>
<td class="py-3 px-4 text-right border-b border-gray-200">${qty}</td>
<td class="py-3 px-4 text-right border-b border-gray-200">${formatCurrency(price)}</td>
<td class="py-3 px-4 text-right border-b border-gray-200">${formatCurrency(total)}</td>
`;
previewItems.appendChild(tr);
});
// Update totals
const subtotal = parseFloat(document.getElementById('subtotal').textContent.replace(/[^0-9.-]+/g,"")) || 0;
const vatRate = parseFloat(document.getElementById('vat-rate').value) || 0;
const vatAmount = parseFloat(document.getElementById('vat-amount').textContent.replace(/[^0-9.-]+/g,"")) || 0;
const total = parseFloat(document.getElementById('total').textContent.replace(/[^0-9.-]+/g,"")) || 0;
document.getElementById('preview-subtotal').textContent = formatCurrency(subtotal);
document.getElementById('preview-vat-rate').textContent = vatRate;
document.getElementById('preview-vat-amount').textContent = formatCurrency(vatAmount);
document.getElementById('preview-total').textContent = formatCurrency(total);
// Show preview
document.getElementById('invoice-preview').classList.remove('hidden');
}
function clearForm() {
if (confirm('Are you sure you want to clear the form? All data will be lost.')) {
// Clear customer details
document.getElementById('customer-name').value = '';
document.getElementById('customer-email').value = '';
document.getElementById('customer-address').value = '';
// Clear items (keep one empty item)
const itemsContainer = document.getElementById('items-container');
itemsContainer.innerHTML = '';
addNewItem();
// Reset date to today
const today = new Date().toISOString().split('T')[0];
document.getElementById('invoice-date').value = today;
// Reset VAT rate
document.getElementById('vat-rate').value = '15';
// Hide preview
document.getElementById('invoice-preview').classList.add('hidden');
// Generate new invoice number
const invoiceNumber = `PHB-${new Date().getFullYear()}-${Math.floor(1000 + Math.random() * 9000)}`;
document.getElementById('invoice-number').textContent = invoiceNumber;
// Recalculate totals
calculateTotals();
}
}
function printInvoice() {
window.print();
}
function downloadPDF() {
alert('In a real implementation, this would generate and download a PDF invoice.');
// In a real app, this would call a backend endpoint to generate PDF using Dompdf/TCPDF
}
function sendEmail() {
alert('In a real implementation, this would send the invoice to the customer\'s email using PHPMailer.');
// In a real app, this would call a backend endpoint to send email
}
function saveInvoice() {
// Validate form
const customerName = document.getElementById('customer-name').value.trim();
if (!customerName) {
alert('Please enter customer name');
return;
}
// Collect invoice data
const invoiceData = {
invoiceNumber: document.getElementById('invoice-number').textContent,
customerName: customerName,
customerEmail: document.getElementById('customer-email').value.trim(),
customerAddress: document.getElementById('customer-address').value.trim(),
date: document.getElementById('invoice-date').value,
vatRate: parseFloat(document.getElementById('vat-rate').value) || 0,
items: [],
subtotal: parseFloat(document.getElementById('subtotal').textContent.replace(/[^0-9.-]+/g,"")) || 0,
vatAmount: parseFloat(document.getElementById('vat-amount').textContent.replace(/[^0-9.-]+/g,"")) || 0,
total: parseFloat(document.getElementById('total').textContent.replace(/[^0-9.-]+/g,"")) || 0,
status: 'Unpaid'
};
// Collect items
document.querySelectorAll('.item-row').forEach(row => {
invoiceData.items.push({
description: row.querySelector('.item-desc').value.trim(),
quantity: parseFloat(row.querySelector('.item-qty').value) || 0,
price: parseFloat(row.querySelector('.item-price').value) || 0
});
});
console.log('Invoice data to be saved:', invoiceData);
alert('In a real implementation, this would save the invoice to the database via PHP backend.');
// In a real app, this would send data to backend via fetch/XMLHttpRequest
/*
fetch('save_invoice.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(invoiceData)
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
alert('Invoice saved successfully!');
})
.catch((error) => {
console.error('Error:', error);
alert('Error saving invoice');
});
*/
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Batfly/invoicer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>