@bildvitta/quasar-ui-asteroid 3.11.0 → 3.12.0-beta.1

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.11.0",
4
+ "version": "3.12.0-beta.1",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -115,7 +115,7 @@ export default {
115
115
  },
116
116
 
117
117
  hasCompaniesSelect () {
118
- return this.companiesOptions.length > 1
118
+ return !!this.companiesOptions.length
119
119
  }
120
120
  },
121
121
 
@@ -224,9 +224,14 @@ export default {
224
224
  }
225
225
  },
226
226
 
227
- created () {
228
- this.fetchFilters()
229
- this.watchOnceFields()
227
+ async created () {
228
+ await this.fetchFilters()
229
+
230
+ if (this.useUpdateRoute) {
231
+ this.updateValues()
232
+ this.updateCurrentFilters()
233
+ }
234
+
230
235
  this.handleSearchModelOnCreate()
231
236
  },
232
237
 
@@ -371,18 +376,6 @@ export default {
371
376
  return isMultiple ? [value] : value
372
377
  },
373
378
 
374
- watchOnceFields () {
375
- if (!this.useUpdateRoute) return
376
-
377
- const watchOnce = this.$watch('fields', values => {
378
- if (Object.keys(values || {}).length) {
379
- this.updateValues()
380
- this.updateCurrentFilters()
381
- watchOnce()
382
- }
383
- })
384
- },
385
-
386
379
  handleSearchModelOnCreate () {
387
380
  if (this.useUpdateRoute && !this.useFilterButton) {
388
381
  this.setSearch()
@@ -264,7 +264,7 @@ export default {
264
264
  },
265
265
 
266
266
  setFuse () {
267
- const list = [...this.list]
267
+ const list = this.mx_getOptions(this.list)
268
268
 
269
269
  this.mx_filteredOptions = list
270
270
  this.fuse = new Fuse(list, this.defaultFuseOptions)
@@ -0,0 +1,379 @@
1
+ <template>
2
+ <div class="app-select-list-dialog full-width">
3
+ <header class="flex items-center justify-between no-wrap">
4
+ <qas-label v-bind="labelProps" />
5
+
6
+ <qas-btn
7
+ v-bind="defaultAddButtonProps"
8
+ @click="toggleDialog"
9
+ />
10
+ </header>
11
+
12
+ <div
13
+ v-if="props.description"
14
+ class="q-mt-md text-body1 text-grey-8"
15
+ >
16
+ {{ props.description }}
17
+ </div>
18
+
19
+ <qas-box
20
+ v-if="hasBox"
21
+ class="q-mt-md relative-position"
22
+ >
23
+ <span class="text-grey-9 text-subtitle1">
24
+ {{ props.listLabel }}
25
+ </span>
26
+
27
+ <q-virtual-scroll #default="{ item, index }" class="app-select-list-dialog__list q-mt-md" :items="selectedOptions" separator>
28
+ <q-item class="q-px-none text-body1 text-grey-8">
29
+ <q-item-section>
30
+ {{ item.label }}
31
+ </q-item-section>
32
+
33
+ <q-item-section avatar>
34
+ <qas-btn v-bind="getRemoveButtonProps({ index, option: item })" />
35
+ </q-item-section>
36
+ </q-item>
37
+ </q-virtual-scroll>
38
+
39
+ <q-inner-loading :showing="props.loading">
40
+ <q-spinner
41
+ color="grey"
42
+ size="2em"
43
+ />
44
+ </q-inner-loading>
45
+ </qas-box>
46
+
47
+ <span
48
+ v-if="hasError"
49
+ class="app-select-list-dialog__error"
50
+ >
51
+ {{ errorMessage }}
52
+ </span>
53
+
54
+ <qas-dialog
55
+ v-bind="defaultDialogProps"
56
+ v-model="showDialog"
57
+ >
58
+ <template v-for="(_, name) in $slots" #[name]="context">
59
+ <slot :name="`dialog-${name}`" v-bind="context || {}" />
60
+ </template>
61
+
62
+ <template #description>
63
+ <slot name="dialog-description">
64
+ <div v-if="dialogDescription" class="q-mb-xl text-center">
65
+ {{ dialogDescription }}
66
+ </div>
67
+
68
+ <qas-select-list
69
+ v-model="listModel"
70
+ v-bind="defaultSelectListProps"
71
+ />
72
+ </slot>
73
+ </template>
74
+ </qas-dialog>
75
+ </div>
76
+ </template>
77
+
78
+ <script setup>
79
+ import { computed, ref, watch } from 'vue'
80
+
81
+ defineOptions({ name: 'QasSelectListDialog' })
82
+
83
+ const props = defineProps({
84
+ addButtonProps: {
85
+ type: Object,
86
+ default: () => ({})
87
+ },
88
+
89
+ description: {
90
+ type: String,
91
+ default: ''
92
+ },
93
+
94
+ disable: {
95
+ type: Boolean
96
+ },
97
+
98
+ error: {
99
+ type: [String, Array],
100
+ default: ''
101
+ },
102
+
103
+ dialogProps: {
104
+ type: Object,
105
+ default: () => ({})
106
+ },
107
+
108
+ options: {
109
+ type: Array,
110
+ default: () => []
111
+ },
112
+
113
+ label: {
114
+ type: String,
115
+ default: ''
116
+ },
117
+
118
+ listLabel: {
119
+ type: String,
120
+ default: ''
121
+ },
122
+
123
+ loading: {
124
+ type: Boolean
125
+ },
126
+
127
+ modelValue: {
128
+ type: Array,
129
+ default: () => []
130
+ },
131
+
132
+ selectListProps: {
133
+ type: Object,
134
+ default: () => ({})
135
+ }
136
+ })
137
+
138
+ const emit = defineEmits(['add', 'remove', 'update:modelValue'])
139
+
140
+ const hasError = computed(() => Array.isArray(props.error) ? !!props.error.length : !!props.error)
141
+ const errorMessage = computed(() => Array.isArray(props.error) ? props.error.join(' ') : props.error)
142
+
143
+ const {
144
+ listModel,
145
+ showDialog,
146
+
147
+ defaultDialogProps,
148
+ defaultSelectListProps,
149
+ dialogDescription,
150
+
151
+ toggleDialog
152
+ } = useSelectDialog()
153
+
154
+ const {
155
+ selectedOptions,
156
+
157
+ hasBox,
158
+
159
+ add,
160
+ removeAll,
161
+ remove,
162
+ getRemoveButtonProps
163
+ } = useList()
164
+
165
+ defineExpose({ add, removeAll, remove })
166
+
167
+ const model = ref([...props.modelValue])
168
+
169
+ const defaultAddButtonProps = computed(() => {
170
+ return {
171
+ icon: 'sym_r_add',
172
+ useLabelOnSmallScreen: false,
173
+ ...props.addButtonProps,
174
+ disable: props.disable,
175
+ loading: props.loading
176
+ }
177
+ })
178
+
179
+ const labelProps = computed(() => {
180
+ return {
181
+ label: props.label,
182
+ margin: 'none',
183
+ color: hasError.value ? 'negative' : 'grey-9'
184
+ }
185
+ })
186
+
187
+ const hasLazyLoading = computed(() => !!props.selectListProps?.searchBoxProps?.useLazyLoading)
188
+
189
+ watch(() => props.modelValue, newValue => {
190
+ model.value = [...newValue]
191
+ })
192
+
193
+ function updateModel () {
194
+ emit('update:modelValue', model.value)
195
+ }
196
+
197
+ // ------------------------- composable functions ------------------------------
198
+ function useList () {
199
+ const filteredOptions = ref(props.options)
200
+
201
+ const selectedOptions = computed(() => {
202
+ const options = []
203
+
204
+ model.value.forEach(value => {
205
+ const option = filteredOptions.value.find(option => option.value === value)
206
+
207
+ if (option) return options.push(option)
208
+
209
+ options.push({ label: value })
210
+ })
211
+
212
+ return options
213
+ })
214
+
215
+ watch(() => props.options, options => {
216
+ filteredOptions.value = [...options]
217
+ })
218
+
219
+ const hasBox = computed(() => hasFilteredOptions.value || props.loading)
220
+ const hasFilteredOptions = computed(() => model.value.length)
221
+
222
+ /*
223
+ * caso não passe o value, o valor sera automaticamente o value da option
224
+ */
225
+ function add ({ options = [], value }) {
226
+ const normalizedItems = Array.isArray(options) ? options : [options]
227
+
228
+ /*
229
+ * Validação necessária pois se não estiver com lazyloading e adicionar mais opções, as options vai vir duplicadas
230
+ * com as opções que já foram adicionadas
231
+ */
232
+ if (hasLazyLoading.value) {
233
+ filteredOptions.value.push(...normalizedItems)
234
+ }
235
+
236
+ const newModel = value || normalizedItems.map(item => item.value)
237
+
238
+ model.value.push(...newModel)
239
+
240
+ emit('add', newModel)
241
+
242
+ updateModel()
243
+ }
244
+
245
+ function removeAll () {
246
+ model.value = []
247
+ filteredOptions.value = []
248
+
249
+ updateModel()
250
+ }
251
+
252
+ function remove (value) {
253
+ const index = model.value.findIndex(item => item === value)
254
+ const optionIndex = filteredOptions.value.findIndex(option => option.value === value)
255
+
256
+ if (~index) model.value.splice(index, 1)
257
+ if (~optionIndex) filteredOptions.value.splice(optionIndex, 1)
258
+
259
+ emit('remove', value)
260
+ updateModel()
261
+ }
262
+
263
+ function getRemoveButtonProps ({ option }) {
264
+ return {
265
+ color: 'grey-9',
266
+ icon: 'sym_r_delete',
267
+ variant: 'tertiary',
268
+ disable: props.disable || !!option.disable,
269
+ onClick: () => remove(option.value)
270
+ }
271
+ }
272
+
273
+ return {
274
+ filteredOptions,
275
+ selectedOptions,
276
+
277
+ hasBox,
278
+ hasFilteredOptions,
279
+
280
+ add,
281
+ removeAll,
282
+ remove,
283
+ getRemoveButtonProps
284
+ }
285
+ }
286
+
287
+ function useSelectDialog () {
288
+ const showDialog = ref(false)
289
+ const listModel = ref([])
290
+
291
+ const defaultDialogProps = computed(() => {
292
+ return {
293
+ useFullMaxWidth: true,
294
+
295
+ ...props.dialogProps,
296
+
297
+ onBeforeShow: event => {
298
+ resetListModel()
299
+
300
+ props.dialogProps.onBeforeShow && props.dialogProps.onBeforeShow(event)
301
+ },
302
+
303
+ ok: {
304
+ label: 'Adicionar',
305
+
306
+ disable: !listModel.value.length,
307
+
308
+ ...props.dialogProps.ok,
309
+
310
+ onClick: () => {
311
+ props.dialogProps.ok?.onClick?.()
312
+ onAdd()
313
+ }
314
+ }
315
+ }
316
+ })
317
+
318
+ const defaultSelectListProps = computed(() => {
319
+ return {
320
+ emitValue: false,
321
+
322
+ ...props.selectListProps,
323
+
324
+ searchBoxProps: {
325
+ list: props.options,
326
+ height: '160px',
327
+ optionsToExclude: model.value,
328
+
329
+ ...props.selectListProps.searchBoxProps
330
+ }
331
+ }
332
+ })
333
+
334
+ const dialogDescription = computed(() => props.dialogProps.card?.description)
335
+
336
+ function toggleDialog () {
337
+ showDialog.value = !showDialog.value
338
+ }
339
+
340
+ function resetListModel () {
341
+ listModel.value = []
342
+ }
343
+
344
+ function onAdd () {
345
+ if (listModel.value.length) add({ options: listModel.value })
346
+ }
347
+
348
+ return {
349
+ listModel,
350
+ showDialog,
351
+
352
+ defaultDialogProps,
353
+ defaultSelectListProps,
354
+ dialogDescription,
355
+
356
+ toggleDialog
357
+ }
358
+ }
359
+ </script>
360
+
361
+ <style lang="scss">
362
+ .app-select-list-dialog {
363
+ .q-item {
364
+ @include set-typography($body1)
365
+ }
366
+
367
+ // simulando q-field--error
368
+ &__error {
369
+ padding-top: var(--qas-spacing-sm);
370
+ color: var(--q-negative) !important;
371
+ font-size: 12px;
372
+ }
373
+
374
+ &__list {
375
+ max-height: 250px;
376
+ overflow-y: auto;
377
+ }
378
+ }
379
+ </style>
@@ -0,0 +1,103 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente de seleção de itens dentro do QasDialog.
5
+
6
+ props:
7
+ add-button-props:
8
+ desc: Props repassadas para o botão de adicionar (QasBtn)
9
+ default: {}
10
+ type: Object
11
+
12
+ description:
13
+ desc: Descrição acima da lista de itens selecionados.
14
+ default: ''
15
+ type: String
16
+
17
+ disable:
18
+ desc: Desabilita o componente (remoção e adição).
19
+ default: false
20
+ type: Boolean
21
+
22
+ dialog-props:
23
+ desc: Propriedades repassadas para o dialog (QasDialog).
24
+ default: {}
25
+ type: Object
26
+
27
+ error:
28
+ desc: Mensagem de erro.
29
+ default: ''
30
+ type: String
31
+
32
+ options:
33
+ desc: Opções de itens disponíveis para seleção.
34
+ default: []
35
+ type: Array
36
+
37
+ label:
38
+ desc: Label do componente.
39
+ default: ''
40
+ type: String
41
+
42
+ list-label:
43
+ desc: Label da acima dos itens selecionados.
44
+ default: ''
45
+ type: String
46
+
47
+ loading:
48
+ desc: Estado de carregamento do componente.
49
+ default: false
50
+ type: Boolean
51
+
52
+ model-value:
53
+ desc: Model do componente.
54
+ default: []
55
+ type: Array
56
+ model: true
57
+
58
+ select-list-props:
59
+ desc: Propriedades repassadas para o componente QasSelectList.
60
+ default: []
61
+ type: Array
62
+
63
+ slots:
64
+ dialog-actions:
65
+ desc: Slot para substituir ações do dialog.
66
+
67
+ dialog-description:
68
+ desc: Slot para substituir a descrição do dialog.
69
+
70
+ dialog-header:
71
+ desc: Slot para substituir cabeçalho do dialog.
72
+
73
+ events:
74
+ 'add -> function(list)':
75
+ desc: Dispara toda vez que é adicionado novos itens.
76
+ params:
77
+ list:
78
+ desc: Lista de itens adicionados.
79
+ type: Array
80
+
81
+ 'remove -> function(value)':
82
+ desc: Dispara toda vez que é removido um item.
83
+ params:
84
+ value:
85
+ desc: Valor do item (uuid/id/slug).
86
+ type: String
87
+
88
+ 'update:model-value -> function(list)':
89
+ desc: Dispara toda vez que o model é atualizado.
90
+ params:
91
+ list:
92
+ desc: Lista de itens selecionados.
93
+ type: Array
94
+
95
+ methods:
96
+ 'add ({ options = [], value }) => undefined':
97
+ desc: Adiciona itens a lista de selecionados, é preciso adicionar quais opções são referentes ao model.
98
+
99
+ 'remove (value) => undefined':
100
+ desc: remove um item da lista de selecionados passando o valor referente a ele na lista.
101
+
102
+ 'removeAll () => undefined':
103
+ desc: remove todos os itens da lista de selecionados.
@@ -1,16 +1,26 @@
1
1
  <template>
2
- <div ref="parent" class="full-width">
3
- <div class="justify-between no-wrap row text-no-wrap">
4
- <div ref="truncate" :class="truncateTextClass">
5
- <slot>{{ text }}</slot>
2
+ <div ref="parent" :class="classes">
3
+ <div class="no-wrap row text-no-wrap">
4
+ <div ref="truncate" class="ellipsis">
5
+ <slot>{{ displayText }}</slot>
6
6
  </div>
7
7
 
8
- <div v-if="isTruncated" class="cursor-pointer text-primary" @click.stop.prevent="toggle">
9
- {{ seeMoreLabel }}
10
- </div>
8
+ <qas-btn v-if="hasButton" class="q-ml-sm" :label="buttonLabel" @click.stop.prevent="toggle" />
11
9
  </div>
12
10
 
13
- <qas-dialog v-model="show" v-bind="defaultProps" aria-label="Diálogo de texto completo" role="dialog" />
11
+ <qas-dialog v-model="show" v-bind="defaultProps" aria-label="Diálogo de texto completo" role="dialog">
12
+ <template v-if="isCounterMode" #description>
13
+ <div class="q-col-gutter-y-md row">
14
+ <div
15
+ v-for="(item, index) in normalizedList"
16
+ :key="index"
17
+ class="col-12"
18
+ >
19
+ {{ item }}
20
+ </div>
21
+ </div>
22
+ </template>
23
+ </qas-dialog>
14
24
  </div>
15
25
  </template>
16
26
 
@@ -23,8 +33,6 @@ import {
23
33
  watch
24
34
  } from 'vue'
25
35
 
26
- import useScreen from '../../composables/use-screen'
27
-
28
36
  import QasDialog from '../dialog/QasDialog.vue'
29
37
 
30
38
  // define component name
@@ -32,6 +40,11 @@ defineOptions({ name: 'QasTextTruncate' })
32
40
 
33
41
  // props
34
42
  const props = defineProps({
43
+ color: {
44
+ type: String,
45
+ default: 'grey-8'
46
+ },
47
+
35
48
  dialogProps: {
36
49
  type: Object,
37
50
  default: () => ({ persistent: false })
@@ -47,6 +60,11 @@ const props = defineProps({
47
60
  default: 0
48
61
  },
49
62
 
63
+ maxVisibleItem: {
64
+ type: Number,
65
+ default: 1
66
+ },
67
+
50
68
  seeMoreLabel: {
51
69
  type: String,
52
70
  default: 'Ver mais'
@@ -55,6 +73,20 @@ const props = defineProps({
55
73
  text: {
56
74
  type: String,
57
75
  default: ''
76
+ },
77
+
78
+ typography: {
79
+ type: String,
80
+ default: 'body1'
81
+ },
82
+
83
+ list: {
84
+ type: Array,
85
+ default: () => []
86
+ },
87
+
88
+ useObjectList: {
89
+ type: Boolean
58
90
  }
59
91
  })
60
92
 
@@ -66,8 +98,6 @@ const parent = ref(null)
66
98
  const {
67
99
  textContent,
68
100
  isTruncated,
69
- truncateTextClass,
70
-
71
101
  truncateText
72
102
  } = useTruncate({ parent, props })
73
103
 
@@ -77,8 +107,18 @@ const {
77
107
  toggle
78
108
  } = useDialog({ props, textContent })
79
109
 
110
+ const {
111
+ buttonLabel,
112
+ displayText,
113
+ hasButton,
114
+ isCounterMode,
115
+ normalizedList
116
+ } = useTemplate()
117
+
80
118
  useMutationObserver({ truncate, callbackFn: truncateText })
81
119
 
120
+ const classes = computed(() => [`text-${props.color}`, `text-${props.typography}`])
121
+
82
122
  // composable functions
83
123
  function useDialog ({ props, textContent }) {
84
124
  // reactive vars
@@ -136,9 +176,6 @@ function useMutationObserver ({ truncate, callbackFn = () => {} }) {
136
176
  }
137
177
 
138
178
  function useTruncate ({ parent, props }) {
139
- // global
140
- const screen = useScreen()
141
-
142
179
  // reactive vars
143
180
  const maxPossibleWidth = ref('')
144
181
  const textContent = ref('')
@@ -153,10 +190,6 @@ function useTruncate ({ parent, props }) {
153
190
  // computed
154
191
  const isTruncated = computed(() => textWidth.value > maxPossibleWidth.value)
155
192
 
156
- const truncateTextClass = computed(() => {
157
- return (isTruncated.value || screen.isSmall) && 'ellipsis q-pr-sm'
158
- })
159
-
160
193
  // methods
161
194
  function truncateText () {
162
195
  parent.value.style.maxWidth = '100%'
@@ -174,9 +207,61 @@ function useTruncate ({ parent, props }) {
174
207
  textWidth,
175
208
 
176
209
  isTruncated,
177
- truncateTextClass,
178
210
 
179
211
  truncateText
180
212
  }
181
213
  }
214
+
215
+ function useTemplate () {
216
+ const {
217
+ counterLabel,
218
+ normalizedList,
219
+ normalizedCounterText
220
+ } = useCounter()
221
+
222
+ const isCounterMode = computed(() => !!props.list.length)
223
+
224
+ const hasButton = computed(() => {
225
+ return isCounterMode.value ? normalizedList.value.length > props.maxVisibleItem : isTruncated.value
226
+ })
227
+
228
+ const displayText = computed(() => {
229
+ return isCounterMode.value ? normalizedCounterText.value : props.text
230
+ })
231
+
232
+ const buttonLabel = computed(() => {
233
+ return isCounterMode.value ? counterLabel.value : props.seeMoreLabel
234
+ })
235
+
236
+ return {
237
+ buttonLabel,
238
+ displayText,
239
+ hasButton,
240
+ isCounterMode,
241
+ normalizedList
242
+ }
243
+ }
244
+
245
+ function useCounter () {
246
+ const normalizedList = computed(() => {
247
+ return props.useObjectList ? props.list.map(({ label }) => label) : props.list
248
+ })
249
+
250
+ const counterText = computed(() => {
251
+ return props.maxVisibleItem > 1
252
+ ? normalizedList.value.slice(0, props.maxVisibleItem).join(', ')
253
+ : normalizedList.value[0]
254
+ })
255
+
256
+ const normalizedCounterText = computed(() => counterText.value || '-')
257
+
258
+ const counter = computed(() => (normalizedList.value.length || 1) - props.maxVisibleItem)
259
+ const counterLabel = computed(() => `+${counter.value}`)
260
+
261
+ return {
262
+ normalizedList,
263
+ normalizedCounterText,
264
+ counterLabel
265
+ }
266
+ }
182
267
  </script>
@@ -4,6 +4,11 @@ meta:
4
4
  desc: Trunca um texto baseado no tamanho do elemento pai e adiciona um rotulo "ver mais" que quando clicado mostra um dialog com o texto original completo (sem ser truncado).
5
5
 
6
6
  props:
7
+ color:
8
+ desc: Cor do texto.
9
+ type: String
10
+ default: grey-8
11
+
7
12
  dialog-props:
8
13
  desc: Repassa todas props e eventos para o componente `QasDialog`.
9
14
  type: Object
@@ -18,6 +23,11 @@ props:
18
23
  type: Number
19
24
  default: 0
20
25
 
26
+ max-visible-item:
27
+ desc: Quantidade de itens a serem exibidos fora do dialog (uso junto a prop list).
28
+ type: Number
29
+ default: 0
30
+
21
31
  see-more-label:
22
32
  desc: Texto que vai aparecer para ser clicado quando o texto original for truncado.
23
33
  type: String
@@ -27,6 +37,20 @@ props:
27
37
  desc: Texto a ser truncado.
28
38
  type: String
29
39
 
40
+ typography:
41
+ desc: Tipografia.
42
+ default: body1
43
+ type: String
44
+
45
+ list:
46
+ desc: Lista de itens a serem exibidos (uso junto a prop list).
47
+ default: []
48
+ type: Array
49
+
50
+ use-object-list:
51
+ desc: Utiliza a propriedade "list" como array de objeto contendo label (uso junto a prop list).
52
+ type: Boolean
53
+
30
54
  slots:
31
55
  default:
32
56
  desc: slot padrão que é utilizado para acessar o texto original (tanto o que é truncado quando o de dentro do dialog)
@@ -0,0 +1,140 @@
1
+ <template>
2
+ <q-timeline
3
+ class="qas-timeline"
4
+ color="grey-6"
5
+ layout="comfortable"
6
+ >
7
+ <template
8
+ v-for="(item, index) in list"
9
+ :key="`timeline-${index}-${uid()}`"
10
+ >
11
+ <q-timeline-entry
12
+ side="left"
13
+ :subtitle="getFormattedValue(item, Masks.Date)"
14
+ >
15
+ <slot :item="item">
16
+ <div class="column justify-center q-gutter-sm q-py-md qas-timeline__content">
17
+ <slot :item="item" name="hour">
18
+ <div class="text-body2 text-grey-8">
19
+ Adicionado às {{ getFormattedValue(item, Masks.Hour) }}
20
+ </div>
21
+ </slot>
22
+
23
+ <slot :item="item" name="description">
24
+ <div class="text-body1 text-grey-9">
25
+ {{ item[descriptionKey] }}
26
+ </div>
27
+ </slot>
28
+ </div>
29
+ </slot>
30
+ </q-timeline-entry>
31
+ </template>
32
+ </q-timeline>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { date as dateFn } from '../../helpers/filters'
37
+ import { uid } from 'quasar'
38
+
39
+ defineOptions({ name: 'QasTimeline' })
40
+
41
+ const Masks = {
42
+ Hour: 'HH:mm',
43
+ Date: 'dd MMM yyyy'
44
+ }
45
+
46
+ const props = defineProps({
47
+ list: {
48
+ type: Array,
49
+ default: () => []
50
+ },
51
+
52
+ dateKey: {
53
+ type: String,
54
+ default: 'date'
55
+ },
56
+
57
+ hourKey: {
58
+ type: String,
59
+ default: 'date'
60
+ },
61
+
62
+ descriptionKey: {
63
+ type: String,
64
+ default: 'description'
65
+ }
66
+ })
67
+
68
+ function isInvalidDate (date) {
69
+ const day = new Date(date).getDay()
70
+
71
+ return isNaN(day)
72
+ }
73
+
74
+ function getFormattedValue (item, mask) {
75
+ const itemKey = mask === Masks.Hour ? props.hourKey : props.dateKey
76
+
77
+ const date = item[itemKey]
78
+
79
+ return isInvalidDate(date) ? date : dateFn(date, mask)
80
+ }
81
+ </script>
82
+
83
+ <style lang="scss">
84
+ .qas-timeline {
85
+ margin: 0;
86
+
87
+ &__content {
88
+ min-height: 100px;
89
+ }
90
+
91
+ .q-timeline__subtitle {
92
+ color: $dark;
93
+ opacity: initial;
94
+ padding-right: 0;
95
+ position: relative;
96
+ text-align: center;
97
+ vertical-align: middle;
98
+ width: 80px;
99
+
100
+ &::before {
101
+ background-color: currentColor;
102
+ bottom: 0;
103
+ content: "";
104
+ display: block;
105
+ opacity: 0.4;
106
+ position: absolute;
107
+ right: -30px;
108
+ top: 0px;
109
+ transition: opacity var(--qas-generic-transition) ease;
110
+ width: 3px;
111
+ }
112
+ }
113
+
114
+ .q-timeline__entry {
115
+ &:hover,
116
+ .active {
117
+ .q-timeline__subtitle {
118
+ &::before {
119
+ background-color: $primary;
120
+ opacity: 1;
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ .q-timeline__content {
127
+ padding-bottom: 0;
128
+ vertical-align: middle;
129
+ }
130
+
131
+ .q-timeline__title {
132
+ margin-bottom: 0;
133
+ }
134
+
135
+ .q-timeline__dot::before,
136
+ .q-timeline__dot::after {
137
+ display: none;
138
+ }
139
+ }
140
+ </style>
@@ -0,0 +1,36 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente para timeline que implementa o "QTimeline".
5
+
6
+ props:
7
+ list:
8
+ desc: Lista de itens com datas e descrições que serão exibidios na timeline.
9
+ default: []
10
+ type: Array
11
+ examples: ["[{ date: '2023-01-11T14:58:40.000000Z', description: 'Descrição' }"]
12
+
13
+ dateKey:
14
+ desc: Chave do campo que será exibido à esquerda da timeline.
15
+ default: date
16
+ type: String
17
+
18
+ hourKey:
19
+ desc: Chave do campo que será exibido acima da descrição.
20
+ default: date
21
+ type: String
22
+
23
+ descriptionKey:
24
+ desc: Chave do campo que será exibido na descrição.
25
+ default: description
26
+ type: String
27
+
28
+ slots:
29
+ default:
30
+ desc: slot para substituir o conteúdo todo da timeline.
31
+
32
+ hour:
33
+ desc: slot para substituir o conteúdo acima da descrição.
34
+
35
+ description:
36
+ desc: slot para substituir o conteúdo da descrição.
@@ -1,3 +1,5 @@
1
- export { default as useHistory } from './use-history.js'
1
+ export { default as useContext } from './use-context.js'
2
2
  export { default as useForm } from './use-form.js'
3
+ export { default as useHistory } from './use-history.js'
4
+ export { default as useQueryCache } from './use-query-cache.js'
3
5
  export { default as useScreen } from './use-screen.js'
@@ -0,0 +1,15 @@
1
+ import { computed } from 'vue'
2
+ import { useRoute } from 'vue-router'
3
+
4
+ export default function () {
5
+ const route = useRoute()
6
+
7
+ const context = computed(() => {
8
+ const { limit, ordering, page, search, ...filters } = route.query
9
+ return { filters, limit, ordering, page: page ? parseInt(page) : 1, search }
10
+ })
11
+
12
+ return {
13
+ context
14
+ }
15
+ }
@@ -0,0 +1,53 @@
1
+ import { SessionStorage } from 'quasar'
2
+ import { filterObject } from '../helpers'
3
+
4
+ const cachedFilters = SessionStorage.getItem('cachedFilters') || {}
5
+
6
+ function updateSessionStorage () {
7
+ SessionStorage.set('cachedFilters', cachedFilters)
8
+ }
9
+
10
+ export default function () {
11
+ function addOne (key, { label, value }) {
12
+ cachedFilters[key][label] = value
13
+ updateSessionStorage()
14
+ }
15
+
16
+ function addMany (key, filters) {
17
+ cachedFilters[key] = filters
18
+ updateSessionStorage()
19
+ }
20
+
21
+ function findOne (key, filter) {
22
+ return cachedFilters[key][filter]
23
+ }
24
+
25
+ function findAll (key) {
26
+ return cachedFilters[key]
27
+ }
28
+
29
+ function clearOne (key, filter) {
30
+ delete cachedFilters[key][filter]
31
+ updateSessionStorage()
32
+ }
33
+
34
+ function clearAll (key, { exclude = [] } = {}) {
35
+ if (exclude.length) {
36
+ cachedFilters[key] = filterObject(cachedFilters[key], exclude)
37
+ } else {
38
+ delete cachedFilters[key]
39
+ }
40
+
41
+ updateSessionStorage()
42
+ }
43
+
44
+ return {
45
+ addOne,
46
+ addMany,
47
+ findOne,
48
+ findAll,
49
+ clearOne,
50
+ clearAll,
51
+ cachedFilters
52
+ }
53
+ }
package/src/vue-plugin.js CHANGED
@@ -21,7 +21,6 @@ import QasDialog from './components/dialog/QasDialog.vue'
21
21
  import QasDialogRouter from './components/dialog-router/QasDialogRouter.vue'
22
22
  import QasEmptyResultText from './components/empty-result-text/QasEmptyResultText.vue'
23
23
  import QasField from './components/field/QasField.vue'
24
- import QasSearchInput from './components/search-input/QasSearchInput.vue'
25
24
  import QasFilters from './components/filters/QasFilters.vue'
26
25
  import QasFormGenerator from './components/form-generator/QasFormGenerator.vue'
27
26
  import QasFormView from './components/form-view/QasFormView.vue'
@@ -44,8 +43,10 @@ import QasPasswordStrengthChecker from './components/password-strength-checker/Q
44
43
  import QasProfile from './components/profile/QasProfile.vue'
45
44
  import QasResizer from './components/resizer/QasResizer.vue'
46
45
  import QasSearchBox from './components/search-box/QasSearchBox.vue'
46
+ import QasSearchInput from './components/search-input/QasSearchInput.vue'
47
47
  import QasSelect from './components/select/QasSelect.vue'
48
48
  import QasSelectList from './components/select-list/QasSelectList.vue'
49
+ import QasSelectListDialog from './components/select-list-dialog/QasSelectListDialog.vue'
49
50
  import QasSignaturePad from './components/signature-pad/QasSignaturePad.vue'
50
51
  import QasSignatureUploader from './components/signature-uploader/QasSignatureUploader.vue'
51
52
  import QasSingleView from './components/single-view/QasSingleView.vue'
@@ -54,6 +55,7 @@ import QasStatus from './components/status/QasStatus.vue'
54
55
  import QasTableGenerator from './components/table-generator/QasTableGenerator.vue'
55
56
  import QasTabsGenerator from './components/tabs-generator/QasTabsGenerator.vue'
56
57
  import QasTextTruncate from './components/text-truncate/QasTextTruncate.vue'
58
+ import QasTimeline from './components/timeline/QasTimeline.vue'
57
59
  import QasTransfer from './components/transfer/QasTransfer.vue'
58
60
  import QasTreeGenerator from './components/tree-generator/QasTreeGenerator.vue'
59
61
  import QasUploader from './components/uploader/QasUploader.vue'
@@ -102,7 +104,6 @@ async function install (app) {
102
104
  app.component('QasDialogRouter', QasDialogRouter)
103
105
  app.component('QasEmptyResultText', QasEmptyResultText)
104
106
  app.component('QasField', QasField)
105
- app.component('QasSearchInput', QasSearchInput)
106
107
  app.component('QasFilters', QasFilters)
107
108
  app.component('QasFormGenerator', QasFormGenerator)
108
109
  app.component('QasFormView', QasFormView)
@@ -125,8 +126,10 @@ async function install (app) {
125
126
  app.component('QasProfile', QasProfile)
126
127
  app.component('QasResizer', QasResizer)
127
128
  app.component('QasSearchBox', QasSearchBox)
129
+ app.component('QasSearchInput', QasSearchInput)
128
130
  app.component('QasSelect', QasSelect)
129
131
  app.component('QasSelectList', QasSelectList)
132
+ app.component('QasSelectListDialog', QasSelectListDialog)
130
133
  app.component('QasSignaturePad', QasSignaturePad)
131
134
  app.component('QasSignatureUploader', QasSignatureUploader)
132
135
  app.component('QasSingleView', QasSingleView)
@@ -135,6 +138,7 @@ async function install (app) {
135
138
  app.component('QasTableGenerator', QasTableGenerator)
136
139
  app.component('QasTabsGenerator', QasTabsGenerator)
137
140
  app.component('QasTextTruncate', QasTextTruncate)
141
+ app.component('QasTimeline', QasTimeline)
138
142
  app.component('QasTransfer', QasTransfer)
139
143
  app.component('QasTreeGenerator', QasTreeGenerator)
140
144
  app.component('QasUploader', QasUploader)
@@ -181,7 +185,6 @@ export {
181
185
  QasDialogRouter,
182
186
  QasEmptyResultText,
183
187
  QasField,
184
- QasSearchInput,
185
188
  QasFilters,
186
189
  QasFormGenerator,
187
190
  QasFormView,
@@ -204,8 +207,10 @@ export {
204
207
  QasProfile,
205
208
  QasResizer,
206
209
  QasSearchBox,
210
+ QasSearchInput,
207
211
  QasSelect,
208
212
  QasSelectList,
213
+ QasSelectListDialog,
209
214
  QasSignaturePad,
210
215
  QasSignatureUploader,
211
216
  QasSingleView,
@@ -214,6 +219,7 @@ export {
214
219
  QasTableGenerator,
215
220
  QasTabsGenerator,
216
221
  QasTextTruncate,
222
+ QasTimeline,
217
223
  QasTransfer,
218
224
  QasTreeGenerator,
219
225
  QasUploader,