@mixd-id/web-scaffold 0.1.2301231352 → 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.2301231352",
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",
@@ -56,7 +56,8 @@ export default {
56
56
  return {
57
57
  computedStyle: null,
58
58
  isOpen: false,
59
- context: null
59
+ context: null,
60
+ closing: false
60
61
  }
61
62
  },
62
63
 
@@ -80,16 +81,17 @@ export default {
80
81
  if(typeof window === 'undefined') return
81
82
 
82
83
  const transitionEnd = () => {
83
- this.$emit('open')
84
84
  window.addEventListener('click', this.onDismiss)
85
85
  window.addEventListener('scroll', this.onDismiss)
86
86
  this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
87
+ this.$emit('open')
87
88
  }
88
89
 
90
+ this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
89
91
  this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
90
- window.setTimeout(() => {
92
+ this.$nextTick(() => {
91
93
  this.$refs.contextMenu.classList.add(this.$style.active)
92
- }, 77)
94
+ })
93
95
  },
94
96
 
95
97
  onDismiss(e){
@@ -104,6 +106,14 @@ export default {
104
106
 
105
107
  open(caller, context){
106
108
 
109
+ if(this.isOpen){
110
+ return this.close(() => {
111
+ window.setTimeout(() => {
112
+ this.open(caller, context)
113
+ }, 30)
114
+ })
115
+ }
116
+
107
117
  if(caller){
108
118
  this.isOpen = true
109
119
  }
@@ -181,10 +191,9 @@ export default {
181
191
  else{
182
192
  console.warn('Invalid context menu caller', this.caller)
183
193
  }
184
-
185
194
  },
186
195
 
187
- close(){
196
+ close(fn){
188
197
  const transitionEnd = () => {
189
198
  window.removeEventListener('click', this.onDismiss)
190
199
  window.removeEventListener('scroll', this.onDismiss)
@@ -194,7 +203,9 @@ export default {
194
203
  this.computedStyle = null
195
204
  this.isOpen = false
196
205
  this.$emit('dismiss')
206
+ if(fn) fn()
197
207
  }
208
+ this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
198
209
  this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
199
210
  this.$refs.contextMenu.classList.remove(this.$style.active)
200
211
  }
@@ -43,16 +43,14 @@
43
43
  </div>
44
44
  </div>
45
45
 
46
- <VirtualScroll v-if="isMobile.value" :items="items" class="flex-1" @scroll-end="loadNext">
47
- <template #item="{ item, index }">
48
- <slot v-if="$slots.mobile" name="mobile" :item="item" :index="index"></slot>
49
- <div v-else>
50
- <div class="h-[2rem] bg-base-300 rounded-lg my-2"></div>
51
- </div>
52
- </template>
53
- </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"/>
54
50
 
55
- <VirtualTable v-else class="flex-1" :columns="presetColumns" :items="items"
51
+ </div>
52
+
53
+ <VirtualTable v-if="!hideDetails" class="flex-1" :columns="presetColumns" :items="items"
56
54
  :appearances="appearances" @scroll-end="loadNext" :pinned="pinned">
57
55
  <template v-for="column in presetColumns" #[colOf(column.key)]="{}">
58
56
  <div :class="getHeader(column)" @click="openColumnOptions(column.key, $event.target.closest('.' + $style.header))">
@@ -163,7 +161,7 @@
163
161
  </div>
164
162
 
165
163
  <div class="flex justify-center">
166
- <Tabs v-model="layout.presetTab" :items="[{text:'Columns',value:'column'},{text:'Filters',value:'filter'},{text:'Sorts',value:'sort'}]" />
164
+ <Tabs v-model="layout.presetTab" :items="tabItems" />
167
165
  </div>
168
166
  </div>
169
167
 
@@ -187,7 +185,7 @@
187
185
  </ListItem>
188
186
  </div>
189
187
 
190
- <div class="flex flex-col" v-if="layout.presetTab === 'filter'">
188
+ <div class="flex flex-col" v-else-if="layout.presetTab === 'filter'">
191
189
  <div v-if="filterableColumns.length > 0">
192
190
  <ListPage1Filter v-if="preset.filters" v-for="filter in preset.filters"
193
191
  :filter="filter" :column="columns[filter.key]"
@@ -206,7 +204,7 @@
206
204
  </div>
207
205
  </div>
208
206
 
209
- <div class="flex flex-col" v-if="layout.presetTab === 'sort'">
207
+ <div class="flex flex-col" v-else-if="layout.presetTab === 'sort'">
210
208
  <div v-if="sortableColumns.length > 0" class="my-8">
211
209
  <div v-for="sort in preset.sorts" class="py-4">
212
210
  <div class="flex flex-row items-center gap-2">
@@ -244,6 +242,72 @@
244
242
  </div>
245
243
  </div>
246
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
+
247
311
  </div>
248
312
 
249
313
  <div class="px-6 py-3 flex flex-row gap-2"></div>
@@ -261,9 +325,15 @@
261
325
 
262
326
  import throttle from "lodash/throttle";
263
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)
264
332
 
265
333
  export default{
266
334
 
335
+ components: { Bar },
336
+
267
337
  inject: [ 'socket', 'confirm', 'alert', 'socketEmit', 'toast' ],
268
338
 
269
339
  props:{
@@ -345,12 +415,90 @@ export default{
345
415
  currentFilters(){
346
416
  if(!this.preset.filters) return []
347
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;
348
492
  }
349
493
 
350
494
  },
351
495
 
352
496
  data(){
353
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
+ },
354
502
  layout: {
355
503
  presetOpen: false,
356
504
  presetTab: 'column',
@@ -358,12 +506,19 @@ export default{
358
506
  presetFilterSelector: null,
359
507
  presetSortSelector: null,
360
508
  },
509
+ tabItems: [
510
+ { text:'Columns', value:'column' },
511
+ { text:'Filters', value:'filter' },
512
+ { text:'Sorts', value:'sort' },
513
+ { text:'Summary', value:'summary' },
514
+ ],
361
515
  name: null,
362
516
  columns: [],
363
517
  selectedColumn: null,
364
518
  presets: [],
365
519
  presetIdx: -1,
366
520
  items: [],
521
+ summary: null,
367
522
  count: null,
368
523
  hasNext: false,
369
524
  modal: null
@@ -399,6 +554,7 @@ export default{
399
554
  this.patchPresets()
400
555
  this.$nextTick(() => {
401
556
  this.items = data.items
557
+ this.summary = data.summary
402
558
  this.hasNext = data.hasNext
403
559
  this.count = data.count
404
560
  })
@@ -452,8 +608,10 @@ export default{
452
608
  preset: this.preset
453
609
  }, (res) => {
454
610
  const data = res.data ? res.data : res
455
- //console.log('load', data)
456
- Object.assign(this.$data, data)
611
+ this.items = data.items
612
+ this.summary = data.summary
613
+ this.hasNext = data.hasNext
614
+ this.count = data.count
457
615
  })
458
616
  },
459
617
 
@@ -1,5 +1,5 @@
1
- const { Op } = require('sequelize')
2
- const Sequelize = require('sequelize')
1
+ const { Op } = module.parent.require('sequelize')
2
+ const Sequelize = module.parent.require('sequelize')
3
3
  const dayjs = require("dayjs");
4
4
  const util = require("util");
5
5
 
@@ -124,7 +124,7 @@ let ListPage1 = {
124
124
 
125
125
  let attributeIncludes
126
126
  if(this.getAttributeIncludes){
127
- attributeIncludes = this.getAttributeIncludes()
127
+ attributeIncludes = this.getAttributeIncludes(preset)
128
128
  }
129
129
 
130
130
  const { rows:items, count } = await this.model.findAndCountAll({
@@ -136,7 +136,7 @@ let ListPage1 = {
136
136
  order,
137
137
  limit: itemsPerPage + 1,
138
138
  replacements,
139
- include: this.getModelIncludes ? this.getModelIncludes() : undefined
139
+ include: this.getModelIncludes ? this.getModelIncludes(preset) : undefined
140
140
  })
141
141
 
142
142
  const hasNext = items.length > itemsPerPage
@@ -144,6 +144,14 @@ let ListPage1 = {
144
144
  items.splice(items.length - 1, 1)
145
145
  }
146
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
+
147
155
  if(this.socket){
148
156
  this.socket.joinWithArgs(this.channel, { preset })
149
157
  }
@@ -151,7 +159,8 @@ let ListPage1 = {
151
159
  return {
152
160
  items,
153
161
  hasNext,
154
- count
162
+ count,
163
+ summary
155
164
  }
156
165
  },
157
166