Total Redemptions
${totalRedemptions.toLocaleString()}
Total Discount
$${totalDiscount.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
Average Order Value
$${aov.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}
`;
}
function renderComparisonChart(data) {
const ctx = document.getElementById('comparisonChart').getContext('2d');
const metric = document.getElementById('metricSelector').value;
const activeData = data.filter(p => promoCodes.find(pc => pc.code === p.code && pc.status === 'active'));
const labels = activeData.map(p => p.code);
const chartData = activeData.map(p => p[metric]);
if (comparisonChart) comparisonChart.destroy();
comparisonChart = new Chart(ctx, {
type: 'bar',
data: { labels, datasets: [{
label: metric.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()),
data: chartData,
backgroundColor: '#4f46e5',
}]},
options: { responsive: true, maintainAspectRatio: false }
});
}
function renderTrendChart(startDate, endDate) {
const ctx = document.getElementById('trendChart').getContext('2d');
const dateMap = {};
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
dateMap[d.toISOString().split('T')[0]] = 0;
}
redemptions.forEach(r => {
const rDate = new Date(r.timestamp);
if (rDate >= startDate && rDate <= endDate) {
const dateKey = rDate.toISOString().split('T')[0];
if (dateMap.hasOwnProperty(dateKey)) {
dateMap[dateKey]++;
}
}
});
const labels = Object.keys(dateMap);
const data = Object.values(dateMap);
if (trendChart) trendChart.destroy();
trendChart = new Chart(ctx, {
type: 'line',
data: { labels, datasets: [{
label: 'Redemptions',
data,
borderColor: '#6366f1',
tension: 0.1,
fill: false
}]},
options: {
responsive: true, maintainAspectRatio: false,
scales: { x: { type: 'time', time: { unit: 'day' } } }
}
});
}
function renderTable(data, sortBy = 'netRevenue', sortOrder = 'desc') {
const headers = [
{ key: 'code', name: 'Promo Code' },
{ key: 'redemptions', name: 'Redemptions' },
{ key: 'totalRevenue', name: 'Total Revenue' },
{ key: 'totalDiscount', name: 'Total Discount' },
{ key: 'netRevenue', name: 'Net Revenue' },
{ key: 'aov', name: 'AOV' }
];
data.sort((a, b) => {
const res = sortOrder === 'asc' ? a[sortBy] - b[sortBy] : b[sortBy] - a[sortBy];
if (typeof a[sortBy] === 'string') {
return sortOrder === 'asc' ? a[sortBy].localeCompare(b[sortBy]) : b[sortBy].localeCompare(a[sortBy]);
}
return res;
});
const headerRow = document.getElementById('table-header');
headerRow.innerHTML = headers.map(h => `
${h.name} | `).join('');
const tableBody = document.getElementById('table-body');
tableBody.innerHTML = data.map(p => `
| ${p.code} |
${p.redemptions.toLocaleString()} |
$${p.totalRevenue.toFixed(2)} |
$${p.totalDiscount.toFixed(2)} |
$${p.netRevenue.toFixed(2)} |
$${p.aov.toFixed(2)} |
`).join('');
}
async function generatePdf() {
const startDate = new Date(document.getElementById('startDate').value);
const endDate = new Date(document.getElementById('endDate').value);
document.getElementById('pdf-date-range').textContent = `${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`;
document.getElementById('pdf-kpis').innerHTML = document.getElementById('kpi-cards').innerHTML;
document.getElementById('pdf-chart-image').src = comparisonChart.toBase64Image();
const tableClone = document.querySelector('table').cloneNode(true);
tableClone.classList.add("w-full", "text-xs");
document.getElementById('pdf-table-container').innerHTML = '';
document.getElementById('pdf-table-container').appendChild(tableClone);
const reportEl = document.getElementById('pdf-report');
reportEl.classList.remove('hidden');
const canvas = await html2canvas(reportEl, { scale: 2 });
reportEl.classList.add('hidden');
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'l', unit: 'in', format: 'letter' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save('Promo-Performance-Report.pdf');
}
function setupEventListeners() {
document.getElementById('startDate').addEventListener('change', updateDashboard);
document.getElementById('endDate').addEventListener('change', updateDashboard);
document.getElementById('metricSelector').addEventListener('change', updateDashboard);
document.getElementById('downloadPdfBtn').addEventListener('click', generatePdf);
document.getElementById('table-header').addEventListener('click', e => {
if(e.target.dataset.sort) {
const sortBy = e.target.dataset.sort;
updateDashboard(sortBy);
}
});
}
function initialize() {
generateMockData();
const today = new Date();
const thirtyDaysAgo = new Date(new Date().setDate(today.getDate() - 30));
document.getElementById('endDate').value = today.toISOString().split('T')[0];
document.getElementById('startDate').value = thirtyDaysAgo.toISOString().split('T')[0];
setupEventListeners();
updateDashboard();
}
initialize();
});