Simple Habit Tracker

Simple Habit Tracker

Track Your Habits (Last 7 Days)

Add habits in the 'Manage Habits' tab to get started!

Add New Habit

Current Habits

Habit Name Action

Add habits in the 'Manage Habits' tab to get started!

"; return; } habitIds.forEach(id => { const habit = db.habits[id]; const habitItem = document.createElement('div'); habitItem.className = 'sht-habit-item'; const streak = calculateStreak(habit.log, dates); let daysHtml = ''; dates.forEach(dateInfo => { const isChecked = habit.log[dateInfo.iso] === true; daysHtml += `
${dateInfo.label}
`; }); habitItem.innerHTML = `
${escapeHTML(habit.name)}
${daysHtml}
Streak: ${streak}
`; // Add event listeners AFTER setting innerHTML const checkboxes = habitItem.querySelectorAll('.sht-day-checkbox'); checkboxes.forEach(checkbox => { checkbox.addEventListener('change', handleCheckboxChange); }); habitsList.appendChild(habitItem); }); } function handleCheckboxChange(event) { const checkbox = event.target; const habitId = checkbox.dataset.habitId; const date = checkbox.dataset.date; const isChecked = checkbox.checked; if (db.habits[habitId]) { if (isChecked) { db.habits[habitId].log[date] = true; } else { delete db.habits[habitId].log[date]; // Save space, remove false entry } // Update streak display for this specific habit const habitItem = checkbox.closest('.sht-habit-item'); if (habitItem) { const streakSpan = habitItem.querySelector('.sht-habit-streak span'); const dates = getLastNDates(TRACKING_DAYS); // Need dates for streak calc streakSpan.textContent = calculateStreak(db.habits[habitId].log, dates); } } // console.log("DB Updated:", db.habits[habitId].log); // For debugging } function calculateStreak(log, dates) { let currentStreak = 0; // Iterate backwards from today (last element in dates) for (let i = dates.length - 1; i >= 0; i--) { if (log[dates[i].iso] === true) { currentStreak++; } else { // Stop counting as soon as a day is missed break; } } return currentStreak; } // --- Date Utilities --- function formatDateISO(date) { return date.toISOString().split('T')[0]; // YYYY-MM-DD } function getShortDayLabel(date) { // e.g., "Mon", "Tue" return date.toLocaleDateString('en-US', { weekday: 'short' }); } function getLastNDates(n) { const dates = []; const today = new Date(); today.setHours(0, 0, 0, 0); // Normalize to start of day for (let i = 0; i < n; i++) { const date = new Date(today); date.setDate(today.getDate() - i); dates.push({ iso: formatDateISO(date), label: getShortDayLabel(date) }); } return dates.reverse(); // Return oldest to newest } // --- PDF Download Function --- if (pdfDownloadBtn) { pdfDownloadBtn.addEventListener("click", () => { if ( typeof window.jspdf === "undefined" || typeof window.jspdf.jsPDF === "undefined" ) { alert( "Error: PDF library (jsPDF) is not loaded. Please check your internet connection." ); return; } if (typeof window.jspdf.plugin.autotable === "undefined") { alert( "Error: PDF library (jsPDF-AutoTable) is not loaded. Please check your internet connection." ); return; } if (Object.keys(db.habits).length === 0) { alert("Add some habits before downloading a report."); return; } generatePDF(); }); } function generatePDF() { const { jsPDF } = window.jspdf; const doc = new jsPDF(); const primaryColor = getComputedStyle(container).getPropertyValue( "--sht-primary-color" ) || "#059669"; const headStyles = { fillColor: primaryColor, textColor: "#ffffff", fontStyle: "bold", }; // Title doc.setFont("helvetica", "bold"); doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text("Habit Tracker Report", 105, 20, { align: "center", }); doc.setFontSize(10); doc.setTextColor("#888"); doc.text( `Report Generated: ${new Date().toLocaleDateString()}`, 105, 26, { align: "center" } ); // 1. Results Table const dates = getLastNDates(TRACKING_DAYS); const head = [ ['Habit', ...dates.map(d => d.label), 'Current Streak'] ]; const body = Object.values(db.habits).map(habit => { const streak = calculateStreak(habit.log, dates); const dailyStatus = dates.map(dateInfo => habit.log[dateInfo.iso] ? '✅' : '❌'); return [habit.name, ...dailyStatus, streak]; }); doc.autoTable({ startY: 40, head: head, body: body, theme: "striped", headStyles: headStyles, columnStyles: { // Center checkmarks and streak 1: { halign: 'center' }, 2: { halign: 'center' }, 3: { halign: 'center' }, 4: { halign: 'center' }, 5: { halign: 'center' }, 6: { halign: 'center' }, 7: { halign: 'center' }, 8: { halign: 'center', fontStyle: 'bold' } // Streak column }, didDrawPage: function (data) { // Page Footer doc.setFontSize(8); doc.setTextColor("#888"); doc.text( "Page " + doc.internal.getNumberOfPages(), data.settings.margin.left, doc.internal.pageSize.height - 10 ); }, }); doc.save("Habit-Tracker-Report.pdf"); } // --- Utility Function --- function escapeHTML(str) { if (!str) return ""; const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } // --- Initial Load --- function loadSampleData() { // Per spec: USA-relevant sample data (generic habits) const samples = [ { id: 'habit_1', name: 'Exercise for 30 minutes', log: {} }, { id: 'habit_2', name: 'Read for 15 minutes', log: {} }, { id: 'habit_3', name: 'Drink 8 glasses of water', log: {} }, ]; samples.forEach(habit => { db.habits[habit.id] = habit; // Pre-populate some data for demo const today = new Date(); if (habit.id === 'habit_1') { db.habits[habit.id].log[formatDateISO(today)] = true; db.habits[habit.id].log[formatDateISO(new Date(today.setDate(today.getDate()-1)))] = true; } if (habit.id === 'habit_2') { db.habits[habit.id].log[formatDateISO(new Date())] = true; // Use fresh date obj } }); } loadSampleData(); renderHabitConfigTable(); renderDashboard(); updateNavButtons(); // Set initial state } // --- End of initializeHabitTracker() function --- /* * This new logic checks if the page is already loaded (common in Elementor). * If it is, it runs the setup function immediately. * If it's still loading (rare), it waits for the DOM event. */ if ( document.readyState === "complete" || document.readyState === "interactive" ) { // DOM is already ready, run the function now. initializeHabitTracker(); } else { // Still loading, wait for the event document.addEventListener( "DOMContentLoaded", initializeHabitTracker ); }