Chore Tracker with Rewards

Chore Tracker with Rewards

Chore Tracker with Rewards

Current Points Balance

0

Complete a Chore

Redeem Rewards

Recent Activity

Manage Chores

pts

Manage Rewards

pts
'; } ctrConfig.rewards.forEach(reward => { const canAfford = ctrState.currentPoints >= reward.cost; const item = document.createElement('div'); item.className = 'ctr-reward-item'; item.innerHTML = `
${reward.name} (${formatPoints(reward.cost)})
`; rewardsListEl.appendChild(item); }); } function renderActivityLog() { if (!activityLogEl) return; activityLogEl.innerHTML = ''; if (ctrState.activityLog.length === 0) { activityLogEl.innerHTML = '

No activity yet.

'; return; } ctrState.activityLog.forEach(entry => { const item = document.createElement('div'); item.className = 'ctr-log-entry'; const sign = entry.points > 0 ? '+' : ''; const pointsClass = entry.type === 'chore' ? 'ctr-log-chore' : 'ctr-log-reward'; item.innerHTML = ` [${formatTimestamp(entry.timestamp)}] ${entry.type === 'chore' ? 'Completed:' : 'Redeemed:'} ${entry.name} (${sign}${entry.points} pts) `; activityLogEl.appendChild(item); }); } function renderConfigLists() { if (!configChoreList || !configRewardList) return; // 1. Render Chores configChoreList.innerHTML = ''; if (ctrConfig.chores.length === 0) { configChoreList.innerHTML = '

No chores defined.

'; } ctrConfig.chores.forEach(chore => { const item = document.createElement('div'); item.className = 'ctr-list-item'; item.innerHTML = ` ${chore.name} ${formatPoints(chore.points)}
`; configChoreList.appendChild(item); }); // 2. Render Rewards configRewardList.innerHTML = ''; if (ctrConfig.rewards.length === 0) { configRewardList.innerHTML = '

No rewards defined.

'; } ctrConfig.rewards.forEach(reward => { const item = document.createElement('div'); item.className = 'ctr-list-item'; item.innerHTML = ` ${reward.name} ${formatPoints(reward.cost)}
`; configRewardList.appendChild(item); }); } function updateNavButtons() { if (!nextBtn || !prevBtn) return; prevBtn.disabled = currentTabId === 'ctr-tab-dashboard'; nextBtn.disabled = currentTabId === 'ctr-tab-config'; } function fullRefreshUI() { renderDashboard(); renderConfigLists(); renderActivityLog(); updateNavButtons(); } // === EVENT HANDLERS (Must be global for onclick) === window.ctrShowTab = (tabId, element) => { container.querySelectorAll('.ctr-tab-content').forEach(tab => tab.style.display = 'none'); container.querySelectorAll('.ctr-tab-link').forEach(link => link.classList.remove('ctr-active')); const tabToShow = container.querySelector('#' + tabId); if (tabToShow) tabToShow.style.display = 'block'; if (element) element.classList.add('ctr-active'); currentTabId = tabId; updateNavButtons(); }; window.ctrNavigateTabs = (isNext) => { const tabs = Array.from(container.querySelectorAll('.ctr-tab-link')); const currentIdx = tabs.findIndex(tab => tab.classList.contains('ctr-active')); let newIdx = isNext ? currentIdx + 1 : currentIdx - 1; if (newIdx >= 0 && newIdx < tabs.length) { tabs[newIdx].click(); } }; // --- Config Handlers --- window.ctrAddChore = () => { if (!newChoreName || !newChorePoints) return; const name = newChoreName.value.trim(); const points = parseInt(newChorePoints.value); if (!name) { alert('Please enter a chore name.'); return; } if (isNaN(points) || points <= 0) { alert('Please enter a valid positive point value.'); return; } ctrConfig.chores.push({ id: 'c' + Date.now(), name, points }); newChoreName.value = ''; newChorePoints.value = ''; renderConfigLists(); renderDashboard(); // Update dropdown }; window.ctrDeleteChore = (id) => { if (!confirm('Are you sure you want to delete this chore?')) return; ctrConfig.chores = ctrConfig.chores.filter(c => c.id !== id); renderConfigLists(); renderDashboard(); // Update dropdown }; window.ctrAddReward = () => { if (!newRewardName || !newRewardCost) return; const name = newRewardName.value.trim(); const cost = parseInt(newRewardCost.value); if (!name) { alert('Please enter a reward name.'); return; } if (isNaN(cost) || cost <= 0) { alert('Please enter a valid positive point cost.'); return; } ctrConfig.rewards.push({ id: 'r' + Date.now(), name, cost }); newRewardName.value = ''; newRewardCost.value = ''; renderConfigLists(); renderDashboard(); // Update rewards list }; window.ctrDeleteReward = (id) => { if (!confirm('Are you sure you want to delete this reward?')) return; ctrConfig.rewards = ctrConfig.rewards.filter(r => r.id !== id); renderConfigLists(); renderDashboard(); // Update rewards list }; // --- Dashboard Handlers --- window.ctrCompleteChore = () => { if (!choreSelect) return; const choreId = choreSelect.value; if (!choreId) { alert('Please select a chore to complete.'); return; } const chore = ctrConfig.chores.find(c => c.id === choreId); if (!chore) { alert('Error: Selected chore not found.'); return; } ctrState.currentPoints += chore.points; logActivity('chore', chore, chore.points); renderDashboard(); choreSelect.value = ''; // Reset dropdown }; window.ctrRedeemReward = (id) => { const reward = ctrConfig.rewards.find(r => r.id === id); if (!reward) { alert('Error: Selected reward not found.'); return; } if (ctrState.currentPoints < reward.cost) { alert('Not enough points to redeem this reward!'); return; } if (!confirm(`Redeem "${reward.name}" for ${reward.cost} points?`)) return; ctrState.currentPoints -= reward.cost; logActivity('reward', reward, -reward.cost); renderDashboard(); }; // --- PDF Download --- window.ctrDownloadPDF = () => { if (!jsPDF || !jsPDF.autoTable) { alert('PDF library not loaded.'); return; } try { const doc = new jsPDF(); const pageMargin = 15; const pageWidth = doc.internal.pageSize.getWidth(); let y = 20; // 1. Title doc.setFontSize(20); doc.text("Chore Tracker Report", pageWidth / 2, y, { align: 'center' }); y += 15; // 2. Current Points doc.setFontSize(14); doc.text(`Current Points Balance: ${ctrState.currentPoints}`, pageMargin, y); y += 15; // 3. Recent Activity Table doc.setFontSize(16); doc.text("Recent Activity Log", pageMargin, y); y += 10; const tableHead = [["Timestamp", "Type", "Item", "Points"]]; const tableBody = ctrState.activityLog.slice(0, 30).map(entry => { // Limit to 30 recent entries const sign = entry.points > 0 ? '+' : ''; return [ formatTimestamp(entry.timestamp), entry.type === 'chore' ? 'Chore Completed' : 'Reward Redeemed', entry.name, `${sign}${entry.points}` ]; }); if (tableBody.length > 0) { doc.autoTable({ startY: y, head: tableHead, body: tableBody, theme: 'striped', headStyles: { fillColor: [0, 95, 204] }, // Primary color }); } else { doc.setFontSize(12); doc.text("No activity recorded yet.", pageMargin, y); } doc.save("Chore_Tracker_Report.pdf"); } catch (e) { console.error("Error generating PDF:", e); alert("An error occurred while generating the PDF."); } }; // === INITIALIZATION === fullRefreshUI(); });