diff --git a/src/app/Http/Controllers/API/V4/Admin/StatsController.php b/src/app/Http/Controllers/API/V4/Admin/StatsController.php new file mode 100644 --- /dev/null +++ b/src/app/Http/Controllers/API/V4/Admin/StatsController.php @@ -0,0 +1,103 @@ +errorResponse(404); + } + + $method = 'chart' . implode('', array_map('ucfirst', explode('-', $chart))); + + if (!method_exists($this, $method)) { + return $this->errorResponse(404); + } + + $result = $this->{$method}(); + + return response()->json($result); + } + + /** + * Get created/deleted users chart + */ + protected function chartUsers(): array + { + $weeks = 8; + $start = Carbon::now(); + $labels = []; + + while ($weeks > 0) { + $labels[] = $start->format('Y-W'); + $start->subWeeks(1); + $weeks--; + } + + $labels = array_reverse($labels); + $start->startOfWeek(Carbon::MONDAY); + + $created = DB::table('users') + ->selectRaw("concat(year(created_at), '-', week(created_at, 3)) as period, count(*) as cnt") + ->where('created_at', '>=', $start->toDateString()) + ->groupByRaw('1') + ->get(); + + $deleted = DB::table('users') + ->selectRaw("concat(year(deleted_at), '-', week(deleted_at, 3)) as period, count(*) as cnt") + ->where('deleted_at', '>=', $start->toDateString()) + ->groupByRaw('1') + ->get(); + + $empty = array_fill_keys($labels, 0); + $created = array_merge($empty, $created->pluck('cnt', 'period')->all()); + $deleted = array_merge($empty, $deleted->pluck('cnt', 'period')->all()); + + //$created = [5, 2, 4, 2, 0, 5, 2, 4]; + //$deleted = [1, 2, 3, 1, 2, 1, 2, 3]; + + // See https://frappe.io/charts/docs for format/options description + + return [ + 'title' => 'Users - last 8 weeks', + // 'type' => 'axis-mixed', + 'colors' => [self::COLOR_GREEN, self::COLOR_RED], + 'data' => [ + 'labels' => $labels, + 'datasets' => [ + [ + 'name' => 'Created', + 'chartType' => 'bar', + 'values' => $created + ], + [ + 'name' => 'Deleted', + 'chartType' => 'line', + 'values' => $deleted + ] + ] + ] + ]; + } +} diff --git a/src/package-lock.json b/src/package-lock.json --- a/src/package-lock.json +++ b/src/package-lock.json @@ -6838,6 +6838,11 @@ "map-cache": "^0.2.2" } }, + "frappe-charts": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/frappe-charts/-/frappe-charts-1.5.3.tgz", + "integrity": "sha512-VS5XVxek41ea8mVzetyFF3avNefiwGDcDSDJuHrZyJXgbqiTSXLoqlPFoMqTzuzRm1g+o6TXs+A7wLtVp3Vt0g==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", diff --git a/src/package.json b/src/package.json --- a/src/package.json +++ b/src/package.json @@ -22,6 +22,7 @@ "cross-env": "^7.0", "eslint": "^6.8.0", "eslint-plugin-vue": "^6.2.2", + "frappe-charts": "^1.5.3", "jquery": "^3.5.1", "laravel-mix": "^5.0.6", "openvidu-browser": "^2.15.0", diff --git a/src/resources/js/routes-admin.js b/src/resources/js/routes-admin.js --- a/src/resources/js/routes-admin.js +++ b/src/resources/js/routes-admin.js @@ -3,6 +3,7 @@ import LoginComponent from '../vue/Login' import LogoutComponent from '../vue/Logout' import PageComponent from '../vue/Page' +import StatsComponent from '../vue/Admin/Stats' import UserComponent from '../vue/Admin/User' const routes = [ @@ -33,6 +34,12 @@ component: LogoutComponent }, { + path: '/stats', + name: 'stats', + component: StatsComponent, + meta: { requiresAuth: true } + }, + { path: '/user/:user', name: 'user', component: UserComponent, diff --git a/src/resources/themes/charts.scss b/src/resources/themes/charts.scss new file mode 100644 --- /dev/null +++ b/src/resources/themes/charts.scss @@ -0,0 +1,19 @@ +@import '~frappe-charts/dist/frappe-charts.min.css'; + +.chart-container { + font-family: unset; + + .title { + font-size: 12pt; + font-weight: bold; + } +} + +#stats-container { + & > div { + border: 1px solid lightgrey; + border-radius: 0.5em; + padding-top: 0.5em; + min-height: 250px; + } +} \ No newline at end of file diff --git a/src/resources/themes/default/app.scss b/src/resources/themes/default/app.scss --- a/src/resources/themes/default/app.scss +++ b/src/resources/themes/default/app.scss @@ -5,4 +5,5 @@ @import '../meet'; @import '../toast'; @import '../forms'; +@import '../charts'; @import '../app'; diff --git a/src/resources/vue/Admin/Dashboard.vue b/src/resources/vue/Admin/Dashboard.vue --- a/src/resources/vue/Admin/Dashboard.vue +++ b/src/resources/vue/Admin/Dashboard.vue @@ -37,11 +37,19 @@ -
+
+ + Stats + +
diff --git a/src/routes/api.php b/src/routes/api.php --- a/src/routes/api.php +++ b/src/routes/api.php @@ -143,5 +143,7 @@ Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff'); Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions'); Route::apiResource('discounts', API\V4\Admin\DiscountsController::class); + + Route::get('stats/chart/{chart}', 'API\V4\Admin\StatsController@chart'); } );