Science Fair Budget Generator

Science Fair Budget Generator

1. Project Details
2. Allocated Budget by Category ($)

Allocate your estimated funds here. Total should not exceed your limit.

Category Allocated Budget ($) Actual Spending ($) Variance ($)
TOTAL PROJECT BUDGET $0.00 $0.00 $0.00

Detailed Spending Log

Date Category Item Cost ($) Actions

Review your final budget comparison here before downloading the PDF.

Project: ${meta.projectName} | Student: ${meta.studentName}

Competition: ${meta.schoolName}

1. Budget vs. Actual Summary

`; categories.forEach(cat => { const actual = totals.categoryTotals[cat.name] || 0; const variance = cat.allocated - actual; const varianceStyle = variance < 0 ? 'color: var(--danger-color);' : (variance > 0 ? 'color: var(--success-color);' : 'color: var(--secondary-color);'); html += ` `; }); // Grand Totals Row const overallVarianceStyle = totals.grandTotalVariance < 0 ? 'color: var(--danger-color);' : ''; html += `
Category Allocated ($) Actual Spent ($) Variance ($)
${cat.name} ${formatCurrency(cat.allocated)} ${formatCurrency(actual)} ${formatCurrency(variance)}
GRAND TOTALS ${formatCurrency(totals.grandTotalAllocated)} ${formatCurrency(totals.grandTotalActual)} ${formatCurrency(totals.grandTotalVariance)}

Total Budget Limit: ${formatCurrency(meta.budgetLimit)} | Remaining Available: ${formatCurrency(meta.budgetLimit - totals.grandTotalActual)}

2. Detailed Expense Log

`; reviewArea.innerHTML = html; // Manually build the detailed expense table for the review area const expenseTableContainer = document.getElementById('review-expense-table-container'); let expenseTableHTML = ``; expenses.forEach(expense => { expenseTableHTML += ` `; }); expenseTableHTML += `
Date Category Item Description Cost ($)
${expense.date} ${expense.category} ${expense.item} ${formatCurrency(expense.cost)}
`; expenseTableContainer.innerHTML = expenseTableHTML; } /** * PDF Generation Function (Ensuring professional formatting) */ function downloadPDF() { const meta = getMetaFormData(); const totals = calculateTotals(); const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt', 'a4'); let currentY = 40; const margin = 40; const pageWidth = doc.internal.pageSize.width; const maxWidth = pageWidth - (margin * 2); const checkPageBreak = (spaceNeeded) => { if (currentY + spaceNeeded > doc.internal.pageSize.height - margin) { doc.addPage(); currentY = margin; } }; const addSectionHeader = (title) => { checkPageBreak(30); doc.setFontSize(16); doc.setFont('Helvetica', 'bold'); doc.setTextColor(33, 150, 243); /* Info color */ doc.text(title, margin, currentY); currentY += 10; doc.setLineWidth(0.5); doc.setDrawColor(200); doc.line(margin, currentY, pageWidth - margin, currentY); currentY += 15; doc.setTextColor(0); }; // --- PDF Content --- // Title Block doc.setFontSize(22); doc.setFont('Helvetica', 'bold'); doc.setTextColor(76, 175, 80); /* Primary color */ doc.text("Science Fair Budget Report", pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.setFontSize(12); doc.setFont('Helvetica', 'normal'); doc.setTextColor(108, 117, 125); doc.text(`Project: ${meta.projectName} | Student: ${meta.studentName}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.text(`Competition: ${meta.schoolName}`, pageWidth / 2, currentY, { align: 'center' }); currentY += 30; doc.setTextColor(0); // 1. Budget Summary Table addSectionHeader("1. Budget vs. Actual Summary"); const summaryHead = [["Category", "Allocated ($)", "Actual Spent ($)", "Variance ($)"]]; const summaryBody = []; categories.forEach(cat => { const actual = totals.categoryTotals[cat.name] || 0; const variance = cat.allocated - actual; summaryBody.push([ cat.name, formatCurrency(cat.allocated), formatCurrency(actual), formatCurrency(variance) ]); }); doc.autoTable({ startY: currentY, head: summaryHead, body: summaryBody, foot: [[ "GRAND TOTALS", formatCurrency(totals.grandTotalAllocated), formatCurrency(totals.grandTotalActual), formatCurrency(totals.grandTotalVariance) ]], theme: 'grid', headStyles: { fillColor: [121, 85, 72], textColor: [255, 255, 255] }, /* Secondary color */ footStyles: { fillColor: [232, 245, 233], textColor: [0], fontStyle: 'bold' }, styles: { fontSize: 10, cellPadding: 5, font: 'Helvetica' }, columnStyles: { 0: { fontStyle: 'bold' }, 1: { halign: 'right' }, 2: { halign: 'right' }, 3: { halign: 'right' } } }); currentY = doc.autoTable.previous.finalY + 10; // Budget Limit summary text const remaining = meta.budgetLimit - totals.grandTotalActual; doc.setFontSize(10); doc.setFont('Helvetica', 'bold'); doc.text(`Total Budget Limit: ${formatCurrency(meta.budgetLimit)}`, margin, currentY); doc.text(`Remaining Available: ${formatCurrency(remaining)}`, margin + 200, currentY); currentY += 20; // 2. Detailed Expense Log Table addSectionHeader("2. Detailed Expense Log"); const expenseHead = [["Date", "Category", "Item Description", "Cost ($)"]]; const expenseBody = expenses.map(expense => [ expense.date, expense.category, expense.item, formatCurrency(expense.cost) ]); doc.autoTable({ startY: currentY, head: expenseHead, body: expenseBody, theme: 'grid', headStyles: { fillColor: [76, 175, 80], textColor: [255, 255, 255] }, /* Primary color */ styles: { fontSize: 9, cellPadding: 4, font: 'Helvetica' }, columnStyles: { 3: { halign: 'right', fontStyle: 'bold' } } }); currentY = doc.autoTable.previous.finalY + 10; doc.save('science_fair_budget_report.pdf'); } function getMetaFormData() { return { projectName: document.getElementById('project-name').value, studentName: document.getElementById('student-name').value, schoolName: document.getElementById('school-name').value, budgetLimit: parseFloat(document.getElementById('budget-limit').value) || 0 }; } // --- Tab Navigation --- function switchTab(tabIndex) { tabs.forEach((tab, index) => { tab.classList.toggle('active', index === tabIndex); contents[index].classList.toggle('active', index === tabIndex); }); currentTab = tabIndex; updateNavButtons(); if (tabIndex === 2) { // Review tab generateReviewReport(); } } function updateNavButtons() { prevBtn.disabled = currentTab === 0; nextBtn.disabled = currentTab === tabs.length - 1; } tabs.forEach((tab, index) => { tab.addEventListener('click', () => switchTab(index)); }); nextBtn.addEventListener('click', () => { if (currentTab < tabs.length - 1) switchTab(currentTab + 1); }); prevBtn.addEventListener('click', () => { if (currentTab > 0) switchTab(currentTab - 1); }); // --- Event Listeners --- document.getElementById('budget-meta-form').addEventListener('input', renderBudgetTable); document.getElementById('expense-cost').addEventListener('input', () => { document.getElementById('expense-cost').value = (parseFloat(document.getElementById('expense-cost').value) || 0).toFixed(2); }); document.getElementById('budget-limit').addEventListener('input', () => { document.getElementById('budget-limit').value = (parseFloat(document.getElementById('budget-limit').value) || 0).toFixed(2); }); document.getElementById('expense-add-update-btn').addEventListener('click', (e) => { e.preventDefault(); expenseForm.dispatchEvent(new Event('submit')); }); document.getElementById('pdf-download-btn').addEventListener('click', downloadPDF); // --- Initial Setup --- renderBudgetTable(); populateExpenseCategorySelect(); renderExpenseTable(); updateNavButtons(); // Set default date for expense input document.getElementById('expense-date').value = new Date().toISOString().split('T')[0]; });
Scroll to Top