Automated Action 0b5aa51985 Add web frontend for invoice generation service
- 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
2025-05-18 21:43:44 +00:00

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);
}