@mixd-id/web-scaffold 0.1.2301231351 → 0.1.2301231353
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.
|
|
4
|
+
"version": "0.1.2301231353",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite serve",
|
|
7
7
|
"build": "vite build",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@vueuse/core": "^9.0.2",
|
|
25
25
|
"adm-zip": "^0.5.10",
|
|
26
26
|
"axios": "^1.3.4",
|
|
27
|
+
"chart.js": "^4.2.1",
|
|
27
28
|
"compression": "^1.7.4",
|
|
28
29
|
"cookie-parser": "^1.4.6",
|
|
29
30
|
"cors": "^2.8.5",
|
|
@@ -41,13 +42,13 @@
|
|
|
41
42
|
"serve-static": "^1.15.0",
|
|
42
43
|
"tailwindcss": "^3.2.4",
|
|
43
44
|
"vue": "^3.2.25",
|
|
45
|
+
"vue-chartjs": "^5.2.0",
|
|
44
46
|
"vue-router": "^4.0.14"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@vitejs/plugin-vue": "^2.2.0",
|
|
48
50
|
"autoprefixer": "^10.4.4",
|
|
49
51
|
"postcss": "^8.4.12",
|
|
50
|
-
"sequelize": "^6.29.0",
|
|
51
52
|
"vite": "^2.8.0"
|
|
52
53
|
},
|
|
53
54
|
"description": "This scaffold based on express vitejs vuejs",
|
|
@@ -30,7 +30,12 @@ export default {
|
|
|
30
30
|
default: "auto" // bottom-left, bottom-right, top-left, top-right, bottom-center, top-center default: bottom-left
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
-
bodyClass: String
|
|
33
|
+
bodyClass: String,
|
|
34
|
+
|
|
35
|
+
dismiss: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: true
|
|
38
|
+
}
|
|
34
39
|
|
|
35
40
|
},
|
|
36
41
|
|
|
@@ -51,7 +56,8 @@ export default {
|
|
|
51
56
|
return {
|
|
52
57
|
computedStyle: null,
|
|
53
58
|
isOpen: false,
|
|
54
|
-
context: null
|
|
59
|
+
context: null,
|
|
60
|
+
closing: false
|
|
55
61
|
}
|
|
56
62
|
},
|
|
57
63
|
|
|
@@ -75,39 +81,39 @@ export default {
|
|
|
75
81
|
if(typeof window === 'undefined') return
|
|
76
82
|
|
|
77
83
|
const transitionEnd = () => {
|
|
78
|
-
this.$emit('open')
|
|
79
84
|
window.addEventListener('click', this.onDismiss)
|
|
80
85
|
window.addEventListener('scroll', this.onDismiss)
|
|
81
86
|
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
87
|
+
this.$emit('open')
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
84
91
|
this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
|
|
85
|
-
|
|
92
|
+
this.$nextTick(() => {
|
|
86
93
|
this.$refs.contextMenu.classList.add(this.$style.active)
|
|
87
|
-
}
|
|
94
|
+
})
|
|
88
95
|
},
|
|
89
96
|
|
|
90
|
-
onDismiss(){
|
|
97
|
+
onDismiss(e){
|
|
91
98
|
if(typeof window === 'undefined') return
|
|
92
99
|
if(!this.$refs.contextMenu) return
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
window.removeEventListener('scroll', this.onDismiss)
|
|
97
|
-
if(this.$refs.contextMenu){
|
|
98
|
-
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
99
|
-
}
|
|
100
|
-
this.computedStyle = null
|
|
101
|
-
this.$emit('dismiss')
|
|
102
|
-
this.isOpen = false
|
|
103
|
-
}
|
|
101
|
+
if(e.target.closest('.' + this.$style.contextMenu) && !this.dismiss)
|
|
102
|
+
return
|
|
104
103
|
|
|
105
|
-
this
|
|
106
|
-
this.$refs.contextMenu.classList.remove(this.$style.active)
|
|
104
|
+
this.close()
|
|
107
105
|
},
|
|
108
106
|
|
|
109
107
|
open(caller, context){
|
|
110
108
|
|
|
109
|
+
if(this.isOpen){
|
|
110
|
+
return this.close(() => {
|
|
111
|
+
window.setTimeout(() => {
|
|
112
|
+
this.open(caller, context)
|
|
113
|
+
}, 30)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
if(caller){
|
|
112
118
|
this.isOpen = true
|
|
113
119
|
}
|
|
@@ -185,7 +191,23 @@ export default {
|
|
|
185
191
|
else{
|
|
186
192
|
console.warn('Invalid context menu caller', this.caller)
|
|
187
193
|
}
|
|
194
|
+
},
|
|
188
195
|
|
|
196
|
+
close(fn){
|
|
197
|
+
const transitionEnd = () => {
|
|
198
|
+
window.removeEventListener('click', this.onDismiss)
|
|
199
|
+
window.removeEventListener('scroll', this.onDismiss)
|
|
200
|
+
if(this.$refs.contextMenu){
|
|
201
|
+
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
202
|
+
}
|
|
203
|
+
this.computedStyle = null
|
|
204
|
+
this.isOpen = false
|
|
205
|
+
this.$emit('dismiss')
|
|
206
|
+
if(fn) fn()
|
|
207
|
+
}
|
|
208
|
+
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
209
|
+
this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
|
|
210
|
+
this.$refs.contextMenu.classList.remove(this.$style.active)
|
|
189
211
|
}
|
|
190
212
|
|
|
191
213
|
}
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
<Suspense @resolve="patch">
|
|
3
3
|
<div :class="$style.comp">
|
|
4
4
|
|
|
5
|
-
<div class="flex-1 flex flex-col">
|
|
5
|
+
<div class="flex-1 flex flex-col p-6">
|
|
6
6
|
|
|
7
|
-
<div class="
|
|
7
|
+
<div class="pb-0 flex flex-row gap-3 items-center mb-6">
|
|
8
8
|
<slot v-if="$slots['lp-start']" name="lp-start" :preset="preset"/>
|
|
9
9
|
<div v-else class="mr-6 hidden md:block">
|
|
10
10
|
<h2>{{ title }}</h2>
|
|
11
11
|
</div>
|
|
12
12
|
<div class="flex-1"></div>
|
|
13
|
-
<div class="overflow-hidden
|
|
13
|
+
<div class="overflow-hidden hidden md:block">
|
|
14
14
|
<slot v-if="$slots['lp-search']" name="lp-search" :preset="preset"/>
|
|
15
|
-
<Textbox v-else :clearable="true" placeholder="
|
|
15
|
+
<Textbox v-else :clearable="true" :placeholder="$t('Search...')" class="w-[200px]" v-model="preset.search"
|
|
16
16
|
@clear="clearSearch" @submit="load">
|
|
17
17
|
<template #start>
|
|
18
18
|
<div class="px-2">
|
|
@@ -36,49 +36,100 @@
|
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
38
|
</Dropdown>
|
|
39
|
-
<button class="border-[1px] border-text-200 bg-base-50 hover:border-text-300 rounded-lg p-
|
|
40
|
-
@click="openPreset">
|
|
41
|
-
<svg width="
|
|
42
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 13.5C5.82843 13.5 6.5 12.8284 6.5 12C6.5 11.1716 5.82843 10.5 5 10.5C4.17157 10.5 3.5 11.1716 3.5 12C3.5 12.8284 4.17157 13.5 5 13.5ZM5 15C6.65685 15 8 13.6569 8 12C8 10.3431 6.65685 9 5 9C3.34315 9 2 10.3431 2 12C2 13.6569 3.34315 15 5 15Z" />
|
|
43
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 13.5C12.8284 13.5 13.5 12.8284 13.5 12C13.5 11.1716 12.8284 10.5 12 10.5C11.1716 10.5 10.5 11.1716 10.5 12C10.5 12.8284 11.1716 13.5 12 13.5ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" />
|
|
44
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 13.5C19.8284 13.5 20.5 12.8284 20.5 12C20.5 11.1716 19.8284 10.5 19 10.5C18.1716 10.5 17.5 11.1716 17.5 12C17.5 12.8284 18.1716 13.5 19 13.5ZM19 15C20.6569 15 22 13.6569 22 12C22 10.3431 20.6569 9 19 9C17.3431 9 16 10.3431 16 12C16 13.6569 17.3431 15 19 15Z" />
|
|
45
|
-
</svg>
|
|
39
|
+
<button class="border-[1px] border-text-200 bg-base-50 hover:border-text-300 rounded-lg p-3"
|
|
40
|
+
@click="openPreset()">
|
|
41
|
+
<svg width="15" height="15" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="fill-text-300 hover:fill-primary"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 256V160H224v96H64zm0 64H224v96H64V320zm224 96V320H448v96H288zM448 256H288V160H448v96zM64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64z"/></svg>
|
|
46
42
|
</button>
|
|
47
43
|
</div>
|
|
48
44
|
</div>
|
|
49
45
|
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
</template>
|
|
57
|
-
</VirtualScroll>
|
|
46
|
+
<div class="flex-1 mb-4 flex flex-col bg-base-500 border-text-50 border-[1px]" v-if="summary">
|
|
47
|
+
<VirtualTable v-if="preset.summary.mode === 'table'" :items="summary.items" :columns="summary.columns" class="flex-1"></VirtualTable>
|
|
48
|
+
|
|
49
|
+
<Bar v-else-if="preset.summary.mode === 'bar'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
|
|
50
|
+
|
|
51
|
+
</div>
|
|
58
52
|
|
|
59
|
-
<VirtualTable v-
|
|
53
|
+
<VirtualTable v-if="!hideDetails" class="flex-1" :columns="presetColumns" :items="items"
|
|
60
54
|
:appearances="appearances" @scroll-end="loadNext" :pinned="pinned">
|
|
61
55
|
<template v-for="column in presetColumns" #[colOf(column.key)]="{}">
|
|
62
|
-
<div :class="getHeader(column)">
|
|
63
|
-
<div
|
|
56
|
+
<div :class="getHeader(column)" @click="openColumnOptions(column.key, $event.target.closest('.' + $style.header))">
|
|
57
|
+
<div>
|
|
64
58
|
{{ column.label }}
|
|
65
59
|
</div>
|
|
66
|
-
<div>
|
|
60
|
+
<div class="absolute top-0 right-0 p-2 bg-base-500" v-if="sortedColumns[column.key] === 'asc'">
|
|
61
|
+
<svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
|
|
62
|
+
<path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
|
|
63
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H21C21.4142 17.75 21.75 17.4142 21.75 17C21.75 16.5858 21.4142 16.25 21 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
|
|
64
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 11.5858 12.5858 11.25 13 11.25H18C18.4142 11.25 18.75 11.5858 18.75 12C18.75 12.4142 18.4142 12.75 18 12.75H13C12.5858 12.75 12.25 12.4142 12.25 12Z"/>
|
|
65
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H15C15.4142 6.25 15.75 6.58579 15.75 7C15.75 7.41421 15.4142 7.75 15 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
|
|
66
|
+
</svg>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="absolute top-0 right-0 p-2 bg-base-500" v-else-if="sortedColumns[column.key] === 'desc'">
|
|
69
|
+
<svg width="19" height="19" viewBox="0 0 24 24" class="fill-text-400" xmlns="http://www.w3.org/2000/svg">
|
|
70
|
+
<path d="M6.75 5C6.75 4.58579 6.41421 4.25 6 4.25C5.58579 4.25 5.25 4.58579 5.25 5V17.6893L3.53033 15.9697C3.23744 15.6768 2.76256 15.6768 2.46967 15.9697C2.17678 16.2626 2.17678 16.7374 2.46967 17.0303L4.76256 19.3232C5.44598 20.0066 6.55402 20.0066 7.23744 19.3232L9.53033 17.0303C9.82322 16.7374 9.82322 16.2626 9.53033 15.9697C9.23744 15.6768 8.76256 15.6768 8.46967 15.9697L6.75 17.6893V5Z"/>
|
|
71
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 7C12.25 6.58579 12.5858 6.25 13 6.25H21C21.4142 6.25 21.75 6.58579 21.75 7C21.75 7.41421 21.4142 7.75 21 7.75H13C12.5858 7.75 12.25 7.41421 12.25 7Z"/>
|
|
72
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 12C12.25 12.4142 12.5858 12.75 13 12.75H18C18.4142 12.75 18.75 12.4142 18.75 12C18.75 11.5858 18.4142 11.25 18 11.25H13C12.5858 11.25 12.25 11.5858 12.25 12Z"/>
|
|
73
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.25 17C12.25 17.4142 12.5858 17.75 13 17.75H15C15.4142 17.75 15.75 17.4142 15.75 17C15.75 16.5858 15.4142 16.25 15 16.25H13C12.5858 16.25 12.25 16.5858 12.25 17Z"/>
|
|
74
|
+
</svg>
|
|
67
75
|
</div>
|
|
68
76
|
</div>
|
|
69
77
|
</template>
|
|
70
|
-
<template v-for="(_, slot) in
|
|
78
|
+
<template v-for="(_, slot) in headerSlots" #[slot]="{ item, index }">
|
|
79
|
+
<div :class="getHeader(slot.replace('col-', ''))" @click="openColumnOptions(slot.replace('col-', ''), $event.target.closest('.' + $style.header))">
|
|
80
|
+
<slot :name="slot" :item="item" :index="index"></slot>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
83
|
+
<template v-for="(_, slot) in contentSlots" #[slot]="{ item, index }">
|
|
71
84
|
<slot :name="slot" :item="item" :index="index"></slot>
|
|
72
85
|
</template>
|
|
73
86
|
</VirtualTable>
|
|
74
87
|
|
|
75
|
-
<ContextMenu
|
|
76
|
-
<div class="
|
|
77
|
-
<
|
|
78
|
-
|
|
88
|
+
<ContextMenu ref="columnMenu" :dismiss="false">
|
|
89
|
+
<div class="flex-1 flex flex-col w-[270px] p-3">
|
|
90
|
+
<div class="flex flex-col">
|
|
91
|
+
<div class="flex flex-row gap-2 items-center">
|
|
92
|
+
<div class="p-2 text-text-300 flex-1">Sort By</div>
|
|
93
|
+
<div class="text-primary cursor-pointer text-sm" @click="openPreset('sort');$refs.columnMenu.close()">Sort Options</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(1)">Sort Ascending</div>
|
|
96
|
+
<div class="p-2 cursor-pointer" :class="$style.hoverable" @click="setSortCurrent(2)">Sort Descending</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="h-[1px] bg-text-50 my-2"></div>
|
|
99
|
+
<div class="flex flex-col">
|
|
100
|
+
<div class="p-2 cursor-pointer" :class="$style.hoverable" @click="hide">Hide</div>
|
|
101
|
+
<div class="p-2 cursor-pointer" :class="$style.hoverable" @click="openPreset();$refs.columnMenu.close()">Column Options</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="h-[1px] bg-text-50 my-2"></div>
|
|
104
|
+
<div class="flex flex-col">
|
|
105
|
+
<div class="flex flex-row gap-2 items-center">
|
|
106
|
+
<div class="p-2 text-text-300 flex-1">Filters</div>
|
|
107
|
+
<div class="text-primary cursor-pointer text-sm" @click="openPreset('filter');$refs.columnMenu.close()">Filter Options</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div v-if="currentFilters.length > 0">
|
|
110
|
+
<ListPage1Filter v-if="preset.filters" v-for="filter in currentFilters"
|
|
111
|
+
:filter="filter" :column="columns[filter.key]"
|
|
112
|
+
@remove="removeFilter(filter)" @change="load" />
|
|
113
|
+
</div>
|
|
114
|
+
<div v-else>
|
|
115
|
+
<div class="p-2 cursor-pointer" :class="$style.hoverable" @click.stop="addCurrentFilter">Add Filter</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
79
118
|
</div>
|
|
80
119
|
</ContextMenu>
|
|
81
120
|
|
|
121
|
+
<div v-if="count > 0 && !summaryTeleport" class="flex flex-row bg-base-500 divide-x divide-text-50 border-[1px] border-text-50">
|
|
122
|
+
<div class="p-2 px-3 text-text-400">
|
|
123
|
+
Count: {{ count }}
|
|
124
|
+
</div>
|
|
125
|
+
<div></div>
|
|
126
|
+
</div>
|
|
127
|
+
<Teleport v-else-if="count > 0" to=".footer-wrap">
|
|
128
|
+
<div>
|
|
129
|
+
Count: {{ count }}
|
|
130
|
+
</div>
|
|
131
|
+
</Teleport>
|
|
132
|
+
|
|
82
133
|
</div>
|
|
83
134
|
|
|
84
135
|
<div v-if="layout.presetOpen" class="w-[360px] border-l-[1px] border-text-50 bg-base-500 flex flex-col">
|
|
@@ -98,8 +149,8 @@
|
|
|
98
149
|
:caller="layout.presetContextMenu ? layout.presetContextMenu.caller : null"
|
|
99
150
|
@dismiss="layout.presetContextMenu = null" position="bottom-right">
|
|
100
151
|
<div class="min-w-[200px]">
|
|
101
|
-
<button class="w-full p-3 hover:bg-text-50" @click="copyPreset">Copy this Preset</button>
|
|
102
|
-
<button class="w-full p-3 text-red-
|
|
152
|
+
<button type="button" class="w-full p-3 hover:bg-text-50 text-left" @click="copyPreset">Copy this Preset</button>
|
|
153
|
+
<button type="button" class="w-full p-3 text-red-600 hover:text-red-500 text-left" @click="removePreset" v-if="presets.length > 1">Remove</button>
|
|
103
154
|
</div>
|
|
104
155
|
</ContextMenu>
|
|
105
156
|
<button @click="closePreset">
|
|
@@ -110,7 +161,7 @@
|
|
|
110
161
|
</div>
|
|
111
162
|
|
|
112
163
|
<div class="flex justify-center">
|
|
113
|
-
<Tabs v-model="layout.presetTab" :items="
|
|
164
|
+
<Tabs v-model="layout.presetTab" :items="tabItems" />
|
|
114
165
|
</div>
|
|
115
166
|
</div>
|
|
116
167
|
|
|
@@ -134,15 +185,14 @@
|
|
|
134
185
|
</ListItem>
|
|
135
186
|
</div>
|
|
136
187
|
|
|
137
|
-
<div class="flex flex-col" v-if="layout.presetTab === 'filter'">
|
|
188
|
+
<div class="flex flex-col" v-else-if="layout.presetTab === 'filter'">
|
|
138
189
|
<div v-if="filterableColumns.length > 0">
|
|
139
190
|
<ListPage1Filter v-if="preset.filters" v-for="filter in preset.filters"
|
|
140
191
|
:filter="filter" :column="columns[filter.key]"
|
|
141
192
|
@remove="removeFilter(filter)" @change="load"/>
|
|
142
|
-
|
|
143
193
|
<div class="py-8">
|
|
144
194
|
<Dropdown @change="addFilter" v-model="layout.presetFilterSelector">
|
|
145
|
-
<option value="" disabled selected>
|
|
195
|
+
<option value="" disabled selected>{{ $t('Add Filter')}}</option>
|
|
146
196
|
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
147
197
|
</Dropdown>
|
|
148
198
|
</div>
|
|
@@ -154,13 +204,13 @@
|
|
|
154
204
|
</div>
|
|
155
205
|
</div>
|
|
156
206
|
|
|
157
|
-
<div class="flex flex-col" v-if="layout.presetTab === 'sort'">
|
|
207
|
+
<div class="flex flex-col" v-else-if="layout.presetTab === 'sort'">
|
|
158
208
|
<div v-if="sortableColumns.length > 0" class="my-8">
|
|
159
209
|
<div v-for="sort in preset.sorts" class="py-4">
|
|
160
210
|
<div class="flex flex-row items-center gap-2">
|
|
161
211
|
<div class="flex-1">
|
|
162
212
|
<Dropdown v-model="sort.key" @change="load">
|
|
163
|
-
<option value="" disabled selected>
|
|
213
|
+
<option value="" disabled selected>{{ $t('Add Sort') }}</option>
|
|
164
214
|
<option v-for="column in sortableColumns" :value="column.key">{{ column.label }}</option>
|
|
165
215
|
</Dropdown>
|
|
166
216
|
</div>
|
|
@@ -177,12 +227,10 @@
|
|
|
177
227
|
</button>
|
|
178
228
|
</div>
|
|
179
229
|
</div>
|
|
180
|
-
|
|
181
230
|
<div v-if="preset.sorts && preset.sorts.length > 0" class="h-[1px] my-4 bg-text-100"></div>
|
|
182
|
-
|
|
183
231
|
<div class="py-4">
|
|
184
232
|
<Dropdown @change="addSort" v-model="layout.presetSortSelector">
|
|
185
|
-
<option value="" disabled selected>
|
|
233
|
+
<option value="" disabled selected>{{ $t('Add Sort') }}</option>
|
|
186
234
|
<option v-for="column in sortableColumns" :value="column.key">{{ column.label }}</option>
|
|
187
235
|
</Dropdown>
|
|
188
236
|
</div>
|
|
@@ -194,6 +242,72 @@
|
|
|
194
242
|
</div>
|
|
195
243
|
</div>
|
|
196
244
|
|
|
245
|
+
<div class="flex flex-col gap-3 py-8" v-else-if="layout.presetTab === 'summary'">
|
|
246
|
+
|
|
247
|
+
<div class="flex flex-row items-center p-3">
|
|
248
|
+
<label class="text-text-400 flex-1">Enabled</label>
|
|
249
|
+
<Checkbox v-model="preset.summary.enabled" @change="load"/>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="flex flex-row items-center p-3">
|
|
253
|
+
<label class="text-text-400 flex-1">Hide Details</label>
|
|
254
|
+
<Checkbox v-model="preset.summary.hideDetails" @change="load"/>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="p-3">
|
|
258
|
+
<label class="text-text-400 flex-1">Row</label>
|
|
259
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
260
|
+
<Dropdown v-model="preset.summary.rows[0].key" class="flex-1" @change="load">
|
|
261
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
262
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
263
|
+
</Dropdown>
|
|
264
|
+
<Dropdown v-model="preset.summary.rows[0].format" class="w-[100px]" @change="load">
|
|
265
|
+
<option value="">{{ $t('Default') }}</option>
|
|
266
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
267
|
+
</Dropdown>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="p-3">
|
|
272
|
+
<label class="text-text-400 flex-1">Column</label>
|
|
273
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
274
|
+
<Dropdown v-model="preset.summary.columns[0].key" class="flex-1" @change="load">
|
|
275
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
276
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
277
|
+
</Dropdown>
|
|
278
|
+
<Dropdown v-model="preset.summary.columns[0].format" class="w-[100px]" @change="load">
|
|
279
|
+
<option value="">{{ $t('Default') }}</option>
|
|
280
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
281
|
+
</Dropdown>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div class="p-3">
|
|
286
|
+
<label class="text-text-400 flex-1">Values</label>
|
|
287
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
288
|
+
<Dropdown v-model="preset.summary.values[0].key" class="flex-1" @change="load">
|
|
289
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
290
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
291
|
+
</Dropdown>
|
|
292
|
+
<Dropdown v-model="preset.summary.values[0].aggregrate" class="w-[100px]" @change="load">
|
|
293
|
+
<option value="">{{ $t('Default') }}</option>
|
|
294
|
+
<option value="count">{{ $t('Count') }}</option>
|
|
295
|
+
</Dropdown>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div class="p-3">
|
|
300
|
+
<label class="text-text-400 flex-1">Type</label>
|
|
301
|
+
<div class="mt-2">
|
|
302
|
+
<Dropdown v-model="preset.summary.mode" class="flex-1" @change="load">
|
|
303
|
+
<option value="table">{{ $t('Table') }}</option>
|
|
304
|
+
<option value="bar">{{ $t('Bar Chart') }}</option>
|
|
305
|
+
</Dropdown>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
</div>
|
|
310
|
+
|
|
197
311
|
</div>
|
|
198
312
|
|
|
199
313
|
<div class="px-6 py-3 flex flex-row gap-2"></div>
|
|
@@ -211,9 +325,15 @@
|
|
|
211
325
|
|
|
212
326
|
import throttle from "lodash/throttle";
|
|
213
327
|
import {urlQuery} from "../utils/helpers.mjs";
|
|
328
|
+
import { Bar } from 'vue-chartjs'
|
|
329
|
+
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
|
|
330
|
+
|
|
331
|
+
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
|
214
332
|
|
|
215
333
|
export default{
|
|
216
334
|
|
|
335
|
+
components: { Bar },
|
|
336
|
+
|
|
217
337
|
inject: [ 'socket', 'confirm', 'alert', 'socketEmit', 'toast' ],
|
|
218
338
|
|
|
219
339
|
props:{
|
|
@@ -224,7 +344,9 @@ export default{
|
|
|
224
344
|
|
|
225
345
|
title: String,
|
|
226
346
|
|
|
227
|
-
pinned: Function
|
|
347
|
+
pinned: Function,
|
|
348
|
+
|
|
349
|
+
summaryTeleport: String
|
|
228
350
|
|
|
229
351
|
},
|
|
230
352
|
|
|
@@ -235,8 +357,9 @@ export default{
|
|
|
235
357
|
},
|
|
236
358
|
|
|
237
359
|
presetColumns(){
|
|
360
|
+
if(!this.preset.columns) return []
|
|
238
361
|
|
|
239
|
-
for(let i = 0 ; i < (this.preset.columns
|
|
362
|
+
for(let i = 0 ; i < (this.preset.columns).length ; i++){
|
|
240
363
|
const presetColumn = this.preset.columns[i]
|
|
241
364
|
const column = this.columns[presetColumn.key] ?? {}
|
|
242
365
|
for(let key in column){
|
|
@@ -246,7 +369,7 @@ export default{
|
|
|
246
369
|
}
|
|
247
370
|
}
|
|
248
371
|
|
|
249
|
-
return this.preset.columns
|
|
372
|
+
return this.preset.columns
|
|
250
373
|
},
|
|
251
374
|
|
|
252
375
|
appearances(){
|
|
@@ -261,14 +384,121 @@ export default{
|
|
|
261
384
|
return Object.values(this.columns).filter((_) => _.sortable)
|
|
262
385
|
},
|
|
263
386
|
|
|
387
|
+
sortedColumns(){
|
|
388
|
+
const c = {};
|
|
389
|
+
(this.preset.sorts ?? []).forEach((_) => c[_.key] = _.type);
|
|
390
|
+
return c
|
|
391
|
+
},
|
|
392
|
+
|
|
264
393
|
computedDataSource(){
|
|
265
394
|
return this.datasource ?? this.model
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
headerSlots(){
|
|
398
|
+
const slots = {}
|
|
399
|
+
for(let key in this.$slots){
|
|
400
|
+
if(key.startsWith('col-'))
|
|
401
|
+
slots[key] = this.$slots[key]
|
|
402
|
+
}
|
|
403
|
+
return slots
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
contentSlots(){
|
|
407
|
+
const slots = {}
|
|
408
|
+
for(let key in this.$slots){
|
|
409
|
+
if(!key.startsWith('col-'))
|
|
410
|
+
slots[key] = this.$slots[key]
|
|
411
|
+
}
|
|
412
|
+
return slots
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
currentFilters(){
|
|
416
|
+
if(!this.preset.filters) return []
|
|
417
|
+
return this.preset.filters.filter((_) => _.key === this.selectedColumn)
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
chartOptions(){
|
|
421
|
+
|
|
422
|
+
var style = getComputedStyle(document.body)
|
|
423
|
+
var gridColor = style.getPropertyValue('--text-50')
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
responsive: true,
|
|
427
|
+
maintainAspectRatio: false,
|
|
428
|
+
plugins: {
|
|
429
|
+
legend: true
|
|
430
|
+
},
|
|
431
|
+
scales: {
|
|
432
|
+
x: {
|
|
433
|
+
grid: {
|
|
434
|
+
color: `rgb(${gridColor})`
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
y: {
|
|
438
|
+
grid: {
|
|
439
|
+
color: `rgb(${gridColor})`
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
chartData(){
|
|
447
|
+
|
|
448
|
+
//console.log(this.summary)
|
|
449
|
+
|
|
450
|
+
const labels = []
|
|
451
|
+
this.summary.items.forEach((item) => {
|
|
452
|
+
labels.push(item.dfDate)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const datasets = []
|
|
456
|
+
this.summary.columns.forEach((column) => {
|
|
457
|
+
if(column.key === 'dfDate') return
|
|
458
|
+
|
|
459
|
+
const data = []
|
|
460
|
+
this.summary.items.forEach((item) => {
|
|
461
|
+
data.push(parseInt(item[column.key]))
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const dataset = {
|
|
465
|
+
label: column.key,
|
|
466
|
+
data,
|
|
467
|
+
backgroundColor: this.chartOpt.backgroundColors[datasets.length % 9],
|
|
468
|
+
borderColor: this.chartOpt.borderColors[datasets.length % 9]
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
datasets.push(dataset)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
//console.log(labels)
|
|
475
|
+
//console.log(datasets)
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
labels,
|
|
479
|
+
datasets
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/*return {
|
|
483
|
+
labels: [ 'January', 'February', 'March' ],
|
|
484
|
+
datasets: [ { data: [40, 20, 12] } ]
|
|
485
|
+
}*/
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
hideDetails(){
|
|
489
|
+
if(this.preset && this.preset.summary && this.preset.summary.hideDetails)
|
|
490
|
+
return this.preset.summary.hideDetails
|
|
491
|
+
return false;
|
|
266
492
|
}
|
|
267
493
|
|
|
268
494
|
},
|
|
269
495
|
|
|
270
496
|
data(){
|
|
271
497
|
return {
|
|
498
|
+
chartOpt: {
|
|
499
|
+
backgroundColors: [ '#5D9CEC', '#4FC1E9', '#48CFAD', '#A0D468', '#FFCE54', '#FC6E51', '#ED5565', '#AC92EC', '#EC87C0' ],
|
|
500
|
+
borderColors: [ '#4A89DC', '#3BAFDA', '#37BC9B', '#8CC152', '#F6BB42', '#E9573F', '#DA4453', '#967ADC', '#D770AD' ],
|
|
501
|
+
},
|
|
272
502
|
layout: {
|
|
273
503
|
presetOpen: false,
|
|
274
504
|
presetTab: 'column',
|
|
@@ -276,11 +506,20 @@ export default{
|
|
|
276
506
|
presetFilterSelector: null,
|
|
277
507
|
presetSortSelector: null,
|
|
278
508
|
},
|
|
509
|
+
tabItems: [
|
|
510
|
+
{ text:'Columns', value:'column' },
|
|
511
|
+
{ text:'Filters', value:'filter' },
|
|
512
|
+
{ text:'Sorts', value:'sort' },
|
|
513
|
+
{ text:'Summary', value:'summary' },
|
|
514
|
+
],
|
|
279
515
|
name: null,
|
|
280
516
|
columns: [],
|
|
517
|
+
selectedColumn: null,
|
|
281
518
|
presets: [],
|
|
282
519
|
presetIdx: -1,
|
|
283
520
|
items: [],
|
|
521
|
+
summary: null,
|
|
522
|
+
count: null,
|
|
284
523
|
hasNext: false,
|
|
285
524
|
modal: null
|
|
286
525
|
}
|
|
@@ -294,6 +533,9 @@ export default{
|
|
|
294
533
|
unmounted() {
|
|
295
534
|
window.removeEventListener('focus', this.onFocus)
|
|
296
535
|
this.socket.offAny(this.onHooks)
|
|
536
|
+
|
|
537
|
+
if(this.name)
|
|
538
|
+
this.socketEmit(this.name + '.unsubscribe', { name:this.name })
|
|
297
539
|
},
|
|
298
540
|
|
|
299
541
|
methods:{
|
|
@@ -303,15 +545,22 @@ export default{
|
|
|
303
545
|
this.socketEmit(`${this.computedDataSource}.patch`, {
|
|
304
546
|
...(urlQuery().reset ? { reset:1 } : {})
|
|
305
547
|
}, (res) => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.
|
|
309
|
-
this.
|
|
548
|
+
const data = res.data ? res.data : res
|
|
549
|
+
//console.log('patch', data)
|
|
550
|
+
this.name = data.name
|
|
551
|
+
this.columns = data.columns
|
|
552
|
+
this.presets = data.presets
|
|
553
|
+
this.presetIdx = data.presetIdx
|
|
310
554
|
this.patchPresets()
|
|
311
555
|
this.$nextTick(() => {
|
|
312
|
-
this.items =
|
|
313
|
-
this.
|
|
556
|
+
this.items = data.items
|
|
557
|
+
this.summary = data.summary
|
|
558
|
+
this.hasNext = data.hasNext
|
|
559
|
+
this.count = data.count
|
|
314
560
|
})
|
|
561
|
+
|
|
562
|
+
if(this.name)
|
|
563
|
+
this.socketEmit(this.name + '.subscribe', { name:this.name })
|
|
315
564
|
})
|
|
316
565
|
},
|
|
317
566
|
|
|
@@ -355,12 +604,14 @@ export default{
|
|
|
355
604
|
},
|
|
356
605
|
|
|
357
606
|
load(){
|
|
358
|
-
console.log('load')
|
|
359
607
|
this.socketEmit(`${this.computedDataSource}.load`, {
|
|
360
608
|
preset: this.preset
|
|
361
609
|
}, (res) => {
|
|
362
|
-
|
|
363
|
-
this.
|
|
610
|
+
const data = res.data ? res.data : res
|
|
611
|
+
this.items = data.items
|
|
612
|
+
this.summary = data.summary
|
|
613
|
+
this.hasNext = data.hasNext
|
|
614
|
+
this.count = data.count
|
|
364
615
|
})
|
|
365
616
|
},
|
|
366
617
|
|
|
@@ -397,10 +648,12 @@ export default{
|
|
|
397
648
|
|
|
398
649
|
selectPreset(preset){
|
|
399
650
|
this.presetIdx = this.presets.findIndex((_) => _ === preset)
|
|
651
|
+
this.load()
|
|
400
652
|
},
|
|
401
653
|
|
|
402
|
-
openPreset(){
|
|
654
|
+
openPreset(presetTab = 'column'){
|
|
403
655
|
this.layout.presetOpen = true
|
|
656
|
+
this.layout.presetTab = presetTab
|
|
404
657
|
},
|
|
405
658
|
|
|
406
659
|
closePreset(){
|
|
@@ -486,6 +739,7 @@ export default{
|
|
|
486
739
|
}
|
|
487
740
|
|
|
488
741
|
this.preset.filters.push({
|
|
742
|
+
enabled: true,
|
|
489
743
|
key: column.key,
|
|
490
744
|
label: column.label,
|
|
491
745
|
type: column.type,
|
|
@@ -496,6 +750,10 @@ export default{
|
|
|
496
750
|
this.layout.presetFilterSelector = null
|
|
497
751
|
},
|
|
498
752
|
|
|
753
|
+
addCurrentFilter(){
|
|
754
|
+
this.addFilter(this.selectedColumn)
|
|
755
|
+
},
|
|
756
|
+
|
|
499
757
|
addSort(key){
|
|
500
758
|
|
|
501
759
|
const column = this.preset.columns.filter((_) => _.key === key).pop()
|
|
@@ -535,10 +793,33 @@ export default{
|
|
|
535
793
|
|
|
536
794
|
return [
|
|
537
795
|
this.$style.header,
|
|
538
|
-
(this.preset.filters ?? []).findIndex((_) => _.key === column.key) >= 0 ?
|
|
796
|
+
(this.preset.filters ?? []).findIndex((_) => _.key === column.key && _.enabled) >= 0 ?
|
|
539
797
|
this.$style.headerSelected : ''
|
|
540
798
|
]
|
|
541
799
|
.join(' ')
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
openColumnOptions(key, target){
|
|
803
|
+
this.selectedColumn = key
|
|
804
|
+
this.$refs.columnMenu.open(target)
|
|
805
|
+
},
|
|
806
|
+
|
|
807
|
+
setSortCurrent(sortType){
|
|
808
|
+
this.preset.sorts = [
|
|
809
|
+
{
|
|
810
|
+
key: this.selectedColumn,
|
|
811
|
+
label: this.columns[this.selectedColumn].label,
|
|
812
|
+
type: sortType === 2 ? 'desc' : 'asc'
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
this.load()
|
|
816
|
+
this.$refs.columnMenu.close()
|
|
817
|
+
},
|
|
818
|
+
|
|
819
|
+
hide(){
|
|
820
|
+
const idx = this.preset.columns.findIndex((_) => _.key === this.selectedColumn)
|
|
821
|
+
this.preset.columns[idx].visible = false
|
|
822
|
+
this.$refs.columnMenu.close()
|
|
542
823
|
}
|
|
543
824
|
|
|
544
825
|
},
|
|
@@ -566,11 +847,18 @@ export default{
|
|
|
566
847
|
}
|
|
567
848
|
|
|
568
849
|
.header{
|
|
569
|
-
@apply
|
|
850
|
+
@apply p-2 cursor-pointer border-b-[2px] border-transparent overflow-hidden;
|
|
851
|
+
}
|
|
852
|
+
.header>*:first-child{
|
|
853
|
+
@apply text-ellipsis whitespace-nowrap overflow-x-hidden;
|
|
570
854
|
}
|
|
571
855
|
|
|
572
856
|
.headerSelected{
|
|
573
|
-
@apply
|
|
857
|
+
@apply border-primary;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.hoverable{
|
|
861
|
+
@apply hover:bg-primary hover:text-white;
|
|
574
862
|
}
|
|
575
863
|
|
|
576
864
|
</style>
|
|
@@ -1,52 +1,49 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<div class="flex flex-row items-center gap-2
|
|
2
|
+
<div :class="$style.comp">
|
|
3
|
+
<div class="flex flex-row items-center gap-2">
|
|
4
|
+
<Checkbox v-model="filter.enabled" @change="onFilterChange(item)"/>
|
|
4
5
|
<div class="flex-1">
|
|
5
|
-
<
|
|
6
|
+
<strong class="block text-ellipsis overflow-x-hidden whitespace-nowrap">{{ filter.label }}</strong>
|
|
6
7
|
</div>
|
|
7
|
-
<
|
|
8
|
+
<div class="w-[60px]">
|
|
9
|
+
<Dropdown v-model="filter.operand" @change="onFilterChange(item)">
|
|
10
|
+
<option value="">And</option>
|
|
11
|
+
<option value="or">Or</option>
|
|
12
|
+
</Dropdown>
|
|
13
|
+
</div>
|
|
14
|
+
<button @click.stop="removeFilter(filter)" class="text-primary">
|
|
8
15
|
<svg width="21" height="21" class="fill-text-100 hover:fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
9
16
|
<path class="secondary" fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/>
|
|
10
17
|
</svg>
|
|
11
18
|
</button>
|
|
12
19
|
</div>
|
|
13
|
-
<div>
|
|
20
|
+
<div class="flex flex-col gap-3 py-3">
|
|
14
21
|
|
|
15
22
|
<div v-for="(item, idx) in filter.filters">
|
|
16
23
|
|
|
17
24
|
<div v-if="column && column.type === 'date'">
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
|
|
26
|
+
<div class="flex flex-row items-start gap-2">
|
|
27
|
+
<Dropdown v-model="item.operator" @change="onFilterChange(item)" class="flex-1 min-w-[100px]">
|
|
28
|
+
<option value="">Select</option>
|
|
29
|
+
<option value="today">Today</option>
|
|
30
|
+
<option value="thisWeek">This Week</option>
|
|
31
|
+
<option value="thisMonth">This Month</option>
|
|
32
|
+
<option value="lastMonth">Last Month</option>
|
|
33
|
+
<option value="thisMonth">This Year</option>
|
|
34
|
+
<option value="lastYear">Last Year</option>
|
|
35
|
+
<option value="between">Between</option>
|
|
36
|
+
</Dropdown>
|
|
37
|
+
<Datepicker v-if="item.operator === 'between'" v-model="item.value[0]" mode="popup" @change="onFilterChange(item)"/>
|
|
38
|
+
<Datepicker v-if="item.operator === 'between'" v-model="item.value[1]" mode="popup" @change="onFilterChange(item)"/>
|
|
39
|
+
<button v-if="filter.filters.length > 1" @click.stop="filter.filters.splice(filter.filters.indexOf(item), 1);onFilterChange(item)"
|
|
20
40
|
class="py-2">
|
|
21
41
|
<svg width="21" height="21" class="fill-text-100 hover:fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
22
42
|
<path class="secondary" fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/>
|
|
23
43
|
</svg>
|
|
24
44
|
</button>
|
|
25
|
-
<div class="w-[60px]">
|
|
26
|
-
<Dropdown v-if="idx === 0" v-model="item.operand" @change="onFilterChange(item)">
|
|
27
|
-
<option value="">And</option>
|
|
28
|
-
<option value="or">Or</option>
|
|
29
|
-
</Dropdown>
|
|
30
|
-
</div>
|
|
31
|
-
<div class="flex-1 flex flex-col gap-2">
|
|
32
|
-
<Dropdown v-model="item.operator" @change="onFilterChange(item)">
|
|
33
|
-
<option value="">Select</option>
|
|
34
|
-
<option value="today">Today</option>
|
|
35
|
-
<option value="thisWeek">This Week</option>
|
|
36
|
-
<option value="thisMonth">This Month</option>
|
|
37
|
-
<option value="lastMonth">Last Month</option>
|
|
38
|
-
<option value="thisMonth">This Year</option>
|
|
39
|
-
<option value="lastYear">Last Year</option>
|
|
40
|
-
<option value="between">Between</option>
|
|
41
|
-
</Dropdown>
|
|
42
|
-
|
|
43
|
-
<Datepicker v-if="item.operator === 'between'" v-model="item.value[0]" mode="popup" @change="onFilterChange(item)"/>
|
|
44
|
-
|
|
45
|
-
<Datepicker v-if="item.operator === 'between'" v-model="item.value[1]" mode="popup" @change="onFilterChange(item)"/>
|
|
46
|
-
|
|
47
|
-
<button v-if="idx === filter.filters.length - 1" @click="addMore" class="text-primary">Add More</button>
|
|
48
|
-
</div>
|
|
49
45
|
</div>
|
|
46
|
+
|
|
50
47
|
</div>
|
|
51
48
|
|
|
52
49
|
<div v-else-if="column && column.type === 'enum'" class="flex flex-col">
|
|
@@ -57,70 +54,57 @@
|
|
|
57
54
|
</div>
|
|
58
55
|
|
|
59
56
|
<div v-else-if="column && [ 'currency', 'number' ].includes(column.type)">
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
|
|
58
|
+
<div class="flex flex-row items-start gap-2">
|
|
59
|
+
<Dropdown v-model="item.operator" @change="onFilterChange(item)">
|
|
60
|
+
<option value="">Select</option>
|
|
61
|
+
<option value="=">=</option>
|
|
62
|
+
<option value=">">></option>
|
|
63
|
+
<option value=">=">>=</option>
|
|
64
|
+
<option value="<"><</option>
|
|
65
|
+
<option value="<="><=</option>
|
|
66
|
+
</Dropdown>
|
|
67
|
+
<Textbox class="flex-1" v-model="item.value" @keyup.enter="onFilterChange(item)"/>
|
|
68
|
+
<button v-if="filter.filters.length > 1" @click.stop="filter.filters.splice(filter.filters.indexOf(item), 1);onFilterChange(item)"
|
|
62
69
|
class="py-2">
|
|
63
70
|
<svg width="21" height="21" class="fill-text-100 hover:fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
64
71
|
<path class="secondary" fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/>
|
|
65
72
|
</svg>
|
|
66
73
|
</button>
|
|
67
|
-
<div class="w-[60px]">
|
|
68
|
-
<Dropdown v-if="idx === 0" v-model="item.operand" @change="onFilterChange(item)">
|
|
69
|
-
<option value="">And</option>
|
|
70
|
-
<option value="or">Or</option>
|
|
71
|
-
</Dropdown>
|
|
72
|
-
</div>
|
|
73
|
-
<div class="flex-1 flex flex-col gap-2">
|
|
74
|
-
<Dropdown v-model="item.operator" @change="onFilterChange(item)">
|
|
75
|
-
<option value="">Select</option>
|
|
76
|
-
<option value="=">=</option>
|
|
77
|
-
<option value=">">></option>
|
|
78
|
-
<option value=">=">>=</option>
|
|
79
|
-
<option value="<"><</option>
|
|
80
|
-
<option value="<="><=</option>
|
|
81
|
-
</Dropdown>
|
|
82
|
-
|
|
83
|
-
<Textbox v-model="item.value" />
|
|
84
|
-
|
|
85
|
-
<button v-if="idx === filter.filters.length - 1" @click="addMore" class="text-primary">Add More</button>
|
|
86
|
-
</div>
|
|
87
74
|
</div>
|
|
75
|
+
|
|
88
76
|
</div>
|
|
89
77
|
|
|
90
78
|
<div v-else>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
|
|
80
|
+
<div class="flex flex-row items-start gap-2">
|
|
81
|
+
<Dropdown v-model="item.operator" @change="onFilterChange(item)" class="flex-1 min-w-[100px]">
|
|
82
|
+
<option value="">{{ $t('Select') }}</option>
|
|
83
|
+
<option value="startsWith">{{ $t('Starts with') }}</option>
|
|
84
|
+
<option value="endsWith">{{ $t('Ends with') }}</option>
|
|
85
|
+
<option value="contains">{{ $t('Contains') }}</option>
|
|
86
|
+
<option value="=">{{ $t('Equal') }}</option>
|
|
87
|
+
<option value="empty">{{ $t('Empty')}}</option>
|
|
88
|
+
<option value="notEmpty">{{ $t('Not empty')}}</option>
|
|
89
|
+
<option value="regex">{{ $t('Regexp')}}</option>
|
|
90
|
+
</Dropdown>
|
|
91
|
+
<Textbox v-if="![ 'empty', 'notEmpty' ].includes(item.operator)" class="w-[150px]" v-model="item.value" @keyup.enter="onFilterChange(item)"/>
|
|
92
|
+
<button v-if="filter.filters.length > 1" @click.stop="filter.filters.splice(filter.filters.indexOf(item), 1);onFilterChange(item)"
|
|
93
|
+
class="py-2">
|
|
94
94
|
<svg width="21" height="21" class="fill-text-100 hover:fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
95
95
|
<path class="secondary" fill-rule="evenodd" d="M15.78 14.36a1 1 0 0 1-1.42 1.42l-2.82-2.83-2.83 2.83a1 1 0 1 1-1.42-1.42l2.83-2.82L7.3 8.7a1 1 0 0 1 1.42-1.42l2.83 2.83 2.82-2.83a1 1 0 0 1 1.42 1.42l-2.83 2.83 2.83 2.82z"/>
|
|
96
96
|
</svg>
|
|
97
97
|
</button>
|
|
98
|
-
<div class="w-[60px]">
|
|
99
|
-
<Dropdown v-if="idx === 0" v-model="item.operand" @change="onFilterChange(item)">
|
|
100
|
-
<option value="">And</option>
|
|
101
|
-
<option value="or">Or</option>
|
|
102
|
-
</Dropdown>
|
|
103
|
-
</div>
|
|
104
|
-
<div class="flex-1 flex flex-col gap-2">
|
|
105
|
-
<Dropdown v-model="item.operator" @change="onFilterChange(item)">
|
|
106
|
-
<option value="">Select</option>
|
|
107
|
-
<option value="startsWith">Dimulai</option>
|
|
108
|
-
<option value="endsWith">Diakhiri</option>
|
|
109
|
-
<option value="contains">Berisi</option>
|
|
110
|
-
<option value="=">=</option>
|
|
111
|
-
</Dropdown>
|
|
112
|
-
|
|
113
|
-
<Textbox v-model="item.value" @keyup.enter="onFilterChange(item)"/>
|
|
114
|
-
|
|
115
|
-
<button v-if="idx === filter.filters.length - 1" @click="addMore" class="text-primary">Add More</button>
|
|
116
|
-
</div>
|
|
117
98
|
</div>
|
|
118
99
|
|
|
119
|
-
|
|
120
100
|
</div>
|
|
121
101
|
|
|
122
102
|
</div>
|
|
123
103
|
|
|
104
|
+
<div class="text-center">
|
|
105
|
+
<button @click="addMore" class="text-primary">{{ $t('Add More') }}</button>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
124
108
|
</div>
|
|
125
109
|
|
|
126
110
|
</div>
|
|
@@ -145,14 +129,12 @@ export default{
|
|
|
145
129
|
case 'date':
|
|
146
130
|
case 'enum':
|
|
147
131
|
this.filter.filters.push({
|
|
148
|
-
operand: 'or',
|
|
149
132
|
value: []
|
|
150
133
|
})
|
|
151
134
|
break
|
|
152
135
|
|
|
153
136
|
default:
|
|
154
137
|
this.filter.filters.push({
|
|
155
|
-
operand: 'or'
|
|
156
138
|
})
|
|
157
139
|
break
|
|
158
140
|
}
|
|
@@ -180,7 +162,7 @@ export default{
|
|
|
180
162
|
<style module>
|
|
181
163
|
|
|
182
164
|
.comp{
|
|
183
|
-
|
|
165
|
+
@apply py-4;
|
|
184
166
|
}
|
|
185
167
|
|
|
186
168
|
</style>
|
|
@@ -262,7 +262,10 @@ export default{
|
|
|
262
262
|
const x2 = e.touches ? e.touches[0].clientX : e.clientX
|
|
263
263
|
const d = x2 - x1
|
|
264
264
|
x1 = x2
|
|
265
|
-
|
|
265
|
+
|
|
266
|
+
let width = parseInt(this.columns[idx].width ?? _DEFAULT_COLUMN_WIDTH) + d
|
|
267
|
+
if(width < 20) width = 20
|
|
268
|
+
this.columns[idx].width = width
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
const onMouseUp = (e) => {
|
package/src/utils/listpage1.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const { Op } = require('sequelize')
|
|
2
|
-
const
|
|
1
|
+
const { Op } = module.parent.require('sequelize')
|
|
2
|
+
const Sequelize = module.parent.require('sequelize')
|
|
3
3
|
const dayjs = require("dayjs");
|
|
4
|
+
const util = require("util");
|
|
4
5
|
|
|
5
6
|
let ListPage1 = {
|
|
6
7
|
|
|
@@ -9,7 +10,7 @@ let ListPage1 = {
|
|
|
9
10
|
model: '',
|
|
10
11
|
channel: '',
|
|
11
12
|
|
|
12
|
-
async patch(params){
|
|
13
|
+
async patch(params, socket){
|
|
13
14
|
|
|
14
15
|
const { reset } = params
|
|
15
16
|
|
|
@@ -80,22 +81,14 @@ let ListPage1 = {
|
|
|
80
81
|
]
|
|
81
82
|
|
|
82
83
|
let attributes = { id:1, updatedAt:1 }
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
case 'item':
|
|
89
|
-
case 'option':
|
|
90
|
-
// Ignored
|
|
91
|
-
break
|
|
92
|
-
|
|
93
|
-
default:
|
|
94
|
-
attributes[column.key] = 1
|
|
95
|
-
break
|
|
84
|
+
if(preset.columns){
|
|
85
|
+
let modelAttributes = this.model.getAttributes();
|
|
86
|
+
preset.columns.forEach((column) => {
|
|
87
|
+
if(column.visible && modelAttributes[column.key]){
|
|
88
|
+
attributes[column.key] = 1
|
|
96
89
|
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
99
92
|
attributes = Object.keys(attributes)
|
|
100
93
|
|
|
101
94
|
if(preset.filters){
|
|
@@ -129,12 +122,21 @@ let ListPage1 = {
|
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
124
|
|
|
132
|
-
|
|
125
|
+
let attributeIncludes
|
|
126
|
+
if(this.getAttributeIncludes){
|
|
127
|
+
attributeIncludes = this.getAttributeIncludes(preset)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { rows:items, count } = await this.model.findAndCountAll({
|
|
133
131
|
where,
|
|
134
|
-
attributes
|
|
132
|
+
attributes: {
|
|
133
|
+
...attributes,
|
|
134
|
+
include: attributeIncludes
|
|
135
|
+
},
|
|
135
136
|
order,
|
|
136
137
|
limit: itemsPerPage + 1,
|
|
137
138
|
replacements,
|
|
139
|
+
include: this.getModelIncludes ? this.getModelIncludes(preset) : undefined
|
|
138
140
|
})
|
|
139
141
|
|
|
140
142
|
const hasNext = items.length > itemsPerPage
|
|
@@ -142,13 +144,37 @@ let ListPage1 = {
|
|
|
142
144
|
items.splice(items.length - 1, 1)
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
let summary
|
|
148
|
+
if(!afterItem && preset.summary && preset.summary.enabled){
|
|
149
|
+
summary = await this.loadSummary(preset, {
|
|
150
|
+
where,
|
|
151
|
+
replacements
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
145
155
|
if(this.socket){
|
|
146
156
|
this.socket.joinWithArgs(this.channel, { preset })
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
return {
|
|
150
160
|
items,
|
|
151
|
-
hasNext
|
|
161
|
+
hasNext,
|
|
162
|
+
count,
|
|
163
|
+
summary
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async subscribe(params, socket){
|
|
168
|
+
const { name } = params
|
|
169
|
+
if(name){
|
|
170
|
+
socket.join(this.name)
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
async unsubscribe(params, socket){
|
|
175
|
+
const { name } = params
|
|
176
|
+
if(name){
|
|
177
|
+
socket.leave(this.name)
|
|
152
178
|
}
|
|
153
179
|
},
|
|
154
180
|
|
|
@@ -163,17 +189,20 @@ let ListPage1 = {
|
|
|
163
189
|
|
|
164
190
|
preset.filters.forEach((filter) => {
|
|
165
191
|
|
|
192
|
+
if(!filter.enabled) return
|
|
193
|
+
|
|
166
194
|
const key = filter.key
|
|
167
195
|
const type = filter.type
|
|
196
|
+
const operand = filter.operand === 'or' ? 'or' : 'and'
|
|
168
197
|
const items = filter.filters ?? []
|
|
169
|
-
const operand = items[0] && items[0].operand && items[0].operand === 'or' && items.length > 1 ?
|
|
170
|
-
'or' : 'and'
|
|
171
198
|
|
|
172
199
|
let whereValue = []
|
|
173
200
|
switch(type){
|
|
174
201
|
|
|
175
202
|
case 'date':
|
|
176
203
|
items.forEach((item) => {
|
|
204
|
+
if(!('operator' in item) || (item.operator === 'between' && !('value' in item))) return
|
|
205
|
+
|
|
177
206
|
const operator = item.operator
|
|
178
207
|
const value = item.value
|
|
179
208
|
|
|
@@ -238,6 +267,8 @@ let ListPage1 = {
|
|
|
238
267
|
|
|
239
268
|
case 'enum':
|
|
240
269
|
items.forEach((item) => {
|
|
270
|
+
if(!('value' in item)) return
|
|
271
|
+
|
|
241
272
|
const value = item.value ?? []
|
|
242
273
|
whereValue = {
|
|
243
274
|
...whereValue,
|
|
@@ -249,6 +280,7 @@ let ListPage1 = {
|
|
|
249
280
|
case 'number':
|
|
250
281
|
case 'currency':
|
|
251
282
|
items.forEach((item) => {
|
|
283
|
+
if(!('operator' in item) || !('value' in item)) return
|
|
252
284
|
|
|
253
285
|
const operator = item.operator
|
|
254
286
|
const value = item.value
|
|
@@ -295,6 +327,9 @@ let ListPage1 = {
|
|
|
295
327
|
|
|
296
328
|
default:
|
|
297
329
|
items.forEach((item) => {
|
|
330
|
+
if(!('operator' in item) ||
|
|
331
|
+
([ 'startsWith', 'endsWith', 'contains', '=' ].includes(item.operator) && !'value' in item))
|
|
332
|
+
return
|
|
298
333
|
|
|
299
334
|
const operator = item.operator
|
|
300
335
|
const value = item.value
|
|
@@ -324,6 +359,33 @@ let ListPage1 = {
|
|
|
324
359
|
[Op.eq]: value
|
|
325
360
|
})
|
|
326
361
|
break
|
|
362
|
+
|
|
363
|
+
case 'empty':
|
|
364
|
+
whereValue.push({
|
|
365
|
+
[Op.or]: [
|
|
366
|
+
{ [Op.eq]: null },
|
|
367
|
+
{ [Op.eq]: '' },
|
|
368
|
+
]
|
|
369
|
+
})
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
case 'notEmpty':
|
|
373
|
+
whereValue.push({
|
|
374
|
+
[Op.and]: [
|
|
375
|
+
{ [Op.ne]: null },
|
|
376
|
+
{ [Op.ne]: '' },
|
|
377
|
+
]
|
|
378
|
+
})
|
|
379
|
+
break
|
|
380
|
+
|
|
381
|
+
case 'length':
|
|
382
|
+
break
|
|
383
|
+
|
|
384
|
+
case 'regex':
|
|
385
|
+
whereValue.push({
|
|
386
|
+
[Op.regexp]: value
|
|
387
|
+
})
|
|
388
|
+
break
|
|
327
389
|
}
|
|
328
390
|
})
|
|
329
391
|
break
|
|
@@ -352,6 +414,8 @@ let ListPage1 = {
|
|
|
352
414
|
})
|
|
353
415
|
}
|
|
354
416
|
|
|
417
|
+
//console.log(util.inspect(where, false, null, true /* enable colors */))
|
|
418
|
+
|
|
355
419
|
/*console.log(util.inspect(preset.filters, false, null, true /!* enable colors *!/))
|
|
356
420
|
console.log(where)
|
|
357
421
|
console.log(replacements)*/
|
|
@@ -416,28 +480,15 @@ let ListPage1 = {
|
|
|
416
480
|
return sortWhere
|
|
417
481
|
},
|
|
418
482
|
|
|
419
|
-
async onHooks(model, event, items,
|
|
483
|
+
async onHooks(model, event, items, socket){
|
|
420
484
|
|
|
421
485
|
switch(event){
|
|
422
|
-
|
|
423
486
|
case 'destroy':
|
|
424
487
|
case 'remove':
|
|
425
|
-
io.to(model).emit(model, event, items)
|
|
426
|
-
break
|
|
427
|
-
|
|
428
488
|
default:
|
|
429
|
-
|
|
430
|
-
where: {
|
|
431
|
-
id: {
|
|
432
|
-
[Op.in]: items.map((_) => _.id)
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
io.to(model).emit(`${model}${event}`, instances)
|
|
489
|
+
socket.emit(model, event, items)
|
|
437
490
|
break
|
|
438
|
-
|
|
439
491
|
}
|
|
440
|
-
|
|
441
492
|
}
|
|
442
493
|
|
|
443
494
|
}
|