feat: 10.0 - Frontend - Check habit interaction

This commit is contained in:
Echo
2026-02-10 12:28:40 +00:00
parent 0483d73ef8
commit 775f1715d1
2 changed files with 323 additions and 1 deletions

View File

@@ -144,6 +144,46 @@
flex-shrink: 0;
}
/* Habit checkbox */
.habit-checkbox {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
flex-shrink: 0;
background: var(--bg-base);
}
.habit-checkbox:hover:not(.disabled) {
border-color: var(--accent);
background: var(--accent-light, rgba(99, 102, 241, 0.1));
}
.habit-checkbox.checked {
background: var(--accent);
border-color: var(--accent);
}
.habit-checkbox.checked svg {
color: white;
}
.habit-checkbox.disabled {
cursor: not-allowed;
opacity: 0.6;
}
.habit-checkbox svg {
width: 18px;
height: 18px;
color: var(--accent);
}
/* Loading state */
.loading-state {
text-align: center;
@@ -575,8 +615,16 @@
// Determine icon based on frequency
const iconName = habit.frequency === 'daily' ? 'calendar' : 'clock';
// Checkbox state
const isChecked = habit.checkedToday || false;
const checkboxClass = isChecked ? 'habit-checkbox checked disabled' : 'habit-checkbox';
const checkIcon = isChecked ? '<i data-lucide="check"></i>' : '';
// Create card HTML
card.innerHTML = `
<div class="${checkboxClass}" data-habit-id="${habit.id}" onclick="checkHabit('${habit.id}', this)">
${checkIcon}
</div>
<div class="habit-icon">
<i data-lucide="${iconName}"></i>
</div>
@@ -585,7 +633,7 @@
<div class="habit-frequency">${habit.frequency === 'daily' ? 'Zilnic' : 'Săptămânal'}</div>
</div>
<div class="habit-streak">
<span>${habit.streak || 0}</span>
<span id="streak-${habit.id}">${habit.streak || 0}</span>
<span>🔥</span>
</div>
`;
@@ -600,6 +648,58 @@
return div.innerHTML;
}
// Check habit (mark as done for today)
async function checkHabit(habitId, checkboxElement) {
// Don't allow checking if already checked
if (checkboxElement.classList.contains('disabled')) {
return;
}
// Optimistic UI update
checkboxElement.classList.add('checked', 'disabled');
checkboxElement.innerHTML = '<i data-lucide="check"></i>';
lucide.createIcons();
// Store original state for rollback
const originalCheckbox = checkboxElement.cloneNode(true);
const streakElement = document.getElementById(`streak-${habitId}`);
const originalStreak = streakElement ? streakElement.textContent : '0';
try {
const response = await fetch(`/api/habits/${habitId}/check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error('Failed to check habit');
}
const data = await response.json();
// Update streak with server value
if (streakElement && data.habit && data.habit.streak !== undefined) {
streakElement.textContent = data.habit.streak;
}
showToast('Obișnuință bifată! 🎉');
} catch (error) {
console.error('Error checking habit:', error);
// Revert checkbox on error
checkboxElement.classList.remove('checked', 'disabled');
checkboxElement.innerHTML = '';
// Revert streak
if (streakElement) {
streakElement.textContent = originalStreak;
}
showToast('Eroare la bifarea obișnuinței. Încearcă din nou.');
}
}
// Load habits on page load
document.addEventListener('DOMContentLoaded', () => {
loadHabits();