413 lines
13 KiB
JavaScript
413 lines
13 KiB
JavaScript
/**
|
|
* Charts.js - Diagramm-Management mit Chart.js für MYP Platform
|
|
*
|
|
* Verwaltet alle Diagramme auf der Statistiken-Seite.
|
|
* Unterstützt Dark Mode und Live-Updates.
|
|
*/
|
|
|
|
// Chart.js Instanzen Global verfügbar machen
|
|
window.statsCharts = {};
|
|
|
|
// Chart.js Konfiguration für Dark/Light Theme
|
|
function getChartTheme() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
|
|
return {
|
|
isDark: isDark,
|
|
backgroundColor: isDark ? 'rgba(30, 41, 59, 0.8)' : 'rgba(255, 255, 255, 0.8)',
|
|
textColor: isDark ? '#e2e8f0' : '#374151',
|
|
gridColor: isDark ? 'rgba(148, 163, 184, 0.1)' : 'rgba(156, 163, 175, 0.2)',
|
|
borderColor: isDark ? 'rgba(148, 163, 184, 0.3)' : 'rgba(156, 163, 175, 0.5)'
|
|
};
|
|
}
|
|
|
|
// Standard Chart.js Optionen
|
|
function getDefaultChartOptions() {
|
|
const theme = getChartTheme();
|
|
|
|
return {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: theme.textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
backgroundColor: theme.backgroundColor,
|
|
titleColor: theme.textColor,
|
|
bodyColor: theme.textColor,
|
|
borderColor: theme.borderColor,
|
|
borderWidth: 1
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: {
|
|
color: theme.textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
},
|
|
grid: {
|
|
color: theme.gridColor
|
|
}
|
|
},
|
|
y: {
|
|
ticks: {
|
|
color: theme.textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
},
|
|
grid: {
|
|
color: theme.gridColor
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Job Status Doughnut Chart
|
|
async function createJobStatusChart() {
|
|
try {
|
|
const response = await fetch('/api/stats/charts/job-status');
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Fehler beim Laden der Job-Status-Daten');
|
|
}
|
|
|
|
const ctx = document.getElementById('job-status-chart');
|
|
if (!ctx) return;
|
|
|
|
// Vorhandenes Chart zerstören falls vorhanden
|
|
if (window.statsCharts.jobStatus) {
|
|
window.statsCharts.jobStatus.destroy();
|
|
}
|
|
|
|
const theme = getChartTheme();
|
|
|
|
window.statsCharts.jobStatus = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: data,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
color: theme.textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif',
|
|
size: 12
|
|
},
|
|
padding: 15
|
|
}
|
|
},
|
|
tooltip: {
|
|
backgroundColor: theme.backgroundColor,
|
|
titleColor: theme.textColor,
|
|
bodyColor: theme.textColor,
|
|
borderColor: theme.borderColor,
|
|
borderWidth: 1,
|
|
callbacks: {
|
|
label: function(context) {
|
|
const label = context.label || '';
|
|
const value = context.parsed;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
|
|
return `${label}: ${value} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
cutout: '60%'
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Job-Status-Charts:', error);
|
|
showChartError('job-status-chart', 'Fehler beim Laden der Job-Status-Daten');
|
|
}
|
|
}
|
|
|
|
// Drucker-Nutzung Bar Chart
|
|
async function createPrinterUsageChart() {
|
|
try {
|
|
const response = await fetch('/api/stats/charts/printer-usage');
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Fehler beim Laden der Drucker-Nutzung-Daten');
|
|
}
|
|
|
|
const ctx = document.getElementById('printer-usage-chart');
|
|
if (!ctx) return;
|
|
|
|
// Vorhandenes Chart zerstören falls vorhanden
|
|
if (window.statsCharts.printerUsage) {
|
|
window.statsCharts.printerUsage.destroy();
|
|
}
|
|
|
|
const options = getDefaultChartOptions();
|
|
options.scales.y.title = {
|
|
display: true,
|
|
text: 'Anzahl Jobs',
|
|
color: getChartTheme().textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
};
|
|
|
|
window.statsCharts.printerUsage = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: data,
|
|
options: options
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Drucker-Nutzung-Charts:', error);
|
|
showChartError('printer-usage-chart', 'Fehler beim Laden der Drucker-Nutzung-Daten');
|
|
}
|
|
}
|
|
|
|
// Jobs Timeline Line Chart
|
|
async function createJobsTimelineChart() {
|
|
try {
|
|
const response = await fetch('/api/stats/charts/jobs-timeline');
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Fehler beim Laden der Jobs-Timeline-Daten');
|
|
}
|
|
|
|
const ctx = document.getElementById('jobs-timeline-chart');
|
|
if (!ctx) return;
|
|
|
|
// Vorhandenes Chart zerstören falls vorhanden
|
|
if (window.statsCharts.jobsTimeline) {
|
|
window.statsCharts.jobsTimeline.destroy();
|
|
}
|
|
|
|
const options = getDefaultChartOptions();
|
|
options.scales.y.title = {
|
|
display: true,
|
|
text: 'Jobs pro Tag',
|
|
color: getChartTheme().textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
};
|
|
options.scales.x.title = {
|
|
display: true,
|
|
text: 'Datum (letzte 30 Tage)',
|
|
color: getChartTheme().textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
};
|
|
|
|
window.statsCharts.jobsTimeline = new Chart(ctx, {
|
|
type: 'line',
|
|
data: data,
|
|
options: options
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Jobs-Timeline-Charts:', error);
|
|
showChartError('jobs-timeline-chart', 'Fehler beim Laden der Jobs-Timeline-Daten');
|
|
}
|
|
}
|
|
|
|
// Benutzer-Aktivität Bar Chart
|
|
async function createUserActivityChart() {
|
|
try {
|
|
const response = await fetch('/api/stats/charts/user-activity');
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || 'Fehler beim Laden der Benutzer-Aktivität-Daten');
|
|
}
|
|
|
|
const ctx = document.getElementById('user-activity-chart');
|
|
if (!ctx) return;
|
|
|
|
// Vorhandenes Chart zerstören falls vorhanden
|
|
if (window.statsCharts.userActivity) {
|
|
window.statsCharts.userActivity.destroy();
|
|
}
|
|
|
|
const options = getDefaultChartOptions();
|
|
options.indexAxis = 'y'; // Horizontales Balkendiagramm
|
|
options.scales.x.title = {
|
|
display: true,
|
|
text: 'Anzahl Jobs',
|
|
color: getChartTheme().textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
};
|
|
options.scales.y.title = {
|
|
display: true,
|
|
text: 'Benutzer',
|
|
color: getChartTheme().textColor,
|
|
font: {
|
|
family: 'Inter, sans-serif'
|
|
}
|
|
};
|
|
|
|
window.statsCharts.userActivity = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: data,
|
|
options: options
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Benutzer-Aktivität-Charts:', error);
|
|
showChartError('user-activity-chart', 'Fehler beim Laden der Benutzer-Aktivität-Daten');
|
|
}
|
|
}
|
|
|
|
// Fehleranzeige in Chart-Container
|
|
function showChartError(chartId, message) {
|
|
const container = document.getElementById(chartId);
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<div class="flex items-center justify-center h-full">
|
|
<div class="text-center">
|
|
<svg class="h-12 w-12 mx-auto text-red-500 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
</svg>
|
|
<p class="text-red-500 font-medium">${message}</p>
|
|
<button onclick="refreshAllCharts()" class="mt-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Alle Charts erstellen
|
|
async function initializeAllCharts() {
|
|
// Loading-Indikatoren anzeigen
|
|
showChartLoading();
|
|
|
|
// Charts parallel erstellen
|
|
await Promise.allSettled([
|
|
createJobStatusChart(),
|
|
createPrinterUsageChart(),
|
|
createJobsTimelineChart(),
|
|
createUserActivityChart()
|
|
]);
|
|
}
|
|
|
|
// Loading-Indikatoren anzeigen
|
|
function showChartLoading() {
|
|
const chartIds = ['job-status-chart', 'printer-usage-chart', 'jobs-timeline-chart', 'user-activity-chart'];
|
|
|
|
chartIds.forEach(chartId => {
|
|
const container = document.getElementById(chartId);
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<div class="flex items-center justify-center h-full">
|
|
<div class="text-center">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
|
|
<p class="text-slate-500 dark:text-slate-400 text-sm">Diagramm wird geladen...</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Alle Charts aktualisieren
|
|
async function refreshAllCharts() {
|
|
console.log('Aktualisiere alle Diagramme...');
|
|
|
|
// Bestehende Charts zerstören
|
|
Object.values(window.statsCharts).forEach(chart => {
|
|
if (chart && typeof chart.destroy === 'function') {
|
|
chart.destroy();
|
|
}
|
|
});
|
|
|
|
// Charts neu erstellen
|
|
await initializeAllCharts();
|
|
|
|
console.log('Alle Diagramme aktualisiert');
|
|
}
|
|
|
|
// Theme-Wechsel handhaben
|
|
function updateChartsTheme() {
|
|
// Alle Charts mit neuem Theme aktualisieren
|
|
refreshAllCharts();
|
|
}
|
|
|
|
// Auto-refresh (alle 5 Minuten)
|
|
let chartRefreshInterval;
|
|
|
|
function startChartAutoRefresh() {
|
|
// Bestehenden Interval stoppen
|
|
if (chartRefreshInterval) {
|
|
clearInterval(chartRefreshInterval);
|
|
}
|
|
|
|
// Neuen Interval starten (5 Minuten)
|
|
chartRefreshInterval = setInterval(() => {
|
|
refreshAllCharts();
|
|
}, 5 * 60 * 1000);
|
|
}
|
|
|
|
function stopChartAutoRefresh() {
|
|
if (chartRefreshInterval) {
|
|
clearInterval(chartRefreshInterval);
|
|
chartRefreshInterval = null;
|
|
}
|
|
}
|
|
|
|
// Cleanup beim Verlassen der Seite
|
|
function cleanup() {
|
|
stopChartAutoRefresh();
|
|
|
|
// Alle Charts zerstören
|
|
Object.values(window.statsCharts).forEach(chart => {
|
|
if (chart && typeof chart.destroy === 'function') {
|
|
chart.destroy();
|
|
}
|
|
});
|
|
|
|
window.statsCharts = {};
|
|
}
|
|
|
|
// Globale Funktionen verfügbar machen
|
|
window.refreshAllCharts = refreshAllCharts;
|
|
window.updateChartsTheme = updateChartsTheme;
|
|
window.startChartAutoRefresh = startChartAutoRefresh;
|
|
window.stopChartAutoRefresh = stopChartAutoRefresh;
|
|
window.cleanup = cleanup;
|
|
|
|
// Event Listeners
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Charts initialisieren wenn auf Stats-Seite
|
|
if (document.getElementById('job-status-chart')) {
|
|
initializeAllCharts();
|
|
startChartAutoRefresh();
|
|
}
|
|
});
|
|
|
|
// Dark Mode Event Listener
|
|
if (typeof window.addEventListener !== 'undefined') {
|
|
window.addEventListener('darkModeChanged', function(e) {
|
|
updateChartsTheme();
|
|
});
|
|
}
|
|
|
|
// Page unload cleanup
|
|
window.addEventListener('beforeunload', cleanup);
|