@mixd-id/web-scaffold 0.1.230406060 → 0.1.230406062
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 +3 -2
- package/src/components/Checkbox.vue +4 -1
- package/src/components/ColorPicker.vue +84 -0
- package/src/components/ImagePreview.vue +1 -1
- package/src/components/ListView.vue +284 -27
- package/src/components/ListViewBarSummary.vue +80 -0
- package/src/components/ListViewLineSummary.vue +75 -0
- package/src/components/ListViewMapSummary.vue +56 -0
- package/src/components/ListViewSettings.vue +44 -212
- package/src/components/ListViewTableSummary.vue +77 -0
- package/src/components/Modal.vue +3 -2
- package/src/components/VirtualScroll.vue +4 -6
- package/src/index.js +5 -0
- package/src/utils/listview.js +1377 -0
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.
|
|
4
|
+
"version": "0.1.230406062",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite serve",
|
|
7
7
|
"build": "vite build",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"import": "./src/utils/helpers.mjs"
|
|
17
17
|
},
|
|
18
18
|
"./importer": "./src/utils/importer.js",
|
|
19
|
-
"./listpage1": "./src/utils/listpage1.js"
|
|
19
|
+
"./listpage1": "./src/utils/listpage1.js",
|
|
20
|
+
"./listview": "./src/utils/listview.js"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@faker-js/faker": "^7.3.0",
|
|
@@ -43,9 +43,12 @@ export default {
|
|
|
43
43
|
if(Array.isArray(this.modelValue)){
|
|
44
44
|
return this.modelValue.includes(this.value)
|
|
45
45
|
}
|
|
46
|
-
else{
|
|
46
|
+
else if(this.modelValue){
|
|
47
47
|
return this.value === this.modelValue
|
|
48
48
|
}
|
|
49
|
+
else{
|
|
50
|
+
return !!this.value
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
else if(this.trueValue && this.falseValue) {
|
|
51
54
|
return this.modelValue === this.trueValue
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.comp">
|
|
3
|
+
<div :class="$style.circle" @click="$refs.contextMenu.open($refs.btn)"
|
|
4
|
+
ref="btn" :style="{ backgroundColor:this.modelValue }"></div>
|
|
5
|
+
<input type="color" :class="$style.inputColor" ref="inputColor"
|
|
6
|
+
@change="select($refs.inputColor.value)"/>
|
|
7
|
+
|
|
8
|
+
<ContextMenu ref="contextMenu" :dismiss="false">
|
|
9
|
+
<div class="p-4 flex flex-col gap-4">
|
|
10
|
+
<div class="grid grid-cols-6 gap-4">
|
|
11
|
+
<div v-for="color in colors">
|
|
12
|
+
<div :class="$style.circle" :style="{ backgroundColor:color }"
|
|
13
|
+
@click="select(color)"></div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<button type="button" v-if="Boolean(customColor)"
|
|
17
|
+
class="w-full p-1 border-text-100 border-[1px] rounded-lg"
|
|
18
|
+
@click="$refs.inputColor.click()">Custom</button>
|
|
19
|
+
</div>
|
|
20
|
+
</ContextMenu>
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
|
|
27
|
+
export default{
|
|
28
|
+
|
|
29
|
+
props: {
|
|
30
|
+
|
|
31
|
+
colors: Array,
|
|
32
|
+
|
|
33
|
+
customColor: undefined,
|
|
34
|
+
|
|
35
|
+
modelValue: String
|
|
36
|
+
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
methods: {
|
|
40
|
+
|
|
41
|
+
select(color){
|
|
42
|
+
this.$emit('update:modelValue', color)
|
|
43
|
+
this.$refs.contextMenu.close()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
computed:{
|
|
49
|
+
|
|
50
|
+
customColorCount(){
|
|
51
|
+
const count = parseInt(this.customColor)
|
|
52
|
+
return isNaN(count) ? 0 : count
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
data(){
|
|
58
|
+
return {
|
|
59
|
+
customColors: []
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<style module>
|
|
68
|
+
|
|
69
|
+
.comp{
|
|
70
|
+
@apply p-[2px] border-[1px] border-text-200 rounded-full relative;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.circle{
|
|
74
|
+
@apply w-[19px] h-[19px] rounded-full cursor-pointer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.inputColor{
|
|
78
|
+
@apply absolute top-0 left-0;
|
|
79
|
+
width: 0;
|
|
80
|
+
height: 0;
|
|
81
|
+
opacity: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="$style.comp"
|
|
2
|
+
<div :class="$style.comp">
|
|
3
3
|
|
|
4
4
|
<slot v-if="$slots.head" name="head"></slot>
|
|
5
5
|
<div v-else class="flex flex-row items-center gap-4 px-6 md:px-0 py-4 md:py-0 bg-base-400 dark:bg-base-300 md:bg-transparent">
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
class="flex-1 md:flex-none flex flex-row gap-1 items-center text-left md:ml-2"
|
|
9
9
|
@click="$refs.presetSelector.toggle($refs.presetSelectorBtn)">
|
|
10
10
|
<h2 class="overflow-hidden whitespace-nowrap text-ellipsis">{{ preset.name }}</h2>
|
|
11
|
-
<svg width="
|
|
11
|
+
<svg width="13" height="13" class="ml-1 relative top-[2px] fill-text hover:fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"/></svg>
|
|
12
12
|
</button>
|
|
13
13
|
|
|
14
14
|
<ContextMenu ref="presetSelector">
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
|
|
54
54
|
<slot name="headerOpt"></slot>
|
|
55
55
|
|
|
56
|
-
<Textbox :placeholder="$t('Search...')" :clearable="true"
|
|
57
|
-
@
|
|
56
|
+
<Textbox v-if="mediaPrefix && mediaPrefix !== 'sm'" :placeholder="$t('Search...')" :clearable="true"
|
|
57
|
+
@clear="clearSearch" v-model="preset.search"
|
|
58
|
+
@keyup.enter="load" :class="$style.searchBox">
|
|
58
59
|
<template #start>
|
|
59
60
|
<div class="pl-2">
|
|
60
61
|
<svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
|
|
@@ -63,9 +64,9 @@
|
|
|
63
64
|
</Textbox>
|
|
64
65
|
</div>
|
|
65
66
|
|
|
66
|
-
<div v-if="mediaPrefix === 'sm'" class="px-6 pb-4 border-b-[1px] border-text-50 bg-base-400 dark:bg-base-300">
|
|
67
|
+
<div v-if="mediaPrefix && mediaPrefix === 'sm'" class="px-6 pb-4 border-b-[1px] border-text-50 bg-base-400 dark:bg-base-300">
|
|
67
68
|
<Textbox :placeholder="$t('Search...')" :clearable="true" @clear="clearSearch" v-model="preset.search"
|
|
68
|
-
@keyup.enter="load" :class="$style.
|
|
69
|
+
@keyup.enter="load" :class="$style.searchBox2">
|
|
69
70
|
<template #start>
|
|
70
71
|
<div class="pl-2">
|
|
71
72
|
<svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M508.5 481.6l-129-129c-2.3-2.3-5.3-3.5-8.5-3.5h-10.3C395 312 416 262.5 416 208 416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c54.5 0 104-21 141.1-55.2V371c0 3.2 1.3 6.2 3.5 8.5l129 129c4.7 4.7 12.3 4.7 17 0l9.9-9.9c4.7-4.7 4.7-12.3 0-17zM208 384c-97.3 0-176-78.7-176-176S110.7 32 208 32s176 78.7 176 176-78.7 176-176 176z"/></svg>
|
|
@@ -74,8 +75,23 @@
|
|
|
74
75
|
</Textbox>
|
|
75
76
|
</div>
|
|
76
77
|
|
|
78
|
+
<div v-if="presetSummary && summary" class="h-[300px] max-h-[30vh] flex">
|
|
79
|
+
<Bar v-if="presetSummary.mode === 'bar'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
|
|
80
|
+
<Line v-else-if="presetSummary.mode === 'line'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
|
|
81
|
+
<VirtualTable v-else-if="presetSummary.mode === 'table'" :items="summaryItems" :columns="summaryColumns" class="flex-1"></VirtualTable>
|
|
82
|
+
<Gmaps v-else-if="presetSummary.mode === 'map'" :data="summary.items" :config="presetSummary.map"
|
|
83
|
+
class="flex-1 w-full p-2" :apiKey="mapApiKey" />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
77
86
|
<div class="flex-1 flex" v-if="mediaPrefix">
|
|
78
|
-
<
|
|
87
|
+
<VirtualScroll v-if="mediaPrefix === 'sm'" :items="items" ref="table2"
|
|
88
|
+
class="flex-1 bg-base-400 dark:bg-base-300" @scroll-end="loadNext">
|
|
89
|
+
<template #item="{ item }">
|
|
90
|
+
<slot name="mobileItem" :item="item"></slot>
|
|
91
|
+
</template>
|
|
92
|
+
</VirtualScroll>
|
|
93
|
+
|
|
94
|
+
<VirtualTable v-else ref="table1" :columns="presetColumns" :items="items"
|
|
79
95
|
class="flex-1 bg-base-400"
|
|
80
96
|
@scroll-end="loadNext">
|
|
81
97
|
<template v-for="column in presetColumns" #[colOf(column.key)]="{}">
|
|
@@ -110,11 +126,6 @@
|
|
|
110
126
|
<slot :name="slot" :item="item" :index="index"></slot>
|
|
111
127
|
</template>
|
|
112
128
|
</VirtualTable>
|
|
113
|
-
<VirtualScroll v-else :items="items" ref="table2" class="flex-1 bg-base-400 dark:bg-base-300" @scroll-end="loadNext">
|
|
114
|
-
<template #item="{ item }">
|
|
115
|
-
<slot name="mobileItem" :item="item"></slot>
|
|
116
|
-
</template>
|
|
117
|
-
</VirtualScroll>
|
|
118
129
|
</div>
|
|
119
130
|
|
|
120
131
|
<ContextMenu ref="columnMenu" :dismiss="false">
|
|
@@ -156,8 +167,12 @@
|
|
|
156
167
|
<script>
|
|
157
168
|
|
|
158
169
|
import throttle from "lodash/throttle";
|
|
170
|
+
import { Bar, Line } from 'vue-chartjs'
|
|
171
|
+
import Chart from 'chart.js/auto'
|
|
172
|
+
import dayjs from "dayjs";
|
|
159
173
|
|
|
160
174
|
export default{
|
|
175
|
+
components: {Line, Bar},
|
|
161
176
|
|
|
162
177
|
emits: [ ],
|
|
163
178
|
|
|
@@ -214,7 +229,7 @@ export default{
|
|
|
214
229
|
this.presetFilterSelector = null
|
|
215
230
|
},
|
|
216
231
|
|
|
217
|
-
calcMediaPrefix
|
|
232
|
+
calcMediaPrefix() {
|
|
218
233
|
if(!this.$el) return
|
|
219
234
|
|
|
220
235
|
const w = this.$el.clientWidth
|
|
@@ -238,7 +253,7 @@ export default{
|
|
|
238
253
|
}
|
|
239
254
|
|
|
240
255
|
this.mediaPrefix = prefix
|
|
241
|
-
},
|
|
256
|
+
},
|
|
242
257
|
|
|
243
258
|
clearSearch(){
|
|
244
259
|
this.preset.search = ''
|
|
@@ -268,10 +283,14 @@ export default{
|
|
|
268
283
|
load(){
|
|
269
284
|
if(!this.src) return
|
|
270
285
|
|
|
271
|
-
this.
|
|
286
|
+
this.$refs.table1 ? this.$refs.table1.setState(2) : this.$refs.table2.setState(3)
|
|
287
|
+
this.socketEmit2(this.src, { columns:this.config.columns, preset:this.preset })
|
|
272
288
|
.then((res) => {
|
|
273
289
|
Object.assign(this.$data, res)
|
|
274
290
|
})
|
|
291
|
+
.finally(() => {
|
|
292
|
+
this.$refs.table1 ? this.$refs.table1.setState(1) : this.$refs.table2.setState(1)
|
|
293
|
+
})
|
|
275
294
|
},
|
|
276
295
|
|
|
277
296
|
loadNext(){
|
|
@@ -293,8 +312,13 @@ export default{
|
|
|
293
312
|
},
|
|
294
313
|
|
|
295
314
|
async loadConfig(){
|
|
315
|
+
|
|
296
316
|
if(!this.configStoreObj || 'reset' in ((this.$route ?? {}).query ?? {})){
|
|
297
317
|
this.configLoaded = true
|
|
318
|
+
|
|
319
|
+
if('reset' in ((this.$route ?? {}).query ?? {}))
|
|
320
|
+
await this.saveConfig()
|
|
321
|
+
|
|
298
322
|
return
|
|
299
323
|
}
|
|
300
324
|
|
|
@@ -308,7 +332,6 @@ export default{
|
|
|
308
332
|
}
|
|
309
333
|
|
|
310
334
|
this.configLoaded = true
|
|
311
|
-
this.$nextTick(() => this.calcMediaPrefix())
|
|
312
335
|
})
|
|
313
336
|
}
|
|
314
337
|
},
|
|
@@ -321,8 +344,9 @@ export default{
|
|
|
321
344
|
preset: this.preset
|
|
322
345
|
})
|
|
323
346
|
.then((res) => {
|
|
324
|
-
|
|
325
|
-
|
|
347
|
+
console.log('res', res)
|
|
348
|
+
/*const data = res && res.data ? res.data : res
|
|
349
|
+
this.summary = data*/
|
|
326
350
|
})
|
|
327
351
|
.catch((err) => {
|
|
328
352
|
this.toast(err)
|
|
@@ -330,20 +354,46 @@ export default{
|
|
|
330
354
|
},
|
|
331
355
|
|
|
332
356
|
onHooks(model, event, items){
|
|
357
|
+
|
|
333
358
|
if(model === this.subscriptionObj.model){
|
|
334
359
|
switch(event){
|
|
335
360
|
|
|
336
361
|
case 'create':
|
|
337
362
|
case 'update':
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
363
|
+
if(Object.keys(items[0]).length <= 1 && Object.keys(items[0])[0] === 'uid'){
|
|
364
|
+
this.socketEmit2(this.src, {
|
|
365
|
+
columns:this.config.columns,
|
|
366
|
+
preset:{
|
|
367
|
+
...this.preset,
|
|
368
|
+
filters: [{
|
|
369
|
+
enabled: true,
|
|
370
|
+
key: "uid",
|
|
371
|
+
filters: [
|
|
372
|
+
{
|
|
373
|
+
operator: "in",
|
|
374
|
+
value: items.map((_) => _.uid)
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
.then(({ items }) => {
|
|
382
|
+
this.$util.unshift(this.items, ...items)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
else{
|
|
386
|
+
this.$util.unshift(this.items, ...items)
|
|
387
|
+
}
|
|
341
388
|
break
|
|
342
389
|
|
|
343
390
|
case 'remove':
|
|
344
391
|
case 'destroy':
|
|
345
392
|
items.forEach((item) => {
|
|
346
|
-
const idx = this.items.findIndex((_) =>
|
|
393
|
+
const idx = this.items.findIndex((_) => {
|
|
394
|
+
return item.uid ? _.uid === item.uid :
|
|
395
|
+
(item.id ? _.id === item.id : false)
|
|
396
|
+
})
|
|
347
397
|
if(idx >= 0){
|
|
348
398
|
this.items.splice(idx, 1)
|
|
349
399
|
}
|
|
@@ -421,7 +471,54 @@ export default{
|
|
|
421
471
|
this.socketEmit2(method, { name:model }).then()
|
|
422
472
|
break
|
|
423
473
|
}
|
|
424
|
-
}
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
summaryColumns(){
|
|
477
|
+
|
|
478
|
+
const columns = [ ...this.summary.columns ]
|
|
479
|
+
|
|
480
|
+
if(this.presetSummary.mode === 'table' && this.presetSummary.table.showColumnTotal){
|
|
481
|
+
columns.push({ key:"_total", label:"Total", visible:true, type:"number" })
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return columns
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
summaryItems(){
|
|
488
|
+
|
|
489
|
+
const items = [ ...this.summary.items ]
|
|
490
|
+
|
|
491
|
+
//console.log('#1', this.presetSummary.showColumnTotal, this.presetSummary.showRowTotal, items.length)
|
|
492
|
+
|
|
493
|
+
if(this.presetSummary.mode === 'table' && this.presetSummary.table.showColumnTotal){
|
|
494
|
+
items.forEach((item) => {
|
|
495
|
+
let total = 0
|
|
496
|
+
for(let key in item){
|
|
497
|
+
if([ 'dfDate', '_total' ].includes(key)) continue
|
|
498
|
+
total += parseInt(item[key])
|
|
499
|
+
}
|
|
500
|
+
item._total = total
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
if(this.presetSummary.mode === 'table' && this.presetSummary.table.showRowTotal){
|
|
506
|
+
const totalItem = { dfDate:"Total" }
|
|
507
|
+
this.summary.items.forEach((item) => {
|
|
508
|
+
|
|
509
|
+
for(let key in item){
|
|
510
|
+
if([ 'dfDate' ].includes(key)) continue
|
|
511
|
+
|
|
512
|
+
if(!(key in totalItem))
|
|
513
|
+
totalItem[key] = 0
|
|
514
|
+
totalItem[key] += parseInt(item[key])
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
items.push(totalItem)
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return items
|
|
521
|
+
},
|
|
425
522
|
|
|
426
523
|
},
|
|
427
524
|
|
|
@@ -429,6 +526,128 @@ export default{
|
|
|
429
526
|
|
|
430
527
|
computed: {
|
|
431
528
|
|
|
529
|
+
|
|
530
|
+
chartOptions(){
|
|
531
|
+
|
|
532
|
+
var style = getComputedStyle(document.body)
|
|
533
|
+
var gridColor = style.getPropertyValue('--text-50')
|
|
534
|
+
var gridColor2 = style.getPropertyValue('--text-200')
|
|
535
|
+
|
|
536
|
+
const baseColumn = this.summary.columns[0] ?? {}
|
|
537
|
+
const baseColumnType = baseColumn.type
|
|
538
|
+
|
|
539
|
+
let highGrids = []
|
|
540
|
+
if(baseColumnType === 'date'){
|
|
541
|
+
this.summary.items.forEach((item) => {
|
|
542
|
+
if(dayjs(item.dfDate).day() === 1){
|
|
543
|
+
highGrids.push(dayjs(item.dfDate).format('D MMM'))
|
|
544
|
+
}
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
responsive: true,
|
|
550
|
+
maintainAspectRatio: false,
|
|
551
|
+
plugins: {
|
|
552
|
+
legend: !this.presetSummary.hideLegends,
|
|
553
|
+
tooltip: {
|
|
554
|
+
callbacks: {
|
|
555
|
+
label: function(context) {
|
|
556
|
+
|
|
557
|
+
const labels = []
|
|
558
|
+
|
|
559
|
+
labels.push(context.dataset.label + ': ' + context.parsed.y)
|
|
560
|
+
|
|
561
|
+
if(context.parsed._stacks){
|
|
562
|
+
let total = 0
|
|
563
|
+
let percent = 0
|
|
564
|
+
for(let key in context.parsed._stacks.y){
|
|
565
|
+
if(!isNaN(parseInt(key))){
|
|
566
|
+
total += parseInt(context.parsed._stacks.y[key])
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
percent = Math.round(parseInt(context.parsed.y) / total * 100)
|
|
570
|
+
labels.push(`Total: ${total} (${percent}%)`)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return labels;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
scales: {
|
|
579
|
+
x: {
|
|
580
|
+
grid: {
|
|
581
|
+
color: function(context){
|
|
582
|
+
if(baseColumnType === 'date' && context.tick && highGrids.includes(context.tick.label)){
|
|
583
|
+
return `rgb(${gridColor2})`
|
|
584
|
+
}
|
|
585
|
+
return `rgb(${gridColor})`
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
stacked: this.presetSummary.mode === 'bar' ? this.presetSummary[this.presetSummary.mode].stacked : undefined
|
|
589
|
+
},
|
|
590
|
+
y: {
|
|
591
|
+
grid: {
|
|
592
|
+
color: function(context){
|
|
593
|
+
return `rgb(${gridColor})`
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
stacked: this.presetSummary.mode === 'bar' ? this.presetSummary[this.presetSummary.mode].stacked : undefined
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
chartData(){
|
|
603
|
+
|
|
604
|
+
const column = this.summary.columns.filter((_) => _.key === 'dfDate').pop() ?? {}
|
|
605
|
+
|
|
606
|
+
const labels = []
|
|
607
|
+
this.summary.items.forEach((item) => {
|
|
608
|
+
|
|
609
|
+
let label = item.dfDate
|
|
610
|
+
switch(column.type){
|
|
611
|
+
|
|
612
|
+
case 'date':
|
|
613
|
+
const dateFormat = column.format ?? 'D MMM YY HH:mm:ss'
|
|
614
|
+
const djs = dayjs(item.dfDate)
|
|
615
|
+
label = djs.isValid() ? djs.format(dateFormat) : item.dfDate
|
|
616
|
+
break
|
|
617
|
+
}
|
|
618
|
+
labels.push(label)
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
const datasets = []
|
|
622
|
+
this.summary.columns.forEach((column) => {
|
|
623
|
+
if(column.key === 'dfDate') return
|
|
624
|
+
|
|
625
|
+
const data = []
|
|
626
|
+
this.summary.items.forEach((item) => {
|
|
627
|
+
data.push(parseInt(item[column.key]))
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
const dataset = {
|
|
631
|
+
label: column.label,
|
|
632
|
+
data,
|
|
633
|
+
backgroundColor: this.chartOpt.backgroundColors[datasets.length % 9],
|
|
634
|
+
borderColor: this.chartOpt.borderColors[datasets.length % 9]
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
datasets.push(dataset)
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
labels,
|
|
642
|
+
datasets
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/*return {
|
|
646
|
+
labels: [ 'January', 'February', 'March' ],
|
|
647
|
+
datasets: [ { data: [40, 20, 12] } ]
|
|
648
|
+
}*/
|
|
649
|
+
},
|
|
650
|
+
|
|
432
651
|
subscriptionObj(){
|
|
433
652
|
const splitted = ((this.subscription ?? '').toString()).split(':')
|
|
434
653
|
const splitted2 = (splitted[1] ?? '').split(',')
|
|
@@ -521,6 +740,10 @@ export default{
|
|
|
521
740
|
return c
|
|
522
741
|
},
|
|
523
742
|
|
|
743
|
+
presetSummary(){
|
|
744
|
+
return ((this.preset ?? {}).summaries ?? []).filter((_) => _.enabled).pop()
|
|
745
|
+
},
|
|
746
|
+
|
|
524
747
|
},
|
|
525
748
|
|
|
526
749
|
data(){
|
|
@@ -531,20 +754,50 @@ export default{
|
|
|
531
754
|
configLoaded: false,
|
|
532
755
|
selectedColumn: null,
|
|
533
756
|
copiedConfig: null,
|
|
534
|
-
mediaPrefix:
|
|
757
|
+
mediaPrefix: null,
|
|
758
|
+
summary: null,
|
|
759
|
+
mapApiKey: null,
|
|
760
|
+
chartOpt: {
|
|
761
|
+
backgroundColors: [
|
|
762
|
+
'#5D9CEC',
|
|
763
|
+
'#A0D468',
|
|
764
|
+
'#FFCE54',
|
|
765
|
+
'#FC6E51',
|
|
766
|
+
'#48CFAD',
|
|
767
|
+
'#AC92EC',
|
|
768
|
+
'#4FC1E9',
|
|
769
|
+
'#FFCE54',
|
|
770
|
+
'#ED5565',
|
|
771
|
+
'#EC87C0'
|
|
772
|
+
],
|
|
773
|
+
borderColors: [
|
|
774
|
+
'#4A89DC',
|
|
775
|
+
'#8CC152',
|
|
776
|
+
'#F6BB42',
|
|
777
|
+
'#E9573F',
|
|
778
|
+
'#37BC9B',
|
|
779
|
+
'#967ADC',
|
|
780
|
+
'#3BAFDA',
|
|
781
|
+
'#F6BB42',
|
|
782
|
+
'#DA4453',
|
|
783
|
+
'#D770AD'
|
|
784
|
+
],
|
|
785
|
+
},
|
|
535
786
|
}
|
|
536
787
|
},
|
|
537
788
|
|
|
538
789
|
|
|
539
790
|
mounted() {
|
|
540
|
-
window.addEventListener('resize', () => this.calcMediaPrefix())
|
|
791
|
+
window.addEventListener('resize', throttle(() => this.calcMediaPrefix(), 100, { leading:true }))
|
|
541
792
|
this.calcMediaPrefix()
|
|
793
|
+
|
|
542
794
|
this.socket.onAny(this.onHooks)
|
|
543
795
|
|
|
544
796
|
window.setTimeout(() => {
|
|
545
797
|
this.loadConfig().then(() => {
|
|
546
|
-
|
|
547
|
-
|
|
798
|
+
window.setTimeout(() => {})
|
|
799
|
+
this.load()
|
|
800
|
+
}, 1000)
|
|
548
801
|
}, 201)
|
|
549
802
|
|
|
550
803
|
this.subscribe()
|
|
@@ -579,6 +832,10 @@ export default{
|
|
|
579
832
|
@apply !bg-base-400 md:w-[300px];
|
|
580
833
|
}
|
|
581
834
|
|
|
835
|
+
.searchBox2{
|
|
836
|
+
@apply bg-text-50;
|
|
837
|
+
}
|
|
838
|
+
|
|
582
839
|
.header{
|
|
583
840
|
@apply p-2 cursor-pointer border-b-[2px] border-transparent overflow-hidden;
|
|
584
841
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="$style.comp">
|
|
3
|
+
|
|
4
|
+
<div class="p-3">
|
|
5
|
+
<label class="text-text-400 flex-1">{{ $t('X-axis') }}</label>
|
|
6
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
7
|
+
<Dropdown v-model="summary.bar.rows[0].key" class="flex-1">
|
|
8
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
9
|
+
<option v-for="column in columns" :value="column.key">{{ column.label ?? column.key }}</option>
|
|
10
|
+
</Dropdown>
|
|
11
|
+
<Dropdown v-model="summary.bar.rows[0].format" class="w-[100px]">
|
|
12
|
+
<option value="">{{ $t('Default') }}</option>
|
|
13
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
14
|
+
<option value="month">{{ $t('Month') }}</option>
|
|
15
|
+
<option value="quarter">{{ $t('Quarterly') }}</option>
|
|
16
|
+
<option value="year">{{ $t('Year') }}</option>
|
|
17
|
+
</Dropdown>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="p-3">
|
|
22
|
+
<label class="text-text-400 flex-1">{{ $t('Column') }}</label>
|
|
23
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
24
|
+
<Dropdown v-model="summary.bar.columns[0].key" class="flex-1">
|
|
25
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
26
|
+
<option value="(none)">{{ $t('None') }}</option>
|
|
27
|
+
<option v-for="column in columns" :value="column.key">{{ column.label ?? column.key }}</option>
|
|
28
|
+
</Dropdown>
|
|
29
|
+
<Dropdown v-model="summary.bar.columns[0].format" class="w-[100px]">
|
|
30
|
+
<option value="">{{ $t('Default') }}</option>
|
|
31
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
32
|
+
</Dropdown>
|
|
33
|
+
</div>
|
|
34
|
+
<Checkbox class="mt-2" v-model="summary.bar.stacked">
|
|
35
|
+
Stacked
|
|
36
|
+
</Checkbox>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="p-3">
|
|
40
|
+
<label class="text-text-400 flex-1">Values</label>
|
|
41
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
42
|
+
<Dropdown v-model="summary.bar.values[0].aggregrate" class="flex-1">
|
|
43
|
+
<option value="" disabled selected>{{ $t('Select') }}</option>
|
|
44
|
+
<option value="count">{{ $t('Count') }}</option>
|
|
45
|
+
<option value="max">{{ $t('Max') }}</option>
|
|
46
|
+
<option value="min">{{ $t('Min') }}</option>
|
|
47
|
+
<option value="avg">{{ $t('Avg') }}</option>
|
|
48
|
+
</Dropdown>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script>
|
|
56
|
+
|
|
57
|
+
export default{
|
|
58
|
+
|
|
59
|
+
props: {
|
|
60
|
+
|
|
61
|
+
columns: Array,
|
|
62
|
+
|
|
63
|
+
summary: {
|
|
64
|
+
type: Object,
|
|
65
|
+
required: true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<style module>
|
|
75
|
+
|
|
76
|
+
.comp{
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
</style>
|