Page MenuHomePhorge

D1822.1774843801.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D1822.1774843801.diff

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 @@
+<?php
+
+namespace App\Http\Controllers\API\V4\Admin;
+
+use App\User;
+use Carbon\Carbon;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+class StatsController extends \App\Http\Controllers\Controller
+{
+ public const COLOR_GREEN = '#48d368'; // '#28a745'
+ public const COLOR_GREEN_DARK = '#19692c';
+ public const COLOR_RED = '#e77681'; // '#dc3545'
+ public const COLOR_RED_DARK = '#a71d2a';
+ public const COLOR_BLUE = '#4da3ff'; // '#007bff'
+ public const COLOR_BLUE_DARK = '#0056b3';
+
+ /**
+ * Fetch chart data
+ *
+ * @param string $chart Name of the chart
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function chart($chart)
+ {
+ if (!preg_match('/^[a-z-]+$/', $chart)) {
+ return $this->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 @@
</table>
</div>
</div>
- <div id="dashboard-nav"></div>
+ <div id="dashboard-nav" class="mt-3">
+ <router-link class="card link-stats" :to="{ name: 'stats' }">
+ <svg-icon icon="chart-line"></svg-icon><span class="name">Stats</span>
+ </router-link>
+ </div>
</div>
</template>
<script>
+ import { library } from '@fortawesome/fontawesome-svg-core'
+ import { faChartLine } from '@fortawesome/free-solid-svg-icons'
+ library.add(faChartLine)
+
export default {
data() {
return {
diff --git a/src/resources/vue/Admin/Stats.vue b/src/resources/vue/Admin/Stats.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Admin/Stats.vue
@@ -0,0 +1,41 @@
+<template>
+ <div id="stats-container" class="container">
+ </div>
+</template>
+
+<script>
+ import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
+
+ export default {
+ data() {
+ return {
+ charts: {}
+ }
+ },
+ mounted() {
+ this.loadChart('users')
+ },
+ methods: {
+ drawChart(name, data) {
+ if (!data.title) {
+ return
+ }
+
+ const ch = new Chart('#chart-' + name, data)
+
+ this.charts[name] = ch
+ },
+ loadChart(name) {
+ const chart = $('<div>').attr({ id: 'chart-' + name }).appendTo(this.$el)
+
+ this.$root.addLoader(chart)
+
+ axios.get('/api/v4/stats/chart/' + name)
+ .then(response => {
+ this.$root.removeLoader(chart)
+ this.drawChart(name, response.data)
+ })
+ }
+ }
+ }
+</script>
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');
}
);

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 30, 4:10 AM (4 d, 4 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18791853
Default Alt Text
D1822.1774843801.diff (8 KB)

Event Timeline