3
0

Replace chart on admin dashboard with echarts

This commit is contained in:
Jože Fortun
2023-01-31 18:06:19 +01:00
parent 4ccafb19da
commit 390a14ea73
6 changed files with 177 additions and 142 deletions
+3 -1
View File
@@ -27,8 +27,8 @@
"acorn": "^6.4.1",
"axios": "0.21.2",
"bootstrap-vue": "^2.21.2",
"echarts": "^5.3.3",
"brace": "^0.11.1",
"chart.js": "^2.9.3",
"copy-to-clipboard": "^3.3.1",
"flush-promises": "^1.0.2",
"fstream": "^1.0.12",
@@ -49,6 +49,7 @@
"stylelint-scss": "3.14.2",
"stylelint-webpack-plugin": "^0.10.5",
"vue": "2.6.14",
"vue-echarts": "^6.2.3",
"vue-native-websocket": "^2.0.15",
"vue-router": "3.1.5",
"vue-select": "3.4.0",
@@ -63,6 +64,7 @@
"@vue/cli-plugin-eslint": "^3.10.0",
"@vue/cli-plugin-unit-mocha": "^3.10.0",
"@vue/cli-service": "^3.10.0",
"@vue/composition-api": "^1.7.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.25",
"babel-eslint": "^10.0.2",
+26
View File
@@ -8,6 +8,22 @@ import { components } from '@cortezaproject/corteza-vue'
import { LMap, LTileLayer, LMarker } from 'vue2-leaflet'
import 'leaflet/dist/leaflet.css'
import { Icon } from 'leaflet'
// import ECharts modules manually to reduce bundle size
import ECharts from 'vue-echarts'
import { use } from 'echarts/core'
import {
SVGRenderer,
} from 'echarts/renderers'
import {
LineChart,
} from 'echarts/charts'
import {
TitleComponent,
GridComponent,
TooltipComponent,
} from 'echarts/components'
const { CCorredorManualButtons, CPermissionsButton, CInputConfirm } = components
Vue.use(PortalVue)
@@ -23,6 +39,16 @@ Vue.component('l-map', LMap)
Vue.component('l-tile-layer', LTileLayer)
Vue.component('l-marker', LMarker)
use([
LineChart,
SVGRenderer,
TitleComponent,
GridComponent,
TooltipComponent,
])
Vue.component('e-charts', ECharts)
delete Icon.Default.prototype._getIconUrl
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
+141 -139
View File
@@ -1,6 +1,6 @@
<template>
<b-container
class="py-3"
class="d-flex flex-column py-3 flex-fill"
>
<c-content-header
:title="$t('title')"
@@ -13,33 +13,40 @@
@click="dispatchCortezaSystemEvent($event)"
/>
</c-content-header>
<b-row>
<b-row
class="flex-fill"
>
<b-col
v-show="users.total"
cols="12"
>
<b-card
class="shadow-sm mb-3"
body-class="position-relative p-0"
header-class="bg-white"
footer-class="bg-white"
class="shadow-sm h-100"
>
<b-card-title title-tag="h3">
<router-link
class="display-3"
:to="{ name: 'system.user.list' }"
:area-label="users.valid + ' ' + $t('users.title')"
>
{{ users.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('users.title') }}
</b-card-sub-title>
<template #header>
<b-card-title title-tag="h3">
<router-link
:to="{ name: 'system.user.list' }"
:area-label="`${users.valid} ${$t('users.title')}`"
class="display-3 text-decoration-none"
>
{{ users.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('users.title') }}
</b-card-sub-title>
</template>
<canvas
ref="userChart"
style="height: 100px; width: 100%"
<c-chart
v-if="userChart"
:chart="userChart"
/>
<b-container class="mt-3">
<template #footer>
<b-row>
<b-col
cols="12"
@@ -49,6 +56,7 @@
<router-link
:to="{ name: 'system.user.list', query: { deleted: 1, suspended: 1 } }"
:aria-label="users.total + ' ' + $t('users.users') + ' ' + $t('users.total')"
class="text-decoration-none"
>
{{ users.total }}
</router-link>
@@ -64,6 +72,7 @@
<router-link
:to="{ name: 'system.user.list', query: { deleted: 1, suspended: 2 } }"
:aria-label="users.suspended + ' ' + $t('users.users') + ' ' + $t('users.suspended')"
class="text-decoration-none"
>
{{ users.suspended }}
</router-link>
@@ -79,6 +88,7 @@
<router-link
:to="{ name: 'system.user.list', query: { deleted: 2, suspended: 1 } }"
:aria-label="users.deleted + ' ' + $t('users.users') + ' ' + $t('users.deleted')"
class="text-decoration-none"
>
{{ users.deleted }}
</router-link>
@@ -87,31 +97,40 @@
</span>
</b-col>
</b-row>
</b-container>
</template>
</b-card>
</b-col>
</b-row>
<b-row align-v="stretch">
<b-col
v-show="roles.total"
cols="12"
md="6"
class="mt-3"
>
<b-card
class="shadow-sm mb-3"
no-body
header-class="bg-white"
footer-class="bg-white"
class="shadow-sm"
>
<b-card-title title-tag="h3">
<router-link
class="display-4"
:to="{ name: 'system.role.list' }"
:aria-label="roles.valid + ' ' + $t('roles.title')"
>
{{ roles.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('roles.title') }}
</b-card-sub-title>
<template #header>
<b-card-title title-tag="h3">
<router-link
:to="{ name: 'system.role.list' }"
:aria-label="roles.valid + ' ' + $t('roles.title')"
class="display-4 text-decoration-none"
>
{{ roles.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('roles.title') }}
</b-card-sub-title>
</template>
<b-container class="mt-3">
<template #footer>
<b-row>
<b-col
cols="12"
@@ -121,6 +140,7 @@
<router-link
:to="{ name: 'system.role.list', query: { deleted: 1, archived: 1 } }"
:aria-label="roles.total + ' ' + $t('roles.roles') + ' ' + $t('roles.total')"
class="text-decoration-none"
>
{{ roles.total }}
</router-link>
@@ -136,6 +156,7 @@
<router-link
:to="{ name: 'system.role.list', query: { deleted: 1, archived: 2 } }"
:aria-label="roles.archived + ' ' + $t('roles.roles') + ' ' + $t('roles.archived')"
class="text-decoration-none"
>
{{ roles.archived }}
</router-link>
@@ -151,6 +172,7 @@
<router-link
:to="{ name: 'system.role.list', query: { deleted: 2, archived: 1 } }"
:aria-label="roles.deleted + ' ' + $t('roles.roles') + ' ' + $t('roles.deleted')"
class="text-decoration-none"
>
{{ roles.deleted }}
</router-link>
@@ -159,31 +181,38 @@
</span>
</b-col>
</b-row>
</b-container>
</template>
</b-card>
</b-col>
<b-col
v-show="applications.total"
cols="12"
md="6"
class="mt-3"
>
<b-card
class="shadow-sm mb-3"
no-body
header-class="bg-white"
footer-class="bg-white"
class="shadow-sm"
>
<b-card-title title-tag="h3">
<router-link
class="display-4"
:to="{ name: 'system.application.list' }"
:aria-label="applications.valid + ' ' + $t('applications.title')"
>
{{ applications.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('applications.title') }}
</b-card-sub-title>
<template #header>
<b-card-title title-tag="h3">
<router-link
:to="{ name: 'system.application.list' }"
:aria-label="applications.valid + ' ' + $t('applications.title')"
class="display-4 text-decoration-none"
>
{{ applications.valid }}
</router-link>
</b-card-title>
<b-card-sub-title sub-title-tag="h4">
{{ $t('applications.title') }}
</b-card-sub-title>
</template>
<b-container class="mt-3">
<template #footer>
<b-row>
<b-col
cols="12"
@@ -193,6 +222,7 @@
<router-link
:to="{ name: 'system.application.list', query: { deleted: 1 } }"
:aria-label="applications.total + ' ' + $t('applications.applications') + ' ' + $t('applications.total')"
class="text-decoration-none"
>
{{ applications.total }}
</router-link>
@@ -208,6 +238,7 @@
<router-link
:to="{ name: 'system.application.list', query: { deleted: 2 } }"
:aria-label="applications.deleted + ' ' + $t('applications.applications') + ' ' + $t('applications.deleted')"
class="text-decoration-none"
>
{{ applications.deleted }}
</router-link>
@@ -216,7 +247,7 @@
</span>
</b-col>
</b-row>
</b-container>
</template>
</b-card>
</b-col>
</b-row>
@@ -224,14 +255,19 @@
</template>
<script>
import ChartJS from 'chart.js'
import * as moment from 'moment'
import { components } from '@cortezaproject/corteza-vue'
const { CChart } = components
export default {
i18nOptions: {
namespaces: 'dashboard',
},
components: {
CChart,
},
data () {
return {
userChart: null,
@@ -259,26 +295,6 @@ export default {
}
},
computed: {
userTimeline () {
const d = this.users.dailyCreated
const unit = this.getComfortableTimeUnit(d)
let aux = {}
for (let i = 0; i < d.length; i += 2) {
const ts = moment.unix(d[i]).startOf(unit).toISOString()
aux[ts] = (aux[ts] || 0) + d[i + 1]
}
let fmt = []
for (let x in aux) {
fmt.push({ x, y: aux[x] })
}
return fmt
},
},
mounted () {
this.$SystemAPI.statsList().then(({ users, roles, applications }) => {
if (users) {
@@ -295,77 +311,71 @@ export default {
this.initUserChart()
})
// this.$nextTick(() => {
// this.$EventBus.Dispatch({
// resourceType: 'ui:admin:dashboard',
// eventType: 'afterMount',
// args: { data: this.$data, $el: this.$el },
// })
// })
},
// updated () {
// this.$nextTick(() => {
// this.$EventBus.Dispatch({
// resourceType: 'ui:admin:dashboard',
// eventType: 'afterUpdate',
// args: { data: this.users, $el: this.$el },
// })
// })
// },
methods: {
initUserChart () {
if (this.users.total === 0) {
return
}
const d = this.users.dailyCreated
const unit = this.getComfortableTimeUnit(d)
const ctx = this.$refs.userChart.getContext('2d')
const { dates, values } = this.getUserTimeline()
this.userChart = new ChartJS(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Created users',
data: this.userTimeline,
fill: false,
borderColor: 'gray',
},
],
this.userChart = {
tooltip: {
trigger: 'value',
},
options: {
layout: {
padding: {
left: 10,
right: 10,
top: 10,
textStyle: {
fontFamily: 'Poppins-Regular',
},
xAxis: {
type: 'category',
data: dates,
boundaryGap: false,
},
yAxis: {
type: 'value',
},
grid: {
top: 20,
right: 50,
bottom: 20,
left: 40,
containLabel: true,
},
series: [
{
name: this.$t('users.created'),
type: 'line',
data: values,
smooth: 0.5,
areaStyle: {
opacity: 0.5,
},
},
legend: {
display: false,
},
scales: {
xAxes: [
{
type: 'time',
time: {
unit: unit,
minUnit: unit,
},
},
],
yAxes: [
{
display: false,
},
],
},
},
})
],
}
},
getUserTimeline () {
const data = this.users.dailyCreated
const unit = this.getComfortableTimeUnit(data)
let aux = {}
for (let i = 0; i < data.length; i += 2) {
const ts = moment.unix(data[i]).startOf(unit).format(unit === 'month' ? 'MMM YYYY' : 'D MMM YYYY')
aux[ts] = (aux[ts] || 0) + data[i + 1]
}
const dates = []
const values = []
for (let date in aux) {
dates.push(date)
values.push(aux[date])
}
return { dates, values }
},
/**
@@ -388,18 +398,10 @@ export default {
const max = ts[ts.length - 1]
const diffInDays = (max - min) / (60 * 60 * 24)
if (diffInDays > 360) {
return 'year'
}
if (diffInDays > 120) {
return 'month'
}
if (diffInDays > 14) {
return 'week'
}
return 'day'
},
},
+5
View File
@@ -1214,6 +1214,11 @@
optionalDependencies:
prettier "^1.18.2"
"@vue/composition-api@^1.7.0":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.7.1.tgz#aa6831be5a12817d93e89e247460c310dd7a3a32"
integrity sha512-xDWoEtxGXhH9Ku3ROYX/rzhcpt4v31hpPU5zF3UeVC/qxA3dChmqU8zvTUYoKh3j7rzpNsoFOwqsWG7XPMlaFA==
"@vue/eslint-config-standard@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@vue/eslint-config-standard/-/eslint-config-standard-4.0.0.tgz#6be447ee674e3b0f733c584098fd9a22e6d76fcd"
+1 -2
View File
@@ -136,8 +136,7 @@
"resolutions": {
"**/**/highlight.js": "^10.4.1",
"**/**/moment": "2.29.2",
"**/**/prosemirror-model": "^1.11.2",
"**/**/chart.js": "2.9.3"
"**/**/prosemirror-model": "^1.11.2"
},
"nyc": {
"all": true,
@@ -11,6 +11,7 @@ roles:
total: Total
title: Dashboard
users:
created: Created users(s)
deleted: Deleted
suspended: Suspended
title: Active user(s)