`;
stageBarChartEl.appendChild(barItem);
});
}
/**
* Renders the drug pipeline data into the data configuration table with edit/delete actions.
*/
function renderManagePipelineTable() {
if (!managePipelineTableBody) {
console.error('Manage pipeline table body not found.');
return;
}
managePipelineTableBody.innerHTML = ''; // Clear existing rows
if (drugPipelineData.length === 0) {
managePipelineTableBody.innerHTML = `
No drug pipeline entries to manage.
`;
return;
}
drugPipelineData.forEach(drug => {
const row = managePipelineTableBody.insertRow();
row.setAttribute('data-drug-id', drug.id); // Set data attribute for easy lookup
row.classList.add('hover:bg-gray-50');
row.innerHTML = `
`;
});
}
/**
* Adds a new drug pipeline entry from the input fields.
*/
function addDrug() {
if (!newDrugNameInput || !newCurrentStageSelect || !newStageStartDateInput || !newEstEndDateInput || !newSuccessProbabilityInput || !newTherapeuticAreaInput || !newLeadScientistInput) {
console.error('New drug input elements not found.');
return;
}
const name = newDrugNameInput.value.trim();
const currentStage = newCurrentStageSelect.value;
const stageStartDate = newStageStartDateInput.value;
const estEndDate = newEstEndDateInput.value;
const successProbability = parseFloat(newSuccessProbabilityInput.value);
const therapeuticArea = newTherapeuticAreaInput.value.trim();
const leadScientist = newLeadScientistInput.value.trim();
if (!name || !currentStage || !stageStartDate || !estEndDate || isNaN(successProbability) || successProbability < 0 || successProbability > 100 || !therapeuticArea || !leadScientist) {
alert('Please fill in all fields correctly: Drug Name, Current Stage, Stage Start Date, Estimated End Date, Success Probability (0-100), Therapeutic Area, and Lead Scientist/Team.');
return;
}
if (new Date(stageStartDate) > new Date(estEndDate)) {
alert('Stage Start Date cannot be after Estimated End Date.');
return;
}
drugPipelineData.push({
id: generateUniqueId(),
name: name,
currentStage: currentStage,
stageStartDate: stageStartDate,
estEndDate: estEndDate,
successProbability: successProbability,
therapeuticArea: therapeuticArea,
leadScientist: leadScientist
});
// Clear input fields
newDrugNameInput.value = '';
newCurrentStageSelect.value = 'Discovery'; // Reset to default
newStageStartDateInput.value = '';
newEstEndDateInput.value = '';
newSuccessProbabilityInput.value = '';
newTherapeuticAreaInput.value = '';
newLeadScientistInput.value = '';
// Re-render tables and update metrics
updateAllViews();
}
/**
* Updates an existing drug pipeline entry based on changes in the manage table.
* This function is exposed globally for `onclick` attributes.
* @param {string} drugId - The ID of the drug to update.
* @param {HTMLElement} buttonElement - The button element that triggered the update.
*/
window.updateDrug = function(drugId, buttonElement) {
const row = buttonElement.closest('tr');
if (!row) {
console.error('Could not find row for drugId:', drugId);
return;
}
const nameInput = row.querySelector('[data-field="name"]');
const currentStageSelect = row.querySelector('[data-field="currentStage"]');
const stageStartDateInput = row.querySelector('[data-field="stageStartDate"]');
const estEndDateInput = row.querySelector('[data-field="estEndDate"]');
const successProbabilityInput = row.querySelector('[data-field="successProbability"]');
const therapeuticAreaInput = row.querySelector('[data-field="therapeuticArea"]');
const leadScientistInput = row.querySelector('[data-field="leadScientist"]');
if (!nameInput || !currentStageSelect || !stageStartDateInput || !estEndDateInput || !successProbabilityInput || !therapeuticAreaInput || !leadScientistInput) {
console.error('Input fields not found in row for drugId:', drugId);
return;
}
const updatedName = nameInput.value.trim();
const updatedCurrentStage = currentStageSelect.value;
const updatedStageStartDate = stageStartDateInput.value;
const updatedEstEndDate = estEndDateInput.value;
const updatedSuccessProbability = parseFloat(successProbabilityInput.value);
const updatedTherapeuticArea = therapeuticAreaInput.value.trim();
const updatedLeadScientist = leadScientistInput.value.trim();
if (!updatedName || !updatedCurrentStage || !updatedStageStartDate || !updatedEstEndDate || isNaN(updatedSuccessProbability) || updatedSuccessProbability < 0 || updatedSuccessProbability > 100 || !updatedTherapeuticArea || !updatedLeadScientist) {
alert('Please ensure all fields are valid before saving.');
return;
}
if (new Date(updatedStageStartDate) > new Date(updatedEstEndDate)) {
alert('Stage Start Date cannot be after Estimated End Date.');
return;
}
const drugIndex = drugPipelineData.findIndex(d => d.id === drugId);
if (drugIndex !== -1) {
drugPipelineData[drugIndex] = {
...drugPipelineData[drugIndex],
name: updatedName,
currentStage: updatedCurrentStage,
stageStartDate: updatedStageStartDate,
estEndDate: updatedEstEndDate,
successProbability: updatedSuccessProbability,
therapeuticArea: updatedTherapeuticArea,
leadScientist: updatedLeadScientist
};
updateAllViews();
} else {
console.warn('Drug pipeline entry not found for update:', drugId);
}
};
/**
* Deletes a drug pipeline entry from the list.
* This function is exposed globally for `onclick` attributes.
* @param {string} drugId - The ID of the drug to delete.
*/
window.deleteDrug = function(drugId) {
const initialLength = drugPipelineData.length;
drugPipelineData = drugPipelineData.filter(drug => drug.id !== drugId);
if (drugPipelineData.length < initialLength) {
updateAllViews();
} else {
console.warn('Drug pipeline entry not found for deletion:', drugId);
}
};
/**
* Updates all relevant views (metrics, dashboard table, manage table, bar chart).
*/
function updateAllViews() {
updateDashboardMetrics();
renderPipelineTable();
renderStageBarChart();
renderManagePipelineTable();
}
/**
* Generates and downloads a PDF of the dashboard content.
*/
function downloadDashboardPdf() {
// Ensure jsPDF is available
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
console.error("jsPDF library not loaded. Cannot generate PDF.");
alert("PDF generation library not loaded. Please try again later.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('landscape'); // Use landscape for wider tables
// Set font and color for consistency
doc.setFont('helvetica');
doc.setTextColor('#1f2937'); // Dark gray
// Title
doc.setFontSize(24);
doc.text('Drug Development Pipeline Dashboard', doc.internal.pageSize.getWidth() / 2, 20, { align: 'center' });
// Add a small separator
doc.setDrawColor('#d1d5db');
doc.line(20, 25, doc.internal.pageSize.getWidth() - 20, 25);
// Dashboard Metrics
doc.setFontSize(14);
let yPos = 40;
const metrics = [
{ label: 'Total Drugs in Pipeline:', value: totalDrugsInPipelineEl ? totalDrugsInPipelineEl.textContent : 'N/A' },
{ label: 'In Clinical Trials:', value: drugsInClinicalTrialsEl ? drugsInClinicalTrialsEl.textContent : 'N/A' },
{ label: 'In Pre-Clinical:', value: drugsInPreClinicalEl ? drugsInPreClinicalEl.textContent : 'N/A' },
{ label: 'Average Success Probability:', value: avgSuccessProbabilityEl ? avgSuccessProbabilityEl.textContent : 'N/A' }
];
metrics.forEach(metric => {
doc.text(`${metric.label} ${metric.value}`, 20, yPos);
yPos += 10;
});
yPos += 15; // Extra space before table
// Drugs by Current Stage (as a table in PDF)
doc.setFontSize(16);
doc.text('Drugs by Current Stage', 20, yPos);
yPos += 10;
const stageCountsForPdf = {};
const orderedStagesForPdf = ['Discovery', 'Pre-Clinical', 'Phase I', 'Phase II', 'Phase III', 'Regulatory Review', 'Approved', 'Discontinued'];
orderedStagesForPdf.forEach(stage => stageCountsForPdf[stage] = 0);
drugPipelineData.forEach(drug => {
if (stageCountsForPdf.hasOwnProperty(drug.currentStage)) {
stageCountsForPdf[drug.currentStage]++;
}
});
const stageTableColumn = ["Stage", "Count"];
const stageTableRows = [];
orderedStagesForPdf.forEach(stage => {
stageTableRows.push([stage, stageCountsForPdf[stage]]);
});
doc.autoTable({
head: [stageTableColumn],
body: stageTableRows,
startY: yPos,
theme: 'grid',
styles: {
fontSize: 10,
cellPadding: 3,
textColor: '#1f2937',
lineColor: '#e5e7eb',
lineWidth: 0.1
},
headStyles: {
fillColor: '#f3f4f6',
textColor: '#4b5563',
fontStyle: 'bold'
},
alternateRowStyles: {
fillColor: '#ffffff'
},
margin: { left: 20, right: 20 },
didDrawPage: function(data) {
let str = "Page " + doc.internal.getNumberOfPages();
doc.setFontSize(10);
doc.setTextColor('#6b7280');
doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' });
}
});
yPos = doc.autoTable.previous.finalY + 15; // Continue from where the last table ended
// Pipeline Overview Table
doc.setFontSize(16);
doc.text('Pipeline Overview', 20, yPos);
yPos += 10;
const overviewTableColumn = ["Drug Name", "Current Stage", "Stage Start Date", "Est. End Date", "Success Prob. (%)", "Therapeutic Area", "Lead Scientist"];
const overviewTableRows = [];
drugPipelineData.forEach(drug => {
overviewTableRows.push([
drug.name,
drug.currentStage,
drug.stageStartDate,
drug.estEndDate,
formatPercentage(drug.successProbability),
drug.therapeuticArea,
drug.leadScientist
]);
});
doc.autoTable({
head: [overviewTableColumn],
body: overviewTableRows,
startY: yPos,
theme: 'grid',
styles: {
fontSize: 6, // Further reduced font size for more columns in landscape
cellPadding: 1,
textColor: '#1f2937',
lineColor: '#e5e7eb',
lineWidth: 0.1
},
headStyles: {
fillColor: '#f3f4f6',
textColor: '#4b5563',
fontStyle: 'bold'
},
alternateRowStyles: {
fillColor: '#ffffff'
},
margin: { left: 10, right: 10 }, // Adjusted margins to fit more content
didDrawPage: function(data) {
let str = "Page " + doc.internal.getNumberOfPages();
doc.setFontSize(10);
doc.setTextColor('#6b7280');
doc.text(str, doc.internal.pageSize.getWidth() - 20, doc.internal.pageSize.getHeight() - 10, { align: 'right' });
}
});
// Save the PDF
doc.save('drug_development_pipeline_dashboard.pdf');
}
// --- Event Listeners ---
// Tab button clicks
if (dashboardTabButton) {
dashboardTabButton.addEventListener('click', function() {
switchTab(0);
});
}
if (dataConfigTabButton) {
dataConfigTabButton.addEventListener('click', function() {
switchTab(1);
});
}
// Navigation button clicks
if (prevTabButton) {
prevTabButton.addEventListener('click', function() {
if (currentTabIndex > 0) {
switchTab(currentTabIndex - 1);
}
});
}
if (nextTabButton) {
nextTabButton.addEventListener('click', function() {
if (currentTabIndex < tabs.length - 1) {
switchTab(currentTabIndex + 1);
}
});
}
// Add Drug button click
if (addDrugButton) {
addDrugButton.addEventListener('click', addDrug);
}
// PDF Download button click
if (downloadPdfButton) {
downloadPdfButton.addEventListener('click', downloadDashboardPdf);
}
// --- Initial Render ---
// Set initial tab to Dashboard
switchTab(0);
// Populate data and update views
updateAllViews();
});