@mixd-id/web-scaffold 0.1.230406366 → 0.1.230406367

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mixd-id/web-scaffold",
3
3
  "private": false,
4
- "version": "0.1.230406366",
4
+ "version": "0.1.230406367",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -0,0 +1,9 @@
1
+ export const component = {
2
+
3
+ type:"DataTable",
4
+
5
+ props:{
6
+ height:[ "h-[50vh]", "md:h-[50vh]", "", "" ]
7
+ }
8
+
9
+ }
package/src/index.js CHANGED
@@ -679,6 +679,7 @@ export default{
679
679
  app.component('PolarArea', defineAsyncComponent(() => import("./widgets/Dashboard/PolarArea.vue")))
680
680
  app.component('Metric', defineAsyncComponent(() => import("./widgets/Dashboard/Metric.vue")))
681
681
  app.component('BarChart', defineAsyncComponent(() => import("./widgets/Dashboard/BarChart.vue")))
682
+ app.component('DataTable', defineAsyncComponent(() => import("./widgets/Dashboard/DataTable.vue")))
682
683
 
683
684
  app.component('BotEditor', defineAsyncComponent(() => import("./widgets/BotEditor.vue")))
684
685
  }
@@ -85,6 +85,7 @@ const getDatasourceItems = async (datasource, opt) => {
85
85
  }
86
86
 
87
87
  const getSequelizeColumns = async (columns, opt) => {
88
+ if(!columns) columns = []
88
89
 
89
90
  const { DataTypes } = opt.Sequelize
90
91
 
package/src/utils/wss.js CHANGED
@@ -247,7 +247,6 @@ class WSS extends EventEmitter2{
247
247
  socket.close(1002, 'ping timeout')
248
248
  if(subscriber){
249
249
  subscriber.disconnect()
250
- console.log('CLOSE SOCKET', token.substring(0, 10) + '...')
251
250
  }
252
251
  else{
253
252
  console.log('CLOSE SOCKET#2', token.substring(0, 10) + '...')
@@ -74,7 +74,7 @@ import {Bar} from 'vue-chartjs'
74
74
  import Chart from 'chart.js/auto'
75
75
  import { color } from 'chart.js/helpers'
76
76
  import {strVars} from "../../utils/helpers.mjs";
77
- import {readyStateMixin} from "../../mixin/ready-state";
77
+ import {useEmitter} from "../../utils/event-bus";
78
78
 
79
79
  export default{
80
80
 
@@ -225,14 +225,43 @@ export default{
225
225
  ],
226
226
 
227
227
  height: '',
228
- datasets: null
228
+ datasets: null,
229
+ readyState: 1,
230
+ value: null
229
231
  }
230
232
  },
231
233
 
232
- inject: [ 'selectPreset' ],
234
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'selectPreset', 'socket' ],
233
235
 
234
236
  methods: {
235
237
 
238
+ load(){
239
+ const preset = this.getViewedPreset()
240
+ const {name, datasource} = preset
241
+
242
+ this.readyState = 2
243
+ this.socket.send(this.getSrc(), {
244
+ name,
245
+ views: [{
246
+ ...this.$props,
247
+ type: 'BarChart'
248
+ }],
249
+ datasource: datasource.map(_datasource => {
250
+ return {
251
+ ..._datasource,
252
+ filters: [
253
+ ...(_datasource.filters ?? []),
254
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
255
+ ]
256
+ }
257
+ })
258
+ })
259
+ .then(_ => {
260
+ this.value = _[this.uid]
261
+ })
262
+ .finally(_ => this.readyState = 1)
263
+ },
264
+
236
265
  onClick(e, segments){
237
266
 
238
267
  const clickInteractions = (this.interactions ?? []).filter(_ => _.event === 'click')
@@ -276,9 +305,17 @@ export default{
276
305
 
277
306
  mounted() {
278
307
  this.height = this.$el.clientHeight + 'px'
279
- },
280
308
 
281
- mixins: [ readyStateMixin ],
309
+ this.load()
310
+
311
+ this.emitter = useEmitter()
312
+ this.emitter.on(`${this.uid}.load`, () => {
313
+ this.load()
314
+ })
315
+ this.emitter.on(`dashboard.load`, () => {
316
+ this.load()
317
+ })
318
+ },
282
319
 
283
320
  props: {
284
321
 
@@ -309,8 +346,6 @@ export default{
309
346
 
310
347
  yAxeOnClick: Array,
311
348
 
312
- value: Object,
313
-
314
349
  interactions: Array,
315
350
 
316
351
  uid: String
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+ <VirtualTable class="flex-1"
4
+ :columns="datasourceColumns"
5
+ @scroll-end="loadNext"
6
+ :items="items">
7
+
8
+ </VirtualTable>
9
+ </div>
10
+ </template>
11
+
12
+ <script>
13
+ import {useEmitter} from "../../utils/event-bus";
14
+ import VirtualTable from "../../components/VirtualTable.vue";
15
+
16
+ export default{
17
+ components: {VirtualTable},
18
+
19
+ data(){
20
+ return {
21
+ value: null,
22
+ readyState: 1,
23
+ items: null,
24
+ hasNext: false
25
+ }
26
+ },
27
+
28
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'socket' ],
29
+
30
+ methods: {
31
+
32
+ load(){
33
+ const preset = this.getViewedPreset()
34
+ const {name, datasource} = preset
35
+
36
+ this.readyState = 2
37
+ this.socket.send(this.getSrc(), {
38
+ name,
39
+ views: [{
40
+ ...this.$props,
41
+ type: 'DataTable'
42
+ }],
43
+ datasource: datasource.map(_datasource => {
44
+ return {
45
+ ..._datasource,
46
+ filters: [
47
+ ...(_datasource.filters ?? []),
48
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
49
+ ]
50
+ }
51
+ })
52
+ })
53
+ .then(_ => {
54
+ const { items, hasNext } = _[this.uid] ?? {}
55
+ this.items = items
56
+ this.hasNext = hasNext
57
+ })
58
+ .finally(_ => this.readyState = 1)
59
+ },
60
+
61
+ loadNext(){
62
+ if(!this.hasNext) return
63
+
64
+ const preset = this.getViewedPreset()
65
+ const {name, datasource} = preset
66
+ const afterItem = this.items[this.items.length - 1]
67
+
68
+ this.socket.send(this.getSrc(), {
69
+ name,
70
+ views: [{
71
+ ...this.$props,
72
+ type: 'DataTable',
73
+ afterItem
74
+ }],
75
+ datasource: datasource.map(_datasource => {
76
+ return {
77
+ ..._datasource,
78
+ filters: [
79
+ ...(_datasource.filters ?? []),
80
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
81
+ ]
82
+ }
83
+ })
84
+ })
85
+ .then(_ => {
86
+ const { items, hasNext } = _[this.uid] ?? {}
87
+ this.items.push(...items)
88
+ this.hasNext = hasNext
89
+ })
90
+ }
91
+
92
+ },
93
+
94
+ mounted() {
95
+ this.load()
96
+
97
+ this.emitter = useEmitter()
98
+ this.emitter.on(`${this.uid}.load`, () => {
99
+ this.load()
100
+ })
101
+ this.emitter.on(`dashboard.load`, () => {
102
+ this.load()
103
+ })
104
+ },
105
+
106
+ props: {
107
+
108
+ datasourceColumns: Array,
109
+ datasourceUid: String,
110
+ label: String,
111
+ uid: String,
112
+
113
+ }
114
+
115
+ }
116
+
117
+ </script>
118
+
119
+ <style module>
120
+
121
+ .comp{
122
+ @apply min-h-[50vh] flex flex-col;
123
+ }
124
+
125
+ </style>
@@ -0,0 +1,239 @@
1
+ <template>
2
+ <div :class="$style.comp">
3
+
4
+ <div class="flex justify-center border-b-[1px] border-text-50" v-if="tabItems.length > 1">
5
+ <Tabs :items="tabItems" v-model="value.tabIndex" />
6
+ </div>
7
+
8
+ <div class="flex-1 overflow-y-auto flex flex-col">
9
+ <div v-if="value.tabIndex === 1" class="flex-1 flex flex-col gap-5 p-6">
10
+
11
+ <div class="flex flex-row items-center ">
12
+ <label class="flex-1">Active</label>
13
+ <Switch v-model="value.props.enabled" :readonly="readonly" />
14
+ </div>
15
+
16
+ <div class="flex flex-col gap-1">
17
+ <label class="text-text-400">Label</label>
18
+ <Textbox v-model="value.props.label" maxlength="50" placeholder="Label" :readonly="readonly" />
19
+ </div>
20
+
21
+ <div class="flex flex-row items-center">
22
+ <label class="flex-1">Datasource</label>
23
+ <div>
24
+ <Dropdown class="min-w-[150px]"
25
+ :readonly="readonly"
26
+ v-model="value.props.datasourceUid">
27
+ <option v-for="_datasource in datasource"
28
+ :value="_datasource.uid">
29
+ {{ _datasource.name }}
30
+ </option>
31
+ </Dropdown>
32
+ </div>
33
+ </div>
34
+
35
+ </div>
36
+
37
+ <div v-else-if="value.tabIndex === 2" class="flex-1 flex flex-col gap-5 p-6">
38
+
39
+ <div>
40
+ <Textbox placeholder="Search..." v-model="search">
41
+ <template #start>
42
+ <div class="pl-3">
43
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z"/></svg>
44
+ </div>
45
+ </template>
46
+ </Textbox>
47
+ </div>
48
+
49
+ <VirtualGrid class="flex-1 border-[1px] border-text-50 rounded-xl"
50
+ container-class="divide-y divide-text-50"
51
+ :items="columns">
52
+ <template #item="{ item }">
53
+ <div class="flex-1 flex flex-row gap-2 items-center p-1 px-3 group">
54
+ <div>
55
+ <Checkbox v-model="item.visible"
56
+ default="true"
57
+ :disabled="readonly"
58
+ @change="$emit('change')" />
59
+ </div>
60
+ <Textbox v-model="item.label2" :placeholder="item.label" :readonly="readonly"
61
+ class="flex-1 border-none bg-transparent" :class="$style.columnTextbox"
62
+ item-class="p-1 px-0" />
63
+ <div class="hidden flex-col justify-center group-hover:flex">
64
+ <button type="button" @click="moveUp(item)">
65
+ <svg width="11" height="11" class="fill-text-300 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M9.39 265.4l127.1-128C143.6 131.1 151.8 128 160 128s16.38 3.125 22.63 9.375l127.1 128c9.156 9.156 11.9 22.91 6.943 34.88S300.9 320 287.1 320H32.01c-12.94 0-24.62-7.781-29.58-19.75S.2333 274.5 9.39 265.4z"/></svg>
66
+ </button>
67
+ <button type="button" @click="moveDown(item)">
68
+ <svg width="11" height="11" class="fill-text-300 hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"/></svg>
69
+ </button>
70
+ </div>
71
+ </div>
72
+ </template>
73
+ </VirtualGrid>
74
+
75
+ </div>
76
+ </div>
77
+
78
+ </div>
79
+ </template>
80
+
81
+ <script>
82
+
83
+ import WebPagePropertySelector from "../WebPageBuilder4/WebPagePropertySelector.vue";
84
+ import {defineAsyncComponent} from "vue";
85
+ import InteractionEdit from "./InteractionEdit.vue";
86
+
87
+ export default{
88
+ components: {
89
+ InteractionEdit,
90
+ HeightSetting: defineAsyncComponent(() => import('../WebPageBuilder4/HeightSetting.vue')),
91
+ WebPagePropertySelector
92
+ },
93
+
94
+ computed: {
95
+
96
+ interactions(){
97
+ if(!Array.isArray(this.value.props.interactions))
98
+ this.value.props.interactions = []
99
+ return this.value.props.interactions
100
+ },
101
+
102
+ selectedDatasource(){
103
+ return this.datasource.find(d => d.uid === this.value.props.datasourceUid)
104
+ },
105
+
106
+ definedProperties(){
107
+ const settings = []
108
+
109
+ for(let key in this.properties){
110
+ if(key in this.value.props){
111
+ settings.push(this.properties[key])
112
+ }
113
+ }
114
+
115
+ return settings
116
+ },
117
+
118
+ tabItems(){
119
+ return [
120
+ { text:'General', value:1 },
121
+ this.value.props.datasourceUid ? { text:'Columns', value:2 } : null,
122
+ ]
123
+ .filter(_ => _)
124
+ },
125
+
126
+ columns(){
127
+ const searches = this.search.toLowerCase().split(' ').filter(_ => _.length > 2)
128
+ return (this.value.props.datasourceColumns ?? []).filter(_ => {
129
+ return !_.key.startsWith('_') &&
130
+ (searches.length < 1 || searches.every(search => _.label.toLowerCase().includes(search)))
131
+ })
132
+ },
133
+
134
+ },
135
+
136
+ data(){
137
+ return {
138
+ properties: {
139
+ 'height': { component:"HeightSetting", text:'Height', group:'Layout' }
140
+ },
141
+ search: ''
142
+ }
143
+ },
144
+
145
+ emits: [ 'change' ],
146
+
147
+ inject: [ 'uploadImage', 'viewTypes' ],
148
+
149
+ props: {
150
+
151
+ datasource: Object,
152
+
153
+ index: Number,
154
+
155
+ readonly: [ Boolean, Number ],
156
+
157
+ value: {
158
+ type: Object,
159
+ required: true
160
+ },
161
+
162
+ viewType: String,
163
+
164
+ },
165
+
166
+ methods: {
167
+
168
+ addInteraction(obj){
169
+ this.$util.push(this.interactions, obj)
170
+ },
171
+
172
+ applyDefault(){
173
+ if(!this.value.props.datasourceUid && this.datasource?.length === 1){
174
+ this.value.props.datasourceUid = this.datasource[0].uid
175
+ }
176
+
177
+ if(!this.value.tabIndex)
178
+ this.value.tabIndex = 1
179
+ },
180
+
181
+ generateColumns(){
182
+ this.value.props.datasourceColumns = (this.selectedDatasource.pivot?.enabled ?
183
+ this.selectedDatasource.pivot.columns :
184
+ this.selectedDatasource.columns)
185
+ .map((_, __) => ({
186
+ ..._,
187
+ visible: __ < 10
188
+ }))
189
+ },
190
+
191
+ moveDown(item){
192
+ const index = this.value.props.datasourceColumns.indexOf(item)
193
+ if(index + 1 < this.value.props.datasourceColumns.length){
194
+ this.value.props.datasourceColumns.splice(index + 1, 0, this.value.props.datasourceColumns.splice(index, 1)[0])
195
+ this.$emit('change')
196
+ }
197
+ },
198
+
199
+ moveUp(item){
200
+ const index = this.value.props.datasourceColumns.indexOf(item)
201
+ if(index - 1 >= 0){
202
+ this.value.props.datasourceColumns.splice(index - 1, 0, this.value.props.datasourceColumns.splice(index, 1)[0])
203
+ this.$emit('change')
204
+ }
205
+ },
206
+
207
+
208
+ },
209
+
210
+ mounted() {
211
+ this.applyDefault()
212
+ },
213
+
214
+ watch: {
215
+
216
+ 'value.props.datasourceUid'(to){
217
+ if(to){
218
+ this.generateColumns()
219
+ this.$emit('change')
220
+ }
221
+ }
222
+
223
+ }
224
+
225
+ }
226
+
227
+ </script>
228
+
229
+ <style module>
230
+
231
+ .comp{
232
+ @apply flex-1 flex flex-col;
233
+ }
234
+
235
+ .columnTextbox input::placeholder{
236
+ @apply text-text;
237
+ }
238
+
239
+ </style>
@@ -22,7 +22,7 @@ import { Doughnut } from 'vue-chartjs'
22
22
  import Chart from 'chart.js/auto'
23
23
  import {color} from "chart.js/helpers";
24
24
  import {strVars} from "../../utils/helpers.mjs";
25
- import {readyStateMixin} from "../../mixin/ready-state";
25
+ import {useEmitter} from "../../utils/event-bus";
26
26
 
27
27
  export default{
28
28
 
@@ -93,14 +93,43 @@ export default{
93
93
  '#FFCE54',
94
94
  '#ED5565',
95
95
  '#EC87C0'
96
- ]
96
+ ],
97
+ readyState: 1,
98
+ value: null,
97
99
  }
98
100
  },
99
101
 
100
- inject: [ 'selectPreset' ],
102
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'selectPreset', 'socket' ],
101
103
 
102
104
  methods: {
103
105
 
106
+ load(){
107
+ const preset = this.getViewedPreset()
108
+ const {name, datasource} = preset
109
+
110
+ this.readyState = 2
111
+ this.socket.send(this.getSrc(), {
112
+ name,
113
+ views: [{
114
+ ...this.$props,
115
+ type: 'Doughnut'
116
+ }],
117
+ datasource: datasource.map(_datasource => {
118
+ return {
119
+ ..._datasource,
120
+ filters: [
121
+ ...(_datasource.filters ?? []),
122
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
123
+ ]
124
+ }
125
+ })
126
+ })
127
+ .then(_ => {
128
+ this.value = _[this.uid]
129
+ })
130
+ .finally(_ => this.readyState = 1)
131
+ },
132
+
104
133
  onClick(e, segments){
105
134
 
106
135
  const clickInteractions = (this.interactions ?? []).filter(_ => _.event === 'click')
@@ -135,15 +164,27 @@ export default{
135
164
 
136
165
  },
137
166
 
138
- mixins: [ readyStateMixin ],
167
+ mounted() {
168
+ this.load()
169
+
170
+ this.emitter = useEmitter()
171
+ this.emitter.on(`${this.uid}.load`, () => {
172
+ this.load()
173
+ })
174
+ this.emitter.on(`dashboard.load`, () => {
175
+ this.load()
176
+ })
177
+ },
139
178
 
140
179
  props: {
141
180
 
142
181
  label: String,
143
182
 
144
- value: Object,
145
-
146
183
  column: String,
184
+ columnModifier: String,
185
+
186
+ rows: String,
187
+ rowsModifier: String,
147
188
 
148
189
  datasourceUid: String,
149
190
 
@@ -11,7 +11,7 @@
11
11
  <Dropdown class="min-w-[150px]"
12
12
  :readonly="readonly"
13
13
  v-model="value.props.datasourceUid"
14
- @change="delete value.props.columns;">
14
+ @change="delete value.props.columns; $emit('change')">
15
15
  <option disabled selected>Select Datasource</option>
16
16
  <option v-for="obj in datasource"
17
17
  :value="obj.uid">
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div :class="$style.comp">
3
3
  <label class="text-text-400 text-ellipsis overflow-hidden whitespace-nowrap">{{ label }}</label>
4
- <div class="flex flex-row gap-2">
4
+ <div class="flex flex-row items-end gap-2">
5
5
  <h1 v-if="readyState === 1" class="text-ellipsis overflow-hidden whitespace-nowrap" :class="column2Enabled ? '' : 'text-green-600'">
6
6
  {{ cValue }}
7
7
  </h1>
@@ -10,8 +10,16 @@
10
10
  </h1>
11
11
 
12
12
  <div class="flex flex-col" v-if="column2Enabled && readyState === 1">
13
- <div class="text-sm text-ellipsis whitespace-nowrap overflow-hidden" :class="value?.comparedPercent <= 50 ? 'text-red-600' : 'text-green-600'">{{ value?.comparedPercent }}%</div>
14
- <div class="text-sm text-ellipsis whitespace-nowrap overflow-hidden">{{ ccValue }}</div>
13
+ <div class="text-sm text-ellipsis whitespace-nowrap overflow-hidden"
14
+ :class="showPercentage ? '' : 'text-green-600'">
15
+ {{ ccValue }}
16
+ </div>
17
+
18
+ <div v-if="showPercentage"
19
+ class="text-sm text-ellipsis whitespace-nowrap overflow-hidden"
20
+ :class="value?.comparedPercent <= 50 ? 'text-red-600' : 'text-green-600'">
21
+ {{ value?.comparedPercent }}
22
+ </div>
15
23
  </div>
16
24
  </div>
17
25
  </div>
@@ -19,29 +27,44 @@
19
27
 
20
28
  <script>
21
29
 
22
- import {readyStateMixin} from "../../mixin/ready-state";
30
+ import {useEmitter} from "../../utils/event-bus";
31
+ import dayjs from "dayjs";
23
32
 
24
33
  export default{
25
34
 
26
35
  computed: {
27
36
 
28
37
  cValue(){
29
- let value = parseFloat(this.value?.value)
30
- if(isNaN(value)) return 'N/A'
31
- return value.toLocaleString()
38
+ if(!this.column) return 'N/A'
39
+ const column = this.datasource?.columns.find(_ => _.key === this.column)
40
+ return this.getValue(this.value?.value, column)
32
41
  },
33
42
 
34
43
  ccValue(){
35
44
  if(!this.column2Enabled) return
45
+ if(!this.value?.comparedValue) return
36
46
 
37
- let value = parseFloat(this.value?.comparedValue)
38
- if(isNaN(value)) return 'N/A'
39
- return value.toLocaleString()
47
+ const column = this.datasource?.columns.find(_ => _.key === this.column2)
48
+ return this.getValue(this.value?.comparedValue, column)
49
+ },
50
+
51
+ datasource(){
52
+ const preset = this.getViewedPreset()
53
+ if(preset){
54
+ return preset.datasource.find(_ => _.uid === this.datasourceUid)
55
+ }
40
56
  }
41
57
 
42
58
  },
43
59
 
44
- mixins: [ readyStateMixin ],
60
+ data(){
61
+ return {
62
+ value: null,
63
+ readyState: 1,
64
+ }
65
+ },
66
+
67
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'socket' ],
45
68
 
46
69
  props: {
47
70
 
@@ -54,12 +77,74 @@ export default{
54
77
 
55
78
  datasourceUid: String,
56
79
 
80
+ showPercentage: [ Number, Boolean ],
81
+
57
82
  label: String,
58
83
 
59
- value: Object,
84
+ uid: String
85
+
86
+ },
87
+
88
+ methods: {
89
+
90
+ getValue(val, column){
91
+ let value
92
+
93
+ switch(column?.type){
94
+
95
+ case 'date':
96
+ value = dayjs(val).format('D MMM')
97
+ break
98
+
99
+ default:
100
+ value = parseFloat(val)
101
+ value = isNaN(value) ?
102
+ 'N/A' :
103
+ value.toLocaleString()
104
+ }
105
+
106
+ return value
107
+ },
108
+
109
+ load(){
110
+ const preset = this.getViewedPreset()
111
+ const {name, datasource} = preset
112
+
113
+ this.readyState = 2
114
+ this.socket.send(this.getSrc(), {
115
+ name,
116
+ views: [{
117
+ ...this.$props,
118
+ type: 'Metric'
119
+ }],
120
+ datasource: datasource.map(_datasource => {
121
+ return {
122
+ ..._datasource,
123
+ filters: [
124
+ ...(_datasource.filters ?? []),
125
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
126
+ ]
127
+ }
128
+ })
129
+ })
130
+ .then(_ => {
131
+ this.value = _[this.uid]
132
+ })
133
+ .finally(_ => this.readyState = 1)
134
+ },
135
+
136
+ },
60
137
 
61
- uid: String,
138
+ mounted() {
139
+ this.load()
62
140
 
141
+ this.emitter = useEmitter()
142
+ this.emitter.on(`${this.uid}.load`, () => {
143
+ this.load()
144
+ })
145
+ this.emitter.on(`dashboard.load`, () => {
146
+ this.load()
147
+ })
63
148
  }
64
149
 
65
150
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <div class="flex flex-col gap-5 p-6">
2
+ <div class="flex flex-col gap-8 p-6">
3
3
 
4
- <div class="flex flex-row items-center">
4
+ <div class="flex flex-row items-center" @click="log(value)">
5
5
  <label class="flex-1">Active</label>
6
6
  <Switch v-model="value.props.enabled" :readonly="readonly" @change="$emit('change')" />
7
7
  </div>
@@ -15,7 +15,7 @@
15
15
  <Dropdown class="min-w-[150px]"
16
16
  :readonly="readonly"
17
17
  v-model="value.props.datasourceUid"
18
- @change="delete value.props.columns">
18
+ @change="delete value.props.columns;$emit('change')">
19
19
  <option v-for="obj in datasource"
20
20
  :value="obj.uid">
21
21
  {{ obj.name }}
@@ -28,25 +28,13 @@
28
28
  <Textbox v-model="value.props.label"
29
29
  maxlength="30"
30
30
  placeholder="Label"
31
- :readonly="readonly"
32
- @keyup.enter="$emit('change')"
33
- @blur="$emit('change')" />
31
+ :readonly="readonly" />
34
32
  </div>
35
33
 
36
- <div v-if="value.props.datasourceUid" class="flex flex-col gap-5">
34
+ <div v-if="value.props.datasourceUid" class="flex flex-col gap-8">
37
35
  <div class="flex flex-col gap-1">
38
36
  <label class="flex-1">Column</label>
39
37
  <div class="flex flex-row gap-2">
40
- <Dropdown class="min-w-[150px] flex-1"
41
- :readonly="readonly"
42
- v-model="value.props.column"
43
- @change="$emit('change')">
44
- <option v-for="column in selectedDatasourceColumns"
45
- :value="column.key">
46
- {{ selectedDatasource.pivot?.enabled ? column.key : column.label }}
47
- </option>
48
- </Dropdown>
49
-
50
38
  <Dropdown class="w-[125px]"
51
39
  :readonly="readonly"
52
40
  v-model="value.props.columnModifier"
@@ -60,6 +48,16 @@
60
48
  <option value="first">First</option>
61
49
  <option value="last">Last</option>
62
50
  </Dropdown>
51
+
52
+ <Dropdown class="min-w-[150px] flex-1"
53
+ :readonly="readonly"
54
+ v-model="value.props.column"
55
+ @change="$emit('change')">
56
+ <option v-for="column in selectedDatasourceColumns"
57
+ :value="column.key">
58
+ {{ selectedDatasource.pivot?.enabled ? column.key : column.label }}
59
+ </option>
60
+ </Dropdown>
63
61
  </div>
64
62
  </div>
65
63
 
@@ -70,16 +68,6 @@
70
68
  </Checkbox>
71
69
  </div>
72
70
  <div class="flex flex-row gap-2">
73
- <Dropdown class="min-w-[150px] flex-1"
74
- :readonly="readonly || !value.props.column2Enabled"
75
- v-model="value.props.column2"
76
- @change="$emit('change')">
77
- <option v-for="column in selectedDatasourceColumns"
78
- :value="column.key">
79
- {{ selectedDatasource.pivot?.enabled ? column.key : column.label }}
80
- </option>
81
- </Dropdown>
82
-
83
71
  <Dropdown class="w-[125px]"
84
72
  :readonly="readonly || !value.props.column2Enabled"
85
73
  v-model="value.props.column2Modifier"
@@ -93,6 +81,32 @@
93
81
  <option value="first">First</option>
94
82
  <option value="last">Last</option>
95
83
  </Dropdown>
84
+
85
+ <Dropdown class="min-w-[150px] flex-1"
86
+ :readonly="readonly || !value.props.column2Enabled"
87
+ v-model="value.props.column2"
88
+ @change="$emit('change')">
89
+ <option v-for="column in selectedDatasourceColumns"
90
+ :value="column.key">
91
+ {{ selectedDatasource.pivot?.enabled ? column.key : column.label }}
92
+ </option>
93
+ </Dropdown>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="flex flex-col gap-2" v-if="canShowPercentage">
98
+ <div>
99
+ <Checkbox v-model="value.props.showPercentage" :disabled="readonly">
100
+ Show Percentage
101
+ </Checkbox>
102
+ </div>
103
+ <div class="flex flex-row gap-2">
104
+ <Dropdown class="w-full"
105
+ :readonly="readonly || !value.props.column2Enabled"
106
+ v-model="value.props.showPercentageOpt"
107
+ @change="$emit('change')">
108
+ <option v-for="arr in showPercentageOptValues" :value="arr[0]">{{ arr[1] }}</option>
109
+ </Dropdown>
96
110
  </div>
97
111
  </div>
98
112
 
@@ -113,10 +127,49 @@ export default{
113
127
 
114
128
  computed: {
115
129
 
130
+ column1(){
131
+ if(!this.value || !this.value.props.column) return
132
+ return this.selectedDatasource.columns.find(_ => _.key === this.value.props.column)
133
+ },
134
+
135
+ column2(){
136
+ if(!this.value || !this.value.props.column2) return
137
+ return this.selectedDatasource.columns.find(_ => _.key === this.value.props.column2)
138
+ },
139
+
140
+ canShowPercentage(){
141
+ return this.column1 && this.column2 && this.column1.type === this.column2.type
142
+ },
143
+
116
144
  selectedDatasource(){
117
145
  return this.datasource.find(d => d.uid === this.value.props.datasourceUid)
118
146
  },
119
147
 
148
+ showPercentageOptValues(){
149
+ if(!this.canShowPercentage) return
150
+
151
+ switch(this.column1.type){
152
+
153
+ case 'date':
154
+ return [
155
+ [ 'day', 'Day' ],
156
+ [ 'hour', 'Hour' ],
157
+ [ 'minute', 'Minute' ],
158
+ [ 'month', 'Month' ],
159
+ [ 'week', 'Week' ],
160
+ [ 'year', 'Year' ],
161
+ ]
162
+
163
+ case 'number':
164
+ default:
165
+ return [
166
+ [ 'percentage', 'Percentage' ],
167
+ [ 'number', 'Number' ],
168
+ ]
169
+
170
+ }
171
+ },
172
+
120
173
  selectedDatasourceColumns(){
121
174
  if(!this.selectedDatasource) return []
122
175
 
@@ -22,7 +22,7 @@ import { Pie } from 'vue-chartjs'
22
22
  import Chart from 'chart.js/auto'
23
23
  import { color } from 'chart.js/helpers'
24
24
  import {strVars} from "../../utils/helpers.mjs";
25
- import {readyStateMixin} from "../../mixin/ready-state";
25
+ import {useEmitter} from "../../utils/event-bus";
26
26
 
27
27
  export default{
28
28
 
@@ -79,10 +79,44 @@ export default{
79
79
 
80
80
  },
81
81
 
82
- inject: [ 'selectPreset' ],
82
+ data(){
83
+ return {
84
+ readyState: 1,
85
+ value: null,
86
+ }
87
+ },
88
+
89
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'selectPreset', 'socket' ],
83
90
 
84
91
  methods: {
85
92
 
93
+ load(){
94
+ const preset = this.getViewedPreset()
95
+ const {name, datasource} = preset
96
+
97
+ this.readyState = 2
98
+ this.socket.send(this.getSrc(), {
99
+ name,
100
+ views: [{
101
+ ...this.$props,
102
+ type: 'Pie'
103
+ }],
104
+ datasource: datasource.map(_datasource => {
105
+ return {
106
+ ..._datasource,
107
+ filters: [
108
+ ...(_datasource.filters ?? []),
109
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
110
+ ]
111
+ }
112
+ })
113
+ })
114
+ .then(_ => {
115
+ this.value = _[this.uid]
116
+ })
117
+ .finally(_ => this.readyState = 1)
118
+ },
119
+
86
120
  onClick(e, segments){
87
121
 
88
122
  const clickInteractions = (this.interactions ?? []).filter(_ => _.event === 'click')
@@ -117,7 +151,17 @@ export default{
117
151
 
118
152
  },
119
153
 
120
- mixins: [ readyStateMixin ],
154
+ mounted() {
155
+ this.load()
156
+
157
+ this.emitter = useEmitter()
158
+ this.emitter.on(`${this.uid}.load`, () => {
159
+ this.load()
160
+ })
161
+ this.emitter.on(`dashboard.load`, () => {
162
+ this.load()
163
+ })
164
+ },
121
165
 
122
166
  props: {
123
167
 
@@ -125,7 +169,11 @@ export default{
125
169
 
126
170
  value: Object,
127
171
 
128
- column: Object,
172
+ column: String,
173
+ columnModifier: String,
174
+
175
+ rows: String,
176
+ rowsModifier: String,
129
177
 
130
178
  datasourceUid: String,
131
179
 
@@ -20,7 +20,7 @@ import { PolarArea } from 'vue-chartjs'
20
20
  import Chart from 'chart.js/auto'
21
21
  import { color } from 'chart.js/helpers'
22
22
  import {strVars} from "../../utils/helpers.mjs";
23
- import {readyStateMixin} from "../../mixin/ready-state";
23
+ import {useEmitter} from "../../utils/event-bus";
24
24
 
25
25
  export default{
26
26
 
@@ -96,13 +96,42 @@ export default{
96
96
  '#ED5565',
97
97
  '#EC87C0'
98
98
  ],
99
+ readyState: 1,
100
+ value: null,
99
101
  }
100
102
  },
101
103
 
102
- inject: [ 'selectPreset' ],
104
+ inject: [ 'getSrc', 'getViewedPreset', 'getQueryFilters', 'selectPreset', 'socket' ],
103
105
 
104
106
  methods: {
105
107
 
108
+ load(){
109
+ const preset = this.getViewedPreset()
110
+ const {name, datasource} = preset
111
+
112
+ this.readyState = 2
113
+ this.socket.send(this.getSrc(), {
114
+ name,
115
+ views: [{
116
+ ...this.$props,
117
+ type: 'PolarArea'
118
+ }],
119
+ datasource: datasource.map(_datasource => {
120
+ return {
121
+ ..._datasource,
122
+ filters: [
123
+ ...(_datasource.filters ?? []),
124
+ ...(this.getQueryFilters()[_datasource.uid] ?? [])
125
+ ]
126
+ }
127
+ })
128
+ })
129
+ .then(_ => {
130
+ this.value = _[this.uid]
131
+ })
132
+ .finally(_ => this.readyState = 1)
133
+ },
134
+
106
135
  onClick(e, segments){
107
136
 
108
137
  const clickInteractions = (this.interactions ?? []).filter(_ => _.event === 'click')
@@ -137,15 +166,27 @@ export default{
137
166
 
138
167
  },
139
168
 
140
- mixins: [ readyStateMixin ],
169
+ mounted() {
170
+ this.load()
171
+
172
+ this.emitter = useEmitter()
173
+ this.emitter.on(`${this.uid}.load`, () => {
174
+ this.load()
175
+ })
176
+ this.emitter.on(`dashboard.load`, () => {
177
+ this.load()
178
+ })
179
+ },
141
180
 
142
181
  props: {
143
182
 
144
183
  label: String,
145
184
 
146
- value: Object,
147
-
148
185
  column: String,
186
+ columnModifier: String,
187
+
188
+ rows: String,
189
+ rowsModifier: String,
149
190
 
150
191
  datasourceUid: String,
151
192
 
@@ -101,7 +101,7 @@ export default{
101
101
  { type:"Pie", name:"Pie", group:"components", import: () => import('../../configs/dashboard/pie.js'), thumbnailUrl:"/static/dashboard/pie.png" },
102
102
  { type:"Doughnut", name:"Doughnut", group:"components", import: () => import('../../configs/dashboard/doughnut.js'), thumbnailUrl:"/static/dashboard/doughnut.png" },
103
103
  { type:"PolarArea", name:"Polar Area", group:"components", import: () => import('../../configs/dashboard/polar-area.js'), thumbnailUrl:"/static/dashboard/polar-area.png" },
104
- { type:"VirtualTable", name:"Table", group:"components", import: () => import('../../configs/dashboard/virtual-table.js'), thumbnailUrl:"/static/dashboard/virtual-table.png" },
104
+ { type:"DataTable", name:"DataTable", group:"components", import: () => import('../../configs/dashboard/data-table.js'), thumbnailUrl:"/static/dashboard/virtual-table.png" },
105
105
  { type:"Metric", name:"Metric", group:"components", import: () => import('../../configs/dashboard/metric.js'), thumbnailUrl:"/static/dashboard/metric.png" },
106
106
  { type:"BarChart", name:"Bar Chart", group:"components", import: () => import('../../configs/dashboard/bar.js'), thumbnailUrl:"/static/dashboard/bar.png" },
107
107
  { type:"GHeatMaps", name:"Heat Map", group:"components", import: () => import('../../configs/dashboard/gheatmaps.js'), thumbnailUrl:"/static/dashboard/gheatmaps.png" },
@@ -33,7 +33,7 @@
33
33
  :readonly="selectedPreset.readonly"
34
34
  :value="selectedView"
35
35
  :view-type="config.viewType"
36
- @change="load({ [selectedView.uid]:selectedView })"/>
36
+ @change="load({ [selectedView.uid]:selectedView })" />
37
37
 
38
38
  </div>
39
39
  </div>
@@ -945,6 +945,7 @@ export default {
945
945
  BarChartSetting: defineAsyncComponent(() => import('./Dashboard/BarChartSetting.vue')),
946
946
  MetricSetting: defineAsyncComponent(() => import('./Dashboard/MetricSetting.vue')),
947
947
  GHeatMapsSetting: defineAsyncComponent(() => import('./Dashboard/GHeatMapsSetting.vue')),
948
+ DataTableSetting: defineAsyncComponent(() => import('./Dashboard/DataTableSetting.vue')),
948
949
  VirtualTableSetting: defineAsyncComponent(() => import('./Dashboard/VirtualTableSetting.vue')),
949
950
  PieSetting: defineAsyncComponent(() => import('./Dashboard/PieSetting.vue')),
950
951
  PolarAreaSetting: defineAsyncComponent(() => import('./Dashboard/PolarAreaSetting.vue')),
@@ -1330,10 +1331,19 @@ export default {
1330
1331
 
1331
1332
  duplicateComponent(item, parent) {
1332
1333
 
1334
+ const recurseSet = (item) => {
1335
+ item.uid = getPresetUid()
1336
+
1337
+ if(Array.isArray(item.items)){
1338
+ for(let subItem of item.items)
1339
+ recurseSet(subItem)
1340
+ }
1341
+ }
1342
+
1333
1343
  const index = parent.findIndex(_ => _ === item)
1334
1344
 
1335
1345
  const newItem = JSON.parse(JSON.stringify(item))
1336
- newItem.uid = getPresetUid()
1346
+ recurseSet(newItem)
1337
1347
  newItem.name += ' (Copy)';
1338
1348
 
1339
1349
  parent.splice(index + 1, null, newItem)
@@ -1363,6 +1373,14 @@ export default {
1363
1373
  },
1364
1374
 
1365
1375
  async load(uids) {
1376
+ if(uids){
1377
+ Object.keys(uids).forEach(uid => this.emitter.emit(`${uid}.load`))
1378
+ }
1379
+ else{
1380
+ this.emitter.emit(`dashboard.load`)
1381
+ }
1382
+ return
1383
+
1366
1384
  if (this.readyState !== 1) return
1367
1385
  if (!this.viewedComponents) return
1368
1386
  if (!this.cConfig) return
@@ -1372,7 +1390,7 @@ export default {
1372
1390
 
1373
1391
  const {name, datasource} = this.viewedPreset
1374
1392
 
1375
- !uids ? this.readyState = 0 : Object.keys(uids).forEach(uid => this.emitter.emit(`${uid}.readyState`, 2))
1393
+ !uids ? this.readyState = 2 : Object.keys(uids).forEach(uid => this.emitter.emit(`${uid}.readyState`, 2))
1376
1394
  this.socket.send(this.src, {
1377
1395
  name,
1378
1396
  views,
@@ -1689,7 +1707,10 @@ export default {
1689
1707
  getConfig: this.getConfig,
1690
1708
  getPresets: this.getPresets,
1691
1709
  selectPreset: this.selectPreset,
1692
- preview: this.preview
1710
+ preview: this.preview,
1711
+ getSrc: () => this.src,
1712
+ getViewedPreset: () => this.viewedPreset,
1713
+ getQueryFilters: () => this.queryFilters,
1693
1714
  }
1694
1715
  },
1695
1716