Online Smart Daily Habit Tracker

Smart Daily Habit Tracker

Build consistency and achieve your goals, one day at a time.

Loading habits...

${habit.name}

Current Streak: ${streak} ${streak === 1 ? 'day' : 'days'}

${daysOfWeek.map(day => ` `).join('')}
`; }).join(''); lucide.createIcons(); }; const calculateStreak = (habit) => { if (!habit.completions) return 0; let streak = 0; const today = new Date(); today.setHours(0,0,0,0); for (let i = 0; i < 365; i++) { // Check up to a year back const d = new Date(today); d.setDate(d.getDate() - i); const dateKey = getDateKey(d); if (habit.completions[dateKey]) { streak++; } else { break; } } return streak; }; // --- MODAL MANAGEMENT --- const openModal = (habit = null) => { DOM.habitForm.reset(); if (habit) { DOM.modalTitle.textContent = 'Edit Habit'; DOM.habitId.value = habit.id; DOM.habitName.value = habit.name; } else { DOM.modalTitle.textContent = 'Add New Habit'; DOM.habitId.value = ''; } DOM.habitModal.classList.remove('hidden'); }; const closeModal = () => { DOM.habitModal.classList.add('hidden'); }; // --- FIRESTORE OPERATIONS --- const saveHabit = async (name, id = null) => { try { const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const habitsCollectionPath = `/artifacts/${appId}/users/${userId}/habits`; if (id) { // Update existing habit const habitDocRef = doc(db, habitsCollectionPath, id); await updateDoc(habitDocRef, { name }); } else { // Add new habit const habitsCollection = collection(db, habitsCollectionPath); await addDoc(habitsCollection, { name, createdAt: serverTimestamp(), completions: {} }); } closeModal(); } catch (error) { console.error("Error saving habit:", error); } }; const deleteHabit = async (habitId) => { const habit = allHabits.find(h => h.id === habitId); if (!habit || !confirm(`Are you sure you want to delete the habit "${habit.name}"?`)) return; try { const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const habitDocRef = doc(db, `/artifacts/${appId}/users/${userId}/habits`, habitId); await deleteDoc(habitDocRef); } catch (error) { console.error("Error deleting habit:", error); } }; const toggleHabitCompletion = async (habitId, dateKey) => { const habit = allHabits.find(h => h.id === habitId); if (!habit) return; const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const habitDocRef = doc(db, `/artifacts/${appId}/users/${userId}/habits`, habitId); const newCompletions = { ...(habit.completions || {}) }; if (newCompletions[dateKey]) { delete newCompletions[dateKey]; } else { newCompletions[dateKey] = true; } try { await updateDoc(habitDocRef, { completions: newCompletions }); } catch (error) { console.error("Error toggling habit completion:", error); } }; // --- PDF GENERATION --- const downloadPDF = () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(18); doc.text("Weekly Habit Summary", 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Week of: ${DOM.weekDisplay.textContent}`, 14, 30); const tableColumns = ["Habit", "Streak", "M", "T", "W", "T", "F", "S", "S", "Weekly Total"]; const tableRows = []; allHabits.forEach(habit => { let weeklyTotal = 0; const dayChecks = []; for (let i = 0; i < 7; i++) { const dayDate = new Date(currentWeekStart); dayDate.setDate(currentWeekStart.getDate() + i); const dateKey = getDateKey(dayDate); if (habit.completions && habit.completions[dateKey]) { dayChecks.push('✔'); weeklyTotal++; } else { dayChecks.push(''); } } tableRows.push([ habit.name, calculateStreak(habit), ...dayChecks, `${weeklyTotal}/7` ]); }); doc.autoTable({ head: [tableColumns], body: tableRows, startY: 40, theme: 'grid', headStyles: { fillColor: [45, 55, 72] }, // slate-700 }); doc.save(`habit_summary_${getDateKey(currentWeekStart)}.pdf`); }; // --- EVENT LISTENERS --- const addEventListeners = () => { DOM.prevWeekBtn.addEventListener('click', () => { currentWeekStart.setDate(currentWeekStart.getDate() - 7); renderWeekDisplay(); renderHabits(); }); DOM.nextWeekBtn.addEventListener('click', () => { currentWeekStart.setDate(currentWeekStart.getDate() + 7); renderWeekDisplay(); renderHabits(); }); DOM.addHabitBtn.addEventListener('click', () => openModal()); DOM.cancelBtn.addEventListener('click', closeModal); DOM.downloadPdfBtn.addEventListener('click', downloadPDF); DOM.habitForm.addEventListener('submit', (e) => { e.preventDefault(); const name = DOM.habitName.value.trim(); const id = DOM.habitId.value; if (name) { saveHabit(name, id); } }); DOM.habitsList.addEventListener('click', (e) => { const dayButton = e.target.closest('.day-checkbox'); const editButton = e.target.closest('.edit-habit-btn'); const deleteButton = e.target.closest('.delete-habit-btn'); if (dayButton) { toggleHabitCompletion(dayButton.dataset.habitId, dayButton.dataset.dateKey); } else if (editButton) { const habit = allHabits.find(h => h.id === editButton.dataset.habitId); if (habit) openModal(habit); } else if (deleteButton) { deleteHabit(deleteButton.dataset.habitId); } }); }; // --- INITIALIZATION --- document.addEventListener('DOMContentLoaded', async () => { // Assign DOM elements DOM.weekDisplay = document.getElementById('week-display'); DOM.habitsList = document.getElementById('habits-list'); DOM.prevWeekBtn = document.getElementById('prev-week-btn'); DOM.nextWeekBtn = document.getElementById('next-week-btn'); DOM.addHabitBtn = document.getElementById('add-habit-btn'); DOM.downloadPdfBtn = document.getElementById('download-pdf-btn'); DOM.habitModal = document.getElementById('habit-modal'); DOM.modalTitle = document.getElementById('modal-title'); DOM.habitForm = document.getElementById('habit-form'); DOM.habitId = document.getElementById('habit-id'); DOM.habitName = document.getElementById('habit-name'); DOM.cancelBtn = document.getElementById('cancel-btn'); DOM.noHabitsMessage = document.getElementById('no-habits-message'); DOM.loadingHabits = document.getElementById('loading-habits'); // Initialize UI lucide.createIcons(); renderWeekDisplay(); addEventListeners(); // Initialize Firebase try { const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}'); const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; if (!firebaseConfig.apiKey) throw new Error("Firebase configuration is missing."); setLogLevel('debug'); const app = initializeApp(firebaseConfig); db = getFirestore(app); auth = getAuth(app); onAuthStateChanged(auth, (user) => { if (user) { userId = user.uid; console.log("User authenticated with UID:", userId); if (habitsUnsubscribe) habitsUnsubscribe(); const habitsCollection = collection(db, `/artifacts/${appId}/users/${userId}/habits`); habitsUnsubscribe = onSnapshot(habitsCollection, (snapshot) => { allHabits = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); renderHabits(); }, (error) => { console.error("Error with snapshot listener:", error); }); } else { console.log("User is signed out."); if (habitsUnsubscribe) habitsUnsubscribe(); allHabits = []; renderHabits(); } }); if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (error) { console.error("Initialization Error:", error); DOM.habitsList.innerHTML = `

Error: Could not connect to the service. ${error.message}

`; DOM.loadingHabits.classList.add('hidden'); } });
Scroll to Top