${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');
}
});