@bildvitta/quasar-ui-asteroid 3.1.0 → 3.2.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.
@@ -1247,6 +1247,62 @@
1247
1247
  "description": "Nos options, você pode passar um array de objeto com qualquer chave/valor, esta prop serve para identificar qual será o equivalente ao \"value\".",
1248
1248
  "type": "string"
1249
1249
  },
1250
+ "qas-tree-generator/form-generator-props": {
1251
+ "description": "Propriedades enviadas para o \"QasFormGenerator\".",
1252
+ "type": "object"
1253
+ },
1254
+ "qas-tree-generator/form-view-props": {
1255
+ "description": "Propriedades enviadas para o \"QasFormView\".",
1256
+ "type": "object"
1257
+ },
1258
+ "qas-tree-generator/label-key": {
1259
+ "description": "Chave identificadora da label, por padrão o componente considera label, é possível alterar o valor enviado para API através desta chave.",
1260
+ "type": "string"
1261
+ },
1262
+ "qas-tree-generator/lazy-nodes": {
1263
+ "description": "Model do lazy, toda vez que é adicionado/editado/excluído um nó este model é atualizado, ele não é um \"two way data binding\" alterações fora do componente não afetarão o componente.",
1264
+ "type": "array"
1265
+ },
1266
+ "qas-tree-generator/nodes": {
1267
+ "description": "Nested de Array de objeto contendo label, uuid, lazy e children, é a propriedade responsável por montar a árvore, precisa ser enviado toda a árvore de uma vez.",
1268
+ "type": "array"
1269
+ },
1270
+ "qas-tree-generator/readonly": {
1271
+ "description": "Habilita a árvore para modo de visualização somente, não sendo possível adicionar/editar/remover nenhum ramo.",
1272
+ "type": "boolean"
1273
+ },
1274
+ "qas-tree-generator/resource": {
1275
+ "description": "Usado como endpoint da API para adicionar/remover/excluir, quando não utilizar o QasFormView.",
1276
+ "type": "string"
1277
+ },
1278
+ "qas-tree-generator/tree-props": {
1279
+ "description": "Propriedades enviada para o QTree.",
1280
+ "type": "object"
1281
+ },
1282
+ "qas-tree-generator/use-add-button": {
1283
+ "description": "Habilita o botão de adicionar novos ramos.",
1284
+ "type": "boolean"
1285
+ },
1286
+ "qas-tree-generator/use-destroy-button": {
1287
+ "description": "Habilita o botão de remover os ramos.",
1288
+ "type": "boolean"
1289
+ },
1290
+ "qas-tree-generator/use-destroy-on-first-node": {
1291
+ "description": "Habilita o botão de remover o primeiro ramo da árvore.",
1292
+ "type": "boolean"
1293
+ },
1294
+ "qas-tree-generator/use-edit-button": {
1295
+ "description": "Habilita o botão de editar os ramos.",
1296
+ "type": "boolean"
1297
+ },
1298
+ "qas-tree-generator/use-form-view-edit": {
1299
+ "description": "Habilita o form-view como componente a ser renderizado ao abrir o dialog em modo de edição.",
1300
+ "type": "boolean"
1301
+ },
1302
+ "qas-tree-generator/use-form-view-add": {
1303
+ "description": "Habilita o form-view como componente a ser renderizado ao abrir o dialog em modo de adição.",
1304
+ "type": "boolean"
1305
+ },
1250
1306
  "qas-uploader/accept-resize-types": {
1251
1307
  "description": "Tipos de arquivos aceitos para fazer o redimensionamento antes de upar.",
1252
1308
  "type": "array"
@@ -545,6 +545,25 @@
545
545
  ],
546
546
  "description": "Componente para transferir itens entre 2 lista (colunas)."
547
547
  },
548
+ "qas-tree-generator": {
549
+ "attributes": [
550
+ "form-generator-props",
551
+ "form-view-props",
552
+ "label-key",
553
+ "lazy-nodes",
554
+ "nodes",
555
+ "readonly",
556
+ "resource",
557
+ "tree-props",
558
+ "use-add-button",
559
+ "use-destroy-button",
560
+ "use-destroy-on-first-node",
561
+ "use-edit-button",
562
+ "use-form-view-edit",
563
+ "use-form-view-add"
564
+ ],
565
+ "description": "Componente de árvore onde é possível adicionar/editar/excluir sem limites de ramos."
566
+ },
548
567
  "qas-uploader": {
549
568
  "attributes": [
550
569
  "accept-resize-types",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bildvitta/quasar-ui-asteroid",
3
3
  "description": "Asteroid",
4
- "version": "3.1.0",
4
+ "version": "3.2.0-beta.0",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -320,8 +320,16 @@ export default {
320
320
  return this.$emit('update:modelValue', value || this.nested)
321
321
  },
322
322
 
323
+ /*
324
+ * Se o item que for removido não tiver o identificador (uuid por ex) e "useRemoveOnDestroy" for "false"
325
+ * ou "useRemoveOnDestroy" for "true" removemos o item do array, senão adicionamos a flag [destroyKey]
326
+ * no item referente do array.
327
+ *
328
+ * Ex: ao adicionar um item e remover sem salvar, mesmo que useRemoveOnDestroy for false ele será removido
329
+ * ao invés de adicionar a flag [destroyKey]
330
+ */
323
331
  destroy (index, row) {
324
- !row[this.identifierItemKey] && this.useRemoveOnDestroy
332
+ !row[this.identifierItemKey] || this.useRemoveOnDestroy
325
333
  ? this.nested.splice(index, 1)
326
334
  : this.nested.splice(index, 1, { [this.destroyKey]: true, ...row })
327
335
 
@@ -27,6 +27,7 @@ props:
27
27
  desc: Model do componente, controla quais itens estão na segunda coluna.
28
28
  default: []
29
29
  type: Array
30
+ model: true
30
31
 
31
32
  options:
32
33
  desc: Array de objetos com todos items que serão transferidos
@@ -0,0 +1,62 @@
1
+ <!--
2
+ * Este é um componente interno para ser utilizado dentro do componente "QasTreeGenerator"
3
+ * por isto não existe documentação para este componente e nem é exportado para fora do asteroid
4
+ -->
5
+ <template>
6
+ <qas-form-view ref="formView" v-model="values" v-model:errors="errors" v-model:fields="fields" :use-actions="false" :use-boundary="false" v-bind="formViewProps">
7
+ <template #default>
8
+ <qas-form-generator v-model="values" :errors="errors" :fields="fields" v-bind="formGeneratorProps" />
9
+ </template>
10
+ </qas-form-view>
11
+ </template>
12
+
13
+ <script>
14
+ import QasFormGenerator from '../form-generator/QasFormGenerator.vue'
15
+ import QasFormView from '../form-view/QasFormView.vue'
16
+
17
+ export default {
18
+ name: 'QasTreeForm',
19
+
20
+ components: {
21
+ QasFormGenerator,
22
+ QasFormView
23
+ },
24
+
25
+ props: {
26
+ formGeneratorProps: {
27
+ type: Object,
28
+ default: () => ({})
29
+ },
30
+
31
+ formViewProps: {
32
+ type: Object,
33
+ default: () => ({})
34
+ },
35
+
36
+ parent: {
37
+ type: String,
38
+ default: ''
39
+ }
40
+ },
41
+
42
+ data () {
43
+ return {
44
+ errors: {},
45
+ fields: {},
46
+ values: {}
47
+ }
48
+ },
49
+
50
+ watch: {
51
+ parent (parent) {
52
+ Object.assign(this.values, { parent })
53
+ }
54
+ },
55
+
56
+ methods: {
57
+ submit () {
58
+ return this.$refs.formView.submitHandler()
59
+ }
60
+ }
61
+ }
62
+ </script>
@@ -0,0 +1,488 @@
1
+ <template>
2
+ <div class="qas-tree-generator">
3
+ <q-tree ref="tree" v-bind="treeProps" node-key="uuid" :nodes="parsedNodes" @lazy-load="onLazyLoad">
4
+ <template #default-header="{ node, tree }">
5
+ <div>
6
+ <span>
7
+ {{ node.label }}
8
+ </span>
9
+
10
+ <span v-if="hasMenuButton(node)" class="q-ml-sm">
11
+ <qas-btn dense flat icon="o_more_vert" round @click.stop>
12
+ <q-menu auto-close>
13
+ <q-list separator>
14
+ <q-item v-if="useAddButton" v-ripple class="qas-tree-generator__item" clickable @click="handleTreeFormDialog(node, true, tree)">
15
+ <q-item-section avatar>
16
+ <q-icon name="o_add_circle_outline" />
17
+ </q-item-section>
18
+
19
+ <q-item-section>Adicionar subnível</q-item-section>
20
+ </q-item>
21
+
22
+ <q-item v-if="useEditButton" v-ripple class="qas-tree-generator__item" clickable @click="handleTreeFormDialog(node)">
23
+ <q-item-section avatar>
24
+ <q-icon name="o_edit" />
25
+ </q-item-section>
26
+
27
+ <q-item-section>Editar</q-item-section>
28
+ </q-item>
29
+
30
+ <q-item v-if="hasDestroyButton(node)" v-ripple class="qas-tree-generator__item" clickable @click="onDestroy(node)">
31
+ <q-item-section avatar>
32
+ <q-icon name="o_highlight_off" />
33
+ </q-item-section>
34
+
35
+ <q-item-section>Excluir</q-item-section>
36
+ </q-item>
37
+ </q-list>
38
+ </q-menu>
39
+ </qas-btn>
40
+ </span>
41
+ </div>
42
+ </template>
43
+ </q-tree>
44
+
45
+ <qas-dialog v-model="showDestroyDialog" v-bind="destroyDialogConfig" />
46
+
47
+ <qas-dialog v-model="showFormDialog" v-bind="formDialogConfig" use-form @hide="resetModels" @validate="onValidate">
48
+ <template #description>
49
+ <div>
50
+ <qas-field v-if="hasAddField" v-model="editModel" :disable="isAdd" :field="addField" :label="nodeTitle" :rules="[required]" />
51
+
52
+ <qas-nested-fields v-if="hasNestedAdd" v-model="nestedModel" class="q-mt-md" :field="nestedFieldProp" :fields-props="fieldsProps" :form-columns="nestedColumns" :row-object="rowObject" :use-duplicate="false" use-inline-actions />
53
+
54
+ <qas-tree-form v-if="hasFormView" ref="treeForm" v-model:submitting="isSubmitting" :form-generator-props="formGeneratorProps" :form-view-props="defaultFormViewProps" @submit-success="onSubmitSuccess" />
55
+ </div>
56
+ </template>
57
+ </qas-dialog>
58
+ </div>
59
+ </template>
60
+
61
+ <script>
62
+ import destroyNestedChildrenByKey from '../../helpers/destroy-nested-children-by-key.js'
63
+ import findChildrenByKey from '../../helpers/find-children-by-key.js'
64
+ import promiseHandler from '../../helpers/promise-handler.js'
65
+ import { required } from '../../helpers/rules.js'
66
+ import { extend } from 'quasar'
67
+ import axios from 'axios'
68
+
69
+ import QasTreeForm from './QasTreeForm.vue'
70
+
71
+ export default {
72
+ name: 'QasTreeGenerator',
73
+
74
+ components: {
75
+ QasTreeForm
76
+ },
77
+
78
+ props: {
79
+ formGeneratorProps: {
80
+ type: Object,
81
+ default: () => ({})
82
+ },
83
+
84
+ formViewProps: {
85
+ type: Object,
86
+ default: () => ({})
87
+ },
88
+
89
+ labelKey: {
90
+ type: String,
91
+ default: 'name'
92
+ },
93
+
94
+ lazyNodes: {
95
+ type: Array,
96
+ default: () => []
97
+ },
98
+
99
+ nodes: {
100
+ type: Array,
101
+ default: () => []
102
+ },
103
+
104
+ readonly: {
105
+ type: Boolean
106
+ },
107
+
108
+ resource: {
109
+ type: String,
110
+ default: ''
111
+ },
112
+
113
+ treeProps: {
114
+ type: Object,
115
+ default: () => ({})
116
+ },
117
+
118
+ useAddButton: {
119
+ type: Boolean,
120
+ default: true
121
+ },
122
+
123
+ useDestroyButton: {
124
+ type: Boolean,
125
+ default: true
126
+ },
127
+
128
+ useDestroyOnFirstNode: {
129
+ type: Boolean
130
+ },
131
+
132
+ useEditButton: {
133
+ type: Boolean,
134
+ default: true
135
+ },
136
+
137
+ useFormViewEdit: {
138
+ type: Boolean
139
+ },
140
+
141
+ useFormViewAdd: {
142
+ type: Boolean
143
+ }
144
+ },
145
+
146
+ emits: [
147
+ 'update:lazyNodes'
148
+ ],
149
+
150
+ data () {
151
+ return {
152
+ showDestroyDialog: false,
153
+ currentNode: {},
154
+ showFormDialog: false,
155
+ nestedModel: [],
156
+ singleModel: { label: '' },
157
+ editModel: '',
158
+ isAdd: true,
159
+ parsedNodes: [],
160
+ isSubmitting: false
161
+ }
162
+ },
163
+
164
+ computed: {
165
+ addField () {
166
+ return { name: 'add', type: 'text', label: this.nodeTitle }
167
+ },
168
+
169
+ destroyDialogConfig () {
170
+ return {
171
+ card: {
172
+ title: 'Excluir ramo da árvore?',
173
+ description: 'Todas as informações serão perdidas. Deseja realmente continuar?'
174
+ },
175
+ ok: {
176
+ label: 'Excluir',
177
+ onClick: this.destroy
178
+ },
179
+ cancel: {
180
+ onClick: this.resetCurrentNode
181
+ }
182
+ }
183
+ },
184
+
185
+ formDialogConfig () {
186
+ return {
187
+ card: {
188
+ title: this.isAdd ? 'Adicionar ramo' : 'Editar ramo'
189
+ },
190
+ ok: {
191
+ label: 'Salvar',
192
+ loading: this.isSubmitting
193
+ }
194
+ }
195
+ },
196
+
197
+ nestedFieldProp () {
198
+ return {
199
+ type: 'nested',
200
+ label: 'Adicionar itens',
201
+ name: 'nested',
202
+ children: {
203
+ label: {
204
+ type: 'text',
205
+ label: 'Adicionar um novo item',
206
+ name: 'label'
207
+ }
208
+ }
209
+ }
210
+ },
211
+
212
+ rowObject () {
213
+ return { label: '' }
214
+ },
215
+
216
+ nestedColumns () {
217
+ return {
218
+ label: {
219
+ col: 12
220
+ }
221
+ }
222
+ },
223
+
224
+ nodeTitle () {
225
+ if (!this.isAdd) {
226
+ return 'Nome do ramo'
227
+ }
228
+
229
+ return this.currentNode.label || 'ramo'
230
+ },
231
+
232
+ fieldsProps () {
233
+ return {
234
+ label: {
235
+ rules: [required]
236
+ }
237
+ }
238
+ },
239
+
240
+ messages () {
241
+ return {
242
+ error: 'Ops! Erro ao salvar item.',
243
+ success: 'Item salvo com sucesso!'
244
+ }
245
+ },
246
+
247
+ defaultFormViewProps () {
248
+ return {
249
+ ...this.formViewProps,
250
+ ...(!this.isAdd && { customId: this.currentNode.uuid }),
251
+ ...(this.isAdd && { parent: this.currentNode.uuid }),
252
+ mode: this.isAdd ? 'create' : 'replace'
253
+ }
254
+ },
255
+
256
+ hasAddField () {
257
+ return this.isAdd || !this.hasFormView
258
+ },
259
+
260
+ hasNestedAdd () {
261
+ return (!this.hasFormView || !this.useFormViewAdd) && this.isAdd
262
+ },
263
+
264
+ hasFormView () {
265
+ return this.isAdd ? this.useFormViewAdd : this.useFormViewEdit
266
+ },
267
+
268
+ parentsList () {
269
+ return this.nodes.map(({ uuid }) => uuid)
270
+ }
271
+ },
272
+
273
+ watch: {
274
+ nodes: {
275
+ handler () {
276
+ this.setInitialValue()
277
+ },
278
+ immediate: true,
279
+ deep: true
280
+ },
281
+
282
+ parsedNodes: {
283
+ handler (nodes) {
284
+ this.$emit('update:lazyNodes', nodes)
285
+ },
286
+ immediate: true,
287
+ deep: true
288
+ }
289
+ },
290
+
291
+ methods: {
292
+ required,
293
+
294
+ handleTreeFormDialog (node, isAdd) {
295
+ this.showFormDialog = !this.showFormDialog
296
+ this.isAdd = !!isAdd
297
+ this.currentNode = node
298
+
299
+ if (!this.isAdd) {
300
+ this.editModel = node.label
301
+ }
302
+ },
303
+
304
+ async add () {
305
+ const { data, error } = await promiseHandler(
306
+ this.nestedModel.map(({ label }) => {
307
+ return axios.post(this.resource, { parent: this.currentNode.uuid, [this.labelKey]: label, lazy: true })
308
+ }),
309
+ {
310
+ successMessage: this.messages.success,
311
+ errorMessage: this.messages.error
312
+ }
313
+ )
314
+
315
+ if (error) return
316
+
317
+ const nestedResponse = data.map(response => {
318
+ const { result } = response.data
319
+ const { name, uuid } = result
320
+
321
+ return {
322
+ label: name,
323
+ lazy: true,
324
+ uuid
325
+ }
326
+ })
327
+
328
+ // Precisa abrir antes de adicionar para casos de ramos que ainda não foram abertos.
329
+ this.$refs.tree.setExpanded(this.currentNode.uuid, true)
330
+
331
+ if (this.currentNode.children) {
332
+ this.currentNode.children.push(...nestedResponse)
333
+ } else {
334
+ this.currentNode.children = [...nestedResponse]
335
+ }
336
+
337
+ this.$nextTick(() => this.$refs.tree.setExpanded(this.currentNode.uuid, true))
338
+ },
339
+
340
+ async edit () {
341
+ const { uuid } = this.currentNode
342
+
343
+ const { error } = await promiseHandler(
344
+ axios.put(
345
+ `${this.resource}/${uuid}`,
346
+ {
347
+ [this.labelKey]: this.editModel,
348
+ uuid,
349
+ lazy: true
350
+ }
351
+ ),
352
+ {
353
+ successMessage: this.messages.success,
354
+ errorMessage: this.messages.error
355
+ }
356
+ )
357
+
358
+ if (error) return
359
+
360
+ this.currentNode.label = this.editModel
361
+ this.$refs.tree.setExpanded(uuid, true)
362
+ },
363
+
364
+ onDestroy (children) {
365
+ this.showDestroyDialog = !this.showDestroyDialog
366
+ this.currentNode = children
367
+ },
368
+
369
+ async destroy () {
370
+ const { error } = await promiseHandler(
371
+ axios.delete(`${this.resource}/${this.currentNode.uuid}`),
372
+ {
373
+ successMessage: 'Item deletado com sucesso!',
374
+ errorMessage: 'Ops! Não foi possível deletar o item.'
375
+ }
376
+ )
377
+
378
+ if (error) return
379
+
380
+ this.currentNode.destroyed = true
381
+ destroyNestedChildrenByKey(this.parsedNodes)
382
+ },
383
+
384
+ resetCurrentNode () {
385
+ this.currentNode = {}
386
+ },
387
+
388
+ resetModels () {
389
+ this.nestedModel = [{ label: '' }]
390
+ this.singleModel = {}
391
+ this.editModel = ''
392
+ },
393
+
394
+ async onValidate (isValid) {
395
+ if (isValid) {
396
+ const canUseFormViewOnAdd = this.isAdd && this.useFormViewAdd
397
+ const canUseFormViewOnEdit = !this.isAdd && this.useFormViewEdit
398
+
399
+ if (canUseFormViewOnAdd || canUseFormViewOnEdit) {
400
+ this.$refs.treeForm.submit()
401
+ return
402
+ }
403
+
404
+ // espera requisição finalizar para poder adicionar/editar
405
+ this.isAdd ? await this.add() : await this.edit()
406
+ }
407
+
408
+ this.showFormDialog = !isValid
409
+ },
410
+
411
+ /*
412
+ * neste método pode ser implementado uma logica para receber os nós da árvore parcialmente
413
+ * utilizando chamadas de API.
414
+ */
415
+ onLazyLoad ({ key, done }) {
416
+ // retorna um proximo nó caso exista.
417
+ return done(this.findNextNode(this.nodes, key, true) || [])
418
+ },
419
+
420
+ setInitialValue () {
421
+ const { children, ...payload } = { ...this.nodes?.[0] }
422
+ this.parsedNodes = Object.keys(payload).length ? [payload] : []
423
+ },
424
+
425
+ findNextNode (list, key, deleteMode) {
426
+ const children = findChildrenByKey(list, 'uuid', key)?.children
427
+
428
+ if (!children?.length) return []
429
+
430
+ // retorna um proximo nó caso exista.
431
+ return (extend(true, [], children).map(item => {
432
+ if (deleteMode) {
433
+ delete item.children
434
+ }
435
+
436
+ return item
437
+ }))
438
+ },
439
+
440
+ onSubmitSuccess (response) {
441
+ const { data: { result } } = response
442
+
443
+ this.currentNode.label = result[this.labelKey]
444
+ this.showFormDialog = false
445
+ },
446
+
447
+ hasDestroyButton ({ uuid }) {
448
+ if (!this.useDestroyButton) return false
449
+
450
+ return !this.useDestroyOnFirstNode ? !this.parentsList.includes(uuid) : true
451
+ },
452
+
453
+ hasMenuButton (node) {
454
+ if (this.readonly) return false
455
+
456
+ const hasEditOrAddButton = this.useAddButton || this.useEditButton
457
+
458
+ if (!this.useDestroyOnFirstNode) {
459
+ const hasDestroyButton = this.hasDestroyButton(node)
460
+
461
+ return hasEditOrAddButton || hasDestroyButton
462
+ }
463
+
464
+ return hasEditOrAddButton || this.useDestroyButton
465
+ }
466
+ }
467
+ }
468
+ </script>
469
+
470
+ <style lang="scss">
471
+ .qas-tree-generator {
472
+ &__item:hover {
473
+ color: var(--q-primary);
474
+ }
475
+
476
+ .q-tree__node-header {
477
+ margin-top: 16px;
478
+
479
+ &::before {
480
+ top: -16px;
481
+ }
482
+
483
+ &::after {
484
+ top: -16px;
485
+ }
486
+ }
487
+ }
488
+ </style>