`).join('');
}
// --- COMPLIANCE LOGIC --- //
function handleAddEmployee() {
const name = newEmployeeNameInput.value.trim();
if (!name) {
showNotification('Employee name is required.', 'warning');
return;
}
const newEmployee = { id: Date.now(), name };
employees.push(newEmployee);
// Create compliance records for the new employee for all existing policies
policies.forEach(pol => {
complianceRecords.push({ employeeId: newEmployee.id, policyId: pol.id, acknowledged: false });
});
saveDataToStorage();
renderComplianceTracking();
updateDashboard();
newEmployeeNameInput.value = '';
showNotification('Employee added successfully.', 'success');
}
function handleLogAcknowledgment() {
const employeeId = parseInt(ackEmployeeSelect.value);
const policyId = parseInt(ackPolicySelect.value);
const record = complianceRecords.find(r => r.employeeId === employeeId && r.policyId === policyId);
if (record) {
record.acknowledged = true;
saveDataToStorage();
renderComplianceTracking();
updateDashboard();
showNotification('Acknowledgment logged.', 'success');
} else {
showNotification('Could not find compliance record.', 'warning');
}
}
function handleComplianceAction(e) {
const button = e.target.closest('button[data-action="revoke"]');
if (!button) return;
const employeeId = parseInt(button.dataset.employeeId);
const policyId = parseInt(button.dataset.policyId);
const record = complianceRecords.find(r => r.employeeId === employeeId && r.policyId === policyId);
if (record) {
record.acknowledged = false;
saveDataToStorage();
renderComplianceTracking();
updateDashboard();
showNotification('Acknowledgment revoked.', 'info');
}
}
function renderComplianceTracking() {
// Populate dropdowns
ackEmployeeSelect.innerHTML = employees.map(e => `
`).join('');
ackPolicySelect.innerHTML = policies.map(p => `
`).join('');
// Populate table
complianceTableBody.innerHTML = complianceRecords.map(rec => {
const employee = employees.find(e => e.id === rec.employeeId);
const policy = policies.find(p => p.id === rec.policyId);
if (!employee || !policy) return ''; // Skip if data is inconsistent
const statusText = rec.acknowledged ? 'Acknowledged' : 'Pending';
const statusColor = rec.acknowledged ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800';
const actionBtn = rec.acknowledged ? `
` : '';
return `
| ${employee.name} |
${policy.title} |
${statusText} |
${actionBtn} |
`;
}).join('');
}
// --- DASHBOARD --- //
function updateDashboard() {
const totalPossibleAcks = employees.length * policies.length;
const completedAcks = complianceRecords.filter(r => r.acknowledged).length;
const pendingAcks = totalPossibleAcks - completedAcks;
const overallCompliance = totalPossibleAcks > 0 ? Math.round((completedAcks / totalPossibleAcks) * 100) : 100;
overallComplianceStat.textContent = `${overallCompliance}%`;
totalPoliciesStat.textContent = policies.length;
pendingAcksStat.textContent = pendingAcks;
const policyComplianceRates = policies.map(policy => {
const totalForPolicy = employees.length;
const completedForPolicy = complianceRecords.filter(r => r.policyId === policy.id && r.acknowledged).length;
const rate = totalForPolicy > 0 ? (completedForPolicy / totalForPolicy) * 100 : 0;
return { title: policy.title, rate: rate };
});
renderComplianceChart(policyComplianceRates);
}
function renderComplianceChart(data) {
const ctx = document.getElementById('complianceChart');
if (!ctx) return;
const labels = data.map(d => d.title);
const rates = data.map(d => d.rate);
if (complianceChart) complianceChart.destroy();
complianceChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Compliance Rate (%)',
data: rates,
backgroundColor: 'rgba(59, 130, 246, 0.7)',
borderColor: 'rgba(59, 130, 246, 1)',
borderWidth: 1
}]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: { y: { beginAtZero: true, max: 100 } },
plugins: { legend: { display: false } }
}
});
}
// --- PDF GENERATION --- //
function generatePDF() {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
let y = 40;
const margin = 40;
const pdfWidth = pdf.internal.pageSize.getWidth();
pdf.setFontSize(20);
pdf.setFont('helvetica', 'bold');
pdf.text('Compliance Summary Report', pdfWidth / 2, y, { align: 'center' });
y += 40;
pdf.setFontSize(12);
pdf.text(`Overall Compliance Rate: ${overallComplianceStat.textContent}`, margin, y);
y += 20;
pdf.text(`Pending Acknowledgments: ${pendingAcksStat.textContent}`, margin, y);
y += 40;
const headers = [['Employee', 'Policy', 'Status']];
const body = complianceRecords.filter(rec => !rec.acknowledged).map(rec => {
const employee = employees.find(e => e.id === rec.employeeId);
const policy = policies.find(p => p.id === rec.policyId);
return [employee ? employee.name : 'N/A', policy ? policy.title : 'N/A', 'Pending'];
});
pdf.setFontSize(14);
pdf.setFont('helvetica', 'bold');
pdf.text('Outstanding Compliance Items', margin, y);
pdf.autoTable({
startY: y + 20,
head: headers,
body: body,
theme: 'grid',
headStyles: { fillColor: [31, 41, 55] }
});
pdf.save('Compliance-Report.pdf');
}
// --- TAB NAVIGATION --- //
function setActiveTab(index) {
tabs.forEach((tab, i) => tab.classList.toggle('active', i === index));
tabPanels.forEach((panel, i) => panel.classList.toggle('hidden', i !== index));
if (index === 0) updateDashboard(); // Refresh dashboard on view
}
// --- KICK IT OFF --- //
initializeApp();
});