@mixd-id/web-scaffold 0.1.2301231347 → 0.1.2301231349
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 +1 -1
- package/src/components/Image.vue +2 -2
- package/src/components/ListPage1.vue +54 -3
- package/src/components/ListPage1Filter.vue +3 -3
- package/src/index.js +0 -1
- package/src/utils/helpers.mjs +13 -0
- package/src/utils/listpage1.js +31 -20
- package/src/components/ListPage2.vue +0 -459
package/package.json
CHANGED
package/src/components/Image.vue
CHANGED
|
@@ -128,9 +128,9 @@ export default{
|
|
|
128
128
|
src['all'] = text
|
|
129
129
|
}
|
|
130
130
|
})
|
|
131
|
-
for(const b in screens){
|
|
132
|
-
if(window.innerWidth > b && src[screens[b]]){
|
|
133
131
|
|
|
132
|
+
for(const b in screens){
|
|
133
|
+
if(window.innerWidth > b && screens[b] in src){
|
|
134
134
|
var img = new Image()
|
|
135
135
|
img.addEventListener('load', () => {
|
|
136
136
|
this.status = 2
|
|
@@ -58,11 +58,27 @@
|
|
|
58
58
|
|
|
59
59
|
<VirtualTable v-else class="flex-1 m-6" :columns="presetColumns" :items="items"
|
|
60
60
|
:appearances="appearances" @scroll-end="loadNext" :pinned="pinned">
|
|
61
|
+
<template v-for="column in presetColumns" #[colOf(column.key)]="{}">
|
|
62
|
+
<div :class="getHeader(column)">
|
|
63
|
+
<div class="flex-1 p-2 cursor-pointer" @click="$refs[column.key][0].open($event.target)">
|
|
64
|
+
{{ column.label }}
|
|
65
|
+
</div>
|
|
66
|
+
<div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
61
70
|
<template v-for="(_, slot) in $slots" #[slot]="{ item, index }">
|
|
62
71
|
<slot :name="slot" :item="item" :index="index"></slot>
|
|
63
72
|
</template>
|
|
64
73
|
</VirtualTable>
|
|
65
74
|
|
|
75
|
+
<ContextMenu v-for="column in presetColumns" :ref="column.key">
|
|
76
|
+
<div class="p-4 min-w-[200px] max-w-[300px] min-h-[200px] flex flex-col gap-1">
|
|
77
|
+
<h5>{{ column.label }}</h5>
|
|
78
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec metus nisi. Etiam faucibus tortor ut faucibus scelerisque. In vel odio mollis, facilisis arcu et, tristique dolor. Morbi sapien velit, porttitor ut neque id, facilisis tristique mi. Donec sed odio felis. Mauris id urna tempor, sagittis libero ornare, pulvinar nulla. </p>
|
|
79
|
+
</div>
|
|
80
|
+
</ContextMenu>
|
|
81
|
+
|
|
66
82
|
</div>
|
|
67
83
|
|
|
68
84
|
<div v-if="layout.presetOpen" class="w-[360px] border-l-[1px] border-text-50 bg-base-500 flex flex-col">
|
|
@@ -83,7 +99,7 @@
|
|
|
83
99
|
@dismiss="layout.presetContextMenu = null" position="bottom-right">
|
|
84
100
|
<div class="min-w-[200px]">
|
|
85
101
|
<button class="w-full p-3 hover:bg-text-50" @click="copyPreset">Copy this Preset</button>
|
|
86
|
-
<button class="w-full p-3 text-red-500 hover:text-red-700" @click="removePreset">Remove</button>
|
|
102
|
+
<button class="w-full p-3 text-red-500 hover:text-red-700" @click="removePreset" v-if="presets.length > 1">Remove</button>
|
|
87
103
|
</div>
|
|
88
104
|
</ContextMenu>
|
|
89
105
|
<button @click="closePreset">
|
|
@@ -185,12 +201,16 @@
|
|
|
185
201
|
</div>
|
|
186
202
|
|
|
187
203
|
</div>
|
|
204
|
+
<template #fallback>
|
|
205
|
+
Loading...
|
|
206
|
+
</template>
|
|
188
207
|
</Suspense>
|
|
189
208
|
</template>
|
|
190
209
|
|
|
191
210
|
<script>
|
|
192
211
|
|
|
193
212
|
import throttle from "lodash/throttle";
|
|
213
|
+
import {urlQuery} from "../utils/helpers.mjs";
|
|
194
214
|
|
|
195
215
|
export default{
|
|
196
216
|
|
|
@@ -256,6 +276,7 @@ export default{
|
|
|
256
276
|
presetFilterSelector: null,
|
|
257
277
|
presetSortSelector: null,
|
|
258
278
|
},
|
|
279
|
+
name: null,
|
|
259
280
|
columns: [],
|
|
260
281
|
presets: [],
|
|
261
282
|
presetIdx: -1,
|
|
@@ -279,7 +300,10 @@ export default{
|
|
|
279
300
|
|
|
280
301
|
patch(){
|
|
281
302
|
|
|
282
|
-
this.socketEmit(`${this.computedDataSource}.patch`, {
|
|
303
|
+
this.socketEmit(`${this.computedDataSource}.patch`, {
|
|
304
|
+
...(urlQuery().reset ? { reset:1 } : {})
|
|
305
|
+
}, (res) => {
|
|
306
|
+
this.name = res.name
|
|
283
307
|
this.columns = res.columns
|
|
284
308
|
this.presets = res.presets
|
|
285
309
|
this.presetIdx = res.presetIdx
|
|
@@ -331,6 +355,7 @@ export default{
|
|
|
331
355
|
},
|
|
332
356
|
|
|
333
357
|
load(){
|
|
358
|
+
console.log('load')
|
|
334
359
|
this.socketEmit(`${this.computedDataSource}.load`, {
|
|
335
360
|
preset: this.preset
|
|
336
361
|
}, (res) => {
|
|
@@ -354,7 +379,11 @@ export default{
|
|
|
354
379
|
},
|
|
355
380
|
|
|
356
381
|
savePreset: throttle(function(preset) {
|
|
357
|
-
|
|
382
|
+
|
|
383
|
+
const presetName = this.name ?? this.model
|
|
384
|
+
const presetUrl = this.name ?? 'user'
|
|
385
|
+
|
|
386
|
+
this.socketEmit(`${presetUrl}.save-preset`, { key:presetName, preset:{
|
|
358
387
|
presetIdx: this.presetIdx,
|
|
359
388
|
presets: this.presets
|
|
360
389
|
}}, (res) => {
|
|
@@ -498,6 +527,20 @@ export default{
|
|
|
498
527
|
this.preset.columns.splice(to, 0, this.preset.columns.splice(from, 1)[0])
|
|
499
528
|
},
|
|
500
529
|
|
|
530
|
+
colOf(key){
|
|
531
|
+
return 'col-' + key
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
getHeader(column){
|
|
535
|
+
|
|
536
|
+
return [
|
|
537
|
+
this.$style.header,
|
|
538
|
+
(this.preset.filters ?? []).findIndex((_) => _.key === column.key) >= 0 ?
|
|
539
|
+
this.$style.headerSelected : ''
|
|
540
|
+
]
|
|
541
|
+
.join(' ')
|
|
542
|
+
}
|
|
543
|
+
|
|
501
544
|
},
|
|
502
545
|
|
|
503
546
|
watch: {
|
|
@@ -522,4 +565,12 @@ export default{
|
|
|
522
565
|
@apply flex-1 flex flex-row;
|
|
523
566
|
}
|
|
524
567
|
|
|
568
|
+
.header{
|
|
569
|
+
@apply flex flex-row;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.headerSelected{
|
|
573
|
+
@apply bg-primary-700 border-[1px] border-primary-700;
|
|
574
|
+
}
|
|
575
|
+
|
|
525
576
|
</style>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
</Checkbox>
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
|
-
<div v-else-if="column &&
|
|
59
|
+
<div v-else-if="column && [ 'currency', 'number' ].includes(column.type)">
|
|
60
60
|
<div class="flex flex-row items-start gap-2 my-2">
|
|
61
61
|
<button v-if="filter.filters.length > 1" @click="filter.filters.splice(filter.filters.indexOf(item), 1)"
|
|
62
62
|
class="py-2">
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
|
|
90
90
|
<div v-else>
|
|
91
91
|
<div class="flex flex-row items-start gap-2 my-2">
|
|
92
|
-
<button v-if="filter.filters.length > 1" @click="filter.filters.splice(filter.filters.indexOf(item), 1)"
|
|
92
|
+
<button v-if="filter.filters.length > 1" @click="filter.filters.splice(filter.filters.indexOf(item), 1);onFilterChange(item)"
|
|
93
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"/>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
<option value="=">=</option>
|
|
111
111
|
</Dropdown>
|
|
112
112
|
|
|
113
|
-
<Textbox v-model="item.value" />
|
|
113
|
+
<Textbox v-model="item.value" @keyup.enter="onFilterChange(item)"/>
|
|
114
114
|
|
|
115
115
|
<button v-if="idx === filter.filters.length - 1" @click="addMore" class="text-primary">Add More</button>
|
|
116
116
|
</div>
|
package/src/index.js
CHANGED
|
@@ -171,7 +171,6 @@ export default{
|
|
|
171
171
|
app.component('ImageFullScreen', defineAsyncComponent(() => import("./components/ImageFullScreen.vue")))
|
|
172
172
|
app.component('ImportModal', defineAsyncComponent(() => import("./components/ImportModal.vue")))
|
|
173
173
|
app.component('ListPage1', defineAsyncComponent(() => import("./components/ListPage1.vue")))
|
|
174
|
-
app.component('ListPage2', defineAsyncComponent(() => import("./components/ListPage2.vue")))
|
|
175
174
|
app.component('ListPage1Filter', defineAsyncComponent(() => import("./components/ListPage1Filter.vue")))
|
|
176
175
|
app.component('ListItem', defineAsyncComponent(() => import("./components/ListItem.vue")))
|
|
177
176
|
app.component('Carousel', defineAsyncComponent(() => import("./components/Carousel.vue")))
|
package/src/utils/helpers.mjs
CHANGED
|
@@ -95,6 +95,18 @@ const strVars = function(text, vars){
|
|
|
95
95
|
return text
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
let _QS
|
|
99
|
+
const urlQuery = function(){
|
|
100
|
+
|
|
101
|
+
if(!_QS){
|
|
102
|
+
_QS = new Proxy(new URLSearchParams(window.location.search), {
|
|
103
|
+
get: (searchParams, prop) => searchParams.get(prop),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return _QS
|
|
108
|
+
}
|
|
109
|
+
|
|
98
110
|
let __uid = new Date().getTime()
|
|
99
111
|
const uid = function(prefix){
|
|
100
112
|
return (prefix ?? '') + __uid++;
|
|
@@ -106,6 +118,7 @@ export {
|
|
|
106
118
|
observeInit,
|
|
107
119
|
csvToArray,
|
|
108
120
|
parseBoolean,
|
|
121
|
+
urlQuery
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
function observeInit(){
|
package/src/utils/listpage1.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { Op } = require('sequelize')
|
|
2
|
+
const util = require("util");
|
|
3
|
+
const dayjs = require("dayjs");
|
|
2
4
|
|
|
3
5
|
let ListPage1 = {
|
|
4
6
|
|
|
@@ -7,13 +9,13 @@ let ListPage1 = {
|
|
|
7
9
|
model: '',
|
|
8
10
|
channel: '',
|
|
9
11
|
|
|
10
|
-
async patch(){
|
|
12
|
+
async patch(params){
|
|
13
|
+
|
|
14
|
+
const { reset } = params
|
|
11
15
|
|
|
12
16
|
let currentPreset
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const user = await this.user.withId(userId)
|
|
16
|
-
const preset = await user.loadPreset(this.presetName)
|
|
17
|
+
const preset = this['loadPreset'] && !reset ? await this.loadPreset() : undefined
|
|
18
|
+
if(preset){
|
|
17
19
|
currentPreset = {
|
|
18
20
|
...this.getDefaultPresets(),
|
|
19
21
|
...preset
|
|
@@ -25,6 +27,16 @@ let ListPage1 = {
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/*if(this.user && this.socket && this.socket.auth){
|
|
31
|
+
const { userId } = this.socket.auth
|
|
32
|
+
const user = await this.user.withId(userId)
|
|
33
|
+
const preset = await user.loadPreset(this.presetName)
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
else{
|
|
37
|
+
|
|
38
|
+
}*/
|
|
39
|
+
|
|
28
40
|
const data = await this.load({ preset:currentPreset.presets[currentPreset.presetIdx] })
|
|
29
41
|
|
|
30
42
|
if(this.channel && this.socket){
|
|
@@ -58,7 +70,7 @@ let ListPage1 = {
|
|
|
58
70
|
}
|
|
59
71
|
}*/
|
|
60
72
|
|
|
61
|
-
const { afterItem, itemsPerPage = this.itemsPerPage } = params
|
|
73
|
+
const { afterItem, itemsPerPage = this.itemsPerPage ?? 24 } = params
|
|
62
74
|
const { preset = {} } = params
|
|
63
75
|
let where = {}
|
|
64
76
|
let replacements = []
|
|
@@ -157,7 +169,7 @@ let ListPage1 = {
|
|
|
157
169
|
const operand = items[0] && items[0].operand && items[0].operand === 'or' && items.length > 1 ?
|
|
158
170
|
'or' : 'and'
|
|
159
171
|
|
|
160
|
-
let whereValue =
|
|
172
|
+
let whereValue = []
|
|
161
173
|
switch(type){
|
|
162
174
|
|
|
163
175
|
case 'date':
|
|
@@ -234,6 +246,7 @@ let ListPage1 = {
|
|
|
234
246
|
})
|
|
235
247
|
break
|
|
236
248
|
|
|
249
|
+
case 'number':
|
|
237
250
|
case 'currency':
|
|
238
251
|
items.forEach((item) => {
|
|
239
252
|
|
|
@@ -289,31 +302,27 @@ let ListPage1 = {
|
|
|
289
302
|
switch(operator){
|
|
290
303
|
|
|
291
304
|
case 'startsWith':
|
|
292
|
-
whereValue
|
|
293
|
-
...whereValue,
|
|
305
|
+
whereValue.push({
|
|
294
306
|
[Op.like]: `${value}%`
|
|
295
|
-
}
|
|
307
|
+
})
|
|
296
308
|
break
|
|
297
309
|
|
|
298
310
|
case 'endsWith':
|
|
299
|
-
whereValue
|
|
300
|
-
...whereValue,
|
|
311
|
+
whereValue.push({
|
|
301
312
|
[Op.like]: `%${value}`
|
|
302
|
-
}
|
|
313
|
+
})
|
|
303
314
|
break
|
|
304
315
|
|
|
305
316
|
case 'contains':
|
|
306
|
-
whereValue
|
|
307
|
-
...whereValue,
|
|
317
|
+
whereValue.push({
|
|
308
318
|
[Op.like]: `%${value}%`
|
|
309
|
-
}
|
|
319
|
+
})
|
|
310
320
|
break
|
|
311
321
|
|
|
312
322
|
case '=':
|
|
313
|
-
whereValue
|
|
314
|
-
...whereValue,
|
|
323
|
+
whereValue.push({
|
|
315
324
|
[Op.eq]: value
|
|
316
|
-
}
|
|
325
|
+
})
|
|
317
326
|
break
|
|
318
327
|
}
|
|
319
328
|
})
|
|
@@ -343,7 +352,9 @@ let ListPage1 = {
|
|
|
343
352
|
})
|
|
344
353
|
}
|
|
345
354
|
|
|
346
|
-
|
|
355
|
+
/*console.log(util.inspect(preset.filters, false, null, true /!* enable colors *!/))
|
|
356
|
+
console.log(where)
|
|
357
|
+
console.log(replacements)*/
|
|
347
358
|
|
|
348
359
|
return {
|
|
349
360
|
where,
|
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Suspense @resolve="patch">
|
|
3
|
-
<div :class="$style.comp">
|
|
4
|
-
|
|
5
|
-
<div class="flex-1 flex flex-col">
|
|
6
|
-
|
|
7
|
-
<div class="p-6 pb-0 flex flex-row gap-3 items-center">
|
|
8
|
-
<div class="mr-6">
|
|
9
|
-
<h2>{{ title }}</h2>
|
|
10
|
-
</div>
|
|
11
|
-
<div class="flex-1 overflow-hidden">
|
|
12
|
-
<slot name="lp-tabspace" :preset="preset"/>
|
|
13
|
-
</div>
|
|
14
|
-
<div>
|
|
15
|
-
<slot name="lp-opts" :preset="preset"/>
|
|
16
|
-
</div>
|
|
17
|
-
<div v-if="isSearchable">
|
|
18
|
-
<slot v-if="$slots['lp-search']" name="lp-search" />
|
|
19
|
-
<Textbox v-else :clearable="true" placeholder="Cari..." class="w-[200px]" v-model="preset.params.search"
|
|
20
|
-
@clear="clearSearch" @submit="load">
|
|
21
|
-
<template #start>
|
|
22
|
-
<div class="px-2">
|
|
23
|
-
<svg width="16" height="16" class="fill-text-300" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
24
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.8723 16.8116C19.1996 15.2436 20 13.2153 20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 15.9706 6.02944 20 11 20C13.2153 20 15.2436 19.1996 16.8116 17.8723L19.4697 20.5303C19.7626 20.8232 20.2374 20.8232 20.5303 20.5303C20.8232 20.2374 20.8232 19.7626 20.5303 19.4697L17.8723 16.8116ZM18.5 11C18.5 15.1421 15.1421 18.5 11 18.5C6.85786 18.5 3.5 15.1421 3.5 11C3.5 6.85786 6.85786 3.5 11 3.5C15.1421 3.5 18.5 6.85786 18.5 11Z"/>
|
|
25
|
-
</svg>
|
|
26
|
-
</div>
|
|
27
|
-
</template>
|
|
28
|
-
</Textbox>
|
|
29
|
-
</div>
|
|
30
|
-
<div>
|
|
31
|
-
<Dropdown class="w-[150px]" :value="preset.name" mode="custom" position="bottom-right">
|
|
32
|
-
<div class="min-w-[300px] divide-y divide-text-50 p-3">
|
|
33
|
-
<div v-for="(_preset, idx) in presets" class="cursor-pointer flex flex-row">
|
|
34
|
-
<div class="flex-1 p-3" @click="selectPreset(_preset)">
|
|
35
|
-
{{ _preset.name }}
|
|
36
|
-
</div>
|
|
37
|
-
<div>
|
|
38
|
-
<button class="p-3 text-primary" @click="selectPreset(_preset, true)">
|
|
39
|
-
Edit
|
|
40
|
-
</button>
|
|
41
|
-
<button class="p-3 text-primary" @click="copyPreset(preset)">
|
|
42
|
-
Duplicate
|
|
43
|
-
</button>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</Dropdown>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<VirtualTable class="flex-1 m-6" :columns="columns" :items="items" @scroll-end="loadNext">
|
|
52
|
-
<template v-for="(_, slot) in $slots" #[slot]="{ item }">
|
|
53
|
-
<slot :name="slot" :item="item"></slot>
|
|
54
|
-
</template>
|
|
55
|
-
</VirtualTable>
|
|
56
|
-
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
<div v-if="layout.presetOpen" class="w-[300px] border-l-[1px] border-text-100 flex flex-col">
|
|
60
|
-
|
|
61
|
-
<div class="p-6 pb-0">
|
|
62
|
-
<div class="flex flex-row gap-2 mb-3">
|
|
63
|
-
<input class="flex-1 text-xl bg-transparent outline-none" :value="preset.name" @blur="preset.name = $event.target.value" />
|
|
64
|
-
<button @click="layout.presetContextMenu = { caller:$event.target }">
|
|
65
|
-
<svg width="21" height="21" viewBox="0 0 24 24" class="fill-text-300 pointer-events-none" xmlns="http://www.w3.org/2000/svg">
|
|
66
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 6.5C12.8284 6.5 13.5 5.82843 13.5 5C13.5 4.17157 12.8284 3.5 12 3.5C11.1716 3.5 10.5 4.17157 10.5 5C10.5 5.82843 11.1716 6.5 12 6.5ZM12 8C13.6569 8 15 6.65685 15 5C15 3.34315 13.6569 2 12 2C10.3431 2 9 3.34315 9 5C9 6.65685 10.3431 8 12 8Z"/>
|
|
67
|
-
<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"/>
|
|
68
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 20.5C12.8284 20.5 13.5 19.8284 13.5 19C13.5 18.1716 12.8284 17.5 12 17.5C11.1716 17.5 10.5 18.1716 10.5 19C10.5 19.8284 11.1716 20.5 12 20.5ZM12 22C13.6569 22 15 20.6569 15 19C15 17.3431 13.6569 16 12 16C10.3431 16 9 17.3431 9 19C9 20.6569 10.3431 22 12 22Z"/>
|
|
69
|
-
</svg>
|
|
70
|
-
</button>
|
|
71
|
-
<ContextMenu :state="!!layout.presetContextMenu"
|
|
72
|
-
:caller="layout.presetContextMenu ? layout.presetContextMenu.caller : null"
|
|
73
|
-
@dismiss="layout.presetContextMenu = null" position="bottom-right">
|
|
74
|
-
<div class="min-w-[200px]">
|
|
75
|
-
<button class="w-full p-3 hover:bg-text-50" @click="removePreset(preset)">Remove</button>
|
|
76
|
-
</div>
|
|
77
|
-
</ContextMenu>
|
|
78
|
-
<button @click="layout.presetOpen = false">
|
|
79
|
-
<svg width="21" height="21" viewBox="0 0 24 24" class="fill-text-300" xmlns="http://www.w3.org/2000/svg">
|
|
80
|
-
<path d="M6.53034 5.46965C6.23745 5.17676 5.76257 5.17676 5.46968 5.46965C5.17679 5.76255 5.17679 6.23742 5.46968 6.53031L10.9393 12L5.46967 17.4697C5.17678 17.7626 5.17678 18.2374 5.46967 18.5303C5.76256 18.8232 6.23744 18.8232 6.53033 18.5303L12 13.0606L17.4697 18.5303C17.7626 18.8232 18.2375 18.8232 18.5303 18.5303C18.8232 18.2374 18.8232 17.7626 18.5303 17.4697L13.0607 12L18.5303 6.53032C18.8232 6.23743 18.8232 5.76256 18.5303 5.46966C18.2374 5.17677 17.7626 5.17677 17.4697 5.46966L12 10.9393L6.53034 5.46965Z"/>
|
|
81
|
-
</svg>
|
|
82
|
-
</button>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<Tabs v-model="layout.presetTab">
|
|
86
|
-
<div value="column">Kolom</div>
|
|
87
|
-
<div value="filter">Filter</div>
|
|
88
|
-
<div value="sort">Urutan</div>
|
|
89
|
-
</Tabs>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div class="flex-1 overflow-y-auto px-6 mb-6">
|
|
93
|
-
|
|
94
|
-
<div class="p-2 mt-6 flex flex-col" v-if="layout.presetTab === 'column'">
|
|
95
|
-
<ListItem :items="columns" @reorder="reorderColumns">
|
|
96
|
-
<template v-slot="{ item }">
|
|
97
|
-
<div class="flex flex-row items-center gap-2" :key="item">
|
|
98
|
-
<div class="cursor-move" data-reorder>
|
|
99
|
-
<svg class="fill-text-200" width="21" height="21" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
100
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 16C4 15.4477 4.44772 15 5 15H19C19.5523 15 20 15.4477 20 16C20 16.5523 19.5523 17 19 17H5C4.44772 17 4 16.5523 4 16Z"/>
|
|
101
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C4 7.44772 4.44772 7 5 7H19C19.5523 7 20 7.44772 20 8C20 8.55228 19.5523 9 19 9H5C4.44772 9 4 8.55228 4 8Z"/>
|
|
102
|
-
</svg>
|
|
103
|
-
</div>
|
|
104
|
-
<input :id="`lp1column${item.key}`" type="checkbox" v-model="item.visible" :true-value="true" :false-value="false" @change="load"/>
|
|
105
|
-
<label :for="`lp1column${item.key}`" class="p-2">
|
|
106
|
-
{{ item.label ?? '(No Name)' }}
|
|
107
|
-
</label>
|
|
108
|
-
</div>
|
|
109
|
-
</template>
|
|
110
|
-
</ListItem>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div class="flex flex-col" v-if="layout.presetTab === 'filter'">
|
|
114
|
-
<div v-if="filterableColumns.length > 0">
|
|
115
|
-
<ListPage1Filter v-if="preset.filters" v-for="filter in preset.filters"
|
|
116
|
-
:filter="filter" :column="preset.params.columns.filter((_) => _.key === filter.key).pop()"
|
|
117
|
-
@remove="removeFilter(filter)" @change="load"/>
|
|
118
|
-
|
|
119
|
-
<div class="py-8 border-t-[1px] border-text-100">
|
|
120
|
-
<Dropdown @change="addFilter" v-model="layout.presetFilterSelector">
|
|
121
|
-
<option value="" disabled selected>Tambah Filter</option>
|
|
122
|
-
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
123
|
-
</Dropdown>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
<div v-else>
|
|
127
|
-
<div class="p-6 text-center">
|
|
128
|
-
<label>Filter not available</label>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
<div class="p-2 flex flex-col" v-if="layout.presetTab === 'sort'">
|
|
134
|
-
<div v-if="sortableColumns.length > 0">
|
|
135
|
-
<div v-for="sort in preset.sorts" class="py-4 border-b-[1px] border-text-100">
|
|
136
|
-
<div class="flex flex-row items-center gap-2">
|
|
137
|
-
<div class="flex-1">
|
|
138
|
-
<Dropdown v-model="sort.key">
|
|
139
|
-
<option value="" disabled selected>Tambah Urutan</option>
|
|
140
|
-
<option v-for="column in sortableColumns" :value="column.key">{{ column.label }}</option>
|
|
141
|
-
</Dropdown>
|
|
142
|
-
</div>
|
|
143
|
-
<div>
|
|
144
|
-
<Dropdown v-model="sort.type" @change="load" class="w-[80px]">
|
|
145
|
-
<option value="">Asc</option>
|
|
146
|
-
<option value="desc">Desc</option>
|
|
147
|
-
</Dropdown>
|
|
148
|
-
</div>
|
|
149
|
-
<button @click="removeSort(sort)">
|
|
150
|
-
<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">
|
|
151
|
-
<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"/>
|
|
152
|
-
</svg>
|
|
153
|
-
</button>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
<div class="py-8">
|
|
158
|
-
<Dropdown @change="addSort" v-model="layout.presetSortSelector">
|
|
159
|
-
<option value="" disabled selected>Tambah Urutan</option>
|
|
160
|
-
<option v-for="column in sortableColumns" :value="column.key">{{ column.label }}</option>
|
|
161
|
-
</Dropdown>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
<div v-else>
|
|
165
|
-
<div class="p-6 text-center">
|
|
166
|
-
<label>Sort not available</label>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div class="px-6 py-3 flex flex-row gap-2"></div>
|
|
174
|
-
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
</div>
|
|
178
|
-
</Suspense>
|
|
179
|
-
</template>
|
|
180
|
-
|
|
181
|
-
<script>
|
|
182
|
-
|
|
183
|
-
import throttle from "lodash/throttle";
|
|
184
|
-
import { parseBoolean } from "../utils/helpers.mjs";
|
|
185
|
-
|
|
186
|
-
export default{
|
|
187
|
-
|
|
188
|
-
inject: [ 'socket', 'confirm', 'alert', 'socketEmit', 'toast' ],
|
|
189
|
-
|
|
190
|
-
props:{
|
|
191
|
-
|
|
192
|
-
model: String,
|
|
193
|
-
|
|
194
|
-
title: String,
|
|
195
|
-
|
|
196
|
-
searchable: {
|
|
197
|
-
type: undefined,
|
|
198
|
-
default: true
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
computed: {
|
|
204
|
-
|
|
205
|
-
preset(){
|
|
206
|
-
const presets = this.presets ?? []
|
|
207
|
-
return presets.length === 1 ? presets[0] : (this.presets ?? []).filter((_) => _.selected).pop() ?? { params:{} }
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
columns(){
|
|
211
|
-
return this.preset.params.columns ?? []
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
filterableColumns(){
|
|
215
|
-
return this.preset.params.columns.filter((_) => _.filterable)
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
sortableColumns(){
|
|
219
|
-
return this.preset.params.columns.filter((_) => _.sortable)
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
isSearchable(){
|
|
223
|
-
return parseBoolean(this.searchable)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
data(){
|
|
229
|
-
return {
|
|
230
|
-
layout: {
|
|
231
|
-
presetOpen: false,
|
|
232
|
-
presetTab: 'column',
|
|
233
|
-
presetContextMenu: false,
|
|
234
|
-
presetFilterSelector: null,
|
|
235
|
-
presetSortSelector: null,
|
|
236
|
-
},
|
|
237
|
-
presets: [],
|
|
238
|
-
items: [],
|
|
239
|
-
hasNext: false,
|
|
240
|
-
modal: null
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
mounted() {
|
|
245
|
-
this.socket.onAny(this.onHooks)
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
unmounted() {
|
|
249
|
-
this.socket.offAny(this.onHooks)
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
methods:{
|
|
253
|
-
|
|
254
|
-
patch(){
|
|
255
|
-
this.socketEmit(`${this.model}.patch`, {}, (res) => {
|
|
256
|
-
this.$nextTick(() => {
|
|
257
|
-
this.presets = res.presets
|
|
258
|
-
this.items = res.items
|
|
259
|
-
this.hasNext = res.hasNext
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
load: throttle(function (){
|
|
265
|
-
this.socketEmit(`${this.model}.load`, {
|
|
266
|
-
preset: this.preset
|
|
267
|
-
}, (res) => {
|
|
268
|
-
this.items = res.items
|
|
269
|
-
this.hasNext = res.hasNext
|
|
270
|
-
})
|
|
271
|
-
}, 1000),
|
|
272
|
-
|
|
273
|
-
loadNext(){
|
|
274
|
-
this.socketEmit(`${this.model}.load`, {
|
|
275
|
-
preset: this.preset,
|
|
276
|
-
afterItem: this.items[this.items.length - 1]
|
|
277
|
-
}, (res) => {
|
|
278
|
-
this.items.push(...res.items)
|
|
279
|
-
this.hasNext = res.hasNext
|
|
280
|
-
})
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
savePreset: throttle(function() {
|
|
284
|
-
this.socketEmit('user.save-preset', {
|
|
285
|
-
preset:this.preset
|
|
286
|
-
}, undefined, (err) => {
|
|
287
|
-
console.error(err)
|
|
288
|
-
})
|
|
289
|
-
}, 1000),
|
|
290
|
-
|
|
291
|
-
clearSearch(){
|
|
292
|
-
this.preset.params.search = ''
|
|
293
|
-
this.load()
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
selectPreset(preset, edit){
|
|
297
|
-
|
|
298
|
-
this.presets.forEach((_) => {
|
|
299
|
-
_.selected = _ === preset
|
|
300
|
-
})
|
|
301
|
-
if(edit){
|
|
302
|
-
this.layout.presetOpen = true
|
|
303
|
-
}
|
|
304
|
-
},
|
|
305
|
-
|
|
306
|
-
copyPreset(preset){
|
|
307
|
-
|
|
308
|
-
this.presets.forEach((_) => _.selected = false)
|
|
309
|
-
|
|
310
|
-
const newPreset = Object.assign({}, preset)
|
|
311
|
-
newPreset.name += '(Copy)'
|
|
312
|
-
newPreset.selected = true
|
|
313
|
-
delete newPreset.id
|
|
314
|
-
this.presets.push(newPreset)
|
|
315
|
-
this.layout.presetOpen = true
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
removePreset(){
|
|
319
|
-
|
|
320
|
-
if(this.presets.length <= 1) return
|
|
321
|
-
|
|
322
|
-
this.socketEmit('user.remove-preset', this.preset.id, (res) => {
|
|
323
|
-
const index = this.presets.findIndex((_) => _ === this.preset)
|
|
324
|
-
console.log(index)
|
|
325
|
-
this.presets.splice(index, 1)
|
|
326
|
-
const nextIndex = index - 1 < 0 ? 0 : index - 1
|
|
327
|
-
this.presets[nextIndex].selected = true
|
|
328
|
-
|
|
329
|
-
console.log(this.presets)
|
|
330
|
-
console.log(nextIndex)
|
|
331
|
-
})
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
onHooks(module, event, items){
|
|
335
|
-
|
|
336
|
-
console.log('onHooks', module, event, items)
|
|
337
|
-
|
|
338
|
-
if(!Array.isArray(items)) return
|
|
339
|
-
items = items.filter((_) => _).map((_) => {
|
|
340
|
-
_['_highlight'] = 1
|
|
341
|
-
return _
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
if(module === this.model){
|
|
345
|
-
|
|
346
|
-
switch(event){
|
|
347
|
-
|
|
348
|
-
case 'create':
|
|
349
|
-
this.items.unshift(...items)
|
|
350
|
-
break
|
|
351
|
-
|
|
352
|
-
case 'update':
|
|
353
|
-
items.forEach((item) => {
|
|
354
|
-
const idx = this.items.findIndex((_) => _.id === item.id)
|
|
355
|
-
if(idx >= 0){
|
|
356
|
-
this.items[idx] = {
|
|
357
|
-
...this.items[idx],
|
|
358
|
-
...item
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
})
|
|
362
|
-
break
|
|
363
|
-
|
|
364
|
-
case 'remove':
|
|
365
|
-
items.forEach((item) => {
|
|
366
|
-
const idx = this.items.findIndex((_) => _.id === item.id)
|
|
367
|
-
if(idx >= 0){
|
|
368
|
-
this.items.splice(idx, 1)
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
break
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
addFilter(key){
|
|
377
|
-
|
|
378
|
-
const column = this.preset.params.columns.filter((_) => _.key === key).pop()
|
|
379
|
-
|
|
380
|
-
if(!this.preset.filters){
|
|
381
|
-
this.preset.filters = []
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
let filters = [{}]
|
|
385
|
-
switch(column.type){
|
|
386
|
-
case 'date':
|
|
387
|
-
case 'enum':
|
|
388
|
-
filters = [{ value:[] }]
|
|
389
|
-
break
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
this.preset.filters.push({
|
|
393
|
-
key: column.key,
|
|
394
|
-
label: column.label,
|
|
395
|
-
type: column.type,
|
|
396
|
-
typeParams: column.typeParams,
|
|
397
|
-
filters
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
this.layout.presetFilterSelector = null
|
|
401
|
-
},
|
|
402
|
-
|
|
403
|
-
addSort(key){
|
|
404
|
-
|
|
405
|
-
const column = this.preset.params.columns.filter((_) => _.key === key).pop()
|
|
406
|
-
|
|
407
|
-
if(!this.preset.sorts){
|
|
408
|
-
this.preset.sorts = []
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
this.preset.sorts.push({
|
|
412
|
-
key: column.key,
|
|
413
|
-
label: column.label
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
this.layout.presetSortSelector = null
|
|
417
|
-
this.load()
|
|
418
|
-
},
|
|
419
|
-
|
|
420
|
-
removeSort(sort){
|
|
421
|
-
this.preset.sorts.splice(this.preset.sorts.indexOf(sort), 1)
|
|
422
|
-
this.load()
|
|
423
|
-
},
|
|
424
|
-
|
|
425
|
-
removeFilter(filter){
|
|
426
|
-
this.preset.filters.splice(this.preset.filters.indexOf(filter), 1)
|
|
427
|
-
this.load()
|
|
428
|
-
},
|
|
429
|
-
|
|
430
|
-
reorderColumns(from, to){
|
|
431
|
-
this.preset.params.columns.splice(to, 0, this.preset.params.columns.splice(from, 1)[0])
|
|
432
|
-
},
|
|
433
|
-
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
watch: {
|
|
437
|
-
|
|
438
|
-
preset: {
|
|
439
|
-
deep: true,
|
|
440
|
-
handler(to, from){
|
|
441
|
-
if(from !== null){
|
|
442
|
-
this.savePreset()
|
|
443
|
-
this.load()
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
</script>
|
|
452
|
-
|
|
453
|
-
<style module>
|
|
454
|
-
|
|
455
|
-
.comp{
|
|
456
|
-
@apply flex-1 flex flex-row;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
</style>
|