@bildvitta/quasar-ui-asteroid 3.17.0-beta.11 → 3.17.0-beta.13

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": "@bildvitta/quasar-ui-asteroid",
3
3
  "description": "Asteroid",
4
- "version": "3.17.0-beta.11",
4
+ "version": "3.17.0-beta.13",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -54,7 +54,7 @@
54
54
  "lodash-es": "^4.17.21",
55
55
  "pica": "^9.0.1",
56
56
  "signature_pad": "^4.1.5",
57
- "sortablejs": "^1.15.0"
57
+ "sortablejs": "^1.15.3"
58
58
  },
59
59
  "browserslist": [
60
60
  "defaults"
@@ -1,39 +1,40 @@
1
1
  <template>
2
- <qas-grabbable class="qas-board-generator" use-scroll-bar>
2
+ <qas-grabbable class="qas-board-generator" v-bind="grabbableProps">
3
3
  <div class="no-wrap q-col-gutter-sm q-px-xl row">
4
4
  <div v-for="(header, index) in headers" :key="index" class="q-mr-sm">
5
- <qas-box class="q-mb-md">
6
- <slot :fields="getFieldsByHeader(header)" :header="header" name="header-column" />
5
+ <qas-box class="q-mb-md" v-bind="headerBoxProps">
6
+ <slot :fields="getFieldsByHeader(header)" :header="header" :index="index" name="header-column" />
7
7
  </qas-box>
8
8
 
9
- <div ref="columnContainer" class="qas-board-generator__column secondary-scroll" :style="containerStyle">
10
- <slot v-for="item in getItemsByHeader(header)" :fields="getFieldsByHeader(header)" :item="item" name="column-item" />
9
+ <div ref="columnContainer" class="qas-board-generator__column secondary-scroll" :data-header-key="getKeyByHeader(header)" :style="containerStyle">
10
+ <div v-for="item in getItemsByHeader(header)" :id="item[props.itemIdKey]" :key="item[props.itemIdKey]" class="qas-board-generator__item">
11
+ <slot :column-index="index" :fields="getFieldsByHeader(header)" :item="item" name="column-item" />
12
+ </div>
11
13
 
12
- <div class="full-width justify-center q-mb-md q-mt-sm row">
14
+ <div class="full-width justify-center row">
13
15
  <qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" label="Ver mais" :use-label-on-small-screen="false" variant="tertiary" @click="fetchColumn(header)" />
14
16
 
15
- <q-spinner v-if="columnsLoading[getKeyByHeader(header)]" color="grey-4" size="3em" />
16
-
17
- <qas-empty-result-text v-if="hasEmptyResultText(header)" />
17
+ <q-spinner v-if="columnsLoading[getKeyByHeader(header)]" class="q-mb-md" color="grey-4" size="3em" />
18
18
  </div>
19
+
20
+ <qas-empty-result-text v-if="hasEmptyResultText(header)" />
19
21
  </div>
20
22
  </div>
21
23
  </div>
24
+
25
+ <qas-dialog v-model="showConfirmDialog" v-bind="defaultConfirmDialogProps" />
22
26
  </qas-grabbable>
23
27
  </template>
24
28
 
25
29
  <script setup>
26
- import { ref, watch, computed, onMounted, markRaw, inject } from 'vue'
27
- import promiseHandler from '../../helpers/promise-handler'
30
+ import QasDialog from '../dialog/QasDialog.vue'
28
31
 
29
- defineOptions({ name: 'QasBoardGenerator' })
32
+ import { ref, watch, computed, onUnmounted, markRaw, inject, onMounted } from 'vue'
33
+ import promiseHandler from '../../helpers/promise-handler'
30
34
 
31
- const columnContainer = ref(null)
32
- const columnsPagination = ref({})
33
- const columnsLoading = ref({})
34
- const columnsFieldsModel = ref({})
35
+ import Sortable from 'sortablejs'
35
36
 
36
- const axios = inject('axios')
37
+ defineOptions({ name: 'QasBoardGenerator' })
37
38
 
38
39
  const props = defineProps({
39
40
  headers: {
@@ -46,6 +47,11 @@ const props = defineProps({
46
47
  default: () => ({})
47
48
  },
48
49
 
50
+ headerBoxProps: {
51
+ type: Object,
52
+ default: () => ({})
53
+ },
54
+
49
55
  columnIdKey: {
50
56
  type: String,
51
57
  required: true
@@ -61,11 +67,21 @@ const props = defineProps({
61
67
  required: true
62
68
  },
63
69
 
70
+ confirmDialogProps: {
71
+ type: Object,
72
+ default: () => ({})
73
+ },
74
+
64
75
  height: {
65
76
  type: String,
66
77
  default: ''
67
78
  },
68
79
 
80
+ itemIdKey: {
81
+ type: String,
82
+ default: ''
83
+ },
84
+
69
85
  limitPerColumn: {
70
86
  type: Number,
71
87
  default: 12
@@ -76,11 +92,34 @@ const props = defineProps({
76
92
  default: '300px'
77
93
  },
78
94
 
95
+ sortableConfig: {
96
+ type: Object,
97
+ default: () => ({})
98
+ },
99
+
79
100
  useMarkRaw: {
80
101
  type: Boolean,
81
102
  default: true
82
103
  },
83
104
 
105
+ useDragAndDropX: {
106
+ type: Boolean
107
+ },
108
+
109
+ useDragAndDropY: {
110
+ type: Boolean
111
+ },
112
+
113
+ updatePositionUrl: {
114
+ type: String,
115
+ default: ''
116
+ },
117
+
118
+ updatePositionParams: {
119
+ type: Object,
120
+ default: () => ({})
121
+ },
122
+
84
123
  lazyLoadingFieldsKeys: {
85
124
  type: Array,
86
125
  default: () => []
@@ -92,28 +131,92 @@ const emit = defineEmits([
92
131
  'fetch-column-success',
93
132
  'fetch-column-error',
94
133
  'fetch-columns-success',
95
- 'fetch-columns-error'
134
+ 'fetch-columns-error',
135
+ 'update-success'
96
136
  ])
97
137
 
138
+ defineExpose({ fetchColumns, fetchColumn, reset })
139
+
140
+ // Inject
141
+ const axios = inject('axios')
142
+
143
+ const isFetchSuccessHeader = inject('isFetchListSucceeded', false)
144
+
145
+ const isInsideListView = inject('isListView', false)
146
+
147
+ // Refs
148
+ const columnContainer = ref(null)
149
+ const columnsPagination = ref({})
150
+ const columnsLoading = ref({})
151
+ const columnsFieldsModel = ref({})
152
+ const showConfirmDialog = ref(false)
153
+ const isDragging = ref(false)
154
+ const isLoadingUpdatePosition = ref(false)
155
+
156
+ /**
157
+ * Instâncias do sortable, que são utilizadas para realizar o destroy ao sair da página
158
+ */
159
+ const sortableInstances = ref([])
160
+
161
+ /**
162
+ * Callbacks que recebe o event de movimentação
163
+ */
164
+ const onCancelDrop = ref(() => {})
165
+ const onConfirmDrop = ref(() => {})
166
+
167
+ /**
168
+ * Variável auxiliar que controla quando estou atualizando o header em caso de drag and drop
169
+ */
170
+ const isUpdatePosition = ref(false)
171
+
172
+ // Consts
173
+ const hasDragAndDrop = !!props.useDragAndDropX || !!props.useDragAndDropY
174
+
175
+ const grabbableProps = {
176
+ useScrollBar: true,
177
+
178
+ ...(hasDragAndDrop && {
179
+ cancelMouseDownTarget: 'qas-board-generator__item'
180
+ })
181
+ }
182
+
183
+ // Watchers
184
+ watch(
185
+ () => isFetchSuccessHeader.value,
186
+ value => {
187
+ /**
188
+ * isFetchSuccessHeader é uma variavel que pego do listView por inject/provide, no qual caso eu faça request do header e dê sucesso, eu chamo as demais funções.
189
+ */
190
+ if (!value) return
191
+
192
+ fetchColumnsValues()
193
+ }
194
+ )
195
+
98
196
  watch(
99
197
  () => props.headers,
100
198
  () => {
101
- reset()
102
- setColumnHeightContainer()
103
- setColumnsPagination()
104
- fetchColumns()
199
+ if (!isUpdatePosition.value) return
200
+
201
+ isUpdatePosition.value = false
105
202
  }
106
203
  )
107
204
 
108
205
  watch(columnContainer, setColumnHeightContainer)
109
206
 
207
+ // Lifecycles
110
208
  onMounted(() => {
111
- setColumnsPagination()
112
- fetchColumns()
209
+ /**
210
+ * Caso eu use o listView (valor pego por provide), a request é feito pelo watch quando se ocorre o sucesso do `fetchList`
211
+ */
212
+ if (isInsideListView) return
213
+
214
+ fetchColumnsValues()
113
215
  })
114
216
 
115
- defineExpose({ fetchColumns, fetchColumn, reset })
217
+ onUnmounted(destroySortable)
116
218
 
219
+ // Computeds
117
220
  const columnsResultsModel = computed({
118
221
  get () {
119
222
  return props.results
@@ -124,10 +227,32 @@ const columnsResultsModel = computed({
124
227
  }
125
228
  })
126
229
 
127
- const hasColumnsLength = computed(() => Object.keys(columnsResultsModel.value).length)
230
+ const hasColumnsLength = computed(() => !!Object.keys(columnsResultsModel.value).length)
128
231
 
129
232
  const containerStyle = computed(() => `width: ${props.columnWidth};`)
130
233
 
234
+ const hasConfirmDialogProps = computed(() => !!Object.keys(props.confirmDialogProps).length)
235
+
236
+ const defaultConfirmDialogProps = computed(() => {
237
+ const defaultProps = {
238
+ ok: {
239
+ label: 'Confirmar',
240
+ onClick: onConfirmDrop.value,
241
+ loading: isLoadingUpdatePosition.value
242
+ },
243
+
244
+ cancel: {
245
+ onClick: onCancelDrop.value
246
+ }
247
+ }
248
+
249
+ return {
250
+ ...props.confirmDialogProps,
251
+ ...defaultProps
252
+ }
253
+ })
254
+
255
+ // functions
131
256
  /*
132
257
  * Setar o tamanho do container do board, onde deverá ser a altura passada via prop, ou o default será ocupar o maximo
133
258
  * de espaço que ele conseguir considerando a altura do container em relação ao topo.
@@ -157,6 +282,8 @@ async function fetchColumns () {
157
282
  }
158
283
 
159
284
  emit('fetch-columns-success')
285
+
286
+ if (hasDragAndDrop) handleElementsList()
160
287
  }
161
288
 
162
289
  /*
@@ -189,22 +316,25 @@ async function fetchColumn (header) {
189
316
  throw error
190
317
  }
191
318
 
192
- /*
193
- * exemplo de como columnsResultsModel irá ficar:
194
- *
195
- * {
196
- * '2024-02-15': [...],
197
- * '2024-02-16': [...]
198
- * }
199
- *
200
- * onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsFieldsModel", "columnsLoading" e
201
- * "columnPagination", organizando os fields, loadings e o controle de paginação por chave identificadora do header.
202
- */
319
+ const newValues = response.data?.results || []
320
+ const resultsModel = columnsResultsModel.value[headerKey] || []
321
+
203
322
  const newColumnValues = [
204
- ...columnsResultsModel.value[headerKey] || [],
205
- ...response.data?.results || []
323
+ ...resultsModel,
324
+ ...newValues
206
325
  ]
207
326
 
327
+ /**
328
+ * exemplo de como columnsResultsModel irá ficar:
329
+ *
330
+ * {
331
+ * '2024-02-15': [...],
332
+ * '2024-02-16': [...]
333
+ * }
334
+ *
335
+ * onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsFieldsModel", "columnsLoading" e
336
+ * "columnPagination", organizando os fields, loadings e o controle de paginação por chave identificadora do header.
337
+ */
208
338
  columnsResultsModel.value[headerKey] = props.useMarkRaw ? markRaw(newColumnValues) : newColumnValues
209
339
 
210
340
  /*
@@ -293,8 +423,24 @@ function setColumnsPagination () {
293
423
  })
294
424
  }
295
425
 
426
+ function fetchColumnsValues () {
427
+ reset()
428
+ setColumnHeightContainer()
429
+ setColumnsPagination()
430
+ fetchColumns()
431
+ }
432
+
433
+ /**
434
+ * Descricao:
435
+ * Exibe o texto quando:
436
+ * - Nao esta carregando a coluna
437
+ * - Nao tem itens na coluna
438
+ * - Nao estou fazendo o drag and drop
439
+ *
440
+ * @param {Object} header
441
+ */
296
442
  function hasEmptyResultText (header) {
297
- return !columnsLoading.value[getKeyByHeader(header)] && !getItemsByHeader(header)?.length
443
+ return !columnsLoading.value[getKeyByHeader(header)] && !getItemsByHeader(header)?.length && !isDragging.value
298
444
  }
299
445
 
300
446
  /*
@@ -319,6 +465,216 @@ function getFieldsByHeader (header) {
319
465
 
320
466
  return columnsFieldsModel.value[headerKey] || {}
321
467
  }
468
+
469
+ /**
470
+ * Loopa todos os itens da coluna com base no ref para pegar o elemento HTML e setar e instaciar o sortable.
471
+ */
472
+ function handleElementsList () {
473
+ columnContainer.value.forEach((element, index) => {
474
+ const sortable = setSortable(element, index)
475
+
476
+ sortableInstances.value.push(sortable)
477
+ })
478
+ }
479
+
480
+ /**
481
+ * Descricao:
482
+ * Seta a instancia do sortable, no qual varia de acordo com as props passadas.
483
+ *
484
+ * @param {HTMLElement} element
485
+ * @param {Number} index
486
+ */
487
+ function setSortable (element, index) {
488
+ const defaultSortableConfig = {
489
+ animation: 500,
490
+ swapThreshold: 1,
491
+ delay: 50,
492
+ delayOnTouchOnly: true,
493
+ emptyInsertThreshold: 0
494
+ }
495
+
496
+ /**
497
+ * Caso seja apenas drag and drop no eixo Y
498
+ */
499
+ const useOnlyDragAndDropY = !!props.useDragAndDropY && !props.useDragAndDropX
500
+
501
+ const sortable = new Sortable(element, {
502
+ sort: props.useDragAndDropY,
503
+
504
+ ...defaultSortableConfig,
505
+
506
+ ...props.sortableConfig,
507
+
508
+ group: useOnlyDragAndDropY ? `column-${index}` : 'shared',
509
+
510
+ direction: useOnlyDragAndDropY ? 'vertical' : 'horizontal',
511
+
512
+ onStart: toggleIsDragging,
513
+
514
+ onAdd: event => onDropCard(event),
515
+
516
+ ...(props.useDragAndDropY && {
517
+ onSort: event => onDropCard(event)
518
+ })
519
+ })
520
+
521
+ return sortable
522
+ }
523
+
524
+ function toggleIsDragging () {
525
+ isDragging.value = !isDragging.value
526
+ }
527
+
528
+ function onDropCard (event) {
529
+ onCancelDrop.value = () => cancelDrop(event)
530
+
531
+ onConfirmDrop.value = () => confirmDrop(event)
532
+
533
+ hasConfirmDialogProps.value
534
+ ? toggleConfirmDialog()
535
+ : confirmDrop(event)
536
+ }
537
+
538
+ function toggleConfirmDialog () {
539
+ showConfirmDialog.value = !showConfirmDialog.value
540
+ }
541
+
542
+ /**
543
+ * @param {event} event
544
+ */
545
+ function cancelDrop (event) {
546
+ /**
547
+ * Insere na posição antiga que pertencia (event.oldIndex) dentro do seu antigo pai (event.from)
548
+ */
549
+ if (props.useDragAndDropX) event.from.insertBefore(event.item, event.from.children[event.oldIndex])
550
+
551
+ if (props.useDragAndDropY) {
552
+ const oldIndex = event.oldIndex
553
+
554
+ /**
555
+ * Se oldIndex for 0, o targetIndex deverá ser 0, pois isso indica que se o item é o primeiro da lista, ele não será movido para outra posição.
556
+ *
557
+ * Caso o oldIndex for diferente, devo incrementar 1 para adicionar, pois isso permite que o item seja inserido logo após sua posição original.
558
+ */
559
+ const targetIndex = oldIndex === 0 ? oldIndex : oldIndex + 1
560
+
561
+ /**
562
+ * Verifica se o índice alvo é válido, caso contrário, define como o final
563
+ */
564
+ const insertBeforeElement = targetIndex < event.from.children.length
565
+ ? event.from.children[targetIndex]
566
+ : null
567
+
568
+ event.from.insertBefore(event.item, insertBeforeElement)
569
+ }
570
+
571
+ if (hasConfirmDialogProps.value) toggleConfirmDialog()
572
+
573
+ toggleIsDragging()
574
+ }
575
+
576
+ function confirmDrop (event) {
577
+ const { from, to, item: { id: itemId } } = event
578
+
579
+ const { headerKey: newHeaderKey } = to.dataset
580
+ const { headerKey: oldHeaderKey } = from.dataset
581
+
582
+ updatePosition({ newHeaderKey, oldHeaderKey, itemId, event })
583
+ }
584
+
585
+ /**
586
+ *
587
+ * @param {{
588
+ * headerKey: string,
589
+ * itemId: string
590
+ * }}
591
+ */
592
+ function removeItemFromList ({ headerKey, itemId }) {
593
+ /**
594
+ * Coluna referente ao model de resultado
595
+ */
596
+ const columnItemList = columnsResultsModel.value[headerKey]
597
+
598
+ /**
599
+ * Busca o item com base em seu ID na lista de itens da coluna
600
+ */
601
+ const itemIndex = columnItemList.findIndex(itemContent => itemContent[props.itemIdKey] === itemId)
602
+
603
+ /**
604
+ * Remove o item da listagem com base no index, sendo que preciso subtrair 1 para pegar o index correto
605
+ */
606
+ columnItemList.splice(itemIndex, 1)
607
+
608
+ /**
609
+ * Remove o item do count da coluna para não mostrar o botão de "Ver mais¨.
610
+ */
611
+ columnsPagination.value[headerKey].count -= 1
612
+ }
613
+
614
+ /**
615
+ * Descricao:
616
+ * Metodo que realiza a request de update
617
+ *
618
+ * @param {{
619
+ * newHeaderKey: string - ID da coluna de destino,
620
+ * oldHeaderKey: string - ID da antiga coluna,
621
+ * itemId: string - ID do meu item a ser movimento,
622
+ * event: event
623
+ * }}
624
+ */
625
+ async function updatePosition ({ newHeaderKey, oldHeaderKey, itemId, event }) {
626
+ const params = {
627
+ [props.columnIdKey]: newHeaderKey,
628
+ ...(props.useDragAndDropY && { newIndex: event.newIndex }),
629
+ ...props.updatePositionParams
630
+ }
631
+
632
+ const { data, error } = await promiseHandler(
633
+ axios.patch(`${props.updatePositionUrl}/${itemId}/update-position`, params),
634
+ {
635
+ errorMessage: 'Ocorreu um erro ao atualizar a posição de seu item.',
636
+ useLoading: false,
637
+ onLoading: value => {
638
+ isLoadingUpdatePosition.value = value
639
+
640
+ columnsLoading.value[newHeaderKey] = value
641
+ }
642
+ }
643
+ )
644
+
645
+ if (error) {
646
+ onCancelDrop.value()
647
+
648
+ return
649
+ }
650
+
651
+ removeItemFromList({ headerKey: oldHeaderKey, itemId })
652
+
653
+ setItemList({ headerKey: newHeaderKey, data: data.data, index: event.newIndex })
654
+
655
+ isUpdatePosition.value = true
656
+
657
+ toggleIsDragging()
658
+ toggleConfirmDialog()
659
+
660
+ emit('update-success')
661
+ }
662
+
663
+ function setItemList ({ headerKey, data, index }) {
664
+ /**
665
+ * Coluna referente ao model de resultado
666
+ */
667
+ const columnItemList = columnsResultsModel.value[headerKey]
668
+
669
+ /**
670
+ * Adiciona o item na posição do event escolhido.
671
+ */
672
+ columnItemList.splice(index, 0, data.result)
673
+ }
674
+
675
+ function destroySortable () {
676
+ sortableInstances.value.forEach(sortable => sortable.destroy())
677
+ }
322
678
  </script>
323
679
 
324
680
  <style lang="scss">
@@ -341,5 +697,10 @@ function getFieldsByHeader (header) {
341
697
  display: none;
342
698
  }
343
699
  }
700
+
701
+ // 60px é o valor do padding definido no container da column.
702
+ &__column-items {
703
+ height: calc(100% - 60px);
704
+ }
344
705
  }
345
706
  </style>
@@ -20,19 +20,33 @@ props:
20
20
  required: true
21
21
 
22
22
  column-width:
23
- desc: Largura da coluna
23
+ desc: Largura da coluna.
24
24
  type: String
25
25
  default: 288
26
26
 
27
+ confirm-dialog-props:
28
+ desc: Propriedade repassada ao `QasDialog` para confirmar um drag and drop. Caso não seja passado, não é usado dialog de confirmação. As ações de confirmar e cancelar são feitas por padrão no componente.
29
+ type: Object
30
+ default: {}
31
+
32
+ header-box-props:
33
+ desc: Propriedades repassadas ao `QasBox` do header.
34
+ type: Object
35
+ default: {}
36
+
27
37
  headers:
28
38
  desc: Lista de itens sendo cada um o header de cada coluna.
29
39
  default: []
30
40
  type: Array
31
-
41
+
32
42
  height:
33
- desc: Altura do container do board. Caso não seja passado um height, o calculo é feito para ocupar a maior altura possível da página.
43
+ desc: Altura do container do board. Caso não seja passado um height, o cálculo é feito para ocupar a maior altura possível da página.
44
+ type: String
45
+
46
+ item-id-key:
47
+ desc: Identificador único do item a ser movido, sendo que será usado nas requisições realizadas pelo drop.
34
48
  type: String
35
-
49
+
36
50
  lazy-loading-fields-keys:
37
51
  desc: Chaves dos campos que são lazy loading para o tratamento de merge das options.
38
52
  type: Array
@@ -44,7 +58,7 @@ props:
44
58
  default: 12
45
59
 
46
60
  use-mark-raw:
47
- desc: Define se os valores dos itens das colunas irá ser atribuido utilizando o "markRaw", onde somente a primeira camada do model será reativa.(https://vuejs.org/api/reactivity-advanced.html#markraw)
61
+ desc: Define se os valores dos itens das colunas irão ser atribuídos utilizando o "markRaw", onde somente a primeira camada do model será reativa. (https://vuejs.org/api/reactivity-advanced.html#markraw)
48
62
  type: Boolean
49
63
  default: true
50
64
 
@@ -55,6 +69,30 @@ props:
55
69
  examples: [v-model:results="resultsColumns"]
56
70
  model: true
57
71
 
72
+ sortable-config:
73
+ desc: Configuração passada para a instância do SortableJS.
74
+ type: Object
75
+ default: "{ animation: 500, swapThreshold: 1, delay: 50, delayOnTouchOnly: true, emptyInsertThreshold: 0 }"
76
+ debugger: true
77
+
78
+ use-drag-and-drop-x:
79
+ desc: Controla se irá ter drag and drop no eixo X (entre colunas).
80
+ type: Boolean
81
+
82
+ use-drag-and-drop-y:
83
+ desc: Controla se irá ter drag and drop no eixo Y (mesma coluna, utilizado para ordenação de itens).
84
+ type: Boolean
85
+
86
+ update-position-url:
87
+ desc: URL usada para realizar o PATCH do drag and drop.
88
+ type: String
89
+ default: ''
90
+
91
+ update-position-params:
92
+ desc: Parâmetros customizados caso precise passar para a API de PATCH.
93
+ type: Object
94
+ default: {}
95
+
58
96
  slots:
59
97
  header-column:
60
98
  desc: Slot para acessar o card do header de cada coluna.
@@ -63,9 +101,9 @@ slots:
63
101
  desc: Fields referente à coluna atual do template.
64
102
  type: Object
65
103
  default: {}
66
-
104
+
67
105
  header:
68
- desc: Informações de cada item do header que foi fornecido através da prop "headers"
106
+ desc: Informações de cada item do header que foi fornecido através da prop "headers".
69
107
  type: Object
70
108
  default: {}
71
109
 
@@ -76,7 +114,7 @@ slots:
76
114
  desc: Fields referente à coluna atual do template.
77
115
  type: Object
78
116
  default: {}
79
-
117
+
80
118
  item:
81
119
  desc: Informações de cada item da coluna que foi buscado através da API.
82
120
  type: Object
@@ -98,14 +136,14 @@ events:
98
136
  type: Object
99
137
 
100
138
  header:
101
- desc: header da coluna atual.
139
+ desc: Header da coluna atual.
102
140
  type: Object
103
141
 
104
142
  '@update:fetch-column-error -> function(error)':
105
143
  desc: Dispara quando o "fetchColumn" cai em uma exceção.
106
144
  params:
107
145
  error:
108
- desc: Retorna todos os dados "cru" respondido na exceção do fetch.
146
+ desc: Retorna todos os dados "crus" respondidos na exceção do fetch.
109
147
  type: Object
110
148
 
111
149
  '@update:fetch-columns-success -> function()':
@@ -115,12 +153,15 @@ events:
115
153
  desc: Dispara quando algum "fetchColumn" cai em uma exceção.
116
154
  params:
117
155
  error:
118
- desc: Retorna todos os dados "cru" respondido na exceção do fetch.
156
+ desc: Retorna todos os dados "crus" respondidos na exceção do fetch.
119
157
  type: Object
120
158
 
159
+ '@update-success -> function()':
160
+ desc: Dispara quando o PATCH do drag and drop é com sucesso.
161
+
121
162
  methods:
122
163
  'fetchColumns: () => void':
123
164
  desc: Busca todas colunas com base nos headers fornecidos.
124
165
 
125
166
  'fetchColumn: (header) => void':
126
- desc: Busca uma coluna especifica com base no header fornecido.
167
+ desc: Busca uma coluna específica com base no header fornecido.
@@ -3,7 +3,7 @@
3
3
  <qas-box class="rounded-borders-right" v-bind="boxProps">
4
4
  <q-card class="column full-height overflow-hidden shadow-0">
5
5
  <div class="items-center justify-between row">
6
- <component :is="titleComponent" class="text-h4 text-no-decoration" :class="titleClasses" :to="route">
6
+ <component :is="titleComponent" class="text-h5 text-no-decoration" :class="titleClasses" :to="route">
7
7
  <slot name="title">
8
8
  {{ props.title }}
9
9
  </slot>
@@ -89,7 +89,7 @@ const titleClasses = computed(() => {
89
89
  }
90
90
  })
91
91
 
92
- const titleComponent = computed(() => hasRoute.value ? 'router-link' : 'div')
92
+ const titleComponent = computed(() => hasRoute.value ? 'router-link' : 'h5')
93
93
 
94
94
  const style = computed(() => {
95
95
  if (!props.statusColor) return
@@ -85,7 +85,7 @@ const props = defineProps({
85
85
 
86
86
  group: {
87
87
  type: String,
88
- default: ''
88
+ default: undefined
89
89
  },
90
90
 
91
91
  label: {
@@ -19,6 +19,11 @@ defineOptions({ name: 'QasGrabbable' })
19
19
  const props = defineProps({
20
20
  useScrollBar: {
21
21
  type: Boolean
22
+ },
23
+
24
+ cancelMouseDownTarget: {
25
+ type: String,
26
+ default: ''
22
27
  }
23
28
  })
24
29
 
@@ -61,17 +66,20 @@ function handleEnableScrollOnGrab () {
61
66
  function initScrollOnGrab () {
62
67
  if (hasScrollOnGrab.value) return
63
68
 
64
- scrollOnGrab.value = setScrollOnGrab(grabContainer.value, {
65
- onGrabFn: onGrab,
66
- onMoveFn: setGrabPosition,
67
- onScrollFn: setGrabPosition
68
- })
69
+ scrollOnGrab.value = setScrollOnGrab(
70
+ grabContainer.value,
71
+ {
72
+ onGrabFn: onGrab,
73
+ onMoveFn: setGrabPosition,
74
+ onScrollFn: setGrabPosition
75
+ },
76
+ props.cancelMouseDownTarget
77
+ )
69
78
  }
70
79
 
71
80
  function destroyScrollOnGrab () {
72
81
  if (!hasScrollOnGrab.value) return
73
82
 
74
- scrollOnGrab.value.destroyEvents()
75
83
  scrollOnGrab.value.element.style.cursor = 'auto'
76
84
  scrollOnGrab.value = {}
77
85
 
@@ -9,6 +9,10 @@ props:
9
9
  default: false
10
10
  type: Boolean
11
11
 
12
+ cancel-mouse-down-target:
13
+ desc: Classe do elemento no qual será ignorado para não realizar o grab.
14
+ type: String
15
+
12
16
  slots:
13
17
  default:
14
18
  desc: Slot para ter o conteúdo que terá o scroll na horizontal.
@@ -13,11 +13,11 @@
13
13
  </div>
14
14
  </div>
15
15
 
16
- <slot name="actions">
17
- <div v-if="hasActionsSection" class="q-mt-xs text-right">
16
+ <div v-if="hasActionsSection" class="text-right">
17
+ <slot name="actions">
18
18
  <component :is="actionsComponent.is" v-if="hasActionsComponent" v-bind="actionsComponent.props" />
19
- </div>
20
- </slot>
19
+ </slot>
20
+ </div>
21
21
  </div>
22
22
 
23
23
  <div class="items-start no-wrap q-col-gutter-sm row" :class="descriptionSectionClasses">
@@ -27,7 +27,7 @@
27
27
  </slot>
28
28
  </div>
29
29
 
30
- <div v-if="!hasLabelSection" class="justify-end row">
30
+ <div v-if="!hasLabelSection" class="justify-end row text-right">
31
31
  <slot name="actions">
32
32
  <component :is="actionsComponent.is" v-if="hasActionsComponent" v-bind="actionsComponent.props" />
33
33
  </slot>
@@ -33,6 +33,11 @@ const props = defineProps({
33
33
  default: () => []
34
34
  },
35
35
 
36
+ fields: {
37
+ type: Object,
38
+ default: () => ({})
39
+ },
40
+
36
41
  limitPerPage: {
37
42
  type: Number,
38
43
  default: 12
@@ -62,7 +67,10 @@ const props = defineProps({
62
67
 
63
68
  defineExpose({ refresh, remove })
64
69
 
65
- const emit = defineEmits(['update:list', 'fetch-success', 'fetch-error'])
70
+ const emit = defineEmits(['fetch-success', 'fetch-error'])
71
+
72
+ const modelList = defineModel('list', { type: Array, default: () => [] })
73
+ const modelFields = defineModel('fields', { type: Object, default: () => ({}) })
66
74
 
67
75
  const axios = inject('axios')
68
76
 
@@ -74,7 +82,7 @@ const hasMadeFirstFetch = ref(false)
74
82
  const count = ref(0)
75
83
  const offset = ref(0)
76
84
 
77
- const listLength = computed(() => model.value.length)
85
+ const listLength = computed(() => modelList.value.length)
78
86
 
79
87
  const attributes = computed(() => ({
80
88
  offset: 100,
@@ -91,16 +99,6 @@ const containerStyle = computed(() => ({
91
99
  ...(props.maxHeight && { maxHeight: props.maxHeight, overflow: 'auto' })
92
100
  }))
93
101
 
94
- const model = computed({
95
- get () {
96
- return props.list
97
- },
98
-
99
- set (newList) {
100
- emit('update:list', newList)
101
- }
102
- })
103
-
104
102
  async function onLoad (_, done) {
105
103
  const hasMadeFirstFetchAndHasNoData = hasMadeFirstFetch.value && !listLength.value
106
104
  const hasFetchAllData = listLength.value && listLength.value >= count.value
@@ -124,11 +122,12 @@ async function fetchList () {
124
122
  params: { offset: offset.value, limit: props.limitPerPage, ...props.params }
125
123
  })
126
124
 
127
- const newList = [...model.value, ...(data.results || [])]
125
+ const newList = [...modelList.value, ...(data.results || [])]
128
126
 
129
- model.value = newList
127
+ modelList.value = newList
130
128
  offset.value = newList.length
131
129
  count.value = data.count
130
+ modelFields.value = data.fields
132
131
 
133
132
  /**
134
133
  * Sinalizar que houve já uma busca, para evitar que onLoad entre em looping,
@@ -136,7 +135,7 @@ async function fetchList () {
136
135
  */
137
136
  hasMadeFirstFetch.value = true
138
137
 
139
- emit('fetch-success', { list: newList, offset: offset.value, count: count.value })
138
+ emit('fetch-success', { list: newList, fields: modelFields.value, offset: offset.value, count: count.value })
140
139
  } catch (error) {
141
140
  NotifyError('Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.')
142
141
 
@@ -151,7 +150,7 @@ async function fetchList () {
151
150
  function refresh () {
152
151
  count.value = 0
153
152
  offset.value = 0
154
- model.value = []
153
+ modelList.value = []
155
154
 
156
155
  hasMadeFirstFetch.value = false
157
156
 
@@ -162,7 +161,7 @@ function refresh () {
162
161
  }
163
162
 
164
163
  function remove (index) {
165
- model.value.splice(index, 1)
164
+ modelList.value.splice(index, 1)
166
165
  count.value -= 1
167
166
  offset.value -= 1
168
167
  }
@@ -4,6 +4,13 @@ meta:
4
4
  desc: Componente de infinite scroll que implementa o "QInfiniteScroll".
5
5
 
6
6
  props:
7
+ fields:
8
+ desc: Model dos fields.
9
+ default: {}
10
+ type: Object
11
+ examples: [v-model:fields="fields"]
12
+ model: true
13
+
7
14
  list:
8
15
  desc: Model da lista de itens.
9
16
  default: []
@@ -47,6 +47,7 @@ import debug from 'debug'
47
47
  import { extend } from 'quasar'
48
48
  import { getState, getAction } from '@bildvitta/store-adapter'
49
49
  import { viewMixin, contextMixin } from '../../mixins'
50
+ import { computed } from 'vue'
50
51
 
51
52
  const log = debug('asteroid-ui:qas-list-view')
52
53
 
@@ -58,6 +59,13 @@ export default {
58
59
 
59
60
  mixins: [contextMixin, viewMixin],
60
61
 
62
+ provide () {
63
+ return {
64
+ isFetchListSucceeded: computed(() => this.isFetchListSucceeded),
65
+ isListView: true
66
+ }
67
+ },
68
+
61
69
  props: {
62
70
  filtersProps: {
63
71
  default: () => ({}),
@@ -107,7 +115,8 @@ export default {
107
115
  data () {
108
116
  return {
109
117
  page: 1,
110
- resultsQuantity: 0
118
+ resultsQuantity: 0,
119
+ isFetchListSucceeded: false
111
120
  }
112
121
  },
113
122
 
@@ -188,6 +197,7 @@ export default {
188
197
 
189
198
  async fetchList (externalPayload = {}) {
190
199
  this.mx_isFetching = true
200
+ this.isFetchListSucceeded = false
191
201
 
192
202
  try {
193
203
  const payload = {
@@ -215,6 +225,8 @@ export default {
215
225
  metadata: this.mx_metadata
216
226
  })
217
227
 
228
+ this.isFetchListSucceeded = true
229
+
218
230
  this.$emit('fetch-success', response)
219
231
 
220
232
  log(`[${this.entity}]:fetchList:success`, response)
@@ -158,3 +158,12 @@ events:
158
158
  value:
159
159
  desc: Retorna se está ou não fazendo fetching de dados.
160
160
  type: Boolean
161
+
162
+ provide:
163
+ is-fetch-list-succeeded:
164
+ desc: Valor que diz se o `fetchList` foi realizado com sucesso ou não.
165
+ type: Boolean
166
+
167
+ is-list-view:
168
+ desc: Provide que diz quando se está utilizando o listView
169
+ type: Boolean
@@ -17,7 +17,7 @@ props:
17
17
  desc: Propriedades que serão repassadas para o QasStepper.
18
18
  type: Object
19
19
 
20
- inject:
20
+ provide:
21
21
  stepper-model:
22
22
  desc: Model do stepper caso precise recuperar o step atual para alguma lógica. Possui reatividade, portanto para acessar é necessário do ".value"
23
23
  type: Number
@@ -7,10 +7,11 @@
7
7
  * onMoveFn: function({ element: HTMLElement, event: MouseEvent | TouchEvent }),
8
8
  * onScrollFn: function({ element: HTMLElement, event: Event })
9
9
  * }} options
10
+ * @param {String} cancelMouseDownTarget
10
11
  *
11
12
  * @returns {{ element: HTMLElement, destroyEvents: function }}
12
13
  */
13
- export default function (element, options = {}) {
14
+ export default function (element, options = {}, cancelMouseDownTarget) {
14
15
  let isDown = false
15
16
  let startX
16
17
  let scrollLeft
@@ -46,6 +47,13 @@ export default function (element, options = {}) {
46
47
  }
47
48
 
48
49
  function onMouseEnter (event) {
50
+ /**
51
+ * closest busca ancestral mais próximo de um elemento, ou seja, verifica se no event que recebo, tenho a classe no qual nao se deve aplicar o grab.
52
+ */
53
+ const targetElement = event.target.closest(`.${cancelMouseDownTarget}`)
54
+
55
+ if (!!cancelMouseDownTarget && !!targetElement) return null
56
+
49
57
  onEnter()
50
58
 
51
59
  startX = event.pageX - element.offsetLeft