@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,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
|
|
4
|
+
<div v-for="(header, index) in headers" :key="index" class="q-mr-sm">
|
|
5
5
|
<qas-box class="q-mb-md">
|
|
6
|
-
<slot :
|
|
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)" :
|
|
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,
|
|
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
|
-
|
|
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: '
|
|
76
|
+
default: '300px'
|
|
76
77
|
},
|
|
77
78
|
|
|
78
|
-
|
|
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.
|
|
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
|
|
117
|
+
const columnsResultsModel = computed({
|
|
106
118
|
get () {
|
|
107
|
-
return props.
|
|
119
|
+
return props.results
|
|
108
120
|
},
|
|
109
121
|
|
|
110
122
|
set (newValues) {
|
|
111
|
-
emit('update:
|
|
123
|
+
emit('update:results', newValues)
|
|
112
124
|
}
|
|
113
125
|
})
|
|
114
126
|
|
|
115
|
-
const hasColumnsLength = computed(() => Object.keys(
|
|
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.
|
|
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)
|
|
186
|
+
if (error) {
|
|
187
|
+
emit('fetch-column-error', error)
|
|
188
|
+
|
|
189
|
+
throw error
|
|
190
|
+
}
|
|
165
191
|
|
|
166
192
|
/*
|
|
167
|
-
* exemplo de como
|
|
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 "
|
|
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
|
-
...
|
|
204
|
+
...columnsResultsModel.value[headerKey] || [],
|
|
179
205
|
...response.data?.results || []
|
|
180
206
|
]
|
|
181
207
|
|
|
182
|
-
|
|
208
|
+
columnsResultsModel.value[headerKey] = props.useMarkRaw ? markRaw(newColumnValues) : newColumnValues
|
|
183
209
|
|
|
184
|
-
|
|
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 ?
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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-
|
|
42
|
-
desc: Define se os valores dos itens das colunas irá ser atribuido utilizando o "
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
58
|
-
desc:
|
|
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
|
-
|
|
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:
|
|
72
|
-
desc: Dispara quando o
|
|
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]"
|
|
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
|
-
|
|
168
|
-
|
|
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
|
|
79
|
-
default:
|
|
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.
|