
- Add Jinja2 templates and static files for web UI - Create frontend routes for invoice management - Implement home page with recent invoices list - Add invoice creation form with dynamic items - Create invoice search functionality - Implement invoice details view with status update - Add JavaScript for form validation and dynamic UI - Update main.py to serve static files - Update documentation
150 lines
5.2 KiB
JavaScript
150 lines
5.2 KiB
JavaScript
// Main JavaScript file for the Invoice Generation Service
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Invoice items dynamic form functionality
|
|
const addItemButton = document.getElementById('add-item-btn');
|
|
if (addItemButton) {
|
|
addItemButton.addEventListener('click', addInvoiceItem);
|
|
}
|
|
|
|
// Add event listeners to any initial remove buttons
|
|
const removeButtons = document.querySelectorAll('.remove-item-btn');
|
|
removeButtons.forEach(button => {
|
|
button.addEventListener('click', removeInvoiceItem);
|
|
});
|
|
|
|
// Calculate amounts when quantity or unit price changes
|
|
const invoiceItems = document.querySelector('.invoice-items');
|
|
if (invoiceItems) {
|
|
invoiceItems.addEventListener('input', function(e) {
|
|
if (e.target.name && (e.target.name.includes('quantity') || e.target.name.includes('unit_price'))) {
|
|
updateItemAmount(e.target);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Form validation
|
|
const invoiceForm = document.getElementById('invoice-form');
|
|
if (invoiceForm) {
|
|
invoiceForm.addEventListener('submit', validateInvoiceForm);
|
|
}
|
|
});
|
|
|
|
// Function to add a new invoice item row
|
|
function addInvoiceItem(e) {
|
|
e.preventDefault();
|
|
|
|
const invoiceItems = document.querySelector('.invoice-items');
|
|
const itemTemplate = document.querySelector('.invoice-item').cloneNode(true);
|
|
|
|
// Clear values in the cloned template
|
|
const inputs = itemTemplate.querySelectorAll('input');
|
|
inputs.forEach(input => {
|
|
input.value = '';
|
|
// Update the input name with a new index
|
|
const currentIndex = parseInt(input.name.match(/\d+/)[0]);
|
|
input.name = input.name.replace(`[${currentIndex}]`, `[${currentIndex + 1}]`);
|
|
});
|
|
|
|
// Add event listener to the remove button
|
|
const removeButton = itemTemplate.querySelector('.remove-item-btn');
|
|
if (removeButton) {
|
|
removeButton.addEventListener('click', removeInvoiceItem);
|
|
}
|
|
|
|
invoiceItems.appendChild(itemTemplate);
|
|
}
|
|
|
|
// Function to remove an invoice item row
|
|
function removeInvoiceItem(e) {
|
|
e.preventDefault();
|
|
|
|
const invoiceItems = document.querySelector('.invoice-items');
|
|
|
|
// Don't remove if it's the only item
|
|
if (invoiceItems.children.length > 1) {
|
|
e.target.closest('.invoice-item').remove();
|
|
} else {
|
|
// Clear values if it's the last item
|
|
const inputs = e.target.closest('.invoice-item').querySelectorAll('input');
|
|
inputs.forEach(input => {
|
|
input.value = '';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Function to update the amount field based on quantity and unit price
|
|
function updateItemAmount(input) {
|
|
const itemRow = input.closest('.invoice-item');
|
|
const quantityInput = itemRow.querySelector('input[name*="quantity"]');
|
|
const unitPriceInput = itemRow.querySelector('input[name*="unit_price"]');
|
|
const amountDisplay = itemRow.querySelector('.amount-display');
|
|
|
|
if (quantityInput && unitPriceInput && amountDisplay) {
|
|
const quantity = parseFloat(quantityInput.value) || 0;
|
|
const unitPrice = parseFloat(unitPriceInput.value) || 0;
|
|
const amount = (quantity * unitPrice).toFixed(2);
|
|
|
|
amountDisplay.textContent = amount;
|
|
}
|
|
}
|
|
|
|
// Function to validate the invoice form before submission
|
|
function validateInvoiceForm(e) {
|
|
let valid = true;
|
|
|
|
// Reset any previous error messages
|
|
const errorMessages = document.querySelectorAll('.error-message');
|
|
errorMessages.forEach(message => message.remove());
|
|
|
|
// Validate customer name
|
|
const customerName = document.getElementById('customer_name');
|
|
if (!customerName.value.trim()) {
|
|
showError(customerName, 'Customer name is required');
|
|
valid = false;
|
|
}
|
|
|
|
// Validate due date
|
|
const dueDate = document.getElementById('due_date');
|
|
if (!dueDate.value) {
|
|
showError(dueDate, 'Due date is required');
|
|
valid = false;
|
|
}
|
|
|
|
// Validate invoice items
|
|
const itemRows = document.querySelectorAll('.invoice-item');
|
|
let hasValidItems = false;
|
|
|
|
itemRows.forEach(row => {
|
|
const description = row.querySelector('input[name*="description"]');
|
|
const quantity = row.querySelector('input[name*="quantity"]');
|
|
const unitPrice = row.querySelector('input[name*="unit_price"]');
|
|
|
|
if (description.value.trim() && quantity.value && unitPrice.value) {
|
|
hasValidItems = true;
|
|
}
|
|
});
|
|
|
|
if (!hasValidItems) {
|
|
const invoiceItems = document.querySelector('.invoice-items');
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'error-message';
|
|
errorDiv.textContent = 'At least one complete invoice item is required';
|
|
errorDiv.style.color = 'red';
|
|
invoiceItems.parentNode.insertBefore(errorDiv, invoiceItems.nextSibling);
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
// Helper function to show error messages
|
|
function showError(input, message) {
|
|
const errorDiv = document.createElement('div');
|
|
errorDiv.className = 'error-message';
|
|
errorDiv.textContent = message;
|
|
errorDiv.style.color = 'red';
|
|
input.parentNode.appendChild(errorDiv);
|
|
} |