pui9-dashboard 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "pui9-dashboard",
3
+ "description": "PUI9 Dashboard Components",
4
+ "author": {
5
+ "name": "Prodevelop S.L."
6
+ },
7
+ "version": "1.16.4",
8
+ "license": "Copyright",
9
+ "main": "dist/pui9-dashboard.common.js",
10
+ "scripts": {
11
+ "serve": "vue-cli-service serve",
12
+ "build-bundle": "vue-cli-service build --target lib --name pui9-dashboard ./src/index.js",
13
+ "prepare": "npm run build-bundle",
14
+ "lint": "vue-cli-service lint"
15
+ },
16
+ "dependencies": {
17
+ "echarts": "5.1.2",
18
+ "vue-grid-layout": "2.3.12"
19
+ },
20
+ "devDependencies": {
21
+ "@vue/cli-plugin-babel": "4.5.13",
22
+ "@vue/cli-plugin-eslint": "4.5.13",
23
+ "@vue/cli-service": "4.5.13",
24
+ "autoprefixer": "9.8.6",
25
+ "babel-eslint": "10.1.0",
26
+ "core-js": "3.17.2",
27
+ "css-loader": "0.28.11",
28
+ "deepmerge": "4.2.2",
29
+ "eslint": "6.8.0",
30
+ "eslint-plugin-vue": "6.2.2",
31
+ "postcss-css-variables": "0.18.0",
32
+ "postcss-import": "12.0.1",
33
+ "postcss-loader": "4.1.0",
34
+ "postcss-nested": "4.2.3",
35
+ "postcss-url": "8.0.0",
36
+ "sass": "1.27.0",
37
+ "sass-loader": "10.0.2",
38
+ "stylus": "0.54.8",
39
+ "stylus-loader": "3.0.2",
40
+ "vue": "2.6.14",
41
+ "vue-i18n": "8.25.0",
42
+ "vue-cli-plugin-vuetify": "2.4.1",
43
+ "vue-template-compiler": "2.6.14",
44
+ "vuetify-loader": "1.7.3",
45
+ "vuetify": "2.3.23"
46
+ },
47
+ "eslintConfig": {
48
+ "root": true,
49
+ "env": {
50
+ "node": true
51
+ },
52
+ "extends": [
53
+ "plugin:vue/essential",
54
+ "eslint:recommended"
55
+ ],
56
+ "parserOptions": {
57
+ "parser": "babel-eslint"
58
+ },
59
+ "rules": {}
60
+ },
61
+ "browserslist": [
62
+ "> 1%",
63
+ "last 2 versions"
64
+ ],
65
+ "files": [
66
+ "dist/*",
67
+ "src/*",
68
+ "*.json"
69
+ ]
70
+ }
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <div class="ml-4 mr-4 overflow-y-auto" :style="{ 'max-height': maxHeight }">
3
+ <v-select
4
+ v-if="!defaultDashboard"
5
+ v-model="dashboardSelected"
6
+ :items="dashboards"
7
+ :item-text="(item) => `${item.name}`"
8
+ return-object
9
+ placeholder=" "
10
+ outlined
11
+ solo
12
+ hide-details
13
+ ></v-select>
14
+ <pui-grid-layout :key="layoutKey" :layout="layout" :widgets="widgets" :draggable="false" :resizable="false" />
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import PuiGridLayout from './PuiGridLayout.vue';
20
+
21
+ export default {
22
+ name: 'PuiDashboard',
23
+ components: { PuiGridLayout },
24
+ props: {
25
+ defaultDashboard: {
26
+ type: Object,
27
+ required: false,
28
+ default: null
29
+ }
30
+ },
31
+ data() {
32
+ return {
33
+ dashboardSelected: null,
34
+ dashboards: [],
35
+ widgets: [],
36
+ layoutKey: 1
37
+ };
38
+ },
39
+ computed: {
40
+ maxHeight() {
41
+ if (this.$store.state.global.appContainerHeader) {
42
+ return `${window.innerHeight - 78}px`;
43
+ }
44
+ return `${window.innerHeight}px`;
45
+ },
46
+ layout() {
47
+ if (this.defaultDashboard && this.defaultDashboard.definition) {
48
+ return this.defaultDashboard.definition;
49
+ }
50
+ return (this.dashboardSelected && this.dashboardSelected.definition) || [];
51
+ }
52
+ },
53
+ watch: {
54
+ dashboardSelected() {
55
+ // force redraw pui-grid-layout component on dashboard selected
56
+ this.layoutKey += 1;
57
+ }
58
+ },
59
+ mounted() {
60
+ this.$puiRequests.postRequest('/puisearch', { model: 'puidashboard', rows: 100 }, (response) => {
61
+ if (response && response.data && response.data.data) {
62
+ this.dashboards = response.data.data.map((item) => {
63
+ return {
64
+ id: item.id,
65
+ name: item.name,
66
+ definition: JSON.parse(item.definition)
67
+ };
68
+ });
69
+ this.dashboardSelected = this.dashboards[0];
70
+ }
71
+ });
72
+ this.$puiRequests.postRequest('/puisearch', { model: 'puiwidget', rows: 100 }, (response) => {
73
+ if (response && response.data && response.data.data) {
74
+ this.widgets = response.data.data.map((item) => {
75
+ return {
76
+ id: item.id,
77
+ name: item.name,
78
+ component: item.component,
79
+ type: item.type,
80
+ definition: JSON.parse(item.definition)
81
+ };
82
+ });
83
+ }
84
+ });
85
+ }
86
+ };
87
+ </script>
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <pui-dashboard />
3
+ </template>
4
+
5
+ <script>
6
+ import PuiDashboard from './PuiDashboard.vue';
7
+
8
+ export default {
9
+ name: 'PuiDashboardOverview',
10
+ components: { PuiDashboard }
11
+ }
12
+ </script>
@@ -0,0 +1,127 @@
1
+ <template>
2
+ <grid-layout
3
+ :layout.sync="internalLayout"
4
+ :row-height="30"
5
+ :is-draggable="draggable"
6
+ :is-resizable="resizable"
7
+ :responsive="responsive"
8
+ :vertical-compact="true"
9
+ :use-css-transforms="true"
10
+ @layout-ready="layoutReadyEvent"
11
+ >
12
+ <grid-item v-for="item in internalLayout" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i" :key="item.i" @resize="resizeEvent">
13
+ <component
14
+ :is="getWidgetProperty(item.widget, 'component')"
15
+ :type="getWidgetProperty(item.widget, 'type')"
16
+ :definition="getWidgetProperty(item.widget, 'definition')"
17
+ :resized="item.resized"
18
+ />
19
+ <span class="remove" v-if="draggable && resizable" @click="removeItem(item.i)">x</span>
20
+ </grid-item>
21
+ </grid-layout>
22
+ </template>
23
+
24
+ <script>
25
+ import { GridLayout, GridItem } from 'vue-grid-layout';
26
+
27
+ export default {
28
+ components: { GridLayout, GridItem },
29
+ props: {
30
+ layout: {
31
+ type: Array,
32
+ required: true
33
+ },
34
+ widgets: {
35
+ type: Array,
36
+ required: true
37
+ },
38
+ draggable: {
39
+ type: Boolean,
40
+ default: true
41
+ },
42
+ resizable: {
43
+ type: Boolean,
44
+ default: true
45
+ },
46
+ responsive: {
47
+ type: Boolean,
48
+ default: true
49
+ }
50
+ },
51
+ computed: {
52
+ internalLayout: {
53
+ // getter
54
+ get() {
55
+ return [...this.layout];
56
+ },
57
+ // setter
58
+ set() {
59
+ // not remove to prevent vue warning
60
+ }
61
+ }
62
+ },
63
+ methods: {
64
+ layoutReadyEvent() {
65
+ for (let j = 0; j < this.internalLayout.length; j++) {
66
+ const item = this.internalLayout[j];
67
+ item.resized = { width: item.resized.width, height: item.resized.height };
68
+ }
69
+ },
70
+ resizeEvent(i, newH, newW, newHPx, newWPx) {
71
+ for (let j = 0; j < this.internalLayout.length; j++) {
72
+ const item = this.internalLayout[j];
73
+ if (item.i === i) {
74
+ item.resized = { width: newWPx, height: newHPx };
75
+ break;
76
+ }
77
+ }
78
+ },
79
+ getWidgetProperty(widgetId, property) {
80
+ for (let i = 0; i < this.widgets.length; i++) {
81
+ const widget = this.widgets[i];
82
+ if (widget.id === widgetId) {
83
+ return widget[property];
84
+ }
85
+ }
86
+ return null;
87
+ },
88
+ removeItem(index) {
89
+ this.$emit('remove-widget', index);
90
+ }
91
+ }
92
+ };
93
+ </script>
94
+
95
+ <style lang="postcss">
96
+ .vue-grid-layout {
97
+ background: #eee;
98
+ }
99
+ .vue-grid-item .resizing {
100
+ opacity: 0.9;
101
+ }
102
+ .vue-grid-item .no-drag {
103
+ height: 100%;
104
+ width: 100%;
105
+ }
106
+ .vue-draggable-handle {
107
+ position: absolute;
108
+ width: 20px;
109
+ height: 20px;
110
+ top: 0;
111
+ left: 0;
112
+ background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>")
113
+ no-repeat;
114
+ background-position: bottom right;
115
+ padding: 0 8px 8px 0;
116
+ background-repeat: no-repeat;
117
+ background-origin: content-box;
118
+ box-sizing: border-box;
119
+ cursor: pointer;
120
+ }
121
+ .remove {
122
+ position: absolute;
123
+ right: 2px;
124
+ top: 0;
125
+ cursor: pointer;
126
+ }
127
+ </style>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <div class="overflow-y-auto" :style="{ 'max-height': maxHeight }">
3
+ <v-row dense class="ma-3">
4
+ <v-col cols="12" sm="3" md="6" v-for="(item, index) in items" :key="index" style="max-height: 500px">
5
+ <component :is="item.component" :type="item.type" :definition="item.definition" :resized="{ height: 450 }" />
6
+ </v-col>
7
+ </v-row>
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+ export default {
13
+ name: 'PuiWidgetOverview',
14
+ data() {
15
+ return {
16
+ items: []
17
+ };
18
+ },
19
+ computed: {
20
+ maxHeight() {
21
+ if (this.$store.state.global.appContainerHeader) {
22
+ return `${window.innerHeight - 134}px`;
23
+ }
24
+ return `${window.innerHeight - 78}px`;
25
+ }
26
+ },
27
+ mounted() {
28
+ this.$puiRequests.postRequest('/puisearch', { model: 'puiwidget', rows: 100 }, (response) => {
29
+ if (response && response.data && response.data.data) {
30
+ this.items = response.data.data.map(function (item) {
31
+ return {
32
+ component: item.component,
33
+ type: item.type,
34
+ definition: JSON.parse(item.definition)
35
+ };
36
+ });
37
+ }
38
+ });
39
+ }
40
+ };
41
+ </script>
@@ -0,0 +1,143 @@
1
+ <template>
2
+ <div class="pui-form">
3
+ <v-form class="mb-4 pb-4" ref="form" v-model="valid" lazy-validation v-if="modelLoaded">
4
+ <v-row class="pui-form-layout">
5
+ <v-col cols="12">
6
+ <pui-field-set :title="$t('form.puidashboard')">
7
+ <v-row dense>
8
+ <v-col cols="6">
9
+ <pui-text-field
10
+ :label="$t('form.puidashboard.name')"
11
+ v-model="model.name"
12
+ :disabled="formDisabled"
13
+ maxlength="100"
14
+ toplabel
15
+ required
16
+ ></pui-text-field>
17
+ </v-col>
18
+ <v-col cols="6">
19
+ <v-btn @click="dialog = true">Add widget</v-btn>
20
+ </v-col>
21
+ </v-row>
22
+ <v-row dense>
23
+ <v-col cols="12">
24
+ <pui-grid-layout :layout="layout" :widgets="widgets" @remove-widget="removeWidget" />
25
+ </v-col>
26
+ </v-row>
27
+ </pui-field-set>
28
+ </v-col>
29
+ </v-row>
30
+ <pui-form-footer>
31
+ <pui-form-footer-btns :formDisabled="formDisabled" :saveDisabled="saving" :save="save" :back="back"></pui-form-footer-btns>
32
+ </pui-form-footer>
33
+ </v-form>
34
+ <pui-form-loading v-else></pui-form-loading>
35
+
36
+ <v-dialog v-model="dialog" max-width="450">
37
+ <v-card>
38
+ <v-card-title>Select widget</v-card-title>
39
+
40
+ <v-card-text>
41
+ <v-select
42
+ v-model="widgetSelected"
43
+ :items="widgets"
44
+ :item-text="(item) => `${item.name} (${item.type})`"
45
+ return-object
46
+ placeholder=" "
47
+ outlined
48
+ solo
49
+ hide-details
50
+ ></v-select>
51
+ </v-card-text>
52
+
53
+ <v-card-actions>
54
+ <v-spacer></v-spacer>
55
+
56
+ <v-btn text @click="dialog = false">{{ $t('form.close') }}</v-btn>
57
+
58
+ <v-btn
59
+ color="green darken-1"
60
+ text
61
+ @click="
62
+ addWidget(widgetSelected);
63
+ dialog = false;
64
+ "
65
+ >{{ $t('form.ok') }}</v-btn
66
+ >
67
+ </v-card-actions>
68
+ </v-card>
69
+ </v-dialog>
70
+ </div>
71
+ </template>
72
+
73
+ <script>
74
+ import PuiFormMethodsMixin from '../../../../pui9-mixins/PuiFormMethodsMixin';
75
+ import PuiGridLayout from '../PuiGridLayout.vue';
76
+
77
+ export default {
78
+ name: 'PuiDashboardForm',
79
+ mixins: [PuiFormMethodsMixin],
80
+ components: { PuiGridLayout },
81
+ data() {
82
+ return {
83
+ modelName: 'puidashboard',
84
+ dialog: false,
85
+ index: 0,
86
+ layout: [],
87
+ widgets: [],
88
+ widgetSelected: null
89
+ };
90
+ },
91
+ mounted() {
92
+ this.$puiRequests.postRequest('/puisearch', { model: 'puiwidget', rows: 100 }, (response) => {
93
+ if (response && response.data && response.data.data) {
94
+ this.widgets = response.data.data.map((item) => {
95
+ return {
96
+ id: item.id,
97
+ name: item.name,
98
+ component: item.component,
99
+ type: item.type,
100
+ definition: JSON.parse(item.definition)
101
+ };
102
+ });
103
+ }
104
+ });
105
+ },
106
+ methods: {
107
+ addWidget(widget) {
108
+ // Add a new item. It must have a unique key!
109
+ this.layout.push({
110
+ x: (this.layout.length * 2) % (this.colNum || 12),
111
+ y: this.layout.length + (this.colNum || 12), // puts it at the bottom
112
+ w: 6,
113
+ h: 6,
114
+ i: this.index,
115
+ widget: widget.id
116
+ });
117
+ // Increment the counter to ensure key is always unique.
118
+ this.index++;
119
+ // Reset last widget selected
120
+ this.widgetSelected = null;
121
+ },
122
+ removeWidget(widget) {
123
+ const index = this.layout.map((item) => item.i).indexOf(widget);
124
+ this.layout.splice(index, 1);
125
+ },
126
+ afterGetData() {
127
+ if (this.model.definition) {
128
+ this.layout = JSON.parse(this.model.definition);
129
+ this.index =
130
+ Math.max.apply(
131
+ Math,
132
+ this.layout.map(function (item) {
133
+ return item.i;
134
+ })
135
+ ) + 1;
136
+ }
137
+ },
138
+ beforeSave() {
139
+ this.model.definition = JSON.stringify(this.layout);
140
+ }
141
+ }
142
+ };
143
+ </script>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <pui-datatable :modelName="modelName"></pui-datatable>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'PuiDashboardGrid',
8
+ data() {
9
+ return {
10
+ modelName: 'puidashboard'
11
+ };
12
+ }
13
+ };
14
+ </script>
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <div class="pui-form">
3
+ <v-form class="mb-4 pb-4" ref="form" v-model="valid" lazy-validation v-if="modelLoaded">
4
+ <v-row class="pui-form-layout">
5
+ <v-col cols="5">
6
+ <pui-field-set highlighted>
7
+ <v-row dense>
8
+ <v-col cols="12">
9
+ <pui-text-field
10
+ :label="$t('form.puiwidget.name')"
11
+ v-model="model.name"
12
+ :disabled="formDisabled"
13
+ maxlength="100"
14
+ toplabel
15
+ required
16
+ ></pui-text-field>
17
+ </v-col>
18
+ <v-col cols="12">
19
+ <pui-select
20
+ :id="`type-${modelName}`"
21
+ :attach="`type-${modelName}`"
22
+ :label="this.$t('form.puiwidget.type')"
23
+ toplabel
24
+ required
25
+ :disabled="formDisabled"
26
+ v-model="model"
27
+ modelName="puiwidgettype"
28
+ :modelFormMapping="{ id: 'typeid' }"
29
+ :itemsToSelect="[{ id: model.typeid }]"
30
+ itemValue="id"
31
+ itemText="name"
32
+ ></pui-select>
33
+ </v-col>
34
+ <v-col cols="12">
35
+ <component :is="component" :type="componentType" :key="componentKey" :resized="{ height: 450 }" />
36
+ </v-col>
37
+ </v-row>
38
+ </pui-field-set>
39
+ </v-col>
40
+ <v-col cols="7">
41
+ <pui-field-set :title="$t('form.puiwidget.definition.attributes')">
42
+ <v-row dense>
43
+ <v-col cols="12" v-for="(field, index) in definitionModel" :key="index">
44
+ <component
45
+ v-if="field.component === 'pui-select'"
46
+ :is="field.component"
47
+ toplabel
48
+ :label="$t(field.label)"
49
+ v-model="field.value"
50
+ :items="field.items"
51
+ :itemsToSelect="field.value ? [field.value] : []"
52
+ :itemText="
53
+ (item) => {
54
+ return item;
55
+ }
56
+ "
57
+ :required="field.required"
58
+ />
59
+ <component
60
+ v-else
61
+ :is="field.component"
62
+ toplabel
63
+ :label="$t(field.label)"
64
+ v-model="field.value"
65
+ :required="field.required"
66
+ />
67
+ </v-col>
68
+ <v-col cols="12">
69
+ <pui-text-area toplabel v-model="model.definition" maxlength="1000" disabled noeditable />
70
+ </v-col>
71
+ </v-row>
72
+ </pui-field-set>
73
+ </v-col>
74
+ </v-row>
75
+ <pui-form-footer>
76
+ <pui-form-footer-btns :formDisabled="formDisabled" :saveDisabled="saving" :save="save" :back="back"></pui-form-footer-btns>
77
+ </pui-form-footer>
78
+ </v-form>
79
+ <pui-form-loading v-else></pui-form-loading>
80
+ </div>
81
+ </template>
82
+
83
+ <script>
84
+ import PuiFormMethodsMixin from '../../../../pui9-mixins/PuiFormMethodsMixin';
85
+
86
+ export default {
87
+ name: 'PuiWidgetForm',
88
+ mixins: [PuiFormMethodsMixin],
89
+ data() {
90
+ return {
91
+ modelName: 'puiwidget',
92
+ component: null,
93
+ componentType: null,
94
+ componentKey: 1,
95
+ definitionModel: null
96
+ };
97
+ },
98
+ watch: {
99
+ definitionModel: {
100
+ handler(newVal) {
101
+ this.model.definition = JSON.stringify(newVal);
102
+ },
103
+ deep: true
104
+ }
105
+ },
106
+ mounted() {
107
+ this.$puiEvents.$on(`onPuiSelect_selectedItems-type-${this.modelName}`, (newVal) => {
108
+ this.component = newVal.model.component;
109
+ this.componentType = newVal.model.type;
110
+ this.componentKey = this.componentKey + 1;
111
+
112
+ if (this.isCreatingElement || this.model.typeid !== this.initialTypeid) {
113
+ this.model.definition = newVal.model.definition;
114
+ }
115
+ this.definitionModel = JSON.parse(this.model.definition);
116
+ });
117
+ },
118
+ beforeDestroy() {
119
+ this.$puiEvents.$off(`onPuiSelect_selectedItems-type-${this.modelName}`);
120
+ },
121
+ methods: {
122
+ afterGetData() {
123
+ if (!this.isCreatingElement) {
124
+ this.initialTypeid = this.model.typeid;
125
+ }
126
+ }
127
+ }
128
+ };
129
+ </script>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <pui-datatable :modelName="modelName"></pui-datatable>
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'PuiWidgetGrid',
8
+ data() {
9
+ return {
10
+ modelName: 'puiwidget'
11
+ };
12
+ }
13
+ };
14
+ </script>
package/src/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import PuiDashboard from './components/PuiDashboard';
2
+ import PuiWidgetDatatable from './widgets/PuiWidgetDatatable';
3
+ import PuiWidgetEChart from './widgets/PuiWidgetEChart';
4
+
5
+ const Components = {
6
+ PuiDashboard,
7
+ PuiWidgetDatatable,
8
+ PuiWidgetEChart
9
+ };
10
+
11
+ export default {
12
+ install(Vue) {
13
+ Object.keys(Components).forEach((name) => {
14
+ Vue.component(name, Components[name]);
15
+ });
16
+ }
17
+ };