Avg. Case Closure
${avgClosure.toFixed(0)} days
Training Completion
${trainingCompleted.toFixed(0)}%
`;
}
const renderChart = (id, type, data, options) => {
const ctx = document.getElementById(id)?.getContext('2d');
if (!ctx) return;
if(charts[id]) charts[id].destroy();
charts[id] = new Chart(ctx, { type, data, options });
};
function renderCharts(allCases, openCases, allEmployees) {
// By Category
const byCategory = openCases.reduce((acc, c) => { acc[c.category] = (acc[c.category] || 0) + 1; return acc; }, {});
renderChart('ethics-category-chart', 'bar', {
labels: Object.keys(byCategory),
datasets: [{ label: 'Open Cases', data: Object.values(byCategory), backgroundColor: 'rgba(8, 145, 178, 0.7)' }]
}, { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } });
// Over Time
const byMonth = allCases.reduce((acc, c) => {
const month = new Date(c.reportDate).toISOString().slice(0, 7);
acc[month] = (acc[month] || 0) + 1; return acc;
}, {});
renderChart('ethics-time-chart', 'line', {
labels: Object.keys(byMonth).sort(),
datasets: [{ label: 'New Cases Reported', data: Object.values(byMonth), borderColor: '#0ea5e9', tension: 0.1 }]
}, { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } });
// Training
const depts = [...new Set(allEmployees.map(e => e.dept))];
const byDeptTraining = {
Completed: depts.map(dept => allEmployees.filter(e => e.dept === dept && e.training === 'Completed').length),
Pending: depts.map(dept => allEmployees.filter(e => e.dept === dept && e.training === 'Pending').length)
};
renderChart('ethics-training-chart', 'bar', {
labels: depts,
datasets: [
{ label: 'Completed', data: byDeptTraining.Completed, backgroundColor: '#16a34a' },
{ label: 'Pending', data: byDeptTraining.Pending, backgroundColor: '#ef4444' }
]
}, { responsive: true, maintainAspectRatio: false, scales: { x: { stacked: true }, y: { stacked: true } } });
}
function renderCaseQueueTable(openCases) {
document.getElementById('ethics-case-queue-tbody').innerHTML = openCases.map(f => `
| ${f.severity} |
${f.desc} | ${f.dept} |
${f.status} |
${f.status === 'Open' ? `` : ''}
${f.status === 'Under Investigation' ? `` : ''}
|
`).join('');
}
// --- ACTIONS ---
window.ethicsAddCase = function() {
ethicsCases.push({
id: Date.now(),
desc: document.getElementById('add-desc').value,
dept: document.getElementById('add-dept').value,
category: 'Uncategorized', // Simplified for this form
owner: 'Unassigned',
severity: document.getElementById('add-severity').value,
status: 'Open',
reportDate: new Date(),
});
renderAll();
}
function handleActionClick(e) {
if (!e.target.matches('.action-btn')) return;
const id = parseInt(e.target.dataset.id);
const action = e.target.dataset.action;
if (action === 'toggleTraining') {
const emp = employees.find(emp => emp.id === id);
if (emp) emp.training = emp.training === 'Completed' ? 'Pending' : 'Completed';
} else {
const caseItem = ethicsCases.find(c => c.id === id);
if (caseItem) {
if (action === 'investigate') caseItem.status = 'Under Investigation';
else if (action === 'close') {
caseItem.status = 'Closed';
caseItem.closeDate = new Date();
}
}
}
renderAll();
}
// --- UTILITY ---
window.ethicsShowTab = id => {
document.querySelectorAll('.ethics-main-tab-content, .ethics-main-tab-button').forEach(el => el.classList.remove('active'));
document.getElementById(`ethics-tab-${id}`).classList.add('active');
document.querySelector(`.ethics-main-tab-button[onclick="ethicsShowTab('${id}')"]`).classList.add('active');
};
window.ethicsDownloadPDF = () => {
html2canvas(document.getElementById('ethics-pdf-content'), { scale: 2 }).then(canvas => {
const pdf = new jspdf.jsPDF({ orientation: 'landscape', unit: 'pt', format: 'a4' });
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 40, 40, pdf.internal.pageSize.getWidth() - 80, 0);
pdf.save('Ethics_Dashboard_Report.pdf');
});
};
initialize();
});