dashboard.js

6.58 KB
09/07/2025 14:46
JS
dashboard.js
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 = `
                <h3 class="widget-title" id="title-${item.widgetId}"></h3>
                <div class="widget-content" id="content-${item.widgetId}">
                    <div class="loading">Loading...</div>
                </div>
            `;
      container.appendChild(widgetWrapper);
      loadWidget(item.widgetId);
    }
  } catch (error) {
    document.getElementById('dashboard-container').innerHTML = `<div class="error">Failed to load dashboard layout: ${error.message}</div>`;
    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 = `<div class="error">Failed to load widget config: ${error.message}</div>`;
    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 = `<div class="error">Failed to load data: ${error.message}</div>`;
    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 = '<div class="loading">No data available.</div>';
    return;
  }

  let tableHtml = '<div class="table-wrapper"><table><thead><tr>';
  displayOptions.columns.forEach(col => {
    if (col.visible) tableHtml += `<th>${col.displayName}</th>`;
  });
  tableHtml += '</tr></thead><tbody>';

  data.forEach(rowData => {
    let rowClass = '';
    if (displayOptions.conditionalFormatting) {
      displayOptions.conditionalFormatting.forEach(rule => {
        if (rowData[rule.column] == rule.value) {
          rowClass = rule.class;
        }
      });
    }
    tableHtml += `<tr class="${rowClass}">`;
    displayOptions.columns.forEach(col => {
      if (col.visible) tableHtml += `<td>${rowData[col.dataKey] ?? ''}</td>`;
    });
    tableHtml += '</tr>';
  });
  tableHtml += '</tbody></table></div>';

  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 = `<canvas id="${canvasId}"></canvas>`;
  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 = '<div class="loading">No data.</div>';
    return;
  }
  const value = data[0][options.valueKey];
  const formattedValue = new Intl.NumberFormat('en-US').format(value);

  element.innerHTML = `
        <div class="card-content">
            <div class="card-value">${formattedValue}</div>
            <div class="card-label">${options.label}</div>
        </div>
    `;
}