Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117819654
D1822.1775293118.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None
D1822.1775293118.diff
View Options
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
@@ -6853,6 +6853,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
@@ -21,6 +21,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",
"popper.js": "^1.16.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 = [
@@ -32,6 +33,12 @@
name: 'logout',
component: LogoutComponent
},
+ {
+ path: '/stats',
+ name: 'stats',
+ component: StatsComponent,
+ meta: { requiresAuth: true }
+ },
{
path: '/user/:user',
name: 'user',
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
@@ -4,4 +4,5 @@
@import '../menu';
@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
@@ -128,5 +128,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
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 8:58 AM (10 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18828714
Default Alt Text
D1822.1775293118.diff (8 KB)
Attached To
Mode
D1822: Stats
Attached
Detach File
Event Timeline