@bildvitta/quasar-ui-asteroid 3.12.0 → 3.13.0-beta.0

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.12.0",
4
+ "version": "3.13.0-beta.0",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="cursor-pointer items-center no-wrap q-gutter-sm qas-app-user row">
2
+ <div class="cursor-pointer items-center no-wrap q-gutter-sm qas-app-user row" data-cy="app-user">
3
3
  <div class="relative-position">
4
4
  <qas-avatar :image="user.photo" :size="avatarSize" :title="userName" />
5
5
  <q-badge v-if="hasNotifications" color="red" floating>{{ notifications.count }}</q-badge>
@@ -17,7 +17,7 @@
17
17
  <div class="ellipsis qas-app-user__menu-name">{{ userName }}</div>
18
18
  <div class="ellipsis">{{ user.email }}</div>
19
19
 
20
- <qas-select v-if="hasCompaniesSelect" v-model="companiesModel" class="q-my-md" label="Vínculo" :loading="loading" :options="companiesOptions" @update:model-value="setCompanies" />
20
+ <qas-select v-if="hasCompaniesSelect" v-model="companiesModel" class="q-my-md" data-cy="app-user-companies-select" label="Vínculo" :loading="loading" :options="companiesOptions" @update:model-value="setCompanies" />
21
21
 
22
22
  <q-list class="q-mt-md">
23
23
  <q-item v-close-popup :active="false" class="qas-app-user__menu-item" clickable :to="user.to">
@@ -12,26 +12,26 @@
12
12
  </header>
13
13
 
14
14
  <section class="text-body1 text-grey-8">
15
- <component :is="componentTag" ref="form">
15
+ <component :is="componentTag" ref="form" v-bind="componentProps">
16
16
  <slot name="description">
17
17
  <component :is="descriptionComponentTag">{{ card.description }}</component>
18
18
  </slot>
19
+
20
+ <div v-if="!isInfoDialog">
21
+ <slot name="actions">
22
+ <qas-actions v-bind="formattedActionsProps">
23
+ <template v-if="hasOk" #primary>
24
+ <qas-btn v-close-popup="!useForm" class="full-width" variant="primary" v-bind="defaultOk" />
25
+ </template>
26
+
27
+ <template v-if="hasCancel" #secondary>
28
+ <qas-btn v-close-popup class="full-width" v-bind="defaultCancel" variant="secondary" />
29
+ </template>
30
+ </qas-actions>
31
+ </slot>
32
+ </div>
19
33
  </component>
20
34
  </section>
21
-
22
- <footer v-if="!isInfoDialog">
23
- <slot name="actions">
24
- <qas-actions v-bind="formattedActionsProps">
25
- <template v-if="hasOk" #primary>
26
- <qas-btn v-close-popup="!useForm" class="full-width" variant="primary" v-bind="defaultOk" @click="submitHandler" />
27
- </template>
28
-
29
- <template v-if="hasCancel" #secondary>
30
- <qas-btn v-close-popup class="full-width" v-bind="defaultCancel" variant="secondary" />
31
- </template>
32
- </qas-actions>
33
- </slot>
34
- </footer>
35
35
  </div>
36
36
  </q-dialog>
37
37
  </template>
@@ -106,8 +106,13 @@ export default {
106
106
  },
107
107
 
108
108
  emits: [
109
+ // model
109
110
  'update:modelValue',
110
- 'validate'
111
+
112
+ // actions
113
+ 'validate',
114
+ 'ok',
115
+ 'cancel'
111
116
  ],
112
117
 
113
118
  computed: {
@@ -115,7 +120,10 @@ export default {
115
120
  return {
116
121
  label: 'Cancelar',
117
122
  outline: true,
118
- ...this.cancel
123
+
124
+ ...this.cancel,
125
+
126
+ onClick: this.onCancel
119
127
  }
120
128
  },
121
129
 
@@ -123,7 +131,11 @@ export default {
123
131
  return {
124
132
  label: 'Ok',
125
133
  type: this.ok?.type || this.useForm ? 'submit' : 'button',
126
- ...this.ok
134
+
135
+ ...this.ok,
136
+
137
+ // adiciona somente se não estiver usando useForm pois o controle ficará no submit.
138
+ ...(!this.useForm && { onClick: this.onOk })
127
139
  }
128
140
  },
129
141
 
@@ -139,6 +151,16 @@ export default {
139
151
  return this.useForm ? 'q-form' : 'div'
140
152
  },
141
153
 
154
+ componentProps () {
155
+ /**
156
+ * adiciona evento de submit caso useForm seja true,
157
+ * uma vez que somente o q-form possui este evento.
158
+ */
159
+ return {
160
+ ...(this.useForm && { onSubmit: this.onSubmit })
161
+ }
162
+ },
163
+
142
164
  dialogProps () {
143
165
  return {
144
166
  ...(!this.usePlugin && { modelValue: this.modelValue }),
@@ -235,6 +257,29 @@ export default {
235
257
 
236
258
  updateModelValue (value) {
237
259
  this.$emit('update:modelValue', value)
260
+ },
261
+
262
+ onOk () {
263
+ this.ok?.onClick?.()
264
+ this.$emit('ok')
265
+ },
266
+
267
+ onCancel () {
268
+ this.cancel?.onClick?.()
269
+ this.$emit('cancel')
270
+ },
271
+
272
+ /**
273
+ * Sem este método, ao clicar enter com a prop useForm ativada a tela era recarregada,
274
+ * e a ação de click do botão não era chamada pois ele não esta dentro do form.
275
+ */
276
+ onSubmit (event) {
277
+ event.preventDefault()
278
+
279
+ if (this.hasOk) {
280
+ this.onOk()
281
+ this.submitHandler()
282
+ }
238
283
  }
239
284
  }
240
285
  }
@@ -83,3 +83,10 @@ events:
83
83
  value:
84
84
  desc: Retorna se os campos passou ou não na validação
85
85
  type: Boolean
86
+
87
+ '@cancel: -> function ()':
88
+ desc: Dispara toda vez que é clicado no botão "cancel".
89
+
90
+ '@ok: -> function ()':
91
+ desc: Dispara toda vez que é clicado no botão "ok" ou quando useForm for true e o for clicado "enter" estando com foco em algum input (evento de submit).
92
+
@@ -213,6 +213,10 @@ export default {
213
213
  watch: {
214
214
  isSubmitting (value) {
215
215
  this.$emit('update:submitting', value)
216
+ },
217
+
218
+ '$route.path' () {
219
+ this.ignoreRouterGuard = false
216
220
  }
217
221
  },
218
222
 
@@ -374,10 +378,9 @@ export default {
374
378
  if (!this.ignoreKeysInUnsavedChanges.length) return
375
379
 
376
380
  this.ignoreKeysInUnsavedChanges.forEach(key => {
377
- if (!firstValue) return
381
+ if (firstValue) delete firstValue[key]
378
382
 
379
- delete firstValue[key]
380
- delete secondValue[key]
383
+ if (secondValue) delete secondValue[key]
381
384
  })
382
385
  },
383
386
 
@@ -426,14 +429,16 @@ export default {
426
429
  payload
427
430
  })
428
431
 
432
+ const modelValue = { ...this.modelValue, ...response.data.result }
433
+
429
434
  if (this.useDialogOnUnsavedChanges) {
430
- this.cachedResult = extend(true, {}, this.modelValue)
435
+ this.cachedResult = extend(true, {}, modelValue)
431
436
  }
432
437
 
433
438
  this.mx_setErrors()
434
439
  this.$emit('update:errors', this.mx_errors)
435
440
 
436
- NotifySuccess(response.data.status.text || this.defaultNotifyMessages.success)
441
+ this.$emit('update:modelValue', modelValue)
437
442
  this.$emit('submit-success', response, this.modelValue)
438
443
 
439
444
  this.createSubmitSuccessEvent({ ...payload, entity: this.entity })
@@ -441,6 +446,8 @@ export default {
441
446
  this.$qas.logger.group(
442
447
  `QasFormView - submit -> resposta da action ${this.entity}/${this.mode}`, [response]
443
448
  )
449
+
450
+ NotifySuccess(response.data.status.text || this.defaultNotifyMessages.success)
444
451
  } catch (error) {
445
452
  const errors = error?.response?.data?.errors
446
453
  const message = error?.response?.data?.status?.text
@@ -157,17 +157,21 @@ slots:
157
157
  desc: Slot para acessar o header.
158
158
 
159
159
  events:
160
- '@fetch-success -> function(value)':
160
+ '@fetch-success -> function(response, modelValue)':
161
161
  desc: Dispara quando a action "fetchSingle" é executada com sucesso.
162
162
  params:
163
- value:
163
+ response:
164
164
  desc: Retorna todos os dados "cru" respondido pelo fetch.
165
165
  type: Object
166
166
 
167
- '@fetch-error -> function(value)':
167
+ modelValue:
168
+ desc: Retorna o model anterior ao fetch.
169
+ type: Object
170
+
171
+ '@fetch-error -> function(error)':
168
172
  desc: Dispara quando a action "fetchSingle" cai em uma exceção.
169
173
  params:
170
- value:
174
+ error:
171
175
  desc: Retorna todos os dados "cru" respondido na exceção do fetch.
172
176
  type: Object
173
177
 
@@ -212,3 +216,23 @@ events:
212
216
  value:
213
217
  desc: Retorna se está ou não fazendo fetching de dados.
214
218
  type: Boolean
219
+
220
+ '@submit-success -> function(response, modelValue)':
221
+ desc: Dispara quando a action "submit" é executada com sucesso.
222
+ params:
223
+ response:
224
+ desc: Retorna todos os dados "cru" respondido pelo submit.
225
+ type: Object
226
+
227
+ modelValue:
228
+ desc: Retorna o model anterior ao submit.
229
+ type: Object
230
+
231
+ '@submit-error -> function(error)':
232
+ desc: Dispara quando a action "submit" cai em uma exceção.
233
+ params:
234
+ error:
235
+ desc: Retorna todos os dados "cru" respondido na exceção do submit.
236
+ type: Object
237
+
238
+
@@ -0,0 +1,163 @@
1
+ <template>
2
+ <div class="qas-infinite-scroll" :style="containerStyle">
3
+ <q-infinite-scroll
4
+ ref="infiniteScroll"
5
+ v-bind="attributes"
6
+ @load="onLoad"
7
+ >
8
+ <slot />
9
+
10
+ <template #loading>
11
+ <div class="justify-center q-my-md row">
12
+ <q-spinner-dots
13
+ color="primary"
14
+ size="3em"
15
+ />
16
+ </div>
17
+ </template>
18
+ </q-infinite-scroll>
19
+
20
+ <qas-empty-result-text v-if="hasNoResults" />
21
+ </div>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { ref, computed, inject } from 'vue'
26
+ import { NotifyError } from '../../plugins'
27
+
28
+ defineOptions({ name: 'QasInfiniteScroll' })
29
+
30
+ const props = defineProps({
31
+ list: {
32
+ type: Array,
33
+ default: () => []
34
+ },
35
+
36
+ limitPerPage: {
37
+ type: Number,
38
+ default: 12
39
+ },
40
+
41
+ url: {
42
+ type: String,
43
+ default: '',
44
+ required: true
45
+ },
46
+
47
+ params: {
48
+ type: Object,
49
+ default: () => ({})
50
+ },
51
+
52
+ infiniteScrollProps: {
53
+ type: Object,
54
+ default: () => ({})
55
+ },
56
+
57
+ maxHeight: {
58
+ type: String,
59
+ default: ''
60
+ }
61
+ })
62
+
63
+ defineExpose({ refresh, remove })
64
+
65
+ const emits = defineEmits(['update:list'])
66
+
67
+ const axios = inject('axios')
68
+
69
+ const infiniteScroll = ref(null)
70
+
71
+ const hasFetchingError = ref(false)
72
+ const isFetching = ref(false)
73
+ const hasMadeFirstFetch = ref(false)
74
+ const count = ref(0)
75
+ const offset = ref(0)
76
+
77
+ const listLength = computed(() => model.value.length)
78
+
79
+ const attributes = computed(() => ({
80
+ offset: 100,
81
+ debounce: 0,
82
+ ...(props.maxHeight && { scrollTarget: '.qas-infinite-scroll' }),
83
+ ...props.infiniteScrollProps
84
+ }))
85
+
86
+ const isEmptyList = computed(() => !listLength.value && !isFetching.value)
87
+
88
+ const hasNoResults = computed(() => isEmptyList.value && hasMadeFirstFetch.value)
89
+
90
+ const containerStyle = computed(() => ({
91
+ ...(props.maxHeight && { maxHeight: props.maxHeight, overflow: 'auto' })
92
+ }))
93
+
94
+ const model = computed({
95
+ get () {
96
+ return props.list
97
+ },
98
+
99
+ set (newList) {
100
+ emits('update:list', newList)
101
+ }
102
+ })
103
+
104
+ async function onLoad (_, done) {
105
+ const hasMadeFirstFetchAndHasNoData = hasMadeFirstFetch.value && !listLength.value
106
+ const hasFetchAllData = listLength.value && listLength.value >= count.value
107
+ const stop = hasFetchingError.value || isFetching.value || hasMadeFirstFetchAndHasNoData || hasFetchAllData
108
+
109
+ if (stop) {
110
+ infiniteScroll.value.stop()
111
+ } else {
112
+ infiniteScroll.value.resume()
113
+ await fetchList()
114
+ }
115
+
116
+ done()
117
+ }
118
+
119
+ async function fetchList () {
120
+ try {
121
+ isFetching.value = true
122
+
123
+ const { data } = await axios.get(props.url, {
124
+ params: { offset: offset.value, limit: props.limitPerPage, ...props.params }
125
+ })
126
+
127
+ const newList = [...model.value, ...(data.results || [])]
128
+
129
+ model.value = newList
130
+ offset.value = newList.length
131
+ count.value = data.count
132
+
133
+ /**
134
+ * Sinalizar que houve já uma busca, para evitar que onLoad entre em looping,
135
+ * após buscar uma vez e retornar uma lista vazia.
136
+ */
137
+ hasMadeFirstFetch.value = true
138
+ } catch {
139
+ NotifyError('Ops… Não conseguimos acessar as informações. Por favor, tente novamente em alguns minutos.')
140
+
141
+ hasFetchingError.value = true
142
+ } finally {
143
+ isFetching.value = false
144
+ }
145
+ }
146
+
147
+ function refresh () {
148
+ count.value = 0
149
+ offset.value = 0
150
+ model.value = []
151
+
152
+ hasMadeFirstFetch.value = false
153
+
154
+ infiniteScroll.value.reset()
155
+ infiniteScroll.value.resume()
156
+ }
157
+
158
+ function remove (index) {
159
+ model.value.splice(index, 1)
160
+ count.value -= 1
161
+ offset.value -= 1
162
+ }
163
+ </script>
@@ -0,0 +1,49 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente de infinite scroll que implementa o "QInfiniteScroll".
5
+
6
+ props:
7
+ list:
8
+ desc: Model da lista de itens.
9
+ default: []
10
+ type: Array
11
+ examples: [v-model:list="list"]
12
+ model: true
13
+
14
+ limitPerPage:
15
+ desc: é repassado na query da requição sendo responsavel pela quantidade de itens por busca.
16
+ default: 12
17
+ type: Number
18
+
19
+ url:
20
+ desc: URL utilizado para fazer a busca dos itens.
21
+ type: String
22
+
23
+ params:
24
+ desc: Parâmetros passados na requisição de busca dos itens.
25
+ default: {}
26
+ type: Object
27
+ examples: ["{ status: 'is_active' }"]
28
+
29
+ infinite-scroll-props:
30
+ desc: Propriedades repassadas para o QInfiniteScroll
31
+ default: {}
32
+ type: Object
33
+ examples: ["{ offset: 500 }"]
34
+
35
+ max-height:
36
+ desc: Seta a altura do container do infinite scroll, caso queira que o infinite scroll fique em um box por exemplo.
37
+ type: String
38
+
39
+ events:
40
+ '@update:list -> function (newList)':
41
+ desc: Dispara toda vez que é feito uma nova busca e a lista é atualizada.
42
+ params:
43
+ newList:
44
+ desc: Novo valor do list
45
+ type: Array
46
+
47
+ slots:
48
+ default:
49
+ desc: slot para exibir a lista na qual o componente fez a busca.
@@ -2,7 +2,7 @@
2
2
  <div class="qas-tabs-generator">
3
3
  <q-tabs v-model="model" active-color="primary" align="left" :breakpoint="0" content-class="text-grey-8" dense inline-label left-icon="sym_r_chevron_left" outside-arrows right-icon="sym_r_chevron_right">
4
4
  <slot v-for="(tab, key) in formattedTabs" :item="tab" :name="`tab-${tab.value}`">
5
- <q-tab :key="key" v-bind="getTabProps(tab)" class="text-body1" :name="tab.value" no-caps :ripple="false">
5
+ <component :is="tabComponent" :key="key" v-bind="getTabProps(tab)" class="text-body1" :name="tab.value" no-caps :ripple="false">
6
6
  <slot :item="tab" :name="`tab-after-${tab.value}`">
7
7
  <q-icon v-if="tab.icon" :name="tab.icon" size="sm" />
8
8
 
@@ -12,7 +12,7 @@
12
12
  {{ getFormattedLabel(tab) }}
13
13
  </div>
14
14
  </slot>
15
- </q-tab>
15
+ </component>
16
16
  </slot>
17
17
  </q-tabs>
18
18
  </div>
@@ -45,6 +45,10 @@ export default {
45
45
  default: () => ({}),
46
46
  required: true,
47
47
  type: [Object, Array]
48
+ },
49
+
50
+ useRouteTab: {
51
+ type: Boolean
48
52
  }
49
53
  },
50
54
 
@@ -77,6 +81,10 @@ export default {
77
81
 
78
82
  this.$emit('update:modelValue', value)
79
83
  }
84
+ },
85
+
86
+ tabComponent () {
87
+ return this.useRouteTab ? 'q-route-tab' : 'q-tab'
80
88
  }
81
89
  },
82
90
 
@@ -24,6 +24,11 @@ props:
24
24
  type: [Object, Array]
25
25
  examples: ["{ tab1: 'tab1', tab2: 'tab2' }"]
26
26
 
27
+ useRouteTab:
28
+ desc: Quando "true" será utilizado o componente "QRouteTab" do Quasar (https://quasar.dev/vue-components/tabs#qroutetab-api).
29
+ default: false
30
+ type: Boolean
31
+
27
32
  slots:
28
33
  'tab-[nome-da-chave]':
29
34
  desc: Slot dinâmico gerado a partir das chave passada na prop "tabs", substitui todo o "q-tab".
package/src/vue-plugin.js CHANGED
@@ -28,6 +28,7 @@ import QasGallery from './components/gallery/QasGallery.vue'
28
28
  import QasGalleryCard from './components/gallery-card/QasGalleryCard.vue'
29
29
  import QasGridGenerator from './components/grid-generator/QasGridGenerator.vue'
30
30
  import QasHeaderActions from './components/header-actions/QasHeaderActions.vue'
31
+ import QasInfiniteScroll from './components/infinite-scroll/QasInfiniteScroll.vue'
31
32
  import QasInput from './components/input/QasInput.vue'
32
33
  import QasLabel from './components/label/QasLabel.vue'
33
34
  import QasLayout from './components/layout/QasLayout.vue'
@@ -111,6 +112,7 @@ async function install (app) {
111
112
  app.component('QasGalleryCard', QasGalleryCard)
112
113
  app.component('QasGridGenerator', QasGridGenerator)
113
114
  app.component('QasHeaderActions', QasHeaderActions)
115
+ app.component('QasInfiniteScroll', QasInfiniteScroll)
114
116
  app.component('QasInput', QasInput)
115
117
  app.component('QasLabel', QasLabel)
116
118
  app.component('QasLayout', QasLayout)
@@ -192,6 +194,7 @@ export {
192
194
  QasGalleryCard,
193
195
  QasGridGenerator,
194
196
  QasHeaderActions,
197
+ QasInfiniteScroll,
195
198
  QasInput,
196
199
  QasLabel,
197
200
  QasLayout,