${consultant.utilization.toFixed(1)}%
${consultant.billable}h Billable
`;
listEl.innerHTML += item;
});
};
const renderUtilizationTable = (data) => {
const tableBody = document.getElementById('utilization-table-body');
tableBody.innerHTML = '';
if(data.length === 0) {
tableBody.innerHTML = `
| No consultant data to display. |
`;
return;
}
data.forEach(c => {
const row = `
|
${c.name}
|
${c.role} |
${c.targetHours} |
${c.billable} |
${c.utilization.toFixed(1)}%
|
`;
tableBody.innerHTML += row;
});
};
const renderConsultantsList = () => {
const listBody = document.getElementById('consultants-list-body');
listBody.innerHTML = '';
if(consultants.length === 0) {
listBody.innerHTML = `
| No consultants added yet. |
`;
return;
}
consultants.forEach(c => {
const row = `
| ${c.name} |
${c.role} |
$${c.rate} |
${c.targetHours} |
|
`;
listBody.innerHTML += row;
});
};
const populateConsultantSelect = () => {
const select = document.getElementById('hours-consultant-select');
select.innerHTML = '
';
consultants.forEach(c => {
const log = hoursLog.find(h => h.consultantId === c.id);
// Disable option if hours are already logged to prevent duplicates in this simple model
const disabled = log ? 'disabled' : '';
const suffix = log ? ' (Hours Logged)' : '';
select.innerHTML += `
`;
});
};
// --- EVENT HANDLERS & LOGIC ---
// Tab switching
tabButtons.forEach(button => {
button.addEventListener('click', () => {
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
const tabId = button.dataset.tab;
tabContents.forEach(content => {
if (content.id === `${tabId}-tab`) {
content.classList.remove('hidden');
} else {
content.classList.add('hidden');
}
});
});
});
// Consultant Form
consultantForm.addEventListener('submit', (e) => {
e.preventDefault();
const id = parseInt(consultantForm.querySelector('#consultant-id').value);
const name = consultantForm.querySelector('#consultant-name').value;
const role = consultantForm.querySelector('#consultant-role').value;
const rate = parseFloat(consultantForm.querySelector('#consultant-rate').value);
const targetHours = parseInt(consultantForm.querySelector('#consultant-target-hours').value);
if (id) { // Update existing
const index = consultants.findIndex(c => c.id === id);
if (index > -1) {
consultants[index] = { id, name, role, rate, targetHours };
}
} else { // Add new
const newId = consultants.length > 0 ? Math.max(...consultants.map(c => c.id)) + 1 : 1;
consultants.push({ id: newId, name, role, rate, targetHours });
}
consultantForm.reset();
consultantForm.querySelector('#consultant-id').value = '';
renderAll();
});
document.getElementById('clear-consultant-form').addEventListener('click', () => {
consultantForm.reset();
consultantForm.querySelector('#consultant-id').value = '';
});
// Hours Form
hoursForm.addEventListener('submit', (e) => {
e.preventDefault();
const consultantId = parseInt(hoursForm.querySelector('#hours-consultant-select').value);
const billable = parseInt(hoursForm.querySelector('#hours-billable').value);
const nonBillable = parseInt(hoursForm.querySelector('#hours-non-billable').value);
if (!consultantId) {
alert('Please select a consultant.');
return;
}
// In this simple model, we add a new log. A more complex app would update or aggregate.
const existingLog = hoursLog.find(h => h.consultantId === consultantId);
if (existingLog) {
alert('Hours for this consultant are already logged. Please edit their data on the dashboard or implement an "update" feature.');
return;
}
const newLogId = hoursLog.length > 0 ? Math.max(...hoursLog.map(h => h.logId)) + 1 : 1;
hoursLog.push({ logId: newLogId, consultantId, billable, nonBillable });
hoursForm.reset();
renderAll();
});
document.getElementById('clear-hours-form').addEventListener('click', () => {
hoursForm.reset();
});
// PDF Download
document.getElementById('download-pdf-btn').addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const dashboardContent = document.getElementById('dashboard-content-to-print');
const pdfButton = document.getElementById('download-pdf-btn');
// Hide button before capture
pdfButton.classList.add('hidden');
html2canvas(dashboardContent, {
scale: 2 // Increase scale for better resolution
}).then(canvas => {
// Show button again after capture
pdfButton.classList.remove('hidden');
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [canvas.width, canvas.height]
});
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
pdf.save('Consultant_Utilization_Dashboard.pdf');
}).catch(err => {
console.error("PDF generation failed:", err);
// Ensure button is shown even if there's an error
pdfButton.classList.remove('hidden');
});
});
// --- GLOBAL FUNCTIONS FOR ONCLICK ---
window.editConsultant = (id) => {
const consultant = consultants.find(c => c.id === id);
if (consultant) {
document.getElementById('consultant-id').value = consultant.id;
document.getElementById('consultant-name').value = consultant.name;
document.getElementById('consultant-role').value = consultant.role;
document.getElementById('consultant-rate').value = consultant.rate;
document.getElementById('consultant-target-hours').value = consultant.targetHours;
// Switch to config tab for user convenience
document.querySelector('.tab-btn[data-tab="config"]').click();
}
};
window.deleteConsultant = (id) => {
if (confirm('Are you sure you want to delete this consultant and their logged hours?')) {
consultants = consultants.filter(c => c.id !== id);
hoursLog = hoursLog.filter(h => h.consultantId !== id);
renderAll();
}
};
// --- INITIAL RENDER ---
renderAll();
});