Home Maintenance Cost Budgeting Tool

Quick Budget Guideline (Optional)

Estimate a general annual maintenance budget based on your home's value (typically 1-4%). This can serve as a target for your itemized budget.

$

Budget Overview (from Itemized Tasks)

Total Estimated Annual Maintenance Budget: $0.00

Recommended Monthly Savings: $0.00

Add/Edit Maintenance Task

$

Itemized Maintenance Budget

No maintenance tasks added yet. Add tasks above to build your budget.

Overall Budget Summary

Add tasks in Tab 2 to see your budget summary here.

Budget Allocation by Category

No tasks to summarize by category.

Sinking Fund Contributions (for Major Replacements)

No major replacement items added with lifespan details.

Total Estimated Annual Maintenance Budget: $${totalAnnualBudget.toFixed(2)}

Recommended Monthly Savings: $${(totalAnnualBudget / 12).toFixed(2)}

`; } else { finalBudgetSummaryEl.innerHTML = `

Add tasks in Tab 2 to see your budget summary here.

`; } } // Category Summary const categoryTotals = {}; tasks.forEach(task => { categoryTotals[task.category] = (categoryTotals[task.category] || 0) + task.annualizedCost; }); if (Object.keys(categoryTotals).length > 0) { if(categorySummaryTableEl) categorySummaryTableEl.style.display = 'table'; if(categoryPieChartCanvas) categoryPieChartCanvas.style.display = 'block'; if(categorySummaryMessageEl) categorySummaryMessageEl.style.display = 'none'; if(categorySummaryTableBodyEl) categorySummaryTableBodyEl.innerHTML = ''; const sortedCategories = Object.entries(categoryTotals).sort(([,a],[,b]) => b-a); // Sort by amount desc sortedCategories.forEach(([category, total]) => { const percentage = totalAnnualBudget > 0 ? (total / totalAnnualBudget) * 100 : 0; const row = categorySummaryTableBodyEl.insertRow(); row.insertCell().textContent = category; const costCell = row.insertCell(); costCell.textContent = total.toFixed(2); costCell.classList.add('cost-cell'); row.insertCell().textContent = percentage.toFixed(1) + '%'; row.cells[2].classList.add('cost-cell'); }); // Pie Chart if (categoryPieChartCanvas) { if (categoryPieChartInstance) categoryPieChartInstance.destroy(); categoryPieChartInstance = new Chart(categoryPieChartCanvas.getContext('2d'), { type: 'pie', data: { labels: sortedCategories.map(entry => entry[0]), datasets: [{ label: 'Budget by Category', data: sortedCategories.map(entry => entry[1]), backgroundColor: ['#3498db', '#f39c12', '#2ecc71', '#e74c3c', '#9b59b6', '#1abc9c', '#34495e', '#7f8c8d'], hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'right' } } } }); } } else { if(categorySummaryTableEl) categorySummaryTableEl.style.display = 'none'; if(categoryPieChartCanvas) categoryPieChartCanvas.style.display = 'none'; if(categorySummaryMessageEl) categorySummaryMessageEl.style.display = 'block'; if (categoryPieChartInstance) { categoryPieChartInstance.destroy(); categoryPieChartInstance = null;} } // Sinking Fund Summary const sinkingFundItems = tasks.filter(task => task.costType === 'replacement' && task.lifespan > 0); let totalSinkingFundAnnual = 0; if (sinkingFundItems.length > 0) { if(sinkingFundTableEl) sinkingFundTableEl.style.display = 'table'; if(sinkingFundMessageEl) sinkingFundMessageEl.style.display = 'none'; if(sinkingFundTableBodyEl) sinkingFundTableBodyEl.innerHTML = ''; sinkingFundItems.forEach(item => { const annualContribution = item.replacementCost / item.lifespan; totalSinkingFundAnnual += annualContribution; const row = sinkingFundTableBodyEl.insertRow(); row.insertCell().textContent = item.description; const repCostCell = row.insertCell(); repCostCell.textContent = item.replacementCost.toFixed(2); repCostCell.classList.add('cost-cell'); row.insertCell().textContent = item.lifespan; const contribCell = row.insertCell(); contribCell.textContent = annualContribution.toFixed(2); contribCell.classList.add('cost-cell'); }); if(sinkingFundTableTotalEl) sinkingFundTableTotalEl.textContent = `$${totalSinkingFundAnnual.toFixed(2)}`; } else { if(sinkingFundTableEl) sinkingFundTableEl.style.display = 'none'; if(sinkingFundMessageEl) sinkingFundMessageEl.style.display = 'block'; if(sinkingFundTableTotalEl) sinkingFundTableTotalEl.textContent = "$0.00"; } } // --- PDF Download --- if(downloadPdfButton) downloadPdfButton.addEventListener('click', function() { if (tasks.length === 0) { alert("No maintenance tasks to include in the report. Please add some tasks first."); return; } hmbt_updateAllSummaries(); // Ensure data is fresh let pdfHtml = `
`; pdfHtml += `

Home Maintenance Budget Report

`; const homeValForPdf = getNum(homeValueEl); if (homeValForPdf > 0) { pdfHtml += `

For Home Valued At: $${homeValForPdf.toLocaleString()}

`; const lowGuideline = (homeValForPdf * 0.01).toLocaleString(undefined, {minimumFractionDigits:2, maximumFractionDigits:2}); const highGuideline = (homeValForPdf * 0.04).toLocaleString(undefined, {minimumFractionDigits:2, maximumFractionDigits:2}); pdfHtml += `

(Guideline Annual Budget: $${lowGuideline} - $${highGuideline})

`; } pdfHtml += `

Generated on: ${new Date().toLocaleDateString()}

`; const totalAnnualBudget = tasks.reduce((sum, task) => sum + task.annualizedCost, 0); pdfHtml += `

Overall Budget Summary

Total Estimated Annual Maintenance: $${totalAnnualBudget.toFixed(2)}

Recommended Monthly Savings: $${(totalAnnualBudget / 12).toFixed(2)}

`; pdfHtml += `

Itemized Maintenance Tasks

`; tasks.forEach(task => { let costDetails = ''; if (task.costType === 'recurring') { costDetails = `$${(task.costPerOccurrence || 0).toFixed(2)} / ${task.frequency}`; } else { costDetails = `$${(task.replacementCost || 0).toFixed(2)} every ${task.lifespan || '?'} yrs`; } pdfHtml += ``; }); pdfHtml += `
CategoryTaskCost DetailsAnnualized ($)Yrs LeftNotes
${task.category}${task.description}${costDetails}${task.annualizedCost.toFixed(2)}${task.yearsLeft !== null ? task.yearsLeft : '-'}${task.notes || '-'}
`; const categoryTotals = {}; tasks.forEach(task => { categoryTotals[task.category] = (categoryTotals[task.category] || 0) + task.annualizedCost; }); const sortedCategories = Object.entries(categoryTotals).sort(([,a],[,b]) => b-a); if (sortedCategories.length > 0) { pdfHtml += `

Budget by Category

`; sortedCategories.forEach(([category, total]) => { const percentage = totalAnnualBudget > 0 ? (total / totalAnnualBudget) * 100 : 0; pdfHtml += ``; }); pdfHtml += `
CategoryTotal Annualized ($)% of Total
${category}${total.toFixed(2)}${percentage.toFixed(1)}%
TOTAL$${totalAnnualBudget.toFixed(2)}100%
`; // Add Category Pie Chart Image if available if (categoryPieChartInstance) { try { const chartImg = categoryPieChartInstance.toBase64Image(); pdfHtml += `

Category Allocation Chart

`; } catch(e) { pdfHtml += "

(Category chart image not available for PDF)

"; } } pdfHtml += `
`; } const sinkingFundItems = tasks.filter(task => task.costType === 'replacement' && task.lifespan > 0); if (sinkingFundItems.length > 0) { pdfHtml += `

Sinking Fund Contributions

`; let totalSinkingFundAnnualPdf = 0; sinkingFundItems.forEach(item => { const annualContribution = item.replacementCost / item.lifespan; totalSinkingFundAnnualPdf += annualContribution; pdfHtml += ``; }); pdfHtml += `
ItemReplacement Cost ($)Lifespan (Yrs)Annual Sinking Fund ($)
${item.description}${item.replacementCost.toFixed(2)}${item.lifespan}${annualContribution.toFixed(2)}
Total Annual Sinking Fund:$${totalSinkingFundAnnualPdf.toFixed(2)}
`; } pdfHtml += `
`; // Close hmbt-pdf-output if(pdfOutputEl) pdfOutputEl.innerHTML = pdfHtml; const opt = { margin: [0.5, 0.4, 0.5, 0.4], filename: `Home_Maintenance_Budget_${new Date().toISOString().slice(0,10)}.pdf`, image: { type: 'jpeg', quality: 0.95 }, html2canvas: { scale: 2, useCORS: true, logging: false, width: pdfOutputEl ? pdfOutputEl.scrollWidth : 1000, height: pdfOutputEl ? pdfOutputEl.scrollHeight + 50 : 1400}, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } }; if (typeof html2pdf !== 'undefined' && pdfOutputEl) { html2pdf().from(pdfOutputEl).set(opt).save() .catch(err => { console.error("PDF generation failed:", err); alert("PDF generation error."); }); } else { alert("PDF library not loaded or output element missing."); } }); // --- Tab Navigation --- window.hmbt_openTab = function(evt, tabName) { tabs.forEach(tab => { if(tab) tab.style.display = 'none'; }); tabLinks.forEach(link => { if(link) link.classList.remove('active'); }); const activeTabContent = toolContainer.querySelector('#' + tabName); if(activeTabContent) activeTabContent.style.display = 'block'; else { console.error(`Tab content for ${tabName} not found.`); return; } let clickedIndex = -1; if (evt && evt.currentTarget) { evt.currentTarget.classList.add('active'); clickedIndex = tabLinks.indexOf(evt.currentTarget); } else { clickedIndex = tabLinks.findIndex(link => { if (!link) return false; const onclickAttr = link.getAttribute('onclick'); return onclickAttr && onclickAttr.includes(tabName); }); if (clickedIndex !== -1 && tabLinks[clickedIndex]) tabLinks[clickedIndex].classList.add('active'); } currentTabIndex = clickedIndex !== -1 ? clickedIndex : 0; hmbt_updateNavButtons(); if (tabName === 'hmbt-tab3') { // When navigating to summary tab, refresh summaries hmbt_updateAllSummaries(); } } window.hmbt_navigateTab = function(direction) { let newIndex = currentTabIndex; if (direction === 'next' && currentTabIndex < tabs.length - 1) newIndex++; else if (direction === 'prev' && currentTabIndex > 0) newIndex--; if (tabLinks[newIndex] && newIndex !== currentTabIndex) tabLinks[newIndex].click(); } function hmbt_updateNavButtons() { if(prevButton) prevButton.disabled = currentTabIndex === 0; if(nextButton) nextButton.disabled = currentTabIndex >= tabs.length - 1; } // --- Initialization --- function initializeTool() { loadTasks(); hmbt_toggleCostTypeFields(); // Set initial visibility of cost type fields hmbt_renderTasksTable(); hmbt_updateAllSummaries(); hmbt_openTab(null, 'hmbt-tab1'); hmbt_clearTaskForm(); // Initialize form state } let attempts = 0; function checkLibrariesAndInit_hmbt() { if (typeof Chart !== 'undefined' && typeof html2pdf !== 'undefined') { initializeTool(); } else if (attempts < 20) { attempts++; setTimeout(checkLibrariesAndInit_hmbt, 100); } else { alert("Error: Chart.js or html2pdf.js library could not be loaded. Some features may not work."); initializeTool(); // Initialize anyway, functions have internal checks } } checkLibrariesAndInit_hmbt(); })();
Scroll to Top