Yearly Spending Habit Analysis Tool
Step 1: Your Annual Income & Savings Goals
Step 2: Enter Your Annual Spending by Category
Enter your total actual spending for the year in each category. Optionally, add your budgeted amount for comparison.
Step 3: Spending Analysis & Insights
Enter income (Tab 1) and spending (Tab 2), then click "Refresh Analysis".
Step 4: Overall Summary & Download Report
Complete the analysis on previous tabs to view the summary.
Please enter your income and spending details on previous tabs.
";
document.getElementById('downloadSpendingPdfButton').disabled = true;
return;
}
const { annualNetIncome, totals, targetSavings } = spendingReportData;
let targetSavingsAmountText = "N/A";
if (targetSavings.value > 0) {
targetSavingsAmountText = targetSavings.type === "Amount" ? `$${targetSavings.value.toFixed(2)}` : `${targetSavings.value.toFixed(1)}% of net income`;
}
let summaryHTML = `
Analysis Period: ${spendingReportData.analysisPeriodLabel}
Annual Net Income: $${annualNetIncome.toFixed(2)}
Total Actual Annual Spending: $${totals.actualSpending.toFixed(2)}
${totals.budgetedSpending !== null ? `
Total Budgeted Annual Spending: $${totals.budgetedSpending.toFixed(2)}
Overall Budget Variance: $${totals.budgetVariance.toFixed(2)}
` : ''}
Actual Annual Savings: $${totals.actualSavings.toFixed(2)}
Actual Savings Rate: ${totals.actualSavingsRate.toFixed(1)}%
Target Annual Savings Goal: ${targetSavingsAmountText}
`;
const topCats = [...spendingReportData.categories].sort((a,b) => b.actualSpending - a.actualSpending).slice(0,3);
if(topCats.length > 0 && totals.actualSpending > 0) {
summaryHTML += `
Top Spending Categories:
`;
topCats.forEach(cat => {
summaryHTML += `- ${cat.name}: $${cat.actualSpending.toFixed(2)} (${((cat.actualSpending / totals.actualSpending) * 100).toFixed(1)}%)
`;
});
summaryHTML += `
`;
}
container.innerHTML = summaryHTML;
document.getElementById('downloadSpendingPdfButton').disabled = false;
}
function downloadSpendingPdf() {
if (!spendingReportData.totals || (spendingReportData.annualNetIncome === 0 && spendingReportData.totals.actualSpending === 0) ) {
alert("Please enter income and spending data first."); return;
}
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('PDF generation library (jsPDF) is not loaded.'); return;
}
const jsPDFConstructor = window.jspdf.jsPDF;
const doc = new jsPDFConstructor();
if (typeof doc.autoTable !== 'function') {
alert('jsPDF AutoTable plugin not loaded.'); return;
}
const { analysisPeriodLabel, annualNetIncome, targetSavings, categories, totals } = spendingReportData;
const primaryColor = '#007bff', textColor = '#212529', tableHeaderColor = '#e9ecef';
let yPos = 22;
const pageHeight = doc.internal.pageSize.height; const margin = 20;
function checkYPdf(increment = 10) { if (yPos + increment > pageHeight - margin) { doc.addPage(); yPos = margin; } }
doc.setFontSize(18); doc.setTextColor(primaryColor);
doc.text(`Yearly Spending Analysis: ${analysisPeriodLabel}`, 14, yPos); yPos += 8;
doc.setFontSize(10); doc.setTextColor(textColor);
doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 14, yPos); yPos += 10;
checkYPdf(25);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Income & Savings Summary", 14, yPos); yPos += 6;
let incomeBody = [
['Annual Net Income:', `$${annualNetIncome.toFixed(2)}`],
['Target Savings:', `${targetSavings.type === "Amount" ? '$'+targetSavings.value.toFixed(2) : targetSavings.value.toFixed(1)+'% of Net Income'}`],
['Actual Annual Savings:', `$${totals.actualSavings.toFixed(2)}`],
['Actual Savings Rate:', `${totals.actualSavingsRate.toFixed(1)}%`]
];
doc.autoTable({startY: yPos, body: incomeBody, theme:'plain', styles:{fontSize:9, cellPadding:1.5}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 7;
checkYPdf(15);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Overall Spending Summary", 14, yPos); yPos += 6;
let overallSpendingBody = [
['Total Actual Spending:', `$${totals.actualSpending.toFixed(2)}`]
];
if (totals.budgetedSpending !== null) {
overallSpendingBody.push(['Total Budgeted Spending:', `$${totals.budgetedSpending.toFixed(2)}`]);
overallSpendingBody.push(['Overall Budget Variance:', `$${totals.budgetVariance.toFixed(2)} (${totals.budgetVariance > 0 ? 'Over Budget' : totals.budgetVariance < 0 ? 'Under Budget' : 'On Budget'})`]);
}
doc.autoTable({startY: yPos, body: overallSpendingBody, theme:'plain', styles:{fontSize:9, cellPadding:1.5}, columnStyles:{0:{fontStyle:'bold'}}});
yPos = doc.lastAutoTable.finalY + 10;
checkYPdf(20 + categories.length * 6);
doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Detailed Spending by Category", 14, yPos); yPos += 6;
let tableHead = [['Category', 'Actual ($)', '% of Total']];
if (totals.budgetedSpending !== null) {
tableHead[0].push('Budgeted ($)', 'Variance ($)', 'Variance (%)');
}
const tableBody = categories.map(cat => {
const percent = totals.actualSpending > 0 ? (cat.actualSpending / totals.actualSpending) * 100 : 0;
let row = [cat.name, cat.actualSpending.toFixed(2), `${percent.toFixed(1)}%`];
if (totals.budgetedSpending !== null) {
const budget = cat.budgetedSpending !== null ? cat.budgetedSpending : 0;
const variance = cat.actualSpending - budget;
const variancePercent = budget > 0 ? (variance / budget) * 100 : (cat.actualSpending > 0 ? (budget === 0 ? 'Infinite' : 100) : 0);
row.push(cat.budgetedSpending !== null ? budget.toFixed(2) : 'N/A', variance.toFixed(2), cat.budgetedSpending !== null ? (variancePercent === 'Infinite' ? 'Infinite' : variancePercent.toFixed(1) + '%') : 'N/A');
}
return row;
});
doc.autoTable({
startY: yPos, head: tableHead, body: tableBody, theme: 'grid',
headStyles: {fillColor: tableHeaderColor, textColor:textColor, fontStyle:'bold', fontSize:9},
styles:{fontSize:8, cellPadding:1.5},
columnStyles: { 1:{halign:'right'}, 2:{halign:'right'}, 3:{halign:'right'}, 4:{halign:'right'}, 5:{halign:'right'} }
});
yPos = doc.lastAutoTable.finalY + 7;
const topCategoriesPdf = [...categories].sort((a,b) => b.actualSpending - a.actualSpending).slice(0,5);
if(topCategoriesPdf.length > 0 && totals.actualSpending > 0) {
checkYPdf(15 + topCategoriesPdf.length * 5);
doc.setFontSize(10); doc.setTextColor(textColor);
doc.text("Top Spending Categories:", 14, yPos); yPos +=5;
topCategoriesPdf.forEach(cat => {
checkYPdf(5);
doc.text(`- ${cat.name}: $${cat.actualSpending.toFixed(2)} (${((cat.actualSpending / totals.actualSpending) * 100).toFixed(1)}%)`, 16, yPos);
yPos += 5;
});
}
doc.save("Yearly_Spending_Analysis.pdf");
}
// Initialize
updateSHANavButtons();