Invalid data format. Please provide an array of objects.
';
return;
}
const totalOrders = nosqlData.length;
const totalRevenue = nosqlData.reduce((sum, order) => sum + (order.total || 0), 0);
const avgOrderValue = totalRevenue / totalOrders || 0;
const uniqueCustomers = new Set(nosqlData.map(order => order.customer?.id)).size;
const stats = [
{ label: 'Total Documents', value: totalOrders, icon: '📄' },
{ label: 'Total Revenue', value: formatCurrency(totalRevenue), icon: '💰' },
{ label: 'Avg. Order Value', value: formatCurrency(avgOrderValue), icon: '🛒' },
{ label: 'Unique Customers', value: uniqueCustomers, icon: '👥' },
];
statsGrid.innerHTML = stats.map(stat => `
${stat.icon}
${stat.label}
${stat.value}
`).join('');
renderCharts();
};
const renderCharts = () => {
Object.values(charts).forEach(chart => chart.destroy());
if (!Array.isArray(nosqlData)) return;
const salesByCategory = nosqlData.flatMap(o => o.items || []).reduce((acc, item) => {
acc[item.category] = (acc[item.category] || 0) + (item.price * item.quantity);
return acc;
}, {});
const categoryCtx = document.getElementById('categoryChart');
if (categoryCtx) {
charts.categoryChart = new Chart(categoryCtx, {
type: 'doughnut',
data: {
labels: Object.keys(salesByCategory),
datasets: [{ data: Object.values(salesByCategory), backgroundColor: ['#8b5cf6', '#3b82f6', '#10b981', '#f97316', '#ef4444'] }]
},
options: { responsive: true, maintainAspectRatio: false }
});
}
const salesByState = nosqlData.reduce((acc, order) => {
const state = order.customer?.state || 'Unknown';
acc[state] = (acc[state] || 0) + (order.total || 0);
return acc;
}, {});
const stateCtx = document.getElementById('stateChart');
if (stateCtx) {
charts.stateChart = new Chart(stateCtx, {
type: 'bar',
data: {
labels: Object.keys(salesByState),
datasets: [{ label: 'Total Sales', data: Object.values(salesByState), backgroundColor: '#6d28d9' }]
},
options: { responsive: true, maintainAspectRatio: false }
});
}
};
const renderJsonExplorer = () => {
if (!jsonExplorerContainer) return;
const createNode = (key, val) => {
const li = document.createElement('li');
const keySpan = document.createElement('span');
keySpan.className = 'json-key';
keySpan.textContent = key;
li.appendChild(keySpan);
if (typeof val === 'object' && val !== null) {
const nestedUl = document.createElement('ul');
nestedUl.className = 'json-nested hidden';
Object.entries(val).forEach(([nestedKey, nestedVal]) => {
nestedUl.appendChild(createNode(nestedKey, nestedVal));
});
li.appendChild(nestedUl);
keySpan.addEventListener('click', (e) => {
e.stopPropagation();
keySpan.classList.toggle('open');
nestedUl.classList.toggle('hidden');
});
} else {
const valSpan = document.createElement('span');
valSpan.className = 'json-value';
let typeClass = `json-${typeof val}`;
valSpan.innerHTML = `:
${JSON.stringify(val)}`;
li.appendChild(valSpan);
}
return li;
};
jsonExplorerContainer.innerHTML = '';
const rootUl = document.createElement('ul');
Object.entries(nosqlData).forEach(([key, val]) => {
rootUl.appendChild(createNode(key, val));
});
jsonExplorerContainer.appendChild(rootUl);
};
const renderAll = () => {
renderDashboard();
renderJsonExplorer();
};
// --- EVENT HANDLING --- //
tabButtons.forEach((btn, index) => btn.addEventListener('click', () => switchTab(index)));
prevBtn.addEventListener('click', () => switchTab(activeTabIndex - 1));
nextBtn.addEventListener('click', () => switchTab(activeTabIndex + 1));
updateDataBtn.addEventListener('click', () => {
try {
const newData = JSON.parse(jsonInput.value);
nosqlData = newData;
renderAll();
alert('Dashboard updated successfully!');
} catch (e) {
alert('Error: Invalid JSON format. Please check your data.');
console.error("JSON Parse Error:", e);
}
});
downloadPdfBtn.addEventListener('click', () => {
if (!Array.isArray(nosqlData)) {
alert('Cannot generate PDF with invalid data.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const totalOrders = nosqlData.length;
const totalRevenue = nosqlData.reduce((sum, order) => sum + (order.total || 0), 0);
doc.setFontSize(18);
doc.text("NoSQL Data Report", 14, 22);
doc.setFontSize(11);
doc.text(`Total Documents: ${totalOrders} | Total Revenue: ${formatCurrency(totalRevenue)}`, 14, 30);
const head = [['Order ID', 'Date', 'Customer', 'State', 'Total']];
const body = nosqlData.map(o => [o.orderId, o.date, o.customer?.name || 'N/A', o.customer?.state || 'N/A', formatCurrency(o.total || 0)]);
doc.autoTable({
head: head,
body: body,
startY: 40,
theme: 'grid',
headStyles: { fillColor: [109, 40, 217] }, // violet-700
});
const finalY = doc.lastAutoTable.finalY;
if (finalY + 90 < doc.internal.pageSize.height) {
doc.text("Charts", 14, finalY + 15);
const categoryChartImg = charts.categoryChart.toBase64Image();
doc.addImage(categoryChartImg, 'PNG', 14, finalY + 20, 80, 80);
const stateChartImg = charts.stateChart.toBase64Image();
doc.addImage(stateChartImg, 'PNG', 105, finalY + 20, 90, 60);
}
doc.save('NoSQL_Data_Report.pdf');
});
// --- INITIALIZATION --- //
jsonInput.value = JSON.stringify(sampleJson, null, 2);
switchTab(0);
renderAll();
});