`).join('');
}
function renderHabits() {
const todayStr = new Date().toISOString().split('T')[0];
habitsListEl.innerHTML = habits.map(habit => `
`).join('');
}
// --- DASHBOARD & CHARTS --- //
function updateDashboard() {
// Stats
activeGoalsStat.textContent = goals.filter(g => !g.completed).length;
completedGoalsStat.textContent = goals.filter(g => g.completed).length;
// Habit Score
const totalPossibleCompletions = habits.length * 30; // Last 30 days
let actualCompletions = 0;
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
habits.forEach(habit => {
habit.completedDates.forEach(dateStr => {
if (new Date(dateStr) >= thirtyDaysAgo) {
actualCompletions++;
}
});
});
const habitScore = totalPossibleCompletions > 0 ? Math.round((actualCompletions / totalPossibleCompletions) * 100) : 0;
habitScoreStat.textContent = `${habitScore}%`;
// Chart
renderConsistencyChart();
}
function renderConsistencyChart() {
const ctx = document.getElementById('habitConsistencyChart');
if (!ctx) return;
const labels = [];
const data = [];
for (let i = 29; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
labels.push(date);
let dailyCompletions = 0;
habits.forEach(habit => {
if (habit.completedDates.includes(dateStr)) {
dailyCompletions++;
}
});
const dailyScore = habits.length > 0 ? (dailyCompletions / habits.length) * 100 : 0;
data.push(dailyScore);
}
if (habitConsistencyChart) habitConsistencyChart.destroy();
habitConsistencyChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Daily Consistency %',
data: data,
backgroundColor: 'rgba(37, 99, 235, 0.6)',
borderColor: 'rgba(37, 99, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: {
x: { type: 'time', time: { unit: 'day' } },
y: { beginAtZero: true, max: 100 }
}
}
});
}
// --- PDF GENERATION --- //
function generatePDF() {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
let y = 40;
const margin = 40;
const pdfWidth = pdf.internal.pageSize.getWidth();
pdf.setFontSize(20);
pdf.setFont('helvetica', 'bold');
pdf.text('Personal Growth Progress Report', pdfWidth / 2, y, { align: 'center' });
y += 20;
pdf.setFontSize(10);
pdf.setFont('helvetica', 'normal');
pdf.text(new Date().toLocaleDateString(), pdfWidth / 2, y, { align: 'center' });
y += 40;
// Goals Section
pdf.setFontSize(16);
pdf.setFont('helvetica', 'bold');
pdf.text('Goals Summary', margin, y);
y += 20;
const goalHeaders = [['Title', 'Target Date', 'Progress %', 'Status']];
const goalBody = goals.map(g => [
g.title,
new Date(g.targetDate).toLocaleDateString(),
`${g.progress}%`,
g.completed ? 'Completed' : 'In Progress'
]);
pdf.autoTable({
startY: y,
head: goalHeaders,
body: goalBody,
theme: 'striped',
headStyles: { fillColor: [37, 99, 235] }
});
y = pdf.autoTable.previous.finalY + 30;
// Habits Section
pdf.setFontSize(16);
pdf.setFont('helvetica', 'bold');
pdf.text('Habits Summary', margin, y);
y += 20;
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const habitHeaders = [['Habit', 'Consistency (Last 30 Days)']];
const habitBody = habits.map(h => {
const completions = h.completedDates.filter(d => new Date(d) >= thirtyDaysAgo).length;
const consistency = `${Math.round((completions / 30) * 100)}%`;
return [h.name, consistency];
});
pdf.autoTable({
startY: y,
head: habitHeaders,
body: habitBody,
theme: 'striped',
headStyles: { fillColor: [16, 185, 129] }
});
pdf.save('Personal-Growth-Report.pdf');
showNotification('PDF report downloaded!', 'success');
}
// --- TAB NAVIGATION --- //
function setActiveTab(index) {
tabs.forEach((tab, i) => {
tab.classList.toggle('active', i === index);
tabPanels[i].classList.toggle('hidden', i !== index);
});
if (index === 0) updateDashboard(); // Refresh dashboard on view
}
// --- KICK IT OFF --- //
initializeApp();
});