const widgetChartInstances = {}; // Store chart instances to prevent duplicates document.addEventListener('DOMContentLoaded', () => { loadDashboardLayout(); }); async function loadDashboardLayout() { try { const response = await fetch('configs/dashboard.json'); const layoutConfig = await response.json(); document.getElementById('dashboard-title').textContent = layoutConfig.title; const container = document.getElementById('dashboard-container'); container.innerHTML = ''; // Clear previous content for (const item of layoutConfig.layout) { const widgetWrapper = document.createElement('div'); widgetWrapper.id = `widget-wrapper-${item.widgetId}`; widgetWrapper.className = 'widget'; widgetWrapper.style.gridColumnEnd = `span ${item.width}`; widgetWrapper.innerHTML = `

Loading...
`; container.appendChild(widgetWrapper); loadWidget(item.widgetId); } } catch (error) { document.getElementById('dashboard-container').innerHTML = `
Failed to load dashboard layout: ${error.message}
`; console.error('Layout loading error:', error); } } async function loadWidget(widgetId) { try { const configResponse = await fetch(`configs/widgets/${widgetId}.json`); const config = await configResponse.json(); document.getElementById(`title-${config.id}`).textContent = config.title; await fetchAndRender(config); if (config.refreshInterval > 0) { setInterval(() => { console.log(`Refreshing ${widgetId}...`); fetchAndRender(config); }, config.refreshInterval); } } catch (error) { const contentDiv = document.getElementById(`content-${widgetId}`); contentDiv.innerHTML = `
Failed to load widget config: ${error.message}
`; console.error(`Error loading widget config ${widgetId}:`, error); } } async function fetchAndRender(config, page = 1) { const contentDiv = document.getElementById(`content-${config.id}`); const paginationEnabled = config.displayOptions.pagination?.enabled; let url = `api/data_source.php?widget_id=${config.id}`; if (paginationEnabled) { url += `&page=${page}&limit=${config.displayOptions.pagination.limit}`; } try { const dataResponse = await fetch(url); if (!dataResponse.ok) { const errorData = await dataResponse.json(); throw new Error(errorData.error || `HTTP error! status: ${dataResponse.status}`); } const result = await dataResponse.json(); if (result.error) { throw new Error(result.error); } switch (config.type) { case 'table': renderTable(contentDiv, config, result); break; case 'chart': renderChart(contentDiv, config.displayOptions, result.data); break; case 'card': renderCard(contentDiv, config.displayOptions, result.data); break; } } catch (error) { contentDiv.innerHTML = `
Failed to load data: ${error.message}
`; console.error(`Error fetching/rendering widget ${config.id}:`, error); } } function renderTable(element, config, result) { const {displayOptions} = config; const {data, pagination} = result; if (!data || data.length === 0) { element.innerHTML = '
No data available.
'; return; } let tableHtml = '
'; displayOptions.columns.forEach(col => { if (col.visible) tableHtml += ``; }); tableHtml += ''; data.forEach(rowData => { let rowClass = ''; if (displayOptions.conditionalFormatting) { displayOptions.conditionalFormatting.forEach(rule => { if (rowData[rule.column] == rule.value) { rowClass = rule.class; } }); } tableHtml += ``; displayOptions.columns.forEach(col => { if (col.visible) tableHtml += ``; }); tableHtml += ''; }); tableHtml += '
${col.displayName}
${rowData[col.dataKey] ?? ''}
'; element.innerHTML = tableHtml; if (pagination) { renderPaginationControls(element, config, pagination); } } function renderPaginationControls(element, config, pagination) { const {currentPage, totalPages} = pagination; const controlsContainer = document.createElement('div'); controlsContainer.className = 'pagination-controls'; const prevButton = document.createElement('button'); prevButton.innerHTML = '« Previous'; prevButton.disabled = currentPage <= 1; prevButton.addEventListener('click', () => fetchAndRender(config, currentPage - 1)); const pageInfo = document.createElement('span'); pageInfo.textContent = `Page ${currentPage} of ${totalPages}`; const nextButton = document.createElement('button'); nextButton.innerHTML = 'Next »'; nextButton.disabled = currentPage >= totalPages; nextButton.addEventListener('click', () => fetchAndRender(config, currentPage + 1)); controlsContainer.appendChild(prevButton); controlsContainer.appendChild(pageInfo); controlsContainer.appendChild(nextButton); element.appendChild(controlsContainer); } function renderChart(element, options, data) { const canvasId = `chart-${element.id}`; element.innerHTML = ``; const canvas = document.getElementById(canvasId); if (widgetChartInstances[canvasId]) { widgetChartInstances[canvasId].destroy(); } const labels = data.map(row => row[options.labelsColumn]); const datasets = options.datasets.map(col => ({ label: col.label, data: data.map(row => row[col.dataKey]), backgroundColor: col.backgroundColor, borderColor: col.borderColor, borderWidth: col.borderWidth || 1, })); widgetChartInstances[canvasId] = new Chart(canvas, { type: options.chartType, data: {labels, datasets}, options: { responsive: true, maintainAspectRatio: false, scales: {y: {beginAtZero: true}} } }); } function renderCard(element, options, data) { if (!data || data.length === 0) { element.innerHTML = '
No data.
'; return; } const value = data[0][options.valueKey]; const formattedValue = new Intl.NumberFormat('en-US').format(value); element.innerHTML = `
${formattedValue}
${options.label}
`; }