Files
Claper/assets/js/admin-charts.js
Alex Lion 5bd4793b6e Version 2.4.0
## ⚠️ Breaking changes

- S3 variables are now named: S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_REGION and S3_BUCKET
- Users now have roles. Refer to the `roles` table and assign a role to a user with the `role_id` column in the `users` table.

## Features

- Add Admin Panel to manage users and presentations
- Add user roles: user, admin
- Add `LANGUAGES` setting to configure available languages in the app
- Add hideable presenter attendee count (#183 #155)
- Add Hungarian translation (#161)
- Add Latvian translation (#163)
- Add custom S3 endpoint with `S3_SCHEME`, `S3_HOST`, `S3_PORT` and `S3_PUBLIC_URL`

## Fixes and improvements

- Upgrade JS dependencies
- Upgrade Elixir dependencies, including Phoenix Live View to 1.0.17
- Upgrade to Tailwind 4+
- Refactor view templates to use {} instead of <%= %>
- Fix event name validation to be required
- Docker image is now using Ubuntu instead of Alpine for better dependencies support
- Fix scrollbar not showing in event manager when no presentation file (#164) (@aryel780)
- Fix settings scroll for small screen (#168)
- Fix duplicate key quiz when duplicate (#182)
- Fix email change confirmation (#172)
- Fix italian translation (#179)
- Fix random poll choices (#184)
2025-12-26 14:46:16 +01:00

207 lines
4.6 KiB
JavaScript

import {
Chart,
LineElement,
PointElement,
CategoryScale,
LinearScale,
Title,
Tooltip,
Legend,
LineController,
Filler
} from 'chart.js';
import 'chartjs-adapter-moment';
// Register Chart.js components
Chart.register(
LineElement,
PointElement,
CategoryScale,
LinearScale,
Title,
Tooltip,
Legend,
LineController,
Filler
);
export class AdminCharts {
constructor() {
this.charts = {};
this.defaultOptions = {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
padding: 20,
font: {
size: 12,
family: 'Inter, system-ui, sans-serif'
},
color: 'rgba(255, 255, 255, 0.9)'
}
},
tooltip: {
enabled: true,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: 'rgba(255, 255, 255, 0.9)',
bodyColor: 'rgba(255, 255, 255, 0.9)',
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1,
cornerRadius: 8,
displayColors: false,
padding: 12,
titleFont: {
size: 14,
weight: 'bold'
},
bodyFont: {
size: 13
}
}
},
scales: {
x: {
display: true,
grid: {
display: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)',
font: {
size: 11
}
}
},
y: {
display: true,
grid: {
color: 'rgba(255, 255, 255, 0.1)',
drawBorder: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)',
font: {
size: 11
},
callback: function(value) {
return Number.isInteger(value) ? value : '';
}
}
}
},
elements: {
line: {
tension: 0.4,
borderWidth: 3,
fill: true
},
point: {
radius: 0,
hoverRadius: 6,
hoverBorderWidth: 2,
hoverBorderColor: 'rgba(255, 255, 255, 0.9)'
}
},
animation: {
duration: 800,
easing: 'easeInOutQuart'
}
};
}
createUsersChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return null;
const chartData = {
labels: data.labels,
datasets: [{
label: 'Users',
data: data.values,
borderColor: 'rgba(102, 126, 234, 1)',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
pointBackgroundColor: 'rgba(102, 126, 234, 1)',
pointBorderColor: 'rgba(255, 255, 255, 0.9)',
pointHoverBackgroundColor: 'rgba(102, 126, 234, 1)',
pointHoverBorderColor: 'rgba(255, 255, 255, 0.9)',
}]
};
if (this.charts[canvasId]) {
this.charts[canvasId].destroy();
}
this.charts[canvasId] = new Chart(ctx, {
type: 'line',
data: chartData,
options: this.defaultOptions
});
return this.charts[canvasId];
}
createEventsChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return null;
const chartData = {
labels: data.labels,
datasets: [{
label: 'Events',
data: data.values,
borderColor: 'rgba(16, 185, 129, 1)',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
pointBackgroundColor: 'rgba(16, 185, 129, 1)',
pointBorderColor: 'rgba(255, 255, 255, 0.9)',
pointHoverBackgroundColor: 'rgba(16, 185, 129, 1)',
pointHoverBorderColor: 'rgba(255, 255, 255, 0.9)',
}]
};
if (this.charts[canvasId]) {
this.charts[canvasId].destroy();
}
this.charts[canvasId] = new Chart(ctx, {
type: 'line',
data: chartData,
options: this.defaultOptions
});
return this.charts[canvasId];
}
updateChart(canvasId, data) {
const chart = this.charts[canvasId];
if (!chart) return;
chart.data.labels = data.labels;
chart.data.datasets[0].data = data.values;
chart.update('active');
}
destroyChart(canvasId) {
if (this.charts[canvasId]) {
this.charts[canvasId].destroy();
delete this.charts[canvasId];
}
}
destroyAllCharts() {
Object.keys(this.charts).forEach(canvasId => {
this.destroyChart(canvasId);
});
}
}
// Create global instance
window.AdminCharts = new AdminCharts();