`;
reportHTML += `
Spending by Category
`;
if (categoryAnalysis.length > 0) {
reportHTML += `
| Category | Total Spent | % of Total | Threshold | Status |
`;
categoryAnalysis.forEach(cat => {
let status = "No Limit Set"; let statusClass = "";
if (cat.threshold !== null) {
if (cat.spent > cat.threshold) {
status = `Over by ${formatCurrency(cat.spent - cat.threshold)}`;
statusClass = "status-over";
} else {
status = `Within Limit (${formatCurrency(cat.threshold - cat.spent)} remaining)`;
statusClass = "status-within";
}
}
reportHTML += `| ${cat.name} | ${formatCurrency(cat.spent)} |
${totalSpent > 0 ? (cat.spent/totalSpent*100).toFixed(1) : 0}% |
${cat.threshold !== null ? formatCurrency(cat.threshold) : '-'} |
${status} |
`;
});
reportHTML += `
`;
} else { reportHTML += "
No categorized spending in this period.
"; }
reportHTML += `
`;
if (highFrequencyAnalysis.length > 0) {
reportHTML += `
Potential Spending Leaks (High Frequency 'Want'/'Impulse')
`;
highFrequencyAnalysis.forEach(item => reportHTML += `- ${item}
`);
reportHTML += `
`;
}
if (triggerAnalysis.length > 0) {
reportHTML += `
Spending by Trigger/Reason
| Trigger/Reason | Total Spent | # Transactions |
`;
triggerAnalysis.forEach(([trigger, data]) => {
reportHTML += `| ${trigger} | ${formatCurrency(data.amount)} | ${data.count} |
`;
});
reportHTML += `
`;
}
analysisReportContentEl.innerHTML = reportHTML;
return null; // Already rendered
}
// --- PDF Download ---
if (downloadPdfBtn) {
downloadPdfBtn.addEventListener('click', () => {
const analysisData = generateAnalysisReport(true); // Get data for PDF
if (!analysisData) {
alert("Please select a valid date range and ensure there's data to analyze before generating PDF.");
return;
}
if (typeof jsPDF === 'undefined') { alert('PDF library (jsPDF) is not loaded.'); return; }
const doc = new jsPDF('p', 'pt', 'letter');
if (typeof doc.autoTable !== 'function') { alert('PDF table library (jspdf-autotable) is not loaded correctly.'); return; }
const currentCurrency = currencySymbolInput.value || '$';
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 40; let yPos = margin;
const todayFormatted = new Date(2025, 4, 15).toLocaleDateString();
doc.setFontSize(18); doc.setTextColor(document.documentElement.style.getPropertyValue('--primary-color') || '#007bff');
doc.text("Spending Habit Analysis Report", pageWidth / 2, yPos, { align: 'center' }); yPos += 15;
doc.setFontSize(9); doc.setTextColor(100);
doc.text(`Period: ${analysisData.periodText} | Generated: ${todayFormatted} | Currency: ${currentCurrency}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 25;
function addPdfSectionTitle(title) { /* ... */
if (yPos > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); yPos = margin; }
doc.setFontSize(14); doc.setTextColor(document.documentElement.style.getPropertyValue('--primary-darker-color') || '#0056b3');
doc.text(title, margin, yPos); yPos += 15;
doc.setFontSize(10); doc.setTextColor(40);
}
addPdfSectionTitle("1. Overall Spending Summary");
const overallSummaryBody = [
["Total Amount Spent:", formatCurrency(analysisData.overall.totalSpent, currentCurrency)],
["On Needs:", `${formatCurrency(analysisData.overall.byClassification.need, currentCurrency)} (${analysisData.overall.totalSpent > 0 ? (analysisData.overall.byClassification.need/analysisData.overall.totalSpent*100).toFixed(1) : 0}%)`],
["On Wants:", `${formatCurrency(analysisData.overall.byClassification.want, currentCurrency)} (${analysisData.overall.totalSpent > 0 ? (analysisData.overall.byClassification.want/analysisData.overall.totalSpent*100).toFixed(1) : 0}%)`],
["On Impulse/Unsure:", `${formatCurrency(analysisData.overall.byClassification.impulse, currentCurrency)} (${analysisData.overall.totalSpent > 0 ? (analysisData.overall.byClassification.impulse/analysisData.overall.totalSpent*100).toFixed(1) : 0}%)`]
];
doc.autoTable({ startY: yPos, body: overallSummaryBody, theme: 'plain', columnStyles: {0: {fontStyle: 'bold'}} });
yPos = doc.lastAutoTable.finalY + 15;
addPdfSectionTitle("2. Spending by Category Analysis");
if (analysisData.byCategory.length > 0) {
const catTableHead = [['Category', 'Total Spent', '% of Total', 'Threshold', 'Status']];
const catTableBody = analysisData.byCategory.map(cat => {
let status = "No Limit Set";
if (cat.threshold !== null) {
status = cat.spent > cat.threshold ? `Over by ${formatCurrency(cat.spent - cat.threshold, currentCurrency)}` : "Within Limit";
}
return [
cat.name, formatCurrency(cat.spent, currentCurrency),
`${analysisData.overall.totalSpent > 0 ? (cat.spent/analysisData.overall.totalSpent*100).toFixed(1) : 0}%`,
cat.threshold !== null ? formatCurrency(cat.threshold, currentCurrency) : '-',
status
];
});
doc.autoTable({ head: catTableHead, body: catTableBody, startY: yPos, theme: 'grid', headStyles: { fillColor: [0, 123, 255] } });
yPos = doc.lastAutoTable.finalY + 15;
} else { doc.text("No categorized spending for this period.", margin, yPos); yPos += 15; }
addPdfSectionTitle("3. Potential Habit Insights");
if (analysisData.highFreq.length > 0) {
doc.setFont(undefined,'bold'); doc.text("High-Frequency 'Want'/'Impulse' Categories:", margin, yPos); yPos += 10;
doc.setFont(undefined,'normal');
analysisData.highFreq.forEach(item => {
if (yPos > doc.internal.pageSize.getHeight() - 20) { doc.addPage(); yPos = margin; }
doc.text(`- ${item}`, margin + 5, yPos); yPos +=10;
});
} else { doc.text("No specific high-frequency 'want'/'impulse' patterns detected.", margin, yPos); yPos += 10; }
yPos += 5; // Gap
if (analysisData.byTrigger.length > 0) {
if (yPos > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); yPos = margin; }
doc.setFont(undefined,'bold'); doc.text("Spending by Trigger/Reason:", margin, yPos); yPos += 10;
doc.setFont(undefined,'normal');
const triggerTableHead = [['Trigger/Reason', 'Total Spent', '# Transactions']];
const triggerTableBody = analysisData.byTrigger.map(([trigger, data]) => [trigger, formatCurrency(data.amount, currentCurrency), data.count]);
doc.autoTable({ head: triggerTableHead, body: triggerTableBody, startY: yPos, theme: 'striped' });
} else { doc.text("No spending linked to specific triggers/reasons in this period.", margin, yPos); }
doc.save('Spending_Habit_Analysis.pdf');
});
}
// --- Initial Load & Setup ---
const today = new Date(2025, 4, 15); // May 15, 2025
const thirtyDaysAgo = new Date(new Date(2025, 4, 15).setDate(today.getDate() - 30));
if(expenseDateInput) expenseDateInput.valueAsDate = today;
if(analysisStartDateInput) analysisStartDateInput.valueAsDate = thirtyDaysAgo;
if(analysisEndDateInput) analysisEndDateInput.valueAsDate = today;
showTab(0);
renderCategoriesList();
updateExpenseCategoryDropdown();
renderExpenseLog();
generateAnalysisReport(); // Initial analysis for default period
});