document.addEventListener('DOMContentLoaded', fetchHabits); const habitListDiv = document.getElementById('habitList'); const dateModal = document.getElementById('dateModal'); const modalHabitName = document.getElementById('modalHabitName'); const modalDateGrid = document.getElementById('modalDateGrid'); const modalCompleteTodayBtn = document.getElementById('modalCompleteTodayBtn'); const modalEditBtn = document.getElementById('modalEditBtn'); // Neu hinzugefügt const modalDeleteBtn = document.getElementById('modalDeleteBtn'); // Neu hinzugefügt let currentHabitId = null; // Stellt sicher, dass diese globale Variable korrekt ist let currentModalDate = new Date(); // Aktueller Monat im Modal let currentHabitCompletedDates = []; // Completed dates für das aktuelle Habit let currentHabitColor = '#4CAF50'; // Current habit color function getCurrentDate() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function getPastDates(days) { const dates = []; for (let i = days - 1; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); dates.push(`${year}-${month}-${day}`); } return dates; } function calculateStreak(completedDates) { if (!completedDates || completedDates.length === 0) { return 0; } // Sort dates in descending order (newest first) const sortedDates = completedDates.sort((a, b) => new Date(b) - new Date(a)); const today = getCurrentDate(); let streak = 0; let currentDate = new Date(); // Check if today is completed, if not, start from yesterday if (!sortedDates.includes(today)) { currentDate.setDate(currentDate.getDate() - 1); } // Count consecutive days backwards from today (or yesterday) while (true) { const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}`; if (sortedDates.includes(dateStr)) { streak++; currentDate.setDate(currentDate.getDate() - 1); } else { break; } } return streak; } async function fetchHabits() { try { const response = await fetch('/habits'); const habits = await response.json(); renderHabits(habits); } catch (error) { console.error('Error fetching habits:', error); } } function renderHabits(habits) { habitListDiv.innerHTML = ''; // Clear previous habits const last30Days = getPastDates(30); habits.forEach(habit => { const habitItem = document.createElement('div'); habitItem.className = 'habit-item'; habitItem.dataset.id = habit.id; // Store habit ID // Apply custom color to the habit item const habitColor = habit.color || '#4CAF50'; habitItem.style.setProperty('--habit-color', habitColor); const habitHeader = document.createElement('div'); habitHeader.className = 'habit-header'; const habitName = document.createElement('div'); habitName.className = 'habit-name'; habitName.textContent = habit.name; habitHeader.appendChild(habitName); const habitActions = document.createElement('div'); habitActions.className = 'habit-actions'; // Haken-Button für heute abhaken/rückgängig machen const todayToggleButton = document.createElement('button'); const today = getCurrentDate(); const isCompletedToday = habit.completed_dates && habit.completed_dates.includes(today); todayToggleButton.innerHTML = isCompletedToday ? '✓' : '✓'; // Checkmark symbol todayToggleButton.className = isCompletedToday ? 'completed-today' : 'not-completed-today'; todayToggleButton.onclick = () => toggleTodayCompletion(habit.id, today, todayToggleButton); habitActions.appendChild(todayToggleButton); // Drei-Punkte-Menü für erweiterte Optionen const menuButton = document.createElement('button'); menuButton.innerHTML = '⋮'; // Vertical ellipsis (drei Punkte) menuButton.onclick = () => openHabitModal(habit.id, habit.name, habit.completed_dates, habitColor); habitActions.appendChild(menuButton); habitHeader.appendChild(habitActions); habitItem.appendChild(habitHeader); const dateGrid = document.createElement('div'); dateGrid.className = 'date-grid'; last30Days.forEach(date => { const dateSquare = document.createElement('div'); dateSquare.className = 'date-square'; if (habit.completed_dates && habit.completed_dates.includes(date)) { dateSquare.classList.add('completed'); } dateSquare.title = date; // Show date on hover dateGrid.appendChild(dateSquare); }); habitItem.appendChild(dateGrid); // Streak Counter hinzufügen const streakContainer = document.createElement('div'); streakContainer.className = 'streak-container'; const flameIcon = document.createElement('span'); flameIcon.className = 'flame-icon'; flameIcon.innerHTML = '🔥'; // Flammen-Emoji const streakCount = document.createElement('span'); streakCount.className = 'streak-count'; const currentStreak = calculateStreak(habit.completed_dates); streakCount.textContent = `${currentStreak} Tag${currentStreak !== 1 ? 'e' : ''}`; streakContainer.appendChild(flameIcon); streakContainer.appendChild(streakCount); habitItem.appendChild(streakContainer); habitListDiv.appendChild(habitItem); }); } async function addHabit() { const newHabitNameInput = document.getElementById('newHabitName'); const name = newHabitNameInput.value.trim(); if (name) { try { const response = await fetch('/habits', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name }) }); const newHabit = await response.json(); if (response.ok) { newHabitNameInput.value = ''; fetchHabits(); // Re-fetch and re-render all habits } else { console.error('Failed to add habit:', newHabit.error); } } catch (error) { console.error('Error adding habit:', error); } } } async function openHabitModal(habitId, habitName, completedDates, habitColor = '#4CAF50') { currentHabitId = habitId; currentHabitCompletedDates = completedDates; currentHabitColor = habitColor; currentModalDate = new Date(); // Reset to current month modalHabitName.textContent = habitName; // Setzen der habitId auf den Buttons im Modal modalCompleteTodayBtn.dataset.habitId = habitId; modalEditBtn.dataset.habitId = habitId; modalDeleteBtn.dataset.habitId = habitId; // Set color picker to current habit color document.getElementById('habitColorPicker').value = habitColor; // Set CSS variable for modal elements dateModal.style.setProperty('--current-habit-color', habitColor); // Set contrast color for text on colored backgrounds const contrastColor = getContrastColor(habitColor); dateModal.style.setProperty('--current-habit-text-color', contrastColor); renderModalCalendar(); dateModal.style.display = 'flex'; } function renderModalCalendar() { modalDateGrid.innerHTML = ''; // Update month/year display const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; document.getElementById('currentMonthYear').textContent = `${monthNames[currentModalDate.getMonth()]} ${currentModalDate.getFullYear()}`; const today = getCurrentDate(); const year = currentModalDate.getFullYear(); const month = currentModalDate.getMonth(); // Get first day of month and calculate starting position const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startingDayOfWeek = (firstDay.getDay() + 6) % 7; // Monday = 0 // Add empty cells for days before month starts for (let i = 0; i < startingDayOfWeek; i++) { const emptyCell = document.createElement('div'); emptyCell.className = 'date-cell empty'; modalDateGrid.appendChild(emptyCell); } // Add days of the month for (let day = 1; day <= lastDay.getDate(); day++) { const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const dateCell = document.createElement('div'); dateCell.className = 'date-cell'; dateCell.textContent = day; if (currentHabitCompletedDates.includes(dateStr)) { dateCell.classList.add('completed'); } if (dateStr === today) { dateCell.classList.add('today'); } dateCell.dataset.date = dateStr; dateCell.onclick = (event) => toggleCompletionForDate(event, currentHabitId, dateStr); modalDateGrid.appendChild(dateCell); } } function changeMonth(direction) { currentModalDate.setMonth(currentModalDate.getMonth() + direction); renderModalCalendar(); } function closeModal() { dateModal.style.display = 'none'; currentHabitId = null; // Zurücksetzen, wenn das Modal geschlossen wird fetchHabits(); // Refresh habits after closing modal } async function toggleCompletionForDate(event, habitId, date) { const cell = event.target; let response; let data; if (cell.classList.contains('completed')) { // Mark as uncompleted response = await fetch(`/habits/${habitId}/uncomplete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date }) }); data = await response.json(); if (response.ok) { cell.classList.remove('completed'); // Update local completed dates array const index = currentHabitCompletedDates.indexOf(date); if (index > -1) { currentHabitCompletedDates.splice(index, 1); } } } else { // Mark as completed response = await fetch(`/habits/${habitId}/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date }) }); data = await response.json(); if (response.ok) { cell.classList.add('completed'); // Update local completed dates array if (!currentHabitCompletedDates.includes(date)) { currentHabitCompletedDates.push(date); } } } } async function completeHabitForDate(event, habitId, date) { // Der habitId kommt jetzt direkt vom dataset des Buttons oder der Funktion, die ihn aufruft // Sicherstellen, dass habitId gültig ist if (!habitId) { console.error('Habit ID is undefined for completion.'); return; } try { const response = await fetch(`/habits/${habitId}/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date }) }); const data = await response.json(); if (response.ok) { // Update the modal's date grid immediately const dateCells = modalDateGrid.querySelectorAll('.date-cell'); dateCells.forEach(cell => { if (cell.dataset.date === date) { cell.classList.add('completed'); } }); } else { console.error('Failed to complete habit:', data.error); } } catch (error) { console.error('Error completing habit:', error); } } async function editHabitName() { // Sicherstellen, dass currentHabitId gesetzt ist if (!currentHabitId) { console.error('No habit selected for editing.'); return; } const newName = prompt("Neuen Namen für die Gewohnheit eingeben:", modalHabitName.textContent); if (newName && newName.trim() !== "") { try { const response = await fetch(`/habits/${currentHabitId}`, { // Verwendet currentHabitId method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newName.trim() }) }); const data = await response.json(); if (response.ok) { modalHabitName.textContent = newName.trim(); fetchHabits(); // Re-fetch and re-render all habits to update the main view } else { console.error('Failed to update habit:', data.error); } } catch (error) { console.error('Error updating habit:', error); } } } async function deleteHabitFromModal() { // Sicherstellen, dass currentHabitId gesetzt ist if (!currentHabitId) { console.error('No habit selected for deletion.'); return; } if (confirm("Bist du sicher, dass du diese Gewohnheit löschen möchtest?")) { try { const response = await fetch(`/habits/${currentHabitId}`, { // Verwendet currentHabitId method: 'DELETE' }); const data = await response.json(); if (response.ok) { closeModal(); fetchHabits(); // Re-fetch and re-render all habits } else { console.error('Failed to delete habit:', data.error); } } catch (error) { console.error('Error deleting habit:', error); } } } async function toggleTodayCompletion(habitId, date, buttonElement) { try { const isCurrentlyCompleted = buttonElement.classList.contains('completed-today'); let response; if (isCurrentlyCompleted) { // Mark as uncompleted response = await fetch(`/habits/${habitId}/uncomplete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date }) }); } else { // Mark as completed response = await fetch(`/habits/${habitId}/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date }) }); } const data = await response.json(); if (response.ok) { // Update button appearance if (isCurrentlyCompleted) { buttonElement.classList.remove('completed-today'); buttonElement.classList.add('not-completed-today'); } else { buttonElement.classList.remove('not-completed-today'); buttonElement.classList.add('completed-today'); } // Refresh the date grid to show updated completion status fetchHabits(); } else { console.error('Failed to toggle habit completion:', data.error); } } catch (error) { console.error('Error toggling habit completion:', error); } } async function updateHabitColor() { if (!currentHabitId) { console.error('No habit selected for color update.'); return; } const newColor = document.getElementById('habitColorPicker').value; currentHabitColor = newColor; try { const response = await fetch(`/habits/${currentHabitId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ color: newColor }) }); const data = await response.json(); if (response.ok) { // Update CSS variable for modal elements dateModal.style.setProperty('--current-habit-color', newColor); // Update contrast color for text on colored backgrounds const contrastColor = getContrastColor(newColor); dateModal.style.setProperty('--current-habit-text-color', contrastColor); // Update modal calendar colors immediately updateModalColors(newColor); // Refresh habits list to show new color fetchHabits(); } else { console.error('Failed to update habit color:', data.error); } } catch (error) { console.error('Error updating habit color:', error); } } function updateModalColors(color) { // Update completed date cells in modal const completedCells = modalDateGrid.querySelectorAll('.date-cell.completed'); completedCells.forEach(cell => { cell.style.backgroundColor = color; }); } function getContrastColor(hexColor) { // Convert hex to RGB const r = parseInt(hexColor.slice(1, 3), 16); const g = parseInt(hexColor.slice(3, 5), 16); const b = parseInt(hexColor.slice(5, 7), 16); // Calculate luminance const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; // Return dark text for light colors, light text for dark colors return luminance > 0.5 ? '#333333' : '#ffffff'; }