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