154 lines
5.2 KiB
JavaScript
154 lines
5.2 KiB
JavaScript
/**
|
|
* quote-modal.js — Quote create/edit modal
|
|
* Uses shared item-editor for accordion items
|
|
*/
|
|
import { addItem, getItems, resetItemCounter } from '../utils/item-editor.js';
|
|
import { setDefaultDate, showSpinner, hideSpinner } from '../utils/helpers.js';
|
|
|
|
let currentQuoteId = null;
|
|
|
|
export function openQuoteModal(quoteId = null) {
|
|
currentQuoteId = quoteId;
|
|
|
|
if (quoteId) {
|
|
loadQuoteForEdit(quoteId);
|
|
} else {
|
|
prepareNewQuote();
|
|
}
|
|
|
|
document.getElementById('quote-modal').classList.add('active');
|
|
}
|
|
|
|
export function closeQuoteModal() {
|
|
document.getElementById('quote-modal').classList.remove('active');
|
|
currentQuoteId = null;
|
|
}
|
|
|
|
async function loadQuoteForEdit(quoteId) {
|
|
document.getElementById('quote-modal-title').textContent = 'Edit Quote';
|
|
|
|
const response = await fetch(`/api/quotes/${quoteId}`);
|
|
const data = await response.json();
|
|
|
|
// Set customer in Alpine component
|
|
const allCust = window.getCustomers ? window.getCustomers() : (window.customers || []);
|
|
const customer = allCust.find(c => c.id === data.quote.customer_id);
|
|
if (customer) {
|
|
const customerInput = document.querySelector('#quote-modal input[placeholder="Search customer..."]');
|
|
if (customerInput) {
|
|
customerInput.value = customer.name;
|
|
customerInput.dispatchEvent(new Event('input'));
|
|
const alpineData = Alpine.$data(customerInput.closest('[x-data]'));
|
|
if (alpineData) {
|
|
alpineData.search = customer.name;
|
|
alpineData.selectedId = customer.id;
|
|
alpineData.selectedName = customer.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
document.getElementById('quote-customer').value = data.quote.customer_id;
|
|
document.getElementById('quote-date').value = data.quote.quote_date.split('T')[0];
|
|
document.getElementById('quote-tax-exempt').checked = data.quote.tax_exempt;
|
|
|
|
// Load items using shared editor
|
|
document.getElementById('quote-items').innerHTML = '';
|
|
resetItemCounter();
|
|
data.items.forEach(item => {
|
|
addItem('quote-items', { item, type: 'quote', onUpdate: updateQuoteTotals });
|
|
});
|
|
|
|
updateQuoteTotals();
|
|
}
|
|
|
|
function prepareNewQuote() {
|
|
document.getElementById('quote-modal-title').textContent = 'New Quote';
|
|
document.getElementById('quote-form').reset();
|
|
document.getElementById('quote-items').innerHTML = '';
|
|
resetItemCounter();
|
|
setDefaultDate();
|
|
|
|
// Add one default item
|
|
addItem('quote-items', { type: 'quote', onUpdate: updateQuoteTotals });
|
|
}
|
|
|
|
export function addQuoteItem(item = null) {
|
|
addItem('quote-items', { item, type: 'quote', onUpdate: updateQuoteTotals });
|
|
}
|
|
|
|
export function updateQuoteTotals() {
|
|
const items = getItems('quote-items');
|
|
const taxExempt = document.getElementById('quote-tax-exempt').checked;
|
|
|
|
let subtotal = 0;
|
|
let hasTbd = false;
|
|
|
|
items.forEach(item => {
|
|
if (item.rate.toUpperCase() === 'TBD' || item.amount.toUpperCase() === 'TBD') {
|
|
hasTbd = true;
|
|
} else {
|
|
const amount = parseFloat(item.amount.replace(/[$,]/g, '')) || 0;
|
|
subtotal += amount;
|
|
}
|
|
});
|
|
|
|
const taxAmount = taxExempt ? 0 : (subtotal * 8.25 / 100);
|
|
const total = subtotal + taxAmount;
|
|
|
|
document.getElementById('quote-subtotal').textContent = `$${subtotal.toFixed(2)}`;
|
|
document.getElementById('quote-tax').textContent = taxExempt ? '$0.00' : `$${taxAmount.toFixed(2)}`;
|
|
document.getElementById('quote-total').textContent = hasTbd ? `$${total.toFixed(2)}*` : `$${total.toFixed(2)}`;
|
|
document.getElementById('quote-tax-row').style.display = taxExempt ? 'none' : 'block';
|
|
}
|
|
|
|
export async function handleQuoteSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const items = getItems('quote-items');
|
|
if (items.length === 0) {
|
|
alert('Please add at least one item');
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
customer_id: parseInt(document.getElementById('quote-customer').value),
|
|
quote_date: document.getElementById('quote-date').value,
|
|
tax_exempt: document.getElementById('quote-tax-exempt').checked,
|
|
items: items
|
|
};
|
|
|
|
try {
|
|
const url = currentQuoteId ? `/api/quotes/${currentQuoteId}` : '/api/quotes';
|
|
const method = currentQuoteId ? 'PUT' : 'POST';
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeQuoteModal();
|
|
if (window.quoteView) window.quoteView.loadQuotes();
|
|
} else {
|
|
alert('Error saving quote');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('Error saving quote');
|
|
}
|
|
}
|
|
|
|
// Wire up form submit and tax-exempt checkbox
|
|
export function initQuoteModal() {
|
|
const form = document.getElementById('quote-form');
|
|
if (form) form.addEventListener('submit', handleQuoteSubmit);
|
|
|
|
const taxExempt = document.getElementById('quote-tax-exempt');
|
|
if (taxExempt) taxExempt.addEventListener('change', updateQuoteTotals);
|
|
}
|
|
|
|
// Expose for onclick handlers
|
|
window.openQuoteModal = openQuoteModal;
|
|
window.closeQuoteModal = closeQuoteModal;
|
|
window.addQuoteItem = addQuoteItem; |