@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mixd-id/web-scaffold",
3
3
  "private": false,
4
- "version": "0.1.2301231347",
4
+ "version": "0.1.2301231349",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -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`, {}, (res) => {
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
- this.socketEmit('user.save-preset', { key:this.model, preset:{
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 && column.type === 'currency'">
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")))
@@ -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(){
@@ -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
- if(this.user && this.socket && this.socket.auth){
14
- const { userId } = this.socket.auth
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
- //console.log('where#1', util.inspect(where, false, null, true /* enable colors */))
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>