@mixd-id/web-scaffold 0.1.230406289 → 0.1.230406291

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.230406289",
4
+ "version": "0.1.230406291",
5
5
  "scripts": {
6
6
  "dev": "vite serve",
7
7
  "build": "vite build",
@@ -146,7 +146,7 @@ export default{
146
146
  @apply flex items-center justify-center
147
147
  }
148
148
  .button:disabled{
149
- @apply opacity-60;
149
+ @apply text-opacity-50;
150
150
  }
151
151
  .button:active{
152
152
  @apply top-[1px] left-[1px] relative;
@@ -164,7 +164,7 @@ export default{
164
164
  @apply bg-primary-600;
165
165
  }
166
166
  .button-primary:disabled{
167
- @apply bg-primary-500 opacity-50 top-0 left-0 cursor-not-allowed;
167
+ @apply bg-primary-500 top-0 left-0 cursor-not-allowed;
168
168
  @apply top-0 left-0;
169
169
  }
170
170
  .button-primary *{
@@ -188,7 +188,7 @@ export default{
188
188
  .button-outline:hover{
189
189
  }
190
190
  .button-outline:disabled{
191
- @apply opacity-50 top-0 left-0 cursor-not-allowed;
191
+ @apply top-0 left-0 cursor-not-allowed;
192
192
  @apply text-text border-primary-500;
193
193
  }
194
194
  .button-outline *{
@@ -214,7 +214,7 @@ export default{
214
214
  @apply bg-primary-200 border-primary-200;
215
215
  }
216
216
  .button-secondary:disabled{
217
- @apply bg-primary-100 opacity-50 top-0 left-0 cursor-not-allowed;
217
+ @apply bg-primary-100 top-0 left-0 cursor-not-allowed;
218
218
  }
219
219
  .button-secondary *{
220
220
  @apply text-text-500 fill-white;
@@ -238,7 +238,7 @@ export default{
238
238
  @apply bg-red-600 border-red-600;
239
239
  }
240
240
  .button-red:disabled{
241
- @apply bg-red-500 border-red-500 opacity-50 top-0 left-0 cursor-not-allowed;
241
+ @apply bg-red-500 border-red-500 top-0 left-0 cursor-not-allowed;
242
242
  }
243
243
  .button-red *{
244
244
  @apply text-white fill-white;
@@ -288,7 +288,7 @@ export default{
288
288
  }
289
289
 
290
290
  .img{
291
- @apply object-contain w-full h-full;
291
+ @apply w-full h-full;
292
292
  }
293
293
 
294
294
  .editArea{
@@ -120,7 +120,11 @@
120
120
  </div>
121
121
  </slot>
122
122
 
123
- <VirtualTable v-if="presetView === 'table' || pivotEnabled"
123
+ <div v-if="readyState === 3" class="flex-1 flex items-center justify-center">
124
+ <svg class="animate-spin" width="36" height="36" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
125
+ </div>
126
+
127
+ <VirtualTable v-else-if="presetView === 'table' || pivotEnabled"
124
128
  ref="table"
125
129
  :columns="columns"
126
130
  class="flex-1 rounded-lg panel-400"
@@ -244,7 +248,7 @@ import VirtualGrid from "./VirtualGrid.vue";
244
248
  import throttle from "lodash/throttle";
245
249
  import PresetSelector from "../widgets/PresetSelector.vue";
246
250
  import groupBy from "lodash/groupBy";
247
- import {sortsFn} from "../utils/preset-selector.mjs";
251
+ import { generatePivotColumns, sortsFn } from "../utils/preset-selector.mjs";
248
252
  import PresetBar from "../widgets/PresetBar.vue";
249
253
 
250
254
  export default{
@@ -304,114 +308,20 @@ export default{
304
308
  .join(' ')
305
309
  },
306
310
 
307
- generatePivotColumns(items){
308
- if(!this.preset.pivot || !this.preset.pivot.enabled) return
309
- if(!Array.isArray(items) && items.length < 1) return
310
-
311
- const tableColumns = (this.preset.columns ?? this.config.columns)
312
- .reduce((cur, obj) => {
313
- cur[obj.key] = obj
314
- return cur
315
- }, {})
316
-
317
- const currentPivotColumns = [ ...(this.preset.pivot.columns ?? []) ]
318
- .reduce((cur, obj) => {
319
- cur[obj.key] = obj
320
- return cur
321
- }, {})
322
-
323
- const pivotColumns = []
324
-
325
- for(let row of this.preset.pivot.rows){
326
-
327
- let format
328
- switch(row.aggregrate){
329
- case 'year':
330
- format = 'YYYY'
331
- break
332
-
333
- case 'month':
334
- format = 'MMM YYYY'
335
- break
336
-
337
- case 'date':
338
- format = 'D MMM YYYY'
339
- break
340
- }
341
-
342
- if(currentPivotColumns[row.key]){
343
- Object.assign(currentPivotColumns[row.key], {
344
- format,
345
- visible: true
346
- })
347
-
348
- pivotColumns.push(currentPivotColumns[row.key])
349
- }
350
- else{
351
- pivotColumns.push({
352
- ...tableColumns[row.key],
353
- format,
354
- visible: true
355
- })
356
- }
357
- }
358
-
359
- for(let key in items[0]){
360
- if(key.startsWith('_')){
361
- let [ k1, ...k2 ] = key.substring(1).split('-')
362
- k2 = k2.join('-')
363
-
364
- let label = k2.replace(/_/gi, ' ')
365
- const column = tableColumns[k1]
366
- if(column){
367
- switch(column.type){
368
- case 'enum':
369
- label = (column.typeParams ?? []).find(_ => `${_.value}` === `${k2}`)?.text ?? label
370
- break
371
- }
372
- }
373
-
374
- if(currentPivotColumns[key]){
375
- Object.assign(currentPivotColumns[key], {
376
- visible: true
377
- })
378
-
379
- pivotColumns.push(currentPivotColumns[key])
380
- }
381
- else{
382
- pivotColumns.push({
383
- key,
384
- label,
385
- label2: (((tableColumns[k1] ?? {}).label ?? '') + ': ' + label).trim(),
386
- type: 'number',
387
- })
388
- }
389
- }
390
- }
391
-
392
- (this.preset.pivot.columns ?? []).filter(_ => !pivotColumns.find(i => i.key === _.key))
393
- .forEach(_ => {
394
- _.visible = false
395
- pivotColumns.push(_)
396
- })
397
-
398
- this.preset.pivot.columns = pivotColumns
399
-
400
- },
401
-
402
311
  load(){
403
312
  if(this.src){
404
313
  if(!this.preset.columns){
405
314
  this.preset.columns = JSON.parse(JSON.stringify(this.config.columns))
406
315
  }
407
316
 
317
+ this.readyState = 3
408
318
  return this.socket.send(this.src, {
409
319
  ...this.preset,
410
320
  itemsPerPage: this.data.itemsPerPage,
411
321
  })
412
322
  .then(data => {
413
323
 
414
- this.generatePivotColumns(data.items)
324
+ generatePivotColumns(this.preset, data.items)
415
325
  this.loadEnums(data.items)
416
326
  Object.assign(this.data, data ?? {})
417
327
  this.$emit('after-load')
@@ -420,6 +330,7 @@ export default{
420
330
  .catch(err => {
421
331
  this.toast(err)
422
332
  })
333
+ .finally(_ => this.readyState = 1)
423
334
  }
424
335
  return new Promise(resolve => resolve())
425
336
  },
@@ -529,7 +440,7 @@ export default{
529
440
  },
530
441
 
531
442
  loadExt(){
532
- if(!this.preset.filters || !this.extBar.open) return
443
+ if(!this.extBar.open) return
533
444
 
534
445
  this.readyState = 3
535
446
  return this.socket.send(this.src, {
@@ -620,7 +531,6 @@ export default{
620
531
 
621
532
  onSignal(event, items){
622
533
  if(this.pivotEnabled){
623
- console.log('no background update due ti pivot')
624
534
  return
625
535
  }
626
536
 
@@ -654,11 +564,22 @@ export default{
654
564
  const filters = []
655
565
 
656
566
  for(let idx in this.preset.pivot.rows){
567
+ const key = this.preset.pivot.rows[idx].key
568
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
569
+ let value = item[this.preset.pivot.rows[idx].key]
570
+ switch(presetColumn.type){
571
+
572
+ case 'bool':
573
+ case 'boolean':
574
+ value = value === 'true'
575
+ break
576
+ }
577
+
657
578
  filters.push({
658
- key: this.preset.pivot.rows[idx].key,
579
+ key,
659
580
  value: [{
660
581
  operator: '=',
661
- value: item[this.preset.pivot.rows[idx].key]
582
+ value
662
583
  }]
663
584
  })
664
585
  }
@@ -666,12 +587,24 @@ export default{
666
587
  let [ kk, ...vv ] = column.key.split('-')
667
588
  vv = vv.join('-')
668
589
 
669
- if(![ 'count' ].includes(vv)){
590
+ if(![ 'count', 'countDistinct' ].includes(vv)){
591
+
592
+ const key = kk.substring(1)
593
+ const presetColumn = this.preset.columns.find(_ => _.key === key)
594
+ let value = vv
595
+ switch(presetColumn.type){
596
+
597
+ case 'bool':
598
+ case 'boolean':
599
+ value = value === 'true'
600
+ break
601
+ }
602
+
670
603
  filters.push({
671
- key: kk.substring(1),
604
+ key,
672
605
  value: [{
673
606
  operator: '=',
674
- value: vv
607
+ value
675
608
  }]
676
609
  })
677
610
  }
@@ -700,7 +633,7 @@ export default{
700
633
  switch(e.code){
701
634
 
702
635
  case 'Backquote':
703
- this.$refs.contextMenu.open(this.$refs.title)
636
+ this.togglePreset()
704
637
  break
705
638
 
706
639
  default:
@@ -923,7 +856,6 @@ export default{
923
856
 
924
857
  .resize2{
925
858
  @apply h-[5px] hover:bg-primary bg-text-50 cursor-n-resize absolute top-0 left-0 right-0 flex items-center justify-center;
926
- @apply z-20;
927
859
  }
928
860
 
929
861
  .extClose{
@@ -4,10 +4,11 @@
4
4
  <table :class="$style.table" ref="tableHead" :style="tableHeadStyle">
5
5
  <thead>
6
6
  <tr>
7
- <th v-for="column in visibleColumns" :style="thStyle(column)">
7
+ <th v-for="column in visibleColumns" :style="thStyle(column)"
8
+ v-tooltip="`${column.label ? column.label : column.key}`">
8
9
  <slot v-if="$slots['col-' + column.key]" :name="'col-' + column.key" :column="column"></slot>
9
10
  <div v-else :class="headerColumnClass(column)">
10
- <span>{{ column.label ?? column.key }}</span>
11
+ <span>{{ column.label2 ? column.label2 : (column.label ?? column.key) }}</span>
11
12
  </div>
12
13
  <div :class="$style.separator" @mousedown="startResize($event, column)"></div>
13
14
  </th>
@@ -39,7 +40,7 @@
39
40
  @click="$emit('item-click', item, column)">
40
41
  <slot v-if="$slots[column.key]" :name="column.key" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
41
42
  <slot v-else-if="$slots.default" name="default" :column="column" :item="item" :index="visibleStartIndex + index"></slot>
42
- <div v-else :class="columnClass(column)" v-html="formatColumn(item, column)"></div>
43
+ <div v-else :class="columnClass(column, item)" v-html="formatColumn(item, column)"></div>
43
44
  </td>
44
45
  <td :class="$style.spacer"></td>
45
46
  </tr>
@@ -201,7 +202,6 @@ export default{
201
202
  this.resize()
202
203
 
203
204
  const observer = new MutationObserver((mutationsList) => {
204
- console.log(mutationsList)
205
205
  this.resize()
206
206
  });
207
207
  observer.observe(this.$el, { attributes: true });
@@ -254,7 +254,7 @@ export default{
254
254
  this.itemHeight = parseInt(window.getComputedStyle(this.$refs.calc).height)
255
255
  this.maxVisibleItems = elHeight > 0 ? Math.ceil(elHeight / this.itemHeight) + 1 : this.items.length
256
256
 
257
- if(this.itemHeight <= 0){
257
+ if(this.itemHeight <= 0 && import.meta.env.DEV){
258
258
  console.error('[VirtualTable] Unable to calculate item height, make sure not async component.')
259
259
  }
260
260
  }
@@ -323,7 +323,7 @@ export default{
323
323
  window.addEventListener('mouseup', onMouseUp)
324
324
  },
325
325
 
326
- columnClass(column){
326
+ columnClass(column, item){
327
327
 
328
328
  let align
329
329
  if(!column.align){
@@ -334,9 +334,111 @@ export default{
334
334
  }
335
335
  }
336
336
 
337
+ let appearanceClass = ''
338
+ if(item && Array.isArray(column.appearances)){
339
+
340
+ let value
341
+ switch(column.type){
342
+
343
+ case 'number':
344
+ value = parseInt(item[column.key])
345
+ break
346
+
347
+ default:
348
+ value = (item[column.key] ?? '').toString()
349
+ break
350
+ }
351
+
352
+ for(let appearance of column.appearances){
353
+
354
+ let conditionMatched = true
355
+ for(let condition of appearance.conditions ?? []){
356
+ switch(condition.operator){
357
+
358
+ case '>':
359
+ if(value <= condition.value){
360
+ conditionMatched = false
361
+ }
362
+ break
363
+
364
+ case '>=':
365
+ if(value < condition.value){
366
+ conditionMatched = false
367
+ }
368
+ break
369
+
370
+ case '=':
371
+ if(value !== condition.value){
372
+ conditionMatched = false
373
+ }
374
+ break
375
+
376
+ case '<':
377
+ if(value >= condition.value){
378
+ conditionMatched = false
379
+ }
380
+ break
381
+
382
+ case '<=':
383
+ if(value > condition.value){
384
+ conditionMatched = false
385
+ }
386
+ break
387
+
388
+ case 'startsWith':
389
+ if(!value.startsWith(condition.value)){
390
+ conditionMatched = false
391
+ }
392
+ break
393
+
394
+ case 'notStartsWith':
395
+ if(value.startsWith(condition.value)){
396
+ conditionMatched = false
397
+ }
398
+ break
399
+
400
+ case 'contains':
401
+ if(value.indexOf(condition.value) < 0){
402
+ conditionMatched = false
403
+ }
404
+ break
405
+
406
+ case 'notContains':
407
+ if(value.indexOf(condition.value) >= 0){
408
+ conditionMatched = false
409
+ }
410
+ break
411
+
412
+ case 'endsWith':
413
+ if(!value.endsWith(condition.value)){
414
+ conditionMatched = false
415
+ }
416
+ break
417
+
418
+ case 'notEndsWith':
419
+ if(value.endsWith(condition.value)){
420
+ conditionMatched = false
421
+ }
422
+ break
423
+
424
+ case 'regex':
425
+ break
426
+
427
+ }
428
+
429
+ if(!conditionMatched) break
430
+ }
431
+
432
+ if(conditionMatched){
433
+ appearanceClass = appearance.class
434
+ }
435
+ }
436
+ }
437
+
337
438
  return [
338
439
  this.$style.tdDiv,
339
- this.$style['align-' + align]
440
+ this.$style['align-' + align],
441
+ appearanceClass
340
442
  ]
341
443
  .join(' ')
342
444
  },
package/src/index.js CHANGED
@@ -332,7 +332,7 @@ export default{
332
332
  tooltip.classList.add('active')
333
333
  }
334
334
  }
335
- }, 1300)
335
+ }, 1000)
336
336
  el.setAttribute('data-tooltip-open', true)
337
337
  })
338
338
 
@@ -565,6 +565,7 @@ export default{
565
565
  app.component('SvgSetting', defineAsyncComponent(() => import("./widgets/SvgSetting.vue")))
566
566
  app.component('PresetBar', defineAsyncComponent(() => import("./widgets/PresetBar.vue")))
567
567
  app.component('PresetSelector', defineAsyncComponent(() => import("./widgets/PresetSelector.vue")))
568
+ app.component('PresetBarPivotColumnEdit', defineAsyncComponent(() => import("./widgets/PresetBarPivotColumnEdit.vue")))
568
569
  app.component('LogViewer', defineAsyncComponent(() => import("./widgets/LogViewer.vue")))
569
570
  app.component('ColumnSelector', defineAsyncComponent(() => import("./widgets/ColumnSelector.vue")))
570
571
  },
@@ -61,7 +61,10 @@ export const componentMixin = {
61
61
  itemRatio: Array,
62
62
 
63
63
  containerClass: String,
64
- itemClass: String,
64
+ itemClass: {
65
+ type: String,
66
+ default: 'bg-contain'
67
+ },
65
68
 
66
69
  htmlText: String,
67
70
 
@@ -4,7 +4,7 @@ const {inspect} = require("util");
4
4
  const {ftWildcard} = require("./helpers");
5
5
  const util = require("util");
6
6
  const {snakeCase} = require("lodash");
7
- const {Op, literal, fn} = module.parent.require("sequelize");
7
+ const {Op, literal, fn, DataTypes} = module.parent.require("sequelize");
8
8
 
9
9
  const getValue = (filter, opt) => {
10
10
 
@@ -737,28 +737,34 @@ const pivotToSequelizeWhere = async (pivot, opt) => {
737
737
 
738
738
  const sortKeyToOrders = {} // Helper to determine group ordering
739
739
  for(let value of pivot.values){
740
+ const valueKey = model.rawAttributes[value.key].field
740
741
 
741
742
  switch(value.aggregrate){
742
743
 
743
744
  case 'sum':
744
- attributes.push([ fn('SUM', literal(`${model.name}.${value.key}`)), `_${value.key}-${value.aggregrate}` ])
745
+ attributes.push([ fn('SUM', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
745
746
  break
746
747
 
747
748
  case 'avg':
748
- attributes.push([ fn('AVG', literal(`${model.name}.${value.key}`)), `_${value.key}-${value.aggregrate}` ])
749
+ attributes.push([ fn('AVG', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
749
750
  break
750
751
 
751
752
  case 'min':
752
- attributes.push([ fn('MIN', literal(`${model.name}.${value.key}`)), `_${value.key}-${value.aggregrate}` ])
753
+ attributes.push([ fn('MIN', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
753
754
  break
754
755
 
755
756
  case 'max':
756
- attributes.push([ fn('MAX', literal(`${model.name}.${value.key}`)), `_${value.key}-${value.aggregrate}` ])
757
+ attributes.push([ fn('MAX', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
758
+ break
759
+
760
+ case 'countDistinct':
761
+ attributes.push([ literal(`COUNT(DISTINCT ${model.name}.${valueKey})`), `_${value.key}-${value.aggregrate}` ])
762
+ sortKeyToOrders[`_${value.key}-${value.aggregrate}`] = literal(`COUNT(DISTINCT ${model.name}.${valueKey})`)
757
763
  break
758
764
 
759
765
  case 'count':
760
- attributes.push([ fn('COUNT', literal(`${model.name}.${value.key}`)), `_${value.key}-${value.aggregrate}` ])
761
- sortKeyToOrders[`_${value.key}-${value.aggregrate}`] = fn('COUNT', literal(`${model.name}.${value.key}`))
766
+ attributes.push([ fn('COUNT', literal(`${model.name}.${valueKey}`)), `_${value.key}-${value.aggregrate}` ])
767
+ sortKeyToOrders[`_${value.key}-${value.aggregrate}`] = fn('COUNT', literal(`${model.name}.${valueKey}`))
762
768
  break
763
769
 
764
770
  default:
@@ -780,7 +786,10 @@ const pivotToSequelizeWhere = async (pivot, opt) => {
780
786
 
781
787
  const field = model.rawAttributes[value.key].field
782
788
 
789
+
783
790
  groups.forEach(group => {
791
+
792
+ let groupValue
784
793
  if(group[value.key] === null){
785
794
  attributes.push([ literal(`SUM(CASE WHEN ${model.name}.${field} is null THEN 1 ELSE 0 END)`), `_${value.key}-(null)` ])
786
795
  }
@@ -788,8 +797,14 @@ const pivotToSequelizeWhere = async (pivot, opt) => {
788
797
  attributes.push([ literal(`SUM(CASE WHEN ${model.name}.${field} = '' THEN 1 ELSE 0 END)`), `_${value.key}-(empty)` ])
789
798
  }
790
799
  else{
791
- const groupValue = (group[value.key] ?? '').toString().replace(/\s/g, '_')
792
- attributes.push([ literal(`SUM(CASE WHEN ${model.name}.${field} = '${group[value.key]}' THEN 1 ELSE 0 END)`), `_${value.key}-${groupValue}` ])
800
+ if(model.rawAttributes[value.key].type instanceof DataTypes.BOOLEAN){
801
+ groupValue = group[value.key]
802
+ attributes.push([ literal(`SUM(CASE WHEN ${model.name}.${field} = ${groupValue === true ? 1 : 0} THEN 1 ELSE 0 END)`), `_${value.key}-${groupValue}` ])
803
+ }
804
+ else{
805
+ groupValue = (group[value.key] ?? '').toString().replace(/\s/g, '_')
806
+ attributes.push([ literal(`SUM(CASE WHEN ${model.name}.${field} = '${group[value.key]}' THEN 1 ELSE 0 END)`), `_${value.key}-${groupValue}` ])
807
+ }
793
808
  }
794
809
  })
795
810
 
@@ -55,8 +55,111 @@ const setupConfig = (config) => {
55
55
  return config
56
56
  }
57
57
 
58
+ const generatePivotColumns = (preset, items) => {
59
+ if(!preset.pivot || !preset.pivot.enabled) return
60
+ if(!Array.isArray(items) && items.length < 1) return
61
+
62
+ if(preset.pivot.manualColumns === true){
63
+ return
64
+ }
65
+
66
+ const tableColumns = (preset.columns ?? [])
67
+ .reduce((cur, obj) => {
68
+ cur[obj.key] = obj
69
+ return cur
70
+ }, {})
71
+
72
+ const currentPivotColumns = [ ...(preset.pivot.columns ?? []) ]
73
+ .reduce((cur, obj) => {
74
+ cur[obj.key] = obj
75
+ return cur
76
+ }, {})
77
+
78
+ const pivotColumns = []
79
+
80
+ for(let row of preset.pivot.rows){
81
+
82
+ let format
83
+ switch(row.aggregrate){
84
+ case 'year':
85
+ format = 'YYYY'
86
+ break
87
+
88
+ case 'month':
89
+ format = 'MMM YYYY'
90
+ break
91
+
92
+ case 'date':
93
+ format = 'D MMM YYYY'
94
+ break
95
+ }
96
+
97
+ if(currentPivotColumns[row.key]){
98
+ Object.assign(currentPivotColumns[row.key], {
99
+ format,
100
+ visible: true
101
+ })
102
+
103
+ pivotColumns.push(currentPivotColumns[row.key])
104
+ }
105
+ else{
106
+ pivotColumns.push({
107
+ ...tableColumns[row.key],
108
+ format,
109
+ visible: true
110
+ })
111
+ }
112
+ }
113
+
114
+ for(let key in items[0]){
115
+ if(key.startsWith('_')){
116
+ let [ k1, ...k2 ] = key.substring(1).split('-')
117
+ k2 = k2.join('-')
118
+
119
+ let label = k2.replace(/_/gi, ' ')
120
+ const column = tableColumns[k1]
121
+ if(column){
122
+ switch(column.type){
123
+ case 'enum':
124
+ label = (column.typeParams ?? []).find(_ => `${_.value}` === `${k2}`)?.text ?? label
125
+ break
126
+ }
127
+ }
128
+
129
+ if(currentPivotColumns[key]){
130
+ Object.assign(currentPivotColumns[key], {
131
+ visible: true
132
+ })
133
+
134
+ pivotColumns.push(currentPivotColumns[key])
135
+ }
136
+ else{
137
+ pivotColumns.push({
138
+ key,
139
+ label,
140
+ label2: (((tableColumns[k1] ?? {}).label ?? '') + ': ' + label).trim(),
141
+ type: 'number',
142
+ appearances: [
143
+ { class:"text-primary cursor-pointer", conditions:[ { operator:'none' } ] }
144
+ ]
145
+ })
146
+ }
147
+ }
148
+ }
149
+
150
+ (preset.pivot.columns ?? []).filter(_ => !pivotColumns.find(i => i.key === _.key))
151
+ .forEach(_ => {
152
+ _.visible = false
153
+ pivotColumns.push(_)
154
+ })
155
+
156
+ preset.pivot.columns = pivotColumns
157
+
158
+ }
159
+
58
160
  export {
59
161
  sortsFn,
60
162
  pickColumns,
61
- setupConfig
163
+ setupConfig,
164
+ generatePivotColumns
62
165
  }
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
 
16
16
  <div class="p-3">
17
- <div class="p-2">
17
+ <div class="px-2">
18
18
  <label class="text-text-300">Presets</label>
19
19
  </div>
20
20
  <div class="flex flex-col divide-y divide-text-50 bg-base-500 border-[1px] border-text-50 overflow-hidden rounded-lg">
@@ -95,7 +95,8 @@
95
95
  body-class="divide-y divide-text-50 rounded-lg border-[1px] border-text-50 bg-base-300"
96
96
  @reorder="(from, to) => { presetColumns.splice(to, 0, presetColumns.splice(from, 1)[0]); }">
97
97
  <template v-slot="{ item }">
98
- <div v-if="!item.key.startsWith('_') || (item.key.startsWith('_') && (preset.pivot && preset.pivot.enabled))" class="flex flex-row items-center gap-3 px-3 p-2 hover:bg-primary-100">
98
+ <div v-if="!item.key.startsWith('_') || (item.key.startsWith('_') && (preset.pivot && preset.pivot.enabled))"
99
+ class="flex flex-row items-center gap-3 px-3 p-1 hover:bg-primary-100">
99
100
  <div data-reorder>
100
101
  <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
101
102
  </div>
@@ -103,7 +104,9 @@
103
104
  <div>
104
105
  <Checkbox v-model="item.visible" default="true" />
105
106
  </div>
106
- {{ item.label2 ? item.label2 : (item.label ? item.label : item.key) }}
107
+ <Textbox v-model="item.label2" :placeholder="item.label"
108
+ class="border-none bg-transparent" :class="$style.columnTextbox"
109
+ item-class="p-1 px-0" />
107
110
  </div>
108
111
  </div>
109
112
  </template>
@@ -509,7 +512,8 @@
509
512
  </div>
510
513
  <ListItem :items="presetPivot.values"
511
514
  @reorder="(from, to) => { presetPivot.values.splice(to, 0, presetPivot.values.splice(from, 1)[0]); }"
512
- class="mt-2 h-[25vh] overflow-y-auto border-[1px] border-text-200 bg-base-400 rounded-lg p-0.5">
515
+ class="mt-2 h-[25vh] overflow-y-auto border-[1px] border-text-200 bg-base-400 rounded-lg p-0.5"
516
+ container-class="divide-y divide-text-50">
513
517
  <template v-slot="{ item, index }">
514
518
  <div class="flex flex-row items-center gap-3 p-2 bg-base-500 hover:bg-primary-100 first:rounded-t-md last:rounded-b-md">
515
519
  <div data-reorder v-if="(presetPivot.values ?? []).length > 1">
@@ -521,10 +525,11 @@
521
525
  {{ item.label ? item.label : item.key }}
522
526
  </strong>
523
527
  <select v-model="item.aggregrate"
524
- class="px-1 appearance-none bg-base-300 rounded-md border-[1px] border-text-50 outline-none"
528
+ class="px-1 appearance-none bg-base-300 w-[80px] rounded-md border-[1px] border-text-50 outline-none"
525
529
  @change="apply()">
526
530
  <option value="">Default</option>
527
531
  <option value="count">Count</option>
532
+ <option value="countDistinct">Distinct Count</option>
528
533
  <option v-if="[ 'number' ].includes(item.type)" value="sum">Sum</option>
529
534
  <option v-if="[ 'number' ].includes(item.type)" value="avg">Average</option>
530
535
  <option v-if="[ 'number' ].includes(item.type)" value="min">Min</option>
@@ -539,6 +544,40 @@
539
544
  </ListItem>
540
545
  </div>
541
546
 
547
+ <div>
548
+ <div class="flex flex-row items-center gap-2">
549
+ <label class="flex-1">Columns</label>
550
+ <button v-if="presetPivot.manualColumns"
551
+ type="button"
552
+ class="text-primary"
553
+ @click="delete presetPivot.manualColumns; apply()">Reset</button>
554
+ </div>
555
+ <ListItem :items="presetPivot.columns"
556
+ @reorder="(from, to) => { presetPivot.columns.splice(to, 0, presetPivot.columns.splice(from, 1)[0]); presetPivot.manualColumns = true; apply() }"
557
+ class="mt-2 h-[25vh] overflow-y-auto border-[1px] border-text-200 bg-base-400 rounded-lg p-0.5"
558
+ container-class="divide-y divide-text-50">
559
+ <template v-slot="{ item, index }">
560
+ <div class="flex flex-row gap-2 items-center px-2 bg-base-500">
561
+ <div data-reorder>
562
+ <svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M496 288H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16zm0-128H16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-32c0-8.8-7.2-16-16-16z"/></svg>
563
+ </div>
564
+ <Textbox class="flex-1 border-none" :class="$style.columnTextbox" v-model="item.label2" :placeholder="item.label"
565
+ item-class="p-1"/>
566
+ <button type="button" v-if="[ 'number' ].includes(item.type)" @click="">
567
+ <svg width="13" height="13" :class="(item.appearances ?? []).length > 0 ? 'fill-primary' : 'fill-text-300 hover:fill-primary'"
568
+ @click="$refs.presetBarPivotColumnEdit.open(item, next => { Object.assign(item, next); apply() })"
569
+ xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M176 0h-128C21.49 0 0 21.49 0 48v128C0 202.5 21.49 224 48 224h128C202.5 224 224 202.5 224 176v-128C224 21.49 202.5 0 176 0zM176 288h-128C21.49 288 0 309.5 0 336v128C0 490.5 21.49 512 48 512h128C202.5 512 224 490.5 224 464v-128C224 309.5 202.5 288 176 288zM464 0h-128C309.5 0 288 21.49 288 48v128C288 202.5 309.5 224 336 224h128C490.5 224 512 202.5 512 176v-128C512 21.49 490.5 0 464 0zM488 376h-64v-64C424 298.8 413.3 288 400 288s-24 10.75-24 24v64h-64c-13.25 0-24 10.75-24 24s10.75 24 24 24h64v64c0 13.25 10.75 24 24 24s24-10.75 24-24v-64h64c13.25 0 24-10.75 24-24S501.3 376 488 376z"/></svg>
570
+ </button>
571
+ <button type="button" @click="presetPivot.columns.splice(index, 1); presetPivot.manualColumns = true; apply()">
572
+ <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
573
+ </button>
574
+ </div>
575
+ </template>
576
+ </ListItem>
577
+
578
+ <PresetBarPivotColumnEdit ref="presetBarPivotColumnEdit" />
579
+ </div>
580
+
542
581
  </div>
543
582
 
544
583
  </div>
@@ -817,4 +856,8 @@ export default{
817
856
  @apply flex flex-col relative;
818
857
  }
819
858
 
859
+ .columnTextbox input::placeholder{
860
+ @apply text-text;
861
+ }
862
+
820
863
  </style>
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <Modal ref="modal" :hash="hash" :state="state" width="480" height="480">
3
+ <template v-slot:head>
4
+ <div class="relative p-6">
5
+ <h3>{{ obj.label }}</h3>
6
+ <div class="absolute top-0 right-0 p-2">
7
+ <button type="button" class="p-2" @click="close">
8
+ <svg width="24" height="24" viewBox="0 0 24 24" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg">
9
+ <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"/>
10
+ </svg>
11
+ </button>
12
+ </div>
13
+ </div>
14
+ </template>
15
+ <template v-slot:foot>
16
+ <div class="p-6">
17
+ <Button class="w-[100px]" @click="apply">Apply</Button>
18
+ </div>
19
+ </template>
20
+ <div class="flex-1 p-6 flex flex-col gap-4">
21
+
22
+ <div class="flex flex-col gap-1">
23
+ <div class="flex flex-row gap-2">
24
+ <label class="text-text-400">Appearances</label>
25
+ <button type="button" class="text-primary" @click="appearances.push({ conditions:[{}], class:'' })">
26
+ <svg width="16" height="16" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M432 256c0 17.69-14.33 32.01-32 32.01H256v144c0 17.69-14.33 31.99-32 31.99s-32-14.3-32-31.99v-144H48c-17.67 0-32-14.32-32-32.01s14.33-31.99 32-31.99H192v-144c0-17.69 14.33-32.01 32-32.01s32 14.32 32 32.01v144h144C417.7 224 432 238.3 432 256z"/></svg>
27
+ </button>
28
+ </div>
29
+ <ListItem :items="appearances"
30
+ class="bg-transparent"
31
+ @reorder="(from, to) => { appearances.splice(to, 0, appearances.splice(from, 1)[0]); }"
32
+ container-class="flex flex-col gap-1">
33
+ <template v-slot="{ item, index }">
34
+ <div class="flex flex-row items-center gap-3">
35
+ <div class="flex-1 flex flex-col gap-1">
36
+ <div v-for="condition in item.conditions" class="flex flex-row gap-1">
37
+ <Dropdown v-model="condition.operator" :class="condition.operator === 'none' ? 'w-[200px]' : 'w-[100px]'">
38
+ <option v-for="op in operators" :value="op.value">{{ op.text }}</option>
39
+ <option value="none">No Condition</option>
40
+ </Dropdown>
41
+ <Textbox v-if="condition.operator !== 'none'" v-model.numberconditions="condition.value" class="w-[100px]"/>
42
+ </div>
43
+ </div>
44
+ <div :class="item.class" class="px-2">
45
+ Sample
46
+ </div>
47
+ <div>
48
+ <Dropdown class="w-[120px]" v-model="item.class">
49
+ <option value="text-primary cursor-pointer">Default</option>
50
+ <option value="bg-red-100 text-red-600">Red 1</option>
51
+ <option value="bg-red-200 text-red-600">Red 2</option>
52
+ <option value="bg-red-300 text-red-600">Red 3</option>
53
+ <option value="bg-red-400 text-white">Red 4</option>
54
+ <option value="bg-red-500 text-white">Red 5</option>
55
+ <option value="bg-green-100 text-green-700">Green 1</option>
56
+ <option value="bg-green-200 text-green-700">Green 2</option>
57
+ <option value="bg-green-300 text-green-700">Green 3</option>
58
+ <option value="bg-green-400 text-white">Green 4</option>
59
+ <option value="bg-green-500 border-[1px] border-green-600 text-white">Green 5</option>
60
+ <option value="text-primary underline cursor-pointer">Underline</option>
61
+ </Dropdown>
62
+ </div>
63
+ <div>
64
+ <button type="button" @click="appearances.splice(index, 1)">
65
+ <svg width="16" height="16" class="fill-text-300 hover:fill-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </template>
70
+ </ListItem>
71
+ </div>
72
+ </div>
73
+ </Modal>
74
+ </template>
75
+
76
+ <script>
77
+
78
+ export default{
79
+
80
+ emits: [ 'apply' ],
81
+
82
+ inject: [ 'socketEmit2' ],
83
+
84
+ props: {
85
+
86
+ hash: String,
87
+ state: Boolean
88
+
89
+ },
90
+
91
+ computed: {
92
+
93
+ appearances(){
94
+ if(!Array.isArray(this.obj.appearances)){
95
+ this.obj.appearances = []
96
+ }
97
+ return this.obj.appearances
98
+ },
99
+
100
+ operators(){
101
+
102
+ let operators = []
103
+
104
+ switch(this.obj?.type){
105
+
106
+ case 'number':
107
+ operators = [
108
+ { value:">", text:">" },
109
+ { value:">=", text:">=" },
110
+ { value:"=", text:"=" },
111
+ { value:"<=", text:"<=" },
112
+ { value:"<", text:"<" }
113
+ ]
114
+ break
115
+
116
+ default:
117
+ operators = [
118
+ { value:"contains", text:"Contains" },
119
+ { value:"startsWith", text:"Starts With" },
120
+ { value:"endsWith", text:"Ends With" },
121
+ { value:"notContains", text:"Not Contains" },
122
+ { value:"notStartsWith", text:"Not Starts With" },
123
+ { value:"notEndsWith", text:"Not StartEnds With" },
124
+ { value:"regex", text:"Match Pattern" }
125
+ ]
126
+ break
127
+ }
128
+
129
+ return operators
130
+ }
131
+
132
+ },
133
+
134
+ data(){
135
+ return {
136
+ obj: null,
137
+ callback: null
138
+ }
139
+ },
140
+
141
+ methods: {
142
+
143
+ apply(){
144
+ if(typeof this.callback === 'function')
145
+ this.callback(this.obj)
146
+ this.$emit('apply', this.obj)
147
+ this.close()
148
+ },
149
+
150
+ open(obj, callback){
151
+ this.obj = JSON.parse(JSON.stringify(obj))
152
+ this.callback = callback
153
+ this.$refs.modal.open()
154
+ },
155
+
156
+ close(){
157
+ this.$refs.modal.close()
158
+ }
159
+
160
+ }
161
+
162
+ }
163
+
164
+ </script>
165
+
166
+ <style module>
167
+
168
+ .comp{
169
+
170
+ }
171
+
172
+ </style>