290 lines
8.9 KiB
JavaScript
290 lines
8.9 KiB
JavaScript
(() => {
|
|
const dataElement = document.getElementById("dashboard-data");
|
|
if (!dataElement) {
|
|
return;
|
|
}
|
|
|
|
let state = {};
|
|
try {
|
|
state = JSON.parse(dataElement.textContent || "{}");
|
|
} catch (error) {
|
|
console.error("Failed to parse dashboard data", error);
|
|
return;
|
|
}
|
|
|
|
const statusElement = document.getElementById("dashboard-status");
|
|
const summaryContainer = document.getElementById("summary-metrics");
|
|
const summaryEmpty = document.getElementById("summary-empty");
|
|
const scenarioTableBody = document.querySelector("#scenario-table tbody");
|
|
const scenarioEmpty = document.getElementById("scenario-table-empty");
|
|
const overallMetricsList = document.getElementById("overall-metrics");
|
|
const overallMetricsEmpty = document.getElementById("overall-metrics-empty");
|
|
const recentList = document.getElementById("recent-simulations");
|
|
const recentEmpty = document.getElementById("recent-simulations-empty");
|
|
const maintenanceList = document.getElementById("upcoming-maintenance");
|
|
const maintenanceEmpty = document.getElementById(
|
|
"upcoming-maintenance-empty"
|
|
);
|
|
const refreshButton = document.getElementById("refresh-dashboard");
|
|
const costChartCanvas = document.getElementById("cost-chart");
|
|
const costChartEmpty = document.getElementById("cost-chart-empty");
|
|
const activityChartCanvas = document.getElementById("activity-chart");
|
|
const activityChartEmpty = document.getElementById("activity-chart-empty");
|
|
|
|
let costChartInstance = null;
|
|
let activityChartInstance = null;
|
|
|
|
const setStatus = (message, variant = "success") => {
|
|
if (!statusElement) {
|
|
return;
|
|
}
|
|
if (!message) {
|
|
statusElement.hidden = true;
|
|
statusElement.textContent = "";
|
|
statusElement.classList.remove("success", "error");
|
|
return;
|
|
}
|
|
statusElement.textContent = message;
|
|
statusElement.hidden = false;
|
|
statusElement.classList.toggle("success", variant === "success");
|
|
statusElement.classList.toggle("error", variant !== "success");
|
|
};
|
|
|
|
const renderSummaryMetrics = () => {
|
|
if (!summaryContainer || !summaryEmpty) {
|
|
return;
|
|
}
|
|
summaryContainer.innerHTML = "";
|
|
const metrics = Array.isArray(state.summary_metrics)
|
|
? state.summary_metrics
|
|
: [];
|
|
metrics.forEach((metric) => {
|
|
const card = document.createElement("article");
|
|
card.className = "metric-card";
|
|
card.innerHTML = `
|
|
<span class="metric-label">${metric.label}</span>
|
|
<span class="metric-value">${metric.value}</span>
|
|
`;
|
|
summaryContainer.appendChild(card);
|
|
});
|
|
summaryEmpty.hidden = metrics.length > 0;
|
|
};
|
|
|
|
const renderScenarioTable = () => {
|
|
if (!scenarioTableBody || !scenarioEmpty) {
|
|
return;
|
|
}
|
|
scenarioTableBody.innerHTML = "";
|
|
const rows = Array.isArray(state.scenario_rows) ? state.scenario_rows : [];
|
|
rows.forEach((row) => {
|
|
const tr = document.createElement("tr");
|
|
tr.innerHTML = `
|
|
<td>${row.scenario_name}</td>
|
|
<td>${row.parameter_display}</td>
|
|
<td>${row.equipment_display}</td>
|
|
<td>${row.capex_display}</td>
|
|
<td>${row.opex_display}</td>
|
|
<td>${row.production_display}</td>
|
|
<td>${row.consumption_display}</td>
|
|
<td>${row.maintenance_display}</td>
|
|
<td>${row.iterations_display}</td>
|
|
<td>${row.simulation_mean_display}</td>
|
|
`;
|
|
scenarioTableBody.appendChild(tr);
|
|
});
|
|
scenarioEmpty.hidden = rows.length > 0;
|
|
};
|
|
|
|
const renderOverallMetrics = () => {
|
|
if (!overallMetricsList || !overallMetricsEmpty) {
|
|
return;
|
|
}
|
|
overallMetricsList.innerHTML = "";
|
|
const items = Array.isArray(state.overall_report_metrics)
|
|
? state.overall_report_metrics
|
|
: [];
|
|
items.forEach((item) => {
|
|
const li = document.createElement("li");
|
|
li.className = "metric-list-item";
|
|
li.textContent = `${item.label}: ${item.value}`;
|
|
overallMetricsList.appendChild(li);
|
|
});
|
|
overallMetricsEmpty.hidden = items.length > 0;
|
|
};
|
|
|
|
const renderRecentSimulations = () => {
|
|
if (!recentList || !recentEmpty) {
|
|
return;
|
|
}
|
|
recentList.innerHTML = "";
|
|
const runs = Array.isArray(state.recent_simulations)
|
|
? state.recent_simulations
|
|
: [];
|
|
runs.forEach((run) => {
|
|
const item = document.createElement("li");
|
|
item.className = "metric-list-item";
|
|
item.textContent = `${run.scenario_name} · ${run.iterations_display} iterations · ${run.mean_display}`;
|
|
recentList.appendChild(item);
|
|
});
|
|
recentEmpty.hidden = runs.length > 0;
|
|
};
|
|
|
|
const renderMaintenanceReminders = () => {
|
|
if (!maintenanceList || !maintenanceEmpty) {
|
|
return;
|
|
}
|
|
maintenanceList.innerHTML = "";
|
|
const items = Array.isArray(state.upcoming_maintenance)
|
|
? state.upcoming_maintenance
|
|
: [];
|
|
items.forEach((item) => {
|
|
const li = document.createElement("li");
|
|
li.innerHTML = `
|
|
<span class="list-title">${item.equipment_name} · ${item.scenario_name}</span>
|
|
<span class="list-detail">${item.date_display} · ${item.cost_display} · ${item.description}</span>
|
|
`;
|
|
maintenanceList.appendChild(li);
|
|
});
|
|
maintenanceEmpty.hidden = items.length > 0;
|
|
};
|
|
|
|
const buildChartConfig = (dataset, overrides = {}) => ({
|
|
type: dataset.type || "bar",
|
|
data: {
|
|
labels: dataset.labels || [],
|
|
datasets: dataset.datasets || [],
|
|
},
|
|
options: Object.assign(
|
|
{
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { position: "top" },
|
|
tooltip: { enabled: true },
|
|
},
|
|
scales: {
|
|
x: { stacked: dataset.stacked ?? false },
|
|
y: { stacked: dataset.stacked ?? false, beginAtZero: true },
|
|
},
|
|
},
|
|
overrides.options || {}
|
|
),
|
|
});
|
|
|
|
const renderCharts = () => {
|
|
if (costChartInstance) {
|
|
costChartInstance.destroy();
|
|
}
|
|
if (activityChartInstance) {
|
|
activityChartInstance.destroy();
|
|
}
|
|
|
|
const costData = state.scenario_cost_chart || {};
|
|
const activityData = state.scenario_activity_chart || {};
|
|
|
|
if (costChartCanvas && state.cost_chart_has_data) {
|
|
costChartInstance = new Chart(
|
|
costChartCanvas,
|
|
buildChartConfig(costData, {
|
|
options: {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
callback: (value) =>
|
|
typeof value === "number"
|
|
? value.toLocaleString(undefined, {
|
|
maximumFractionDigits: 0,
|
|
})
|
|
: value,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
);
|
|
if (costChartEmpty) {
|
|
costChartEmpty.hidden = true;
|
|
}
|
|
costChartCanvas.classList.remove("hidden");
|
|
} else if (costChartEmpty && costChartCanvas) {
|
|
costChartEmpty.hidden = false;
|
|
costChartCanvas.classList.add("hidden");
|
|
}
|
|
|
|
if (activityChartCanvas && state.activity_chart_has_data) {
|
|
activityChartInstance = new Chart(
|
|
activityChartCanvas,
|
|
buildChartConfig(activityData, {
|
|
options: {
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
callback: (value) =>
|
|
typeof value === "number"
|
|
? value.toLocaleString(undefined, {
|
|
maximumFractionDigits: 0,
|
|
})
|
|
: value,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
);
|
|
if (activityChartEmpty) {
|
|
activityChartEmpty.hidden = true;
|
|
}
|
|
activityChartCanvas.classList.remove("hidden");
|
|
} else if (activityChartEmpty && activityChartCanvas) {
|
|
activityChartEmpty.hidden = false;
|
|
activityChartCanvas.classList.add("hidden");
|
|
}
|
|
};
|
|
|
|
const renderView = () => {
|
|
renderSummaryMetrics();
|
|
renderScenarioTable();
|
|
renderOverallMetrics();
|
|
renderRecentSimulations();
|
|
renderMaintenanceReminders();
|
|
renderCharts();
|
|
};
|
|
|
|
const refreshDashboard = async () => {
|
|
setStatus("Refreshing dashboard…", "success");
|
|
if (refreshButton) {
|
|
refreshButton.classList.add("is-loading");
|
|
}
|
|
|
|
try {
|
|
const response = await fetch("/ui/dashboard/data", {
|
|
headers: { "X-Requested-With": "XMLHttpRequest" },
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Unable to refresh dashboard data.");
|
|
}
|
|
|
|
const payload = await response.json();
|
|
state = payload || {};
|
|
renderView();
|
|
setStatus("Dashboard updated.", "success");
|
|
} catch (error) {
|
|
console.error(error);
|
|
setStatus(error.message || "Failed to refresh dashboard.", "error");
|
|
} finally {
|
|
if (refreshButton) {
|
|
refreshButton.classList.remove("is-loading");
|
|
}
|
|
}
|
|
};
|
|
|
|
renderView();
|
|
|
|
if (refreshButton) {
|
|
refreshButton.addEventListener("click", refreshDashboard);
|
|
}
|
|
})();
|