101 lines
3.6 KiB
JavaScript
101 lines
3.6 KiB
JavaScript
/**
|
|
* quote-view.js — Quote list rendering and actions
|
|
* Analog to invoice-view.js
|
|
*/
|
|
import { formatDate } from '../utils/helpers.js';
|
|
|
|
let quotes = [];
|
|
|
|
export async function loadQuotes() {
|
|
try {
|
|
const response = await fetch('/api/quotes');
|
|
quotes = await response.json();
|
|
renderQuotes();
|
|
} catch (error) {
|
|
console.error('Error loading quotes:', error);
|
|
}
|
|
}
|
|
|
|
export function getQuotesData() {
|
|
return quotes;
|
|
}
|
|
|
|
export function renderQuotes() {
|
|
const tbody = document.getElementById('quotes-list');
|
|
if (!tbody) return;
|
|
|
|
tbody.innerHTML = quotes.map(quote => {
|
|
const total = quote.has_tbd
|
|
? `$${parseFloat(quote.total).toFixed(2)}*`
|
|
: `$${parseFloat(quote.total).toFixed(2)}`;
|
|
return `
|
|
<tr>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${quote.quote_number}</td>
|
|
<td class="px-6 py-4 text-sm text-gray-500">${quote.customer_name || 'N/A'}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatDate(quote.quote_date)}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 font-semibold">${total}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
|
|
<button onclick="window.quoteView.viewPDF(${quote.id})" class="text-green-600 hover:text-green-900">PDF</button>
|
|
<button onclick="window.quoteView.convertToInvoice(${quote.id})" class="text-purple-600 hover:text-purple-900">→ Invoice</button>
|
|
<button onclick="window.quoteView.edit(${quote.id})" class="text-blue-600 hover:text-blue-900">Edit</button>
|
|
<button onclick="window.quoteView.remove(${quote.id})" class="text-red-600 hover:text-red-900">Delete</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
|
|
if (quotes.length === 0) {
|
|
tbody.innerHTML = `<tr><td colspan="5" class="px-6 py-8 text-center text-gray-500">No quotes found.</td></tr>`;
|
|
}
|
|
}
|
|
|
|
export function viewPDF(id) {
|
|
window.open(`/api/quotes/${id}/pdf`, '_blank');
|
|
}
|
|
|
|
export async function edit(id) {
|
|
if (typeof window.openQuoteModal === 'function') {
|
|
await window.openQuoteModal(id);
|
|
}
|
|
}
|
|
|
|
export async function remove(id) {
|
|
if (!confirm('Are you sure you want to delete this quote?')) return;
|
|
try {
|
|
const response = await fetch(`/api/quotes/${id}`, { method: 'DELETE' });
|
|
if (response.ok) loadQuotes();
|
|
else alert('Error deleting quote');
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('Error deleting quote');
|
|
}
|
|
}
|
|
|
|
export async function convertToInvoice(quoteId) {
|
|
if (!confirm('Convert this quote to an invoice?')) return;
|
|
try {
|
|
const response = await fetch(`/api/quotes/${quoteId}/convert-to-invoice`, { method: 'POST' });
|
|
if (response.ok) {
|
|
const invoice = await response.json();
|
|
alert(`Invoice ${invoice.invoice_number || '(Draft)'} created successfully!`);
|
|
if (window.invoiceView) window.invoiceView.loadInvoices();
|
|
if (typeof window.showTab === 'function') window.showTab('invoices');
|
|
} else {
|
|
const error = await response.json();
|
|
alert(error.error || 'Error converting quote to invoice');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error:', error);
|
|
alert('Error converting quote to invoice');
|
|
}
|
|
}
|
|
|
|
// Expose for onclick handlers
|
|
window.quoteView = {
|
|
loadQuotes,
|
|
renderQuotes,
|
|
viewPDF,
|
|
edit,
|
|
remove,
|
|
convertToInvoice
|
|
}; |