Quote/Estimate Generator

Quote/Estimate Generator

Quote/Estimate Generator

Add each item or service below to calculate the raw subtotal before taxes and discounts.

Add New Line Item

Itemized List

# Description Qty Unit Price Amount
Subtotal: $0.00

Enter project details and financial adjustments to calculate the final quote amount.

Project & Client Details

Final Summary

Subtotal

$0.00

Tax Amount

$0.00

Discount

$0.00

GRAND TOTAL

$0.00

No items added yet.

`; } lineItems.forEach((item, index) => { const amount = calculateItemAmount(item.qty, item.price); const tr = document.createElement('tr'); tr.dataset.id = item.id; tr.innerHTML = ` ${index + 1} ${escapeHtml(item.desc)} ${item.qty} ${formatUSD(item.price)} ${formatUSD(amount)} `; tableBody.appendChild(tr); }); qegCalculateFinalTotal(); // Recalculate totals after rendering } // === LINE ITEM CRUD === /** Adds a new line item */ window.qegAddItem = () => { if (!descInput || !qtyInput || !priceInput) return; const desc = descInput.value.trim(); const qty = parseInt(qtyInput.value); const price = parseFloat(priceInput.value); if (isNaN(qty) || qty < 1 || isNaN(price) || price < 0.01) { showMessage("Please enter valid Quantity (>0) and Unit Price (>0).", true); return; } lineItems.push({ id: 'li' + Date.now(), desc, qty, price }); document.getElementById('qeg-add-item-form').reset(); qtyInput.value = 1; // Reset quantity to 1 showMessage(null); renderLineItemTable(); }; /** Deletes a line item */ window.qegDeleteItem = (id) => { if (!confirm('Are you sure you want to delete this line item?')) return; lineItems = lineItems.filter(item => item.id !== id); renderLineItemTable(); }; /** Clears all line items */ window.qegClearAllItems = () => { if (lineItems.length === 0) { showMessage("The item list is already empty.", false); return; } if (!confirm('Are you sure you want to clear ALL line items?')) return; lineItems = []; renderLineItemTable(); }; // === TAB AND NAV LOGIC === /** Switches tabs */ window.qegShowTab = (tabId, element) => { if (!container || !tabLinks) return; container.querySelectorAll('.qeg-tab-content').forEach(tab => tab.classList.remove('qeg-active')); container.querySelectorAll('.qeg-tab-link').forEach(link => link.classList.remove('qeg-active')); const tabToShow = container.querySelector('#' + tabId); if (tabToShow) tabToShow.classList.add('qeg-active'); if (element) element.classList.add('qeg-active'); currentTabId = tabId; updateNavButtons(); // Recalculate and update summary outputs when switching to summary tab if (tabId === 'qeg-tab-summary') { qegCalculateFinalTotal(); } }; /** Handles nav buttons */ window.qegNavigateTabs = (isNext) => { if (!container || !tabLinks) return; const targetTabId = isNext ? 'qeg-tab-summary' : 'qeg-tab-line-items'; const targetTabLink = container.querySelector(`.qeg-tab-link[onclick*="'${targetTabId}'"]`); if(targetTabLink) targetTabLink.click(); }; /** Updates nav button states */ function updateNavButtons() { if (!prevBtn || !nextBtn) return; prevBtn.disabled = currentTabId === 'qeg-tab-line-items'; nextBtn.disabled = currentTabId === 'qeg-tab-summary'; } // === PDF EXPORT === window.qegDownloadPDF = () => { if (typeof jsPDF === 'undefined' || !jsPDF.autoTable) { showMessage("PDF library not loaded.", true); return; } if (lineItems.length === 0) { showMessage("Add line items before downloading the quote.", true); return; } // Recalculate one last time qegCalculateFinalTotal(); const projectName = projectNameInput?.value || 'Untitled Project Estimate (USA)'; const quoteNumber = quoteNumberInput?.value || 'N/A'; const clientName = clientNameInput?.value || 'Client Name'; const subtotal = lineItems.reduce((acc, item) => acc + calculateItemAmount(item.qty, item.price), 0); const taxRate = parseFloat(taxRateInput?.value || 0); const discount = parseFloat(discountInput?.value || 0); const taxAmount = subtotal * (taxRate / 100); const finalTotal = subtotal + taxAmount - discount; // PDF Colors (Matching theme) const PRIMARY_COLOR_HEX = '#3b82f6'; const DARK_GRAY_HEX = '#4b5563'; const SECONDARY_COLOR_HEX = '#10b981'; try { const doc = new jsPDF(); const pageMargin = 40; const pageWidth = doc.internal.pageSize.getWidth(); let y = pageMargin; // 1. Header Block (Title, Quote #, Date) doc.setFontSize(22); doc.setFont(undefined, 'bold'); doc.setTextColor(PRIMARY_COLOR_HEX); doc.text("QUOTE / ESTIMATE", pageWidth - pageMargin, y, { align: 'right' }); y += 28; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(DARK_GRAY_HEX); doc.text(`Project: ${projectName}`, pageMargin, y); doc.text(`Client: ${clientName}`, pageMargin, y + 12); y += 12; doc.text(`Quote #: ${quoteNumber}`, pageWidth - pageMargin, y, { align: 'right' }); y += 12; doc.text(`Date: ${new Date().toLocaleDateString('en-US')}`, pageWidth - pageMargin, y, { align: 'right' }); y += 35; // 2. Line Items Table const tableHead = [["#", "Description", "Qty", "Unit Price", "Amount"]]; const tableBody = lineItems.map((item, index) => [ index + 1, item.desc, item.qty.toString(), formatUSD(item.price), formatUSD(calculateItemAmount(item.qty, item.price)) ]); doc.autoTable({ startY: y, head: tableHead, body: tableBody, theme: 'striped', styles: { fontSize: 9, cellPadding: 4, valign: 'middle' }, headStyles: { fillColor: [59, 130, 246], textColor: 255 }, // Primary Blue columnStyles: { 2: { cellWidth: 30, halign: 'right' }, 3: { halign: 'right' }, 4: { halign: 'right', fontStyle: 'bold' } } }); y = doc.autoTable.previous.finalY + 15; // 3. Financial Summary doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(DARK_GRAY_HEX); const summaryX = pageWidth - pageMargin - 120; // Start summary tables 120pt from right const summaryLines = [ ['Subtotal', formatUSD(subtotal), ''], ['Tax Rate', `${taxRate}%`, formatUSD(taxAmount)], ['Discount', '', formatUSD(discount * -1)], ]; summaryLines.forEach(line => { doc.text(line[0], summaryX, y, { align: 'right' }); doc.text(line[1], summaryX + 75, y, { align: 'right' }); if (line[2]) { doc.text(line[2], summaryX + 150, y, { align: 'right' }); } y += 12; }); doc.setDrawColor(DARK_GRAY_HEX); doc.setLineWidth(1); doc.line(summaryX + 50, y, summaryX + 150, y); // Summary divider y += 15; doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(SECONDARY_COLOR_HEX); // Accent Green for Total doc.text("GRAND TOTAL:", summaryX - 25, y, { align: 'right' }); doc.text(formatUSD(finalTotal), summaryX + 150, y, { align: 'right' }); doc.save(`Quote_${projectName.replace(/\s/g, '_')}_${new Date().getFullYear()}.pdf`); } catch (e) { console.error("Error generating PDF:", e); showMessage("An error occurred while generating the PDF.", true); } }; /** Basic HTML escaping */ function escapeHtml(unsafe) { if (typeof unsafe !== 'string') return unsafe ?? ''; // Handle null/undefined return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** Displays messages globally */ function showMessage(message, isError = false) { if (!messageArea) return; if (!message) { messageArea.style.display = 'none'; return; } const colorClass = isError ? 'text-red-600' : 'text-blue-600'; messageArea.textContent = message; messageArea.className = `mt-4 text-center font-medium ${colorClass}`; messageArea.style.display = 'block'; setTimeout(() => { if(messageArea && messageArea.textContent === message) { messageArea.style.display = 'none'; } }, 4000); } // === Initial Load === renderLineItemTable(); qegCalculateFinalTotal(); });
Scroll to Top