← All changes
|
assets/src/js/frontend/course-builder/builder-dashboard.js
+180
-180
4.3.8
→
4.4.9
View file →
| @@ -1,180 +1,180 @@ | ||
| 1 | -/** | |
| 2 | - * Dashboard charts and interactions for Course Builder. | |
| 3 | - * | |
| 4 | - * @since 4.3.0 | |
| 5 | - * @version 1.0.0 | |
| 6 | - */ | |
| 7 | - | |
| 8 | -import Chart from 'chart.js/auto'; | |
| 9 | - | |
| 10 | -export class BuilderDashboard { | |
| 11 | - constructor() { | |
| 12 | - this.charts = {}; | |
| 13 | - this.init(); | |
| 14 | - } | |
| 15 | - | |
| 16 | - init() { | |
| 17 | - const dataEl = document.getElementById( 'lp-cb-dashboard-chart-data' ); | |
| 18 | - if ( ! dataEl ) { | |
| 19 | - return; | |
| 20 | - } | |
| 21 | - | |
| 22 | - try { | |
| 23 | - this.chartData = JSON.parse( dataEl.textContent ); | |
| 24 | - } catch ( e ) { | |
| 25 | - console.warn( 'Dashboard: Failed to parse chart data', e ); | |
| 26 | - return; | |
| 27 | - } | |
| 28 | - | |
| 29 | - this.initSalesChart(); | |
| 30 | - this.initStudentsChart(); | |
| 31 | - this.bindFilterEvents(); | |
| 32 | - } | |
| 33 | - | |
| 34 | - createGradient( ctx, color ) { | |
| 35 | - const gradient = ctx.createLinearGradient( 0, 0, 0, ctx.canvas.height ); | |
| 36 | - gradient.addColorStop( 0, color + '40' ); | |
| 37 | - gradient.addColorStop( 1, color + '05' ); | |
| 38 | - return gradient; | |
| 39 | - } | |
| 40 | - | |
| 41 | - getChartConfig( labels, data, color, prefix = '', isInteger = false ) { | |
| 42 | - return { | |
| 43 | - type: 'line', | |
| 44 | - data: { | |
| 45 | - labels, | |
| 46 | - datasets: [ { | |
| 47 | - data, | |
| 48 | - borderColor: color, | |
| 49 | - borderWidth: 2.5, | |
| 50 | - backgroundColor: ( context ) => { | |
| 51 | - const { ctx } = context.chart; | |
| 52 | - return this.createGradient( ctx, color ); | |
| 53 | - }, | |
| 54 | - fill: true, | |
| 55 | - tension: 0.4, | |
| 56 | - pointBackgroundColor: color, | |
| 57 | - pointBorderColor: '#fff', | |
| 58 | - pointBorderWidth: 2, | |
| 59 | - pointRadius: 0, | |
| 60 | - pointHoverRadius: 6, | |
| 61 | - pointHoverBorderWidth: 2, | |
| 62 | - } ], | |
| 63 | - }, | |
| 64 | - options: { | |
| 65 | - responsive: true, | |
| 66 | - maintainAspectRatio: false, | |
| 67 | - plugins: { | |
| 68 | - legend: { display: false }, | |
| 69 | - tooltip: { | |
| 70 | - backgroundColor: '#1e1e1e', | |
| 71 | - titleColor: '#fff', | |
| 72 | - bodyColor: '#fff', | |
| 73 | - borderColor: 'rgba(255,255,255,0.1)', | |
| 74 | - borderWidth: 1, | |
| 75 | - cornerRadius: 8, | |
| 76 | - padding: 12, | |
| 77 | - displayColors: false, | |
| 78 | - callbacks: { | |
| 79 | - label: ( context ) => { | |
| 80 | - const val = context.parsed.y || 0; | |
| 81 | - return prefix + val.toLocaleString(); | |
| 82 | - }, | |
| 83 | - }, | |
| 84 | - }, | |
| 85 | - }, | |
| 86 | - scales: { | |
| 87 | - x: { | |
| 88 | - grid: { display: false }, | |
| 89 | - ticks: { | |
| 90 | - color: '#9ca3af', | |
| 91 | - font: { size: 11 }, | |
| 92 | - maxRotation: 0, | |
| 93 | - }, | |
| 94 | - border: { display: false }, | |
| 95 | - }, | |
| 96 | - y: { | |
| 97 | - grid: { | |
| 98 | - color: 'rgba(0,0,0,0.04)', | |
| 99 | - drawBorder: false, | |
| 100 | - }, | |
| 101 | - ticks: { | |
| 102 | - stepSize: isInteger ? 1 : undefined, | |
| 103 | - color: '#9ca3af', | |
| 104 | - font: { size: 11 }, | |
| 105 | - callback: ( value ) => prefix + value.toLocaleString(), | |
| 106 | - }, | |
| 107 | - border: { display: false }, | |
| 108 | - beginAtZero: true, | |
| 109 | - }, | |
| 110 | - }, | |
| 111 | - interaction: { | |
| 112 | - intersect: false, | |
| 113 | - mode: 'index', | |
| 114 | - }, | |
| 115 | - }, | |
| 116 | - }; | |
| 117 | - } | |
| 118 | - | |
| 119 | - initSalesChart() { | |
| 120 | - const canvas = document.getElementById( 'lp-cb-chart-sales' ); | |
| 121 | - if ( ! canvas ) { | |
| 122 | - return; | |
| 123 | - } | |
| 124 | - | |
| 125 | - const { labels = [], data = [] } = this.chartData.sales || {}; | |
| 126 | - const config = this.getChartConfig( labels, data, '#10b981', '$' ); | |
| 127 | - this.charts.sales = new Chart( canvas, config ); | |
| 128 | - } | |
| 129 | - | |
| 130 | - initStudentsChart() { | |
| 131 | - const canvas = document.getElementById( 'lp-cb-chart-students' ); | |
| 132 | - if ( ! canvas ) { | |
| 133 | - return; | |
| 134 | - } | |
| 135 | - | |
| 136 | - const { labels = [], data = [] } = this.chartData.students || {}; | |
| 137 | - const config = this.getChartConfig( labels, data, '#f59e0b', '', true ); | |
| 138 | - this.charts.students = new Chart( canvas, config ); | |
| 139 | - } | |
| 140 | - | |
| 141 | - bindFilterEvents() { | |
| 142 | - const filters = document.querySelectorAll( '.chart-card__filter' ); | |
| 143 | - filters.forEach( ( select ) => { | |
| 144 | - select.addEventListener( 'change', ( e ) => { | |
| 145 | - const chartType = e.target.dataset.chart; | |
| 146 | - const period = e.target.value; | |
| 147 | - this.fetchChartData( chartType, period ); | |
| 148 | - } ); | |
| 149 | - } ); | |
| 150 | - } | |
| 151 | - | |
| 152 | - async fetchChartData( chartType, period ) { | |
| 153 | - const chart = this.charts[ chartType ]; | |
| 154 | - if ( ! chart ) { | |
| 155 | - return; | |
| 156 | - } | |
| 157 | - | |
| 158 | - const formData = new FormData(); | |
| 159 | - formData.append( 'action', 'lp_cb_dashboard_chart_data' ); | |
| 160 | - formData.append( 'chart_type', chartType ); | |
| 161 | - formData.append( 'period', period ); | |
| 162 | - formData.append( 'nonce', this.chartData.nonce ); | |
| 163 | - | |
| 164 | - try { | |
| 165 | - const response = await fetch( this.chartData.ajaxUrl, { | |
| 166 | - method: 'POST', | |
| 167 | - body: formData, | |
| 168 | - } ); | |
| 169 | - | |
| 170 | - const result = await response.json(); | |
| 171 | - if ( result.success && result.data ) { | |
| 172 | - chart.data.labels = result.data.labels || []; | |
| 173 | - chart.data.datasets[ 0 ].data = result.data.data || []; | |
| 174 | - chart.update( 'none' ); | |
| 175 | - } | |
| 176 | - } catch ( e ) { | |
| 177 | - console.warn( 'Dashboard: Failed to fetch chart data', e ); | |
| 178 | - } | |
| 179 | - } | |
| 180 | -} | |
| 1 | +/** | |
| 2 | + * Dashboard charts and interactions for Course Builder. | |
| 3 | + * | |
| 4 | + * @since 4.3.0 | |
| 5 | + * @version 1.0.0 | |
| 6 | + */ | |
| 7 | + | |
| 8 | +import Chart from 'chart.js/auto'; | |
| 9 | + | |
| 10 | +export class BuilderDashboard { | |
| 11 | + constructor() { | |
| 12 | + this.charts = {}; | |
| 13 | + this.init(); | |
| 14 | + } | |
| 15 | + | |
| 16 | + init() { | |
| 17 | + const dataEl = document.getElementById( 'lp-cb-dashboard-chart-data' ); | |
| 18 | + if ( ! dataEl ) { | |
| 19 | + return; | |
| 20 | + } | |
| 21 | + | |
| 22 | + try { | |
| 23 | + this.chartData = JSON.parse( dataEl.textContent ); | |
| 24 | + } catch ( e ) { | |
| 25 | + console.warn( 'Dashboard: Failed to parse chart data', e ); | |
| 26 | + return; | |
| 27 | + } | |
| 28 | + | |
| 29 | + this.initSalesChart(); | |
| 30 | + this.initStudentsChart(); | |
| 31 | + this.bindFilterEvents(); | |
| 32 | + } | |
| 33 | + | |
| 34 | + createGradient( ctx, color ) { | |
| 35 | + const gradient = ctx.createLinearGradient( 0, 0, 0, ctx.canvas.height ); | |
| 36 | + gradient.addColorStop( 0, color + '40' ); | |
| 37 | + gradient.addColorStop( 1, color + '05' ); | |
| 38 | + return gradient; | |
| 39 | + } | |
| 40 | + | |
| 41 | + getChartConfig( labels, data, color, prefix = '', isInteger = false ) { | |
| 42 | + return { | |
| 43 | + type: 'line', | |
| 44 | + data: { | |
| 45 | + labels, | |
| 46 | + datasets: [ { | |
| 47 | + data, | |
| 48 | + borderColor: color, | |
| 49 | + borderWidth: 2.5, | |
| 50 | + backgroundColor: ( context ) => { | |
| 51 | + const { ctx } = context.chart; | |
| 52 | + return this.createGradient( ctx, color ); | |
| 53 | + }, | |
| 54 | + fill: true, | |
| 55 | + tension: 0.4, | |
| 56 | + pointBackgroundColor: color, | |
| 57 | + pointBorderColor: '#fff', | |
| 58 | + pointBorderWidth: 2, | |
| 59 | + pointRadius: 0, | |
| 60 | + pointHoverRadius: 6, | |
| 61 | + pointHoverBorderWidth: 2, | |
| 62 | + } ], | |
| 63 | + }, | |
| 64 | + options: { | |
| 65 | + responsive: true, | |
| 66 | + maintainAspectRatio: false, | |
| 67 | + plugins: { | |
| 68 | + legend: { display: false }, | |
| 69 | + tooltip: { | |
| 70 | + backgroundColor: '#1e1e1e', | |
| 71 | + titleColor: '#fff', | |
| 72 | + bodyColor: '#fff', | |
| 73 | + borderColor: 'rgba(255,255,255,0.1)', | |
| 74 | + borderWidth: 1, | |
| 75 | + cornerRadius: 8, | |
| 76 | + padding: 12, | |
| 77 | + displayColors: false, | |
| 78 | + callbacks: { | |
| 79 | + label: ( context ) => { | |
| 80 | + const val = context.parsed.y || 0; | |
| 81 | + return prefix + val.toLocaleString(); | |
| 82 | + }, | |
| 83 | + }, | |
| 84 | + }, | |
| 85 | + }, | |
| 86 | + scales: { | |
| 87 | + x: { | |
| 88 | + grid: { display: false }, | |
| 89 | + ticks: { | |
| 90 | + color: '#9ca3af', | |
| 91 | + font: { size: 11 }, | |
| 92 | + maxRotation: 0, | |
| 93 | + }, | |
| 94 | + border: { display: false }, | |
| 95 | + }, | |
| 96 | + y: { | |
| 97 | + grid: { | |
| 98 | + color: 'rgba(0,0,0,0.04)', | |
| 99 | + drawBorder: false, | |
| 100 | + }, | |
| 101 | + ticks: { | |
| 102 | + stepSize: isInteger ? 1 : undefined, | |
| 103 | + color: '#9ca3af', | |
| 104 | + font: { size: 11 }, | |
| 105 | + callback: ( value ) => prefix + value.toLocaleString(), | |
| 106 | + }, | |
| 107 | + border: { display: false }, | |
| 108 | + beginAtZero: true, | |
| 109 | + }, | |
| 110 | + }, | |
| 111 | + interaction: { | |
| 112 | + intersect: false, | |
| 113 | + mode: 'index', | |
| 114 | + }, | |
| 115 | + }, | |
| 116 | + }; | |
| 117 | + } | |
| 118 | + | |
| 119 | + initSalesChart() { | |
| 120 | + const canvas = document.getElementById( 'lp-cb-chart-sales' ); | |
| 121 | + if ( ! canvas ) { | |
| 122 | + return; | |
| 123 | + } | |
| 124 | + | |
| 125 | + const { labels = [], data = [] } = this.chartData.sales || {}; | |
| 126 | + const config = this.getChartConfig( labels, data, '#10b981', '$' ); | |
| 127 | + this.charts.sales = new Chart( canvas, config ); | |
| 128 | + } | |
| 129 | + | |
| 130 | + initStudentsChart() { | |
| 131 | + const canvas = document.getElementById( 'lp-cb-chart-students' ); | |
| 132 | + if ( ! canvas ) { | |
| 133 | + return; | |
| 134 | + } | |
| 135 | + | |
| 136 | + const { labels = [], data = [] } = this.chartData.students || {}; | |
| 137 | + const config = this.getChartConfig( labels, data, '#f59e0b', '', true ); | |
| 138 | + this.charts.students = new Chart( canvas, config ); | |
| 139 | + } | |
| 140 | + | |
| 141 | + bindFilterEvents() { | |
| 142 | + const filters = document.querySelectorAll( '.chart-card__filter' ); | |
| 143 | + filters.forEach( ( select ) => { | |
| 144 | + select.addEventListener( 'change', ( e ) => { | |
| 145 | + const chartType = e.target.dataset.chart; | |
| 146 | + const period = e.target.value; | |
| 147 | + this.fetchChartData( chartType, period ); | |
| 148 | + } ); | |
| 149 | + } ); | |
| 150 | + } | |
| 151 | + | |
| 152 | + async fetchChartData( chartType, period ) { | |
| 153 | + const chart = this.charts[ chartType ]; | |
| 154 | + if ( ! chart ) { | |
| 155 | + return; | |
| 156 | + } | |
| 157 | + | |
| 158 | + const formData = new FormData(); | |
| 159 | + formData.append( 'action', 'lp_cb_dashboard_chart_data' ); | |
| 160 | + formData.append( 'chart_type', chartType ); | |
| 161 | + formData.append( 'period', period ); | |
| 162 | + formData.append( 'nonce', this.chartData.nonce ); | |
| 163 | + | |
| 164 | + try { | |
| 165 | + const response = await fetch( this.chartData.ajaxUrl, { | |
| 166 | + method: 'POST', | |
| 167 | + body: formData, | |
| 168 | + } ); | |
| 169 | + | |
| 170 | + const result = await response.json(); | |
| 171 | + if ( result.success && result.data ) { | |
| 172 | + chart.data.labels = result.data.labels || []; | |
| 173 | + chart.data.datasets[ 0 ].data = result.data.data || []; | |
| 174 | + chart.update( 'none' ); | |
| 175 | + } | |
| 176 | + } catch ( e ) { | |
| 177 | + console.warn( 'Dashboard: Failed to fetch chart data', e ); | |
| 178 | + } | |
| 179 | + } | |
| 180 | +} | |