${item.title}
${item.value}
`;
dashboardSummary.appendChild(card);
});
// Populate campaigns table (sorted by start date descending)
const sortedCampaignData = [...campaignData].sort((a, b) => new Date(b.startDate) - new Date(a.startDate));
sortedCampaignData.forEach(item => {
const conversionRate = item.leadsNurtured > 0 ? ((item.convertedCustomers / item.leadsNurtured) * 100).toFixed(2) : '0.00';
const roi = calculateROI(item.convertedCustomers, item.cost).toFixed(2);
const row = campaignsTableBody.insertRow();
row.innerHTML = `
${item.campaignName}
${item.startDate}
${item.endDate}
${item.status}
${item.targetSegment}
${item.leadsNurtured.toLocaleString()}
${item.convertedCustomers.toLocaleString()}
${conversionRate}%
${item.engagementRate.toFixed(2)}%
$${item.cost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
${roi}%
`;
});
}
// --- Data Configuration Table Rendering and Editing ---
/**
* Renders the data in the configuration table.
*/
function renderConfigTable() {
// Null check for config data table body
if (!configDataTableBody) {
console.error("Config data table body not found.");
return;
}
configDataTableBody.innerHTML = ''; // Clear existing rows
campaignData.forEach((item, index) => {
const row = configDataTableBody.insertRow();
row.innerHTML = `
${item.campaignName}
${item.startDate}
${item.endDate}
${item.status}
${item.targetSegment}
${item.leadsNurtured.toLocaleString()}
${item.convertedCustomers.toLocaleString()}
${item.engagementRate.toFixed(2)}%
$${item.cost.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
Edit
Delete
`;
});
}
/**
* Adds a new row or updates an existing row in the campaign data.
*/
window.addRow = function() {
// Null checks for input fields
if (!campaignNameInput || !startDateInput || !endDateInput || !campaignStatusSelect || !targetSegmentSelect || !leadsNurturedInput || !convertedCustomersInput || !engagementRateInput || !campaignCostInput || !notesInput) {
console.error("One or more input fields not found.");
return;
}
const newCampaign = {
campaignName: campaignNameInput.value.trim(),
startDate: startDateInput.value.trim(),
endDate: endDateInput.value.trim(),
status: campaignStatusSelect.value.trim(),
targetSegment: targetSegmentSelect.value.trim(),
leadsNurtured: parseInt(leadsNurturedInput.value) || 0,
convertedCustomers: parseInt(convertedCustomersInput.value) || 0,
engagementRate: parseFloat(engagementRateInput.value) || 0,
cost: parseFloat(campaignCostInput.value) || 0,
notes: notesInput.value.trim()
};
// Simple validation
if (!newCampaign.campaignName || !newCampaign.startDate || !newCampaign.endDate || newCampaign.leadsNurtured < 0 || newCampaign.convertedCustomers < 0 || newCampaign.engagementRate < 0 || newCampaign.engagementRate > 100 || newCampaign.cost < 0) {
showModal("Please fill in all required fields with valid values (e.g., Campaign Name, Dates, positive numbers for metrics, Engagement Rate between 0-100).");
return;
}
// Check for duplicate Campaign Name if adding a new row
if (editingIndex === -1) {
const isDuplicate = campaignData.some(campaign => campaign.campaignName === newCampaign.campaignName);
if (isDuplicate) {
showModal(`Campaign "${newCampaign.campaignName}" already exists. Please use a unique name.`);
return;
}
campaignData.push(newCampaign);
} else {
// Update existing row
campaignData[editingIndex] = newCampaign;
editingIndex = -1; // Reset editing state
updateRowButton.classList.add('hidden');
cancelEditButton.classList.add('hidden');
addRowButton.classList.remove('hidden');
campaignNameInput.disabled = false; // Re-enable campaign name for new entries
}
clearForm();
renderConfigTable();
updateDashboard(); // Keep dashboard in sync
};
/**
* Populates the form with data from the row to be edited.
* @param {HTMLElement} button - The edit button clicked.
*/
window.editRow = function(button) {
// Null checks for input fields and edit buttons
if (!campaignNameInput || !startDateInput || !endDateInput || !campaignStatusSelect || !targetSegmentSelect || !leadsNurturedInput || !convertedCustomersInput || !engagementRateInput || !campaignCostInput || !notesInput || !addRowButton || !updateRowButton || !cancelEditButton) {
console.error("One or more form/button elements not found for editing.");
return;
}
const index = parseInt(button.dataset.index);
editingIndex = index;
const item = campaignData[index];
campaignNameInput.value = item.campaignName;
startDateInput.value = item.startDate;
endDateInput.value = item.endDate;
campaignStatusSelect.value = item.status;
targetSegmentSelect.value = item.targetSegment;
leadsNurturedInput.value = item.leadsNurtured;
convertedCustomersInput.value = item.convertedCustomers;
engagementRateInput.value = item.engagementRate;
campaignCostInput.value = item.cost;
notesInput.value = item.notes;
addRowButton.classList.add('hidden');
updateRowButton.classList.remove('hidden');
cancelEditButton.classList.remove('hidden');
campaignNameInput.disabled = true; // Disable editing Campaign Name during update
};
/**
* Deletes a row from the campaign data.
* @param {HTMLElement} button - The delete button clicked.
*/
window.deleteRow = function(button) {
const index = parseInt(button.dataset.index);
showModal("Are you sure you want to delete this campaign record?", true, (confirmed) => {
if (confirmed) {
campaignData.splice(index, 1);
renderConfigTable();
updateDashboard(); // Keep dashboard in sync
clearForm(); // Clear form if the deleted item was being edited
editingIndex = -1;
updateRowButton.classList.add('hidden');
cancelEditButton.classList.add('hidden');
addRowButton.classList.remove('hidden');
campaignNameInput.disabled = false; // Re-enable campaign name
}
});
};
/**
* Clears the input form fields.
*/
function clearForm() {
// Null checks for input fields
if (!campaignNameInput || !startDateInput || !endDateInput || !campaignStatusSelect || !targetSegmentSelect || !leadsNurturedInput || !convertedCustomersInput || !engagementRateInput || !campaignCostInput || !notesInput) {
console.error("One or more input fields not found for clearing.");
return;
}
campaignNameInput.value = '';
startDateInput.value = '';
endDateInput.value = '';
campaignStatusSelect.value = 'Planned'; // Reset to default status
targetSegmentSelect.value = 'New Leads'; // Reset to default segment
leadsNurturedInput.value = '';
convertedCustomersInput.value = '';
engagementRateInput.value = '';
campaignCostInput.value = '';
notesInput.value = '';
}
/**
* Cancels the current edit operation and clears the form.
*/
window.cancelEdit = function() {
editingIndex = -1;
clearForm();
updateRowButton.classList.add('hidden');
cancelEditButton.classList.add('hidden');
addRowButton.classList.remove('hidden');
campaignNameInput.disabled = false; // Re-enable campaign name
};
// --- PDF Download Functionality ---
/**
* Downloads the dashboard content as a PDF.
*/
window.downloadPdf = async function() {
// Null check for dashboard content
if (!dashboardTabContent) {
console.error("Dashboard content for PDF not found.");
return;
}
// Temporarily show the dashboard content if it's hidden, for accurate PDF generation
const wasHidden = dashboardTabContent.classList.contains('hidden');
if (wasHidden) {
dashboardTabContent.classList.remove('hidden');
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'letter'); // 'p' for portrait, 'pt' for points, 'letter' size
const element = dashboardTabContent; // The element to convert to PDF
// Use html2canvas to render the HTML element to a canvas
await html2canvas(element, {
scale: 2, // Increase scale for better quality
useCORS: true, // Important for images/external resources if any (though we don't use them here)
logging: false // Disable logging for cleaner console
}).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgWidth = 550; // A fixed width for the image in PDF
const pageHeight = doc.internal.pageSize.height;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 30; // Initial Y position for content
doc.setFontSize(22);
doc.setTextColor(31, 41, 55); // Gray 900 equivalent
doc.text("Customer Nurturing Campaign Dashboard", doc.internal.pageSize.getWidth() / 2, position, { align: "center" });
position += 30; // Move down for image
doc.addImage(imgData, 'PNG', 30, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
// If content overflows, add new pages
while (heightLeft >= 0) {
position = heightLeft - imgHeight + 30; // Adjust position for new page
doc.addPage();
doc.addImage(imgData, 'PNG', 30, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
doc.save('Customer_Nurturing_Campaign_Dashboard.pdf');
});
// Restore hidden state if it was hidden initially
if (wasHidden) {
dashboardTabContent.classList.add('hidden');
}
};
// --- Event Listeners ---
if (addRowButton) {
addRowButton.addEventListener('click', addRow);
} else {
console.error("Add Row Button not found.");
}
if (updateRowButton) {
updateRowButton.addEventListener('click', addRow); // addRow function handles both add and update based on editingIndex
} else {
console.error("Update Row Button not found.");
}
if (cancelEditButton) {
cancelEditButton.addEventListener('click', cancelEdit);
} else {
console.error("Cancel Edit Button not found.");
}
// Initial rendering and setup
openTab('dashboard'); // Open dashboard by default
updateDashboard(); // Populate dashboard with initial data
renderConfigTable(); // Populate config table with initial data
});