@bildvitta/quasar-ui-asteroid 3.15.0-beta.4 → 3.15.0-beta.6

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.15.0-beta.4",
4
+ "version": "3.15.0-beta.6",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -1,13 +1,13 @@
1
1
  <template>
2
2
  <qas-grabbable class="qas-board-generator" use-scroll-bar>
3
3
  <div class="no-wrap q-col-gutter-sm q-px-xl row">
4
- <div v-for="(header, index) in results" :key="index" class="q-mr-sm">
4
+ <div v-for="(header, index) in headers" :key="index" class="q-mr-sm">
5
5
  <qas-box class="q-mb-md">
6
- <slot :context="header" name="header-column" />
6
+ <slot :fields="getFieldsByHeader(header)" :header="header" name="header-column" />
7
7
  </qas-box>
8
8
 
9
9
  <div ref="columnContainer" class="qas-board-generator__column secondary-scroll" :style="containerStyle">
10
- <slot v-for="item in getItemsByHeader(header)" :context="item" name="column-item" />
10
+ <slot v-for="item in getItemsByHeader(header)" :fields="getFieldsByHeader(header)" :item="item" name="column-item" />
11
11
 
12
12
  <div class="full-width justify-center q-mb-md q-mt-sm row">
13
13
  <qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" label="Ver mais" :use-label-on-small-screen="false" variant="tertiary" @click="fetchColumn(header)" />
@@ -23,7 +23,7 @@
23
23
  </template>
24
24
 
25
25
  <script setup>
26
- import { ref, watch, computed, onMounted, shallowRef, inject } from 'vue'
26
+ import { ref, watch, computed, onMounted, markRaw, inject } from 'vue'
27
27
  import promiseHandler from '../../helpers/promise-handler'
28
28
 
29
29
  defineOptions({ name: 'QasBoardGenerator' })
@@ -31,15 +31,21 @@ defineOptions({ name: 'QasBoardGenerator' })
31
31
  const columnContainer = ref(null)
32
32
  const columnsPagination = ref({})
33
33
  const columnsLoading = ref({})
34
+ const columnsFieldsModel = ref({})
34
35
 
35
36
  const axios = inject('axios')
36
37
 
37
38
  const props = defineProps({
38
- results: {
39
+ headers: {
39
40
  type: Array,
40
41
  default: () => []
41
42
  },
42
43
 
44
+ results: {
45
+ type: Object,
46
+ default: () => ({})
47
+ },
48
+
43
49
  columnIdKey: {
44
50
  type: String,
45
51
  required: true
@@ -55,11 +61,6 @@ const props = defineProps({
55
61
  required: true
56
62
  },
57
63
 
58
- modelValue: {
59
- type: Object,
60
- default: () => ({})
61
- },
62
-
63
64
  height: {
64
65
  type: String,
65
66
  default: ''
@@ -72,17 +73,30 @@ const props = defineProps({
72
73
 
73
74
  columnWidth: {
74
75
  type: String,
75
- default: '288px'
76
+ default: '300px'
76
77
  },
77
78
 
78
- useShallowRef: {
79
+ useMarkRaw: {
79
80
  type: Boolean,
80
81
  default: true
82
+ },
83
+
84
+ lazyLoadingFieldsKeys: {
85
+ type: Array,
86
+ default: () => []
81
87
  }
82
88
  })
83
89
 
90
+ const emit = defineEmits([
91
+ 'update:results',
92
+ 'fetch-column-success',
93
+ 'fetch-column-error',
94
+ 'fetch-columns-success',
95
+ 'fetch-columns-error'
96
+ ])
97
+
84
98
  watch(
85
- () => props.results,
99
+ () => props.headers,
86
100
  () => {
87
101
  reset()
88
102
  setColumnHeightContainer()
@@ -98,21 +112,19 @@ onMounted(() => {
98
112
  fetchColumns()
99
113
  })
100
114
 
101
- const emit = defineEmits(['update:modelValue'])
102
-
103
115
  defineExpose({ fetchColumns, fetchColumn, reset })
104
116
 
105
- const columnsModel = computed({
117
+ const columnsResultsModel = computed({
106
118
  get () {
107
- return props.modelValue
119
+ return props.results
108
120
  },
109
121
 
110
122
  set (newValues) {
111
- emit('update:modelValue', newValues)
123
+ emit('update:results', newValues)
112
124
  }
113
125
  })
114
126
 
115
- const hasColumnsLength = computed(() => Object.keys(columnsModel.value).length)
127
+ const hasColumnsLength = computed(() => Object.keys(columnsResultsModel.value).length)
116
128
 
117
129
  const containerStyle = computed(() => `width: ${props.columnWidth};`)
118
130
 
@@ -134,7 +146,17 @@ function setColumnHeightContainer () {
134
146
  * Bater API pra cada header
135
147
  */
136
148
  async function fetchColumns () {
137
- props.results.forEach(header => fetchColumn(header))
149
+ const promises = props.headers.map(header => fetchColumn(header))
150
+
151
+ const { error } = await promiseHandler(promises, { useLoading: false })
152
+
153
+ if (error) {
154
+ emit('fetch-columns-error', error)
155
+
156
+ return
157
+ }
158
+
159
+ emit('fetch-columns-success')
138
160
  }
139
161
 
140
162
  /*
@@ -161,32 +183,82 @@ async function fetchColumn (header) {
161
183
  }
162
184
  )
163
185
 
164
- if (error) return
186
+ if (error) {
187
+ emit('fetch-column-error', error)
188
+
189
+ throw error
190
+ }
165
191
 
166
192
  /*
167
- * exemplo de como columnsModel irá ficar:
193
+ * exemplo de como columnsResultsModel irá ficar:
168
194
  *
169
195
  * {
170
196
  * '2024-02-15': [...],
171
197
  * '2024-02-16': [...]
172
198
  * }
173
199
  *
174
- * onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsLoading" e "columnPagination", organizando
175
- * os loadings e o controle de paginação por chave identificadora do header.
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.
176
202
  */
177
203
  const newColumnValues = [
178
- ...columnsModel.value[headerKey] || [],
204
+ ...columnsResultsModel.value[headerKey] || [],
179
205
  ...response.data?.results || []
180
206
  ]
181
207
 
182
- columnsModel.value[headerKey] = props.useShallowRef ? shallowRef(newColumnValues) : newColumnValues
208
+ columnsResultsModel.value[headerKey] = props.useMarkRaw ? markRaw(newColumnValues) : newColumnValues
183
209
 
184
- columnsPagination.value[headerKey].offset = columnsModel.value[headerKey].length
210
+ /*
211
+ * Pode acontecer das options nos fields da segunda página serem diferentes da primeira página,
212
+ * portanto deve ocorrer o merge.
213
+ */
214
+ if (response.data?.fields) {
215
+ columnsFieldsModel.value[headerKey] = markRaw(
216
+ getMergedFields(columnsFieldsModel.value[headerKey], response.data?.fields)
217
+ )
218
+ }
219
+
220
+ columnsPagination.value[headerKey].offset = columnsResultsModel.value[headerKey].length
185
221
  columnsPagination.value[headerKey].count = response.data?.count
222
+
223
+ emit('fetch-column-success', { response, header })
224
+ }
225
+
226
+ /*
227
+ * Mergeia os options antigos com os novos de cada field.
228
+ */
229
+ function getMergedFields (oldFields, newFields) {
230
+ // Primeira vez batendo a API, retorna os novos fields.
231
+ if (!oldFields || !props.lazyLoadingFieldsKeys.length) return newFields
232
+
233
+ // Caso bata a API e por algum motivo não venha fields, mantenha o antigos.
234
+ if (oldFields && !newFields) return oldFields
235
+
236
+ const mergedFields = { ...oldFields }
237
+
238
+ props.lazyLoadingFieldsKeys.forEach(fieldKey => {
239
+ mergedFields[fieldKey].options = getNonDuplicatedOptions(oldFields[fieldKey].options, newFields[fieldKey].options)
240
+ })
241
+
242
+ return mergedFields
243
+ }
244
+
245
+ /*
246
+ * Tratamento para fazer o merge e evitar options duplicados.
247
+ */
248
+ function getNonDuplicatedOptions (oldOptions, newOptions) {
249
+ const options = [...oldOptions]
250
+
251
+ newOptions.forEach(item => {
252
+ const hasOption = options.find(option => option.value === item.value)
253
+
254
+ if (!hasOption) options.push(item)
255
+ })
256
+
257
+ return options
186
258
  }
187
259
 
188
260
  function getItemsByHeader (header) {
189
- return hasColumnsLength.value ? columnsModel.value[getKeyByHeader(header)] : []
261
+ return hasColumnsLength.value ? columnsResultsModel.value[getKeyByHeader(header)] : []
190
262
  }
191
263
 
192
264
  /**
@@ -213,7 +285,7 @@ function setColumnsPagination () {
213
285
  columnsPagination.value = {}
214
286
  columnsLoading.value = {}
215
287
 
216
- props.results.forEach(header => {
288
+ props.headers.forEach(header => {
217
289
  const headerKey = getKeyByHeader(header)
218
290
 
219
291
  columnsPagination.value[headerKey] = { limit: props.limitPerColumn, offset: 0 }
@@ -231,16 +303,22 @@ function hasEmptyResultText (header) {
231
303
  */
232
304
  function hasSeeMore (header) {
233
305
  const headerKey = getKeyByHeader(header)
234
- const hasMorePagination = columnsModel.value[headerKey]?.length < columnsPagination.value[headerKey]?.count
306
+ const hasMorePagination = columnsResultsModel.value[headerKey]?.length < columnsPagination.value[headerKey]?.count
235
307
 
236
308
  return hasMorePagination && !columnsLoading.value[headerKey]
237
309
  }
238
310
 
239
311
  function reset () {
240
- columnsModel.value = {}
312
+ columnsResultsModel.value = {}
241
313
  columnsPagination.value = {}
242
314
  columnsLoading.value = {}
243
315
  }
316
+
317
+ function getFieldsByHeader (header) {
318
+ const headerKey = getKeyByHeader(header)
319
+
320
+ return columnsFieldsModel.value[headerKey] || {}
321
+ }
244
322
  </script>
245
323
 
246
324
  <style lang="scss">
@@ -4,11 +4,6 @@ meta:
4
4
  desc: Componente usado para board de colunas.
5
5
 
6
6
  props:
7
- results:
8
- desc: Lista de itens sendo cada um o header de cada coluna.
9
- default: []
10
- type: Array
11
-
12
7
  column-id-key:
13
8
  desc: chave que será o id usado para ser o identificador da coluna.
14
9
  type: String
@@ -28,53 +23,101 @@ props:
28
23
  desc: Largura da coluna
29
24
  type: String
30
25
  default: 288
26
+
27
+ headers:
28
+ desc: Lista de itens sendo cada um o header de cada coluna.
29
+ default: []
30
+ type: Array
31
31
 
32
32
  height:
33
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.
34
34
  type: String
35
+
36
+ lazy-loading-fields-keys:
37
+ desc: Chaves dos campos que são lazy loading para o tratamento de merge das options.
38
+ type: Array
39
+ default: []
35
40
 
36
41
  limit:
37
42
  desc: Quantidade de itens da coluna por busca na API.
38
43
  type: Number
39
44
  default: 12
40
45
 
41
- use-shallow-ref:
42
- desc: Define se os valores dos itens das colunas irá ser atribuido utilizando o "shallowRef", onde somente a primeira camada do model será reativa.(https://vuejs.org/api/reactivity-advanced.html#shallowref)
46
+ 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)
43
48
  type: Boolean
44
49
  default: true
45
50
 
46
- model-value:
47
- desc: Model do componente, usado para o v-model.
51
+ results:
52
+ desc: Model do componente, usado para o v-model dos itens da coluna.
48
53
  default: {}
49
54
  type: Object
50
- examples: [v-model="value"]
55
+ examples: [v-model:results="resultsColumns"]
51
56
  model: true
52
57
 
53
58
  slots:
54
59
  header-column:
55
60
  desc: Slot para acessar o card do header de cada coluna.
56
61
  scope:
57
- context:
58
- desc: Informações de cada item do header que foi fornecido através do "results".
62
+ fields:
63
+ desc: Fields referente à coluna atual do template.
64
+ type: Object
65
+ default: {}
66
+
67
+ header:
68
+ desc: Informações de cada item do header que foi fornecido através da prop "headers"
59
69
  type: Object
60
70
  default: {}
61
71
 
62
72
  column-item:
63
73
  desc: Slot para acessar o item da coluna.
64
74
  scope:
65
- context:
75
+ fields:
76
+ desc: Fields referente à coluna atual do template.
77
+ type: Object
78
+ default: {}
79
+
80
+ item:
66
81
  desc: Informações de cada item da coluna que foi buscado através da API.
67
82
  type: Object
68
83
  default: {}
69
84
 
70
85
  events:
71
- '@update:model-value -> function(value)':
72
- desc: Dispara quando o model-value altera, também usado para v-model.
86
+ '@update:results -> function(value)':
87
+ desc: Dispara quando o "results" altera, também usado para v-model.
73
88
  params:
74
89
  value:
75
90
  desc: Novo valor do model.
76
91
  type: Object
77
92
 
93
+ '@update:fetch-column-success -> function({ response, header })':
94
+ desc: Dispara quando o "fetchColumn" é executado com sucesso.
95
+ params:
96
+ response:
97
+ desc: Retorno da API.
98
+ type: Object
99
+
100
+ header:
101
+ desc: header da coluna atual.
102
+ type: Object
103
+
104
+ '@update:fetch-column-error -> function(error)':
105
+ desc: Dispara quando o "fetchColumn" cai em uma exceção.
106
+ params:
107
+ error:
108
+ desc: Retorna todos os dados "cru" respondido na exceção do fetch.
109
+ type: Object
110
+
111
+ '@update:fetch-columns-success -> function()':
112
+ desc: Dispara quando todos "fetchColumn" foram executados com sucesso.
113
+
114
+ '@update:fetch-columns-error -> function(error)':
115
+ desc: Dispara quando algum "fetchColumn" cai em uma exceção.
116
+ params:
117
+ error:
118
+ desc: Retorna todos os dados "cru" respondido na exceção do fetch.
119
+ type: Object
120
+
78
121
  methods:
79
122
  'fetchColumns: () => void':
80
123
  desc: Busca todas colunas com base nos headers fornecidos.
@@ -17,7 +17,7 @@
17
17
 
18
18
  <div ref="formGenerator" class="col-12 justify-between q-col-gutter-x-md row">
19
19
  <slot :errors="transformedErrors" :fields="getFields(index, row)" :index="index" name="fields" :update-value="updateValuesFromInput">
20
- <qas-form-generator v-model="nested[index]" :class="formClasses" :columns="formColumns" :disable="isDisabledRow(row)" :errors="transformedErrors[index]" :fields="getFields(index, row)" :fields-props="getFieldsProps(index, row)" @update:model-value="updateValuesFromInput($event, index)">
20
+ <qas-form-generator v-model="nested[index]" class="col" :columns="formColumns" :disable="isDisabledRow(row)" :errors="transformedErrors[index]" :fields="getFields(index, row)" :fields-props="getFieldsProps(index, row)" :gutter="formGutter" @update:model-value="updateValuesFromInput($event, index)">
21
21
  <template v-for="(slot, key) in $slots" #[key]="scope">
22
22
  <slot v-bind="scope" :disabled="isDisabledRow(row)" :errors="transformedErrors" :index="index" :name="key" />
23
23
  </template>
@@ -67,6 +67,7 @@ import QasInput from '../input/QasInput.vue'
67
67
  import QasLabel from '../label/QasLabel.vue'
68
68
 
69
69
  import { constructObject } from '../../helpers'
70
+ import { Spacing } from '../../enums/Spacing'
70
71
 
71
72
  import { TransitionGroup } from 'vue'
72
73
  import debug from 'debug'
@@ -164,17 +165,9 @@ export default {
164
165
  },
165
166
 
166
167
  formGutter: {
167
- type: String,
168
- default: 'md',
169
- validator: value => {
170
- return [
171
- 'xs',
172
- 'sm',
173
- 'md',
174
- 'lg',
175
- 'xl'
176
- ].includes(value)
177
- }
168
+ default: Spacing.Lg,
169
+ type: [String, Boolean],
170
+ validator: value => typeof value === 'boolean' || Object.values(Spacing).includes(value)
178
171
  },
179
172
 
180
173
  identifierItemKey: {
@@ -280,13 +273,6 @@ export default {
280
273
  return this.field?.name
281
274
  },
282
275
 
283
- formClasses () {
284
- return {
285
- col: true,
286
- [`q-col-gutter-x-${this.formGutter}`]: this.useInlineActions
287
- }
288
- },
289
-
290
276
  showDestroyButton () {
291
277
  return this.nested.filter(item => !item[this.destroyKey]).length > 1 || this.hasDestroyAlways
292
278
  },
@@ -75,10 +75,10 @@ props:
75
75
  examples: ["[{ sm: 6, md: 12 }]", "{ name: { sm: 6, md: 12 } }", "12"]
76
76
 
77
77
  form-gutter:
78
- desc: Espaçamento entre cada campo.
79
- default: md
80
- type: String
81
- examples: [xs, sm, md, lg, xl]
78
+ desc: Espaçamento entre colunas do formulário.
79
+ default: lg
80
+ type: [String, Boolean]
81
+ examples: [xs, sm, md, lg, xl, false]
82
82
 
83
83
  identifier-item-key:
84
84
  desc: Define um identificador para o item. O identificador será utilizado para validar exclusão do item, por exemplo.