/** * 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; /** * Auto-set tax exempt based on customer's taxable flag */ function applyCustomerTaxStatus(customerId) { const allCust = window.getCustomers ? window.getCustomers() : (window.customers || []); const customer = allCust.find(c => c.id === parseInt(customerId)); if (customer) { const cb = document.getElementById('quote-tax-exempt'); if (cb) { cb.checked = (customer.taxable === false); updateQuoteTotals(); } } } 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); // Watch for customer selection → auto-set tax exempt (only for new quotes) const customerHidden = document.getElementById('quote-customer'); if (customerHidden) { const observer = new MutationObserver(() => { if (!currentQuoteId && customerHidden.value) { applyCustomerTaxStatus(customerHidden.value); } }); observer.observe(customerHidden, { attributes: true, attributeFilter: ['value'] }); } } // Expose for onclick handlers window.openQuoteModal = openQuoteModal; window.closeQuoteModal = closeQuoteModal; window.addQuoteItem = addQuoteItem;