No recent recognitions yet.
';
return;
}
recentLogs.forEach(log => {
const giver = this.findEmployeeById(log.from);
const receiver = this.findEmployeeById(log.to);
const category = this.findCategoryById(log.categoryId);
if (!giver || !receiver || !category) return;
const item = document.createElement('div');
item.className = 'ers-recognition-item';
item.innerHTML = `
${receiver.name.charAt(0)}
"${log.message || 'For ' + category.name}"
`;
feed.appendChild(item);
});
},
renderRecognitionForm() {
const employeeSelect = this.elements.selectEmployee;
const categorySelect = this.elements.selectCategory;
if (!employeeSelect || !categorySelect) return;
employeeSelect.innerHTML = '
';
this.state.employees.forEach(emp => {
if (emp.id !== this.state.currentUser.id) {
employeeSelect.innerHTML += `
`;
}
});
categorySelect.innerHTML = '
';
this.state.recognitionCategories.forEach(cat => {
categorySelect.innerHTML += `
`;
});
this.updateRecognitionPoints();
},
updateRecognitionPoints() {
const categoryId = this.elements.selectCategory?.value;
if (categoryId) {
const category = this.findCategoryById(parseInt(categoryId));
if (category) {
this.elements.recognitionPoints.value = category.points;
}
} else {
this.elements.recognitionPoints.value = '';
}
},
renderRewards() {
const catalog = this.elements.rewardsCatalog;
if (!catalog) return;
catalog.innerHTML = '';
this.state.rewards.forEach(reward => {
const canAfford = this.state.currentUser.points >= reward.cost;
const card = document.createElement('div');
card.className = 'ers-reward-card';
card.innerHTML = `
${reward.name}
${reward.cost} PTS
`;
catalog.appendChild(card);
});
},
renderHistory() {
const recTable = this.elements.recognitionHistoryTable;
const redTable = this.elements.redemptionHistoryTable;
if (!recTable || !redTable) return;
recTable.innerHTML = '';
this.state.recognitionLog
.filter(log => log.to === this.state.currentUser.id)
.forEach(log => {
const giver = this.findEmployeeById(log.from);
const category = this.findCategoryById(log.categoryId);
if (!giver || !category) return;
recTable.innerHTML += `
| ${log.date} |
${giver.name} |
${category.name} |
${log.message} |
+${category.points} |
`;
});
redTable.innerHTML = '';
this.state.redemptionLog
.filter(log => log.userId === this.state.currentUser.id)
.forEach(log => {
const reward = this.findRewardById(log.rewardId);
if (!reward) return;
redTable.innerHTML += `
| ${log.date} |
${reward.name} |
-${reward.cost} |
`;
});
},
renderAdminTables() {
const empTable = this.elements.admin.employeesTable;
const rewTable = this.elements.admin.rewardsTable;
const catTable = this.elements.admin.categoriesTable;
if (!empTable || !rewTable || !catTable) return;
empTable.innerHTML = '';
this.state.employees.forEach(e => {
empTable.innerHTML += `
| ${e.id} |
${e.name} |
${e.points} |
|
`;
});
rewTable.innerHTML = '';
this.state.rewards.forEach(r => {
rewTable.innerHTML += `
| ${r.id} |
${r.name} |
$${r.cost.toFixed(2)} |
|
`;
});
catTable.innerHTML = '';
this.state.recognitionCategories.forEach(c => {
catTable.innerHTML += `
| ${c.id} |
${c.name} |
${c.points} |
|
`;
});
},
// --- EVENT HANDLERS & ACTIONS ---
changeTab(tabIndex) {
if (tabIndex < 0 || tabIndex >= this.elements.tabButtons.length) return;
this.state.currentTab = tabIndex;
this.elements.tabButtons.forEach((btn, index) => {
btn.classList.toggle('active', index === tabIndex);
});
this.elements.tabContents.forEach((content, index) => {
content.classList.toggle('active', index === tabIndex);
});
this.updateNavButtons();
},
updateNavButtons() {
if (!this.elements.prevTabBtn || !this.elements.nextTabBtn) return;
this.elements.prevTabBtn.style.visibility = this.state.currentTab === 0 ? 'hidden' : 'visible';
this.elements.nextTabBtn.style.visibility = this.state.currentTab === this.elements.tabButtons.length - 1 ? 'hidden' : 'visible';
},
handleRecognitionSubmit(e) {
e.preventDefault();
const form = this.elements.recognitionForm;
if (!form) return;
const employeeId = parseInt(form.querySelector('#select-employee').value);
const categoryId = parseInt(form.querySelector('#select-category').value);
const message = form.querySelector('#recognition-message').value;
if (!employeeId || !categoryId) {
this.showNotification('Please select an employee and a category.', 'error');
return;
}
const category = this.findCategoryById(categoryId);
const recipient = this.findEmployeeById(employeeId);
if (!category || !recipient) return;
// Update state
recipient.points += category.points;
this.state.recognitionLog.push({
from: this.state.currentUser.id,
to: employeeId,
categoryId: categoryId,
message: message,
date: new Date().toISOString().split('T')[0]
});
this.showNotification(`Successfully recognized ${recipient.name}!`, 'success');
form.reset();
this.renderAll();
this.changeTab(0); // Switch to dashboard after submission
},
handleRedeemReward(e) {
if (e.target.matches('[data-reward-id]')) {
const rewardId = parseInt(e.target.dataset.rewardId);
const reward = this.findRewardById(rewardId);
if (!reward) return;
if (this.state.currentUser.points >= reward.cost) {
this.state.currentUser.points -= reward.cost;
this.state.redemptionLog.push({
userId: this.state.currentUser.id,
rewardId: rewardId,
date: new Date().toISOString().split('T')[0]
});
this.showNotification(`You have redeemed "${reward.name}"!`, 'success');
this.renderAll();
} else {
this.showNotification('You do not have enough points for this reward.', 'error');
}
}
},
downloadHistoryPDF() {
// Guard clause for PDF library
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
this.showNotification('PDF generation is currently unavailable.', 'error');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const userName = this.state.currentUser.name;
const date = new Date().toLocaleDateString();
// PDF Header
doc.setFontSize(18);
doc.text(`Recognition & Redemption History for ${userName}`, 14, 22);
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(`Report generated on: ${date}`, 14, 30);
// Recognition Received Table
const recData = this.state.recognitionLog
.filter(log => log.to === this.state.currentUser.id)
.map(log => {
const giver = this.findEmployeeById(log.from);
const category = this.findCategoryById(log.categoryId);
return [log.date, giver.name, category.name, `+${category.points}`, log.message];
});
doc.autoTable({
startY: 40,
head: [['Date', 'From', 'Category', 'Points', 'Message']],
body: recData,
theme: 'striped',
headStyles: { fillColor: [79, 70, 229] } // --primary-color
});
// Rewards Redeemed Table
const redData = this.state.redemptionLog
.filter(log => log.userId === this.state.currentUser.id)
.map(log => {
const reward = this.findRewardById(log.rewardId);
return [log.date, reward.name, `-${reward.cost}`];
});
if(redData.length > 0) {
doc.autoTable({
startY: doc.autoTable.previous.finalY + 15,
head: [['Date', 'Reward Redeemed', 'Point Cost']],
body: redData,
theme: 'striped',
headStyles: { fillColor: [79, 70, 229] }
});
}
doc.save(`${userName}_Recognition_History.pdf`);
this.showNotification('PDF download started.', 'success');
},
// --- ADMIN MODAL & CRUD LOGIC ---
openModal(type, id = null) {
const form = this.elements.modalForm;
const fieldsContainer = this.elements.modalFields;
const title = this.elements.modalTitle;
const editIdInput = this.elements.modalEditId;
if (!form || !fieldsContainer || !title || !editIdInput) return;
form.reset();
form.dataset.type = type;
editIdInput.value = id || '';
let fieldsHTML = '';
let item = null;
switch (type) {
case 'employee':
title.textContent = id ? 'Edit Employee' : 'Add Employee';
item = id ? this.findEmployeeById(id) : {};
fieldsHTML = `
`;
break;
case 'reward':
title.textContent = id ? 'Edit Reward' : 'Add Reward';
item = id ? this.findRewardById(id) : {};
fieldsHTML = `
`;
break;
case 'category':
title.textContent = id ? 'Edit Category' : 'Add Category';
item = id ? this.findCategoryById(id) : {};
fieldsHTML = `
`;
break;
}
fieldsContainer.innerHTML = fieldsHTML;
this.elements.modal.style.display = 'flex';
},
closeModal() {
if (this.elements.modal) this.elements.modal.style.display = 'none';
},
editItem(type, id) {
this.openModal(type, id);
},
deleteItem(type, id) {
if (confirm(`Are you sure you want to delete this ${type}? This action cannot be undone.`)) {
let dataArray;
switch (type) {
case 'employee': dataArray = this.state.employees; break;
case 'reward': dataArray = this.state.rewards; break;
case 'category': dataArray = this.state.recognitionCategories; break;
}
const index = dataArray.findIndex(item => item.id === id);
if (index > -1) {
dataArray.splice(index, 1);
this.showNotification(`${type.charAt(0).toUpperCase() + type.slice(1)} deleted.`, 'success');
this.renderAll();
}
}
},
handleModalFormSubmit(e) {
e.preventDefault();
const type = e.target.dataset.type;
const id = parseInt(this.elements.modalEditId.value);
let dataArray, newItem;
const name = document.getElementById('modal-name').value;
switch (type) {
case 'employee':
dataArray = this.state.employees;
const points = parseInt(document.getElementById('modal-points').value);
newItem = { id: id || this.getNextId(dataArray), name, points };
break;
case 'reward':
dataArray = this.state.rewards;
const cost = parseInt(document.getElementById('modal-cost').value);
newItem = { id: id || this.getNextId(dataArray), name, cost };
break;
case 'category':
dataArray = this.state.recognitionCategories;
const catPoints = parseInt(document.getElementById('modal-points').value);
newItem = { id: id || this.getNextId(dataArray), name, points: catPoints };
break;
}
if (id) { // Editing existing
const index = dataArray.findIndex(item => item.id === id);
if (index > -1) dataArray[index] = newItem;
} else { // Adding new
dataArray.push(newItem);
}
this.showNotification(`${type.charAt(0).toUpperCase() + type.slice(1)} saved successfully.`, 'success');
this.closeModal();
this.renderAll();
},
// --- UTILITY FUNCTIONS ---
findEmployeeById(id) {
return this.state.employees.find(e => e.id === id);
},
findCategoryById(id) {
return this.state.recognitionCategories.find(c => c.id === id);
},
findRewardById(id) {
return this.state.rewards.find(r => r.id === id);
},
getNextId(array) {
return array.length > 0 ? Math.max(...array.map(item => item.id)) + 1 : 1;
},
showNotification(message, type = 'success') {
const box = this.elements.notificationBox;
if (!box) return;
box.textContent = message;
box.className = `ers-notification ${type}`;
box.classList.add('show');
setTimeout(() => {
box.classList.remove('show');
}, 3000);
}
};
// Make ERS object globally accessible for inline onclick handlers
window.ERS = ERS;
// Start the application
ERS.init();
});