@bildvitta/quasar-ui-asteroid 3.17.0-beta.20 → 3.17.0-beta.21
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 +1 -1
- package/src/assets/sounds/nave-notification.mp3 +0 -0
- package/src/asteroid.js +4 -4
- package/src/components/actions/QasActions.vue +81 -81
- package/src/components/actions/QasActions.yml +34 -34
- package/src/components/actions-menu/QasActionsMenu.vue +251 -251
- package/src/components/actions-menu/QasActionsMenu.yml +89 -89
- package/src/components/actions-menu/composables/use-delete.js +30 -30
- package/src/components/actions-menu/composables/use-double-split-actions.js +42 -42
- package/src/components/actions-menu/composables/use-options-actions.js +27 -27
- package/src/components/actions-menu/composables/use-single-action.js +17 -17
- package/src/components/actions-menu/composables/use-single-split-actions.js +35 -35
- package/src/components/actions-menu/composables/use-tooltips.js +43 -43
- package/src/components/actions-menu/utils/get-label.js +3 -3
- package/src/components/actions-menu/utils/set-click-handler.js +6 -6
- package/src/components/alert/QasAlert.test.js +43 -43
- package/src/components/alert/QasAlert.vue +110 -110
- package/src/components/alert/QasAlert.yml +39 -39
- package/src/components/app-bar/QasAppBar.vue +119 -119
- package/src/components/app-bar/QasAppBar.yml +44 -44
- package/src/components/app-menu/QasAppMenu.vue +392 -392
- package/src/components/app-menu/QasAppMenu.yml +73 -73
- package/src/components/app-menu/composables/use-app-menu-dropdown.js +71 -71
- package/src/components/app-menu/composables/use-app-user.js +49 -49
- package/src/components/app-menu/composables/use-development-badge.js +23 -23
- package/src/components/app-menu/private/PvAppMenuDropdown.vue +71 -71
- package/src/components/app-menu/private/PvAppMenuHelpChat.vue +222 -222
- package/src/components/app-user/QasAppUser.vue +281 -281
- package/src/components/app-user/QasAppUser.yml +56 -56
- package/src/components/avatar/QasAvatar.vue +95 -95
- package/src/components/avatar/QasAvatar.yml +38 -38
- package/src/components/avatar/enums/AvatarColors.js +10 -10
- package/src/components/badge/QasBadge.vue +87 -87
- package/src/components/badge/QasBadge.yml +55 -55
- package/src/components/board-generator/QasBoardGenerator.vue +712 -712
- package/src/components/board-generator/QasBoardGenerator.yml +167 -167
- package/src/components/box/QasBox.vue +51 -51
- package/src/components/box/QasBox.yml +29 -29
- package/src/components/breakline/QasBreakline.vue +38 -38
- package/src/components/breakline/QasBreakline.yml +25 -25
- package/src/components/btn/QasBtn.vue +148 -148
- package/src/components/btn/QasBtn.yml +48 -48
- package/src/components/btn-dropdown/QasBtnDropdown.vue +146 -146
- package/src/components/btn-dropdown/QasBtnDropdown.yml +59 -59
- package/src/components/card/QasCard.vue +132 -132
- package/src/components/card/QasCard.yml +44 -44
- package/src/components/card-image/QasCardImage.vue +95 -95
- package/src/components/card-image/QasCardImage.yml +48 -48
- package/src/components/chart-view/QasChartView.vue +441 -441
- package/src/components/chart-view/QasChartView.yml +109 -109
- package/src/components/chart-view/config/charts/bar.js +45 -45
- package/src/components/chart-view/config/charts/doughnut.js +43 -43
- package/src/components/chart-view/config/charts/index.js +9 -9
- package/src/components/chart-view/config/charts/line.js +57 -57
- package/src/components/chart-view/config/defaults/colors.js +19 -19
- package/src/components/chart-view/config/defaults/font.js +5 -5
- package/src/components/chart-view/config/defaults/index.js +2 -2
- package/src/components/chart-view/config/index.js +3 -3
- package/src/components/chart-view/config/plugins/index.js +3 -3
- package/src/components/chart-view/config/plugins/legend.js +9 -9
- package/src/components/chart-view/config/plugins/tooltip.js +15 -15
- package/src/components/chart-view/config/plugins/zoom.js +31 -31
- package/src/components/checkbox/QasCheckbox.vue +150 -150
- package/src/components/checkbox/QasCheckbox.yml +36 -36
- package/src/components/copy/QasCopy.vue +46 -46
- package/src/components/copy/QasCopy.yml +23 -23
- package/src/components/date/QasDate.vue +600 -600
- package/src/components/date/QasDate.yml +69 -69
- package/src/components/date/enums/DateMaskOptions.js +6 -6
- package/src/components/date-time-input/QasDateTimeInput.vue +269 -269
- package/src/components/date-time-input/QasDateTimeInput.yml +64 -64
- package/src/components/debugger/QasDebugger.vue +31 -31
- package/src/components/debugger/QasDebugger.yml +10 -10
- package/src/components/delete/QasDelete.vue +100 -100
- package/src/components/delete/QasDelete.yml +60 -60
- package/src/components/dialog/QasDialog.vue +217 -217
- package/src/components/dialog/QasDialog.yml +117 -117
- package/src/components/dialog/composables/use-cancel.js +40 -40
- package/src/components/dialog/composables/use-dynamic-components.js +86 -86
- package/src/components/dialog/composables/use-ok.js +45 -45
- package/src/components/dialog-router/QasDialogRouter.vue +86 -86
- package/src/components/dialog-router/QasDialogRouter.yml +23 -23
- package/src/components/drawer/QasDrawer.vue +118 -118
- package/src/components/drawer/QasDrawer.yml +58 -58
- package/src/components/empty-result-text/QasEmptyResultText.vue +18 -18
- package/src/components/empty-result-text/QasEmptyResultText.yml +14 -14
- package/src/components/expansion-item/QasExpansionItem.vue +291 -291
- package/src/components/expansion-item/QasExpansionItem.yml +76 -76
- package/src/components/field/QasField.vue +220 -220
- package/src/components/field/QasField.yml +36 -36
- package/src/components/filters/QasFilters.vue +472 -472
- package/src/components/filters/QasFilters.yml +171 -171
- package/src/components/filters/private/PvFiltersButton.vue +101 -101
- package/src/components/form-generator/QasFormGenerator.vue +274 -274
- package/src/components/form-generator/QasFormGenerator.yml +109 -109
- package/src/components/form-view/QasFormView.vue +484 -484
- package/src/components/form-view/QasFormView.yml +253 -253
- package/src/components/gallery/QasGallery.vue +243 -243
- package/src/components/gallery/QasGallery.yml +188 -188
- package/src/components/gallery/composables/use-delete.js +54 -54
- package/src/components/gallery/private/PvGalleryCarouselDialog.vue +92 -92
- package/src/components/gallery/private/PvGalleryDeleteDialog.vue +54 -54
- package/src/components/gallery-card/QasGalleryCard.vue +153 -153
- package/src/components/gallery-card/QasGalleryCard.yml +61 -61
- package/src/components/grabbable/QasGrabbable.vue +185 -185
- package/src/components/grabbable/QasGrabbable.yml +26 -26
- package/src/components/grid-generator/QasGridGenerator.vue +210 -210
- package/src/components/grid-generator/QasGridGenerator.yml +127 -127
- package/src/components/grid-item/QasGridItem.vue +89 -89
- package/src/components/grid-item/QasGridItem.yml +22 -22
- package/src/components/header/QasHeader.vue +152 -152
- package/src/components/header/QasHeader.yml +50 -50
- package/src/components/infinite-scroll/QasInfiniteScroll.vue +168 -168
- package/src/components/infinite-scroll/QasInfiniteScroll.yml +78 -78
- package/src/components/info/QasInfo.vue +155 -155
- package/src/components/info/QasInfo.yml +34 -34
- package/src/components/input/QasInput.vue +252 -252
- package/src/components/input/QasInput.yml +65 -65
- package/src/components/label/QasLabel.vue +61 -61
- package/src/components/label/QasLabel.yml +44 -44
- package/src/components/layout/QasLayout.vue +101 -101
- package/src/components/layout/QasLayout.yml +46 -46
- package/src/components/layout/private/PvLayoutNotificationCard.vue +86 -86
- package/src/components/layout/private/PvLayoutNotificationsDrawer.vue +140 -140
- package/src/components/list-items/QasListItems.vue +125 -125
- package/src/components/list-items/QasListItems.yml +71 -71
- package/src/components/list-view/QasListView.vue +302 -302
- package/src/components/list-view/QasListView.yml +169 -169
- package/src/components/map/QasMap.vue +75 -75
- package/src/components/map/QasMap.yml +33 -33
- package/src/components/nested-fields/QasNestedFields.vue +584 -584
- package/src/components/nested-fields/QasNestedFields.yml +296 -296
- package/src/components/numeric-input/QasNumericInput.vue +206 -206
- package/src/components/numeric-input/QasNumericInput.yml +85 -85
- package/src/components/option-group/QasOptionGroup.vue +54 -54
- package/src/components/option-group/QasOptionGroup.yml +30 -30
- package/src/components/page-header/QasPageHeader.vue +143 -143
- package/src/components/page-header/QasPageHeader.yml +42 -42
- package/src/components/pagination/QasPagination.vue +71 -71
- package/src/components/pagination/QasPagination.yml +4 -4
- package/src/components/password-input/QasPasswordInput.vue +110 -110
- package/src/components/password-input/QasPasswordInput.yml +111 -111
- package/src/components/password-strength-checker/QasPasswordStrengthChecker.vue +71 -71
- package/src/components/password-strength-checker/QasPasswordStrengthChecker.yml +103 -103
- package/src/components/profile/QasProfile.vue +98 -98
- package/src/components/profile/QasProfile.yml +60 -60
- package/src/components/radio/QasRadio.vue +62 -62
- package/src/components/radio/QasRadio.yml +11 -11
- package/src/components/resizer/QasResizer.vue +90 -90
- package/src/components/resizer/QasResizer.yml +24 -24
- package/src/components/search-box/QasSearchBox.vue +294 -294
- package/src/components/search-box/QasSearchBox.yml +154 -154
- package/src/components/search-input/QasSearchInput.vue +106 -106
- package/src/components/search-input/QasSearchInput.yml +48 -48
- package/src/components/select/QasSelect.vue +514 -514
- package/src/components/select/QasSelect.yml +157 -157
- package/src/components/select-list/QasSelectList.vue +238 -238
- package/src/components/select-list/QasSelectList.yml +108 -108
- package/src/components/select-list/private/PvSelectListCheckbox.vue +31 -31
- package/src/components/select-list-dialog/QasSelectListDialog.vue +395 -395
- package/src/components/select-list-dialog/QasSelectListDialog.yml +108 -108
- package/src/components/signature-pad/QasSignaturePad.vue +122 -122
- package/src/components/signature-pad/QasSignaturePad.yml +53 -53
- package/src/components/signature-uploader/QasSignatureUploader.vue +164 -164
- package/src/components/signature-uploader/QasSignatureUploader.yml +37 -37
- package/src/components/single-view/QasSingleView.vue +175 -175
- package/src/components/single-view/QasSingleView.yml +131 -131
- package/src/components/sortable/QasSortable.vue +151 -151
- package/src/components/sortable/QasSortable.yml +65 -65
- package/src/components/status/QasStatus.test.js +11 -11
- package/src/components/status/QasStatus.vue +29 -29
- package/src/components/status/QasStatus.yml +10 -10
- package/src/components/stepper/QasStepper.vue +152 -152
- package/src/components/stepper/QasStepper.yml +35 -35
- package/src/components/stepper-form-view/QasStepperFormView.vue +84 -84
- package/src/components/stepper-form-view/QasStepperFormView.yml +58 -58
- package/src/components/table-generator/QasTableGenerator.vue +416 -416
- package/src/components/table-generator/QasTableGenerator.yml +94 -94
- package/src/components/tabs-generator/QasTabsGenerator.vue +196 -196
- package/src/components/tabs-generator/QasTabsGenerator.yml +53 -53
- package/src/components/text-truncate/QasTextTruncate.vue +274 -274
- package/src/components/text-truncate/QasTextTruncate.yml +60 -60
- package/src/components/timeline/QasTimeline.vue +140 -140
- package/src/components/timeline/QasTimeline.yml +36 -36
- package/src/components/toggle/QasToggle.vue +14 -14
- package/src/components/toggle/QasToggle.yml +5 -5
- package/src/components/toggle-visibility/QasToggleVisibility.vue +79 -79
- package/src/components/toggle-visibility/QasToggleVisibility.yml +30 -30
- package/src/components/transfer/QasTransfer.vue +239 -239
- package/src/components/transfer/QasTransfer.yml +60 -60
- package/src/components/tree-generator/QasTreeForm.vue +62 -62
- package/src/components/tree-generator/QasTreeGenerator.vue +488 -488
- package/src/components/tree-generator/QasTreeGenerator.yml +81 -81
- package/src/components/uploader/QasUploader.vue +645 -645
- package/src/components/uploader/QasUploader.yml +192 -192
- package/src/components/uploader/private/PvUploaderGalleryCard.vue +357 -357
- package/src/components/welcome/QasWelcome.vue +125 -125
- package/src/components/welcome/QasWelcome.yml +23 -23
- package/src/components/welcome/private/PvWelcomeShortcutCard.vue +58 -58
- package/src/components/whatsapp-link/QasWhatsappLink.vue +34 -34
- package/src/components/whatsapp-link/QasWhatsappLink.yml +18 -18
- package/src/composables/index.js +8 -8
- package/src/composables/private/index.js +3 -3
- package/src/composables/private/use-generator.js +174 -174
- package/src/composables/private/use-toggle-visibility.js +48 -48
- package/src/composables/private/use-view.js +186 -186
- package/src/composables/use-context.js +15 -15
- package/src/composables/use-form.js +32 -32
- package/src/composables/use-history.js +46 -46
- package/src/composables/use-notifications.js +128 -114
- package/src/composables/use-query-cache.js +60 -60
- package/src/composables/use-screen.js +42 -42
- package/src/css/base/font-face.scss +28 -28
- package/src/css/base/index.scss +1 -1
- package/src/css/components/base.scss +12 -12
- package/src/css/components/button.scss +116 -116
- package/src/css/components/checkbox.scss +14 -14
- package/src/css/components/editor.scss +7 -7
- package/src/css/components/field.scss +88 -88
- package/src/css/components/index.scss +10 -10
- package/src/css/components/item.scss +52 -52
- package/src/css/components/radio.scss +18 -18
- package/src/css/components/scrollbar.scss +20 -20
- package/src/css/components/tabs.scss +3 -3
- package/src/css/components/toggle.scss +13 -13
- package/src/css/mixins/index.scss +3 -3
- package/src/css/mixins/set-brand.scss +15 -15
- package/src/css/mixins/set-button.scss +26 -26
- package/src/css/mixins/set-typography.scss +8 -8
- package/src/css/plugins/index.scss +2 -2
- package/src/css/plugins/loading.scss +5 -5
- package/src/css/plugins/notify.scss +100 -100
- package/src/css/utils/background.scss +34 -34
- package/src/css/utils/border-radius.scss +29 -29
- package/src/css/utils/border.scss +42 -42
- package/src/css/utils/container.scss +36 -36
- package/src/css/utils/fonts.scss +5 -5
- package/src/css/utils/index.scss +10 -10
- package/src/css/utils/line-height.scss +19 -19
- package/src/css/utils/opacity.scss +5 -5
- package/src/css/utils/scroll.scss +21 -21
- package/src/css/utils/text.scss +15 -15
- package/src/css/utils/unset.scss +8 -8
- package/src/css/variables/button.scss +4 -4
- package/src/css/variables/index.scss +6 -6
- package/src/css/variables/scrollbar.scss +11 -11
- package/src/css/variables/separator.scss +2 -2
- package/src/css/variables/shadow.scss +3 -3
- package/src/css/variables/spacing.scss +98 -98
- package/src/css/variables/typography.scss +149 -149
- package/src/directives/Test.js +13 -13
- package/src/enums/Align.js +7 -7
- package/src/enums/Spacing.js +98 -98
- package/src/enums/Status.js +33 -33
- package/src/helpers/add-counter-suffix.js +3 -3
- package/src/helpers/base-64-to-blob.js +21 -21
- package/src/helpers/camelize-fields-name.js +15 -15
- package/src/helpers/construct-object.js +29 -29
- package/src/helpers/copy-to-clipboard.js +15 -15
- package/src/helpers/destroy-nested-children-by-key.js +33 -33
- package/src/helpers/download-file.js +21 -21
- package/src/helpers/filter-list-by-handle.js +31 -31
- package/src/helpers/filter-object-to-array.js +29 -29
- package/src/helpers/filter-object.js +34 -34
- package/src/helpers/filters.js +163 -163
- package/src/helpers/find-children-by-key.js +14 -14
- package/src/helpers/get-greatest-common-divisor.js +16 -16
- package/src/helpers/get-normalized-options.js +20 -20
- package/src/helpers/get-placeholder.js +19 -19
- package/src/helpers/get-required-label.js +3 -3
- package/src/helpers/get-slot-children-text.js +15 -15
- package/src/helpers/handle-process.js +13 -13
- package/src/helpers/images.js +28 -28
- package/src/helpers/index.js +25 -25
- package/src/helpers/is-empty.js +3 -3
- package/src/helpers/is-local-development.js +3 -3
- package/src/helpers/private/gutter-validator.js +15 -15
- package/src/helpers/private/has-parent-by-class-name.js +15 -15
- package/src/helpers/promise-handler.js +40 -40
- package/src/helpers/rules.js +7 -7
- package/src/helpers/set-scroll-on-grab.js +128 -128
- package/src/index.cjs.js +1 -1
- package/src/index.esm.js +4 -4
- package/src/index.scss +38 -38
- package/src/index.umd.js +2 -2
- package/src/mixins/context.js +8 -8
- package/src/mixins/delete.js +48 -48
- package/src/mixins/dialog-router.js +17 -17
- package/src/mixins/form.js +11 -11
- package/src/mixins/generator.js +94 -94
- package/src/mixins/index.js +17 -17
- package/src/mixins/password.js +86 -86
- package/src/mixins/search-filter.js +354 -354
- package/src/mixins/view.js +156 -156
- package/src/pages/ErrorComponent.vue +74 -74
- package/src/pages/Forbidden.vue +29 -29
- package/src/pages/NotFound.vue +29 -29
- package/src/pages/ServerError.vue +29 -29
- package/src/pages/Unauthorized.vue +29 -29
- package/src/plugins/delete/Delete.js +107 -107
- package/src/plugins/delete/Delete.yml +75 -75
- package/src/plugins/dialog/Dialog.js +14 -14
- package/src/plugins/dialog/Dialog.yml +10 -10
- package/src/plugins/index.js +13 -13
- package/src/plugins/notify-error/NotifyError.js +13 -13
- package/src/plugins/notify-error/NotifyError.yml +11 -11
- package/src/plugins/notify-success/NotifySuccess.js +13 -13
- package/src/plugins/notify-success/NotifySuccess.yml +11 -11
- package/src/plugins/screen/Screen.js +35 -35
- package/src/plugins/screen/Screen.yml +16 -16
- package/src/shared/date-config.js +26 -26
- package/src/shared/fuse-config.js +4 -4
- package/src/shared/notify-config.js +7 -7
- package/src/vue-plugin/third-party-component-handler.js +29 -29
- package/src/vue-plugin.js +282 -282
|
@@ -1,712 +1,712 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<qas-grabbable class="qas-board-generator" v-bind="grabbableProps">
|
|
3
|
-
<div class="no-wrap q-col-gutter-sm q-px-xl row">
|
|
4
|
-
<div v-for="(header, index) in headers" :key="index" class="q-mr-sm">
|
|
5
|
-
<qas-box class="q-mb-md" v-bind="headerBoxProps">
|
|
6
|
-
<slot :fields="getFieldsByHeader(header)" :header="header" :index="index" name="header-column" />
|
|
7
|
-
</qas-box>
|
|
8
|
-
|
|
9
|
-
<div ref="columnContainer" class="qas-board-generator__column secondary-scroll" :data-header-key="getKeyByHeader(header)" :style="containerStyle">
|
|
10
|
-
<div v-for="item in getItemsByHeader(header)" :id="item[props.itemIdKey]" :key="item[props.itemIdKey]" class="qas-board-generator__item">
|
|
11
|
-
<slot :column-index="index" :fields="getFieldsByHeader(header)" :item="item" name="column-item" />
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
<div class="full-width justify-center row">
|
|
15
|
-
<qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" label="Ver mais" :use-label-on-small-screen="false" variant="tertiary" @click="fetchColumn(header)" />
|
|
16
|
-
|
|
17
|
-
<q-spinner v-if="columnsLoading[getKeyByHeader(header)]" class="q-mb-md" color="grey-4" size="3em" />
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<qas-empty-result-text v-if="hasEmptyResultText(header)" />
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
<qas-dialog v-model="showConfirmDialog" v-bind="defaultConfirmDialogProps" />
|
|
26
|
-
</qas-grabbable>
|
|
27
|
-
</template>
|
|
28
|
-
|
|
29
|
-
<script setup>
|
|
30
|
-
import QasDialog from '../dialog/QasDialog.vue'
|
|
31
|
-
|
|
32
|
-
import { ref, watch, computed, onUnmounted, markRaw, inject, onMounted } from 'vue'
|
|
33
|
-
import promiseHandler from '../../helpers/promise-handler'
|
|
34
|
-
|
|
35
|
-
import Sortable from 'sortablejs'
|
|
36
|
-
|
|
37
|
-
defineOptions({ name: 'QasBoardGenerator' })
|
|
38
|
-
|
|
39
|
-
const props = defineProps({
|
|
40
|
-
headers: {
|
|
41
|
-
type: Array,
|
|
42
|
-
default: () => []
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
results: {
|
|
46
|
-
type: Object,
|
|
47
|
-
default: () => ({})
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
headerBoxProps: {
|
|
51
|
-
type: Object,
|
|
52
|
-
default: () => ({})
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
columnIdKey: {
|
|
56
|
-
type: String,
|
|
57
|
-
required: true
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
columnParams: {
|
|
61
|
-
type: Object,
|
|
62
|
-
default: () => ({})
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
columnUrl: {
|
|
66
|
-
type: String,
|
|
67
|
-
required: true
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
confirmDialogProps: {
|
|
71
|
-
type: Object,
|
|
72
|
-
default: () => ({})
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
height: {
|
|
76
|
-
type: String,
|
|
77
|
-
default: ''
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
itemIdKey: {
|
|
81
|
-
type: String,
|
|
82
|
-
default: ''
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
limitPerColumn: {
|
|
86
|
-
type: Number,
|
|
87
|
-
default: 12
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
columnWidth: {
|
|
91
|
-
type: String,
|
|
92
|
-
default: '300px'
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
sortableConfig: {
|
|
96
|
-
type: Object,
|
|
97
|
-
default: () => ({})
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
useMarkRaw: {
|
|
101
|
-
type: Boolean,
|
|
102
|
-
default: true
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
useDragAndDropX: {
|
|
106
|
-
type: Boolean
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
useDragAndDropY: {
|
|
110
|
-
type: Boolean
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
updatePositionUrl: {
|
|
114
|
-
type: String,
|
|
115
|
-
default: ''
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
updatePositionParams: {
|
|
119
|
-
type: Object,
|
|
120
|
-
default: () => ({})
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
lazyLoadingFieldsKeys: {
|
|
124
|
-
type: Array,
|
|
125
|
-
default: () => []
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const emit = defineEmits([
|
|
130
|
-
'update:results',
|
|
131
|
-
'fetch-column-success',
|
|
132
|
-
'fetch-column-error',
|
|
133
|
-
'fetch-columns-success',
|
|
134
|
-
'fetch-columns-error',
|
|
135
|
-
'update-success'
|
|
136
|
-
])
|
|
137
|
-
|
|
138
|
-
defineExpose({ fetchColumns, fetchColumn, reset })
|
|
139
|
-
|
|
140
|
-
// Inject
|
|
141
|
-
const axios = inject('axios')
|
|
142
|
-
|
|
143
|
-
const isFetchSuccessHeader = inject('isFetchListSucceeded', false)
|
|
144
|
-
|
|
145
|
-
const isInsideListView = inject('isListView', false)
|
|
146
|
-
|
|
147
|
-
// Refs
|
|
148
|
-
const columnContainer = ref(null)
|
|
149
|
-
const columnsPagination = ref({})
|
|
150
|
-
const columnsLoading = ref({})
|
|
151
|
-
const columnsFieldsModel = ref({})
|
|
152
|
-
const showConfirmDialog = ref(false)
|
|
153
|
-
const isDragging = ref(false)
|
|
154
|
-
const isLoadingUpdatePosition = ref(false)
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Instâncias do sortable, que são utilizadas para realizar o destroy ao sair da página
|
|
158
|
-
*/
|
|
159
|
-
const sortableInstances = ref([])
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Callbacks que recebe o event de movimentação
|
|
163
|
-
*/
|
|
164
|
-
const onCancelDrop = ref(() => {})
|
|
165
|
-
const onConfirmDrop = ref(() => {})
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Variável auxiliar que controla quando estou atualizando o header em caso de drag and drop
|
|
169
|
-
*/
|
|
170
|
-
const isUpdatingPosition = ref(false)
|
|
171
|
-
|
|
172
|
-
// Consts
|
|
173
|
-
const hasDragAndDrop = !!props.useDragAndDropX || !!props.useDragAndDropY
|
|
174
|
-
|
|
175
|
-
const grabbableProps = {
|
|
176
|
-
useScrollBar: true,
|
|
177
|
-
|
|
178
|
-
...(hasDragAndDrop && {
|
|
179
|
-
cancelMouseDownTarget: 'qas-board-generator__item'
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Watchers
|
|
184
|
-
watch(
|
|
185
|
-
() => isFetchSuccessHeader.value,
|
|
186
|
-
value => {
|
|
187
|
-
/**
|
|
188
|
-
* isFetchSuccessHeader é uma variavel que pego do listView por inject/provide, no qual caso eu faça request do header e dê sucesso, eu chamo as demais funções.
|
|
189
|
-
* Valido se não houve sucesso na requisição do header ou se não é uma atualização de posição, para assim não bater novamente nas colunas apenas no header.
|
|
190
|
-
*/
|
|
191
|
-
if (!value || isUpdatingPosition.value) return
|
|
192
|
-
|
|
193
|
-
fetchColumnsValues()
|
|
194
|
-
}
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
watch(
|
|
198
|
-
() => props.headers,
|
|
199
|
-
() => {
|
|
200
|
-
if (isUpdatingPosition.value) return
|
|
201
|
-
|
|
202
|
-
isUpdatingPosition.value = false
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
watch(columnContainer, setColumnHeightContainer)
|
|
207
|
-
|
|
208
|
-
// Lifecycles
|
|
209
|
-
onMounted(() => {
|
|
210
|
-
/**
|
|
211
|
-
* Caso eu use o listView (valor pego por provide), a request é feito pelo watch quando se ocorre o sucesso do `fetchList`
|
|
212
|
-
*/
|
|
213
|
-
if (isInsideListView) return
|
|
214
|
-
|
|
215
|
-
fetchColumnsValues()
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
onUnmounted(destroySortable)
|
|
219
|
-
|
|
220
|
-
// Computeds
|
|
221
|
-
const columnsResultsModel = computed({
|
|
222
|
-
get () {
|
|
223
|
-
return props.results
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
set (newValues) {
|
|
227
|
-
emit('update:results', newValues)
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
const hasColumnsLength = computed(() => !!Object.keys(columnsResultsModel.value).length)
|
|
232
|
-
|
|
233
|
-
const containerStyle = computed(() => `width: ${props.columnWidth};`)
|
|
234
|
-
|
|
235
|
-
const hasConfirmDialogProps = computed(() => !!Object.keys(props.confirmDialogProps).length)
|
|
236
|
-
|
|
237
|
-
const defaultConfirmDialogProps = computed(() => {
|
|
238
|
-
const defaultProps = {
|
|
239
|
-
ok: {
|
|
240
|
-
label: 'Confirmar',
|
|
241
|
-
onClick: onConfirmDrop.value,
|
|
242
|
-
loading: isLoadingUpdatePosition.value
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
cancel: {
|
|
246
|
-
onClick: onCancelDrop.value
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
...props.confirmDialogProps,
|
|
252
|
-
...defaultProps
|
|
253
|
-
}
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
// functions
|
|
257
|
-
/*
|
|
258
|
-
* Setar o tamanho do container do board, onde deverá ser a altura passada via prop, ou o default será ocupar o maximo
|
|
259
|
-
* de espaço que ele conseguir considerando a altura do container em relação ao topo.
|
|
260
|
-
*/
|
|
261
|
-
function setColumnHeightContainer () {
|
|
262
|
-
columnContainer.value?.forEach(columnElement => {
|
|
263
|
-
const heightToTop = columnElement?.getBoundingClientRect()?.top
|
|
264
|
-
const paddingSpacing = 60
|
|
265
|
-
const value = heightToTop + paddingSpacing
|
|
266
|
-
|
|
267
|
-
columnElement.style.setProperty('height', props.height ? props.height : `calc(100vh - ${value}px)`)
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/*
|
|
272
|
-
* Bater API pra cada header
|
|
273
|
-
*/
|
|
274
|
-
async function fetchColumns () {
|
|
275
|
-
const promises = props.headers.map(header => fetchColumn(header))
|
|
276
|
-
|
|
277
|
-
const { error } = await promiseHandler(promises, { useLoading: false })
|
|
278
|
-
|
|
279
|
-
if (error) {
|
|
280
|
-
emit('fetch-columns-error', error)
|
|
281
|
-
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
emit('fetch-columns-success')
|
|
286
|
-
|
|
287
|
-
if (hasDragAndDrop) handleElementsList()
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/*
|
|
291
|
-
* Busca a coluna com base no header recebido.
|
|
292
|
-
*/
|
|
293
|
-
async function fetchColumn (header) {
|
|
294
|
-
const headerKey = getKeyByHeader(header)
|
|
295
|
-
const { limit, offset } = columnsPagination.value[headerKey] || {}
|
|
296
|
-
|
|
297
|
-
const { data: response, error } = await promiseHandler(
|
|
298
|
-
axios.get(`${props.columnUrl}/${headerKey}`, {
|
|
299
|
-
params: {
|
|
300
|
-
...props.columnParams,
|
|
301
|
-
limit,
|
|
302
|
-
offset
|
|
303
|
-
}
|
|
304
|
-
}),
|
|
305
|
-
{
|
|
306
|
-
onLoading: value => {
|
|
307
|
-
columnsLoading.value[headerKey] = value
|
|
308
|
-
},
|
|
309
|
-
useLoading: false,
|
|
310
|
-
errorMessage: 'Não conseguimos buscar as colunas do board. Por favor, tente novamente em alguns minutos.'
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
if (error) {
|
|
315
|
-
emit('fetch-column-error', error)
|
|
316
|
-
|
|
317
|
-
throw error
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const newValues = response.data?.results || []
|
|
321
|
-
const resultsModel = columnsResultsModel.value[headerKey] || []
|
|
322
|
-
|
|
323
|
-
const newColumnValues = [
|
|
324
|
-
...resultsModel,
|
|
325
|
-
...newValues
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* exemplo de como columnsResultsModel irá ficar:
|
|
330
|
-
*
|
|
331
|
-
* {
|
|
332
|
-
* '2024-02-15': [...],
|
|
333
|
-
* '2024-02-16': [...]
|
|
334
|
-
* }
|
|
335
|
-
*
|
|
336
|
-
* onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsFieldsModel", "columnsLoading" e
|
|
337
|
-
* "columnPagination", organizando os fields, loadings e o controle de paginação por chave identificadora do header.
|
|
338
|
-
*/
|
|
339
|
-
columnsResultsModel.value[headerKey] = props.useMarkRaw ? markRaw(newColumnValues) : newColumnValues
|
|
340
|
-
|
|
341
|
-
/*
|
|
342
|
-
* Pode acontecer das options nos fields da segunda página serem diferentes da primeira página,
|
|
343
|
-
* portanto deve ocorrer o merge.
|
|
344
|
-
*/
|
|
345
|
-
if (response.data?.fields) {
|
|
346
|
-
columnsFieldsModel.value[headerKey] = markRaw(
|
|
347
|
-
getMergedFields(columnsFieldsModel.value[headerKey], response.data?.fields)
|
|
348
|
-
)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
columnsPagination.value[headerKey].offset = columnsResultsModel.value[headerKey].length
|
|
352
|
-
columnsPagination.value[headerKey].count = response.data?.count
|
|
353
|
-
|
|
354
|
-
emit('fetch-column-success', { response, header })
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/*
|
|
358
|
-
* Mergeia os options antigos com os novos de cada field.
|
|
359
|
-
*/
|
|
360
|
-
function getMergedFields (oldFields, newFields) {
|
|
361
|
-
// Primeira vez batendo a API, retorna os novos fields.
|
|
362
|
-
if (!oldFields || !props.lazyLoadingFieldsKeys.length) return newFields
|
|
363
|
-
|
|
364
|
-
// Caso bata a API e por algum motivo não venha fields, mantenha o antigos.
|
|
365
|
-
if (oldFields && !newFields) return oldFields
|
|
366
|
-
|
|
367
|
-
const mergedFields = { ...oldFields }
|
|
368
|
-
|
|
369
|
-
props.lazyLoadingFieldsKeys.forEach(fieldKey => {
|
|
370
|
-
mergedFields[fieldKey].options = getNonDuplicatedOptions(oldFields[fieldKey].options, newFields[fieldKey].options)
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
return mergedFields
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/*
|
|
377
|
-
* Tratamento para fazer o merge e evitar options duplicados.
|
|
378
|
-
*/
|
|
379
|
-
function getNonDuplicatedOptions (oldOptions, newOptions) {
|
|
380
|
-
const options = [...oldOptions]
|
|
381
|
-
|
|
382
|
-
newOptions.forEach(item => {
|
|
383
|
-
const hasOption = options.find(option => option.value === item.value)
|
|
384
|
-
|
|
385
|
-
if (!hasOption) options.push(item)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
return options
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function getItemsByHeader (header) {
|
|
392
|
-
return hasColumnsLength.value ? columnsResultsModel.value[getKeyByHeader(header)] : []
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Pegar key com base na chave identificador, exemplo:
|
|
397
|
-
* header -> { date: '2024-02-12', ... }
|
|
398
|
-
* columnIdKey -> 'date'
|
|
399
|
-
* retorno -> '2024-02-12'
|
|
400
|
-
*
|
|
401
|
-
* Onde esta chave será o "id" da coluna, sendo usado para bater a API, lidar com paginação, loading, etc.
|
|
402
|
-
*
|
|
403
|
-
* @example getKeyByHeader({ date: '2024-02-12', ... })
|
|
404
|
-
* @returns {string} // '2024-02-12'
|
|
405
|
-
*/
|
|
406
|
-
function getKeyByHeader (header = {}) {
|
|
407
|
-
return header[props.columnIdKey]
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/*
|
|
411
|
-
* Para cada header, irá ser criado um item com sua chave identificadora para lidar com paginação e loading.
|
|
412
|
-
* columnsPagination -> { '2024-02-15': { limit: 12, offset: 0 }, '2024-02-16': { limit: 12, offset: 0 }, ... }
|
|
413
|
-
* columnsLoading ->{ '2024-02-15': false, '2024-02-16': false, ... }
|
|
414
|
-
*/
|
|
415
|
-
function setColumnsPagination () {
|
|
416
|
-
columnsPagination.value = {}
|
|
417
|
-
columnsLoading.value = {}
|
|
418
|
-
|
|
419
|
-
props.headers.forEach(header => {
|
|
420
|
-
const headerKey = getKeyByHeader(header)
|
|
421
|
-
|
|
422
|
-
columnsPagination.value[headerKey] = { limit: props.limitPerColumn, offset: 0 }
|
|
423
|
-
columnsLoading.value[headerKey] = false
|
|
424
|
-
})
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function fetchColumnsValues () {
|
|
428
|
-
reset()
|
|
429
|
-
setColumnHeightContainer()
|
|
430
|
-
setColumnsPagination()
|
|
431
|
-
fetchColumns()
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Descricao:
|
|
436
|
-
* Exibe o texto quando:
|
|
437
|
-
* - Nao esta carregando a coluna
|
|
438
|
-
* - Nao tem itens na coluna
|
|
439
|
-
* - Nao estou fazendo o drag and drop
|
|
440
|
-
*
|
|
441
|
-
* @param {Object} header
|
|
442
|
-
*/
|
|
443
|
-
function hasEmptyResultText (header) {
|
|
444
|
-
return !columnsLoading.value[getKeyByHeader(header)] && !getItemsByHeader(header)?.length && !isDragging.value
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/*
|
|
448
|
-
* Valida se o tamanho dos itens da coluna é menor que o valor total de itens que o back retorna e
|
|
449
|
-
* se a coluna não está em carregamento.
|
|
450
|
-
*/
|
|
451
|
-
function hasSeeMore (header) {
|
|
452
|
-
const headerKey = getKeyByHeader(header)
|
|
453
|
-
const hasMorePagination = columnsResultsModel.value[headerKey]?.length < columnsPagination.value[headerKey]?.count
|
|
454
|
-
|
|
455
|
-
return hasMorePagination && !columnsLoading.value[headerKey]
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function reset () {
|
|
459
|
-
columnsResultsModel.value = {}
|
|
460
|
-
columnsPagination.value = {}
|
|
461
|
-
columnsLoading.value = {}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function getFieldsByHeader (header) {
|
|
465
|
-
const headerKey = getKeyByHeader(header)
|
|
466
|
-
|
|
467
|
-
return columnsFieldsModel.value[headerKey] || {}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Loopa todos os itens da coluna com base no ref para pegar o elemento HTML e setar e instaciar o sortable.
|
|
472
|
-
*/
|
|
473
|
-
function handleElementsList () {
|
|
474
|
-
columnContainer.value.forEach((element, index) => {
|
|
475
|
-
const sortable = setSortable(element, index)
|
|
476
|
-
|
|
477
|
-
sortableInstances.value.push(sortable)
|
|
478
|
-
})
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Descricao:
|
|
483
|
-
* Seta a instancia do sortable, no qual varia de acordo com as props passadas.
|
|
484
|
-
*
|
|
485
|
-
* @param {HTMLElement} element
|
|
486
|
-
* @param {Number} index
|
|
487
|
-
*/
|
|
488
|
-
function setSortable (element, index) {
|
|
489
|
-
const defaultSortableConfig = {
|
|
490
|
-
animation: 500,
|
|
491
|
-
swapThreshold: 1,
|
|
492
|
-
delay: 50,
|
|
493
|
-
delayOnTouchOnly: true,
|
|
494
|
-
emptyInsertThreshold: 0
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Caso seja apenas drag and drop no eixo Y
|
|
499
|
-
*/
|
|
500
|
-
const useOnlyDragAndDropY = !!props.useDragAndDropY && !props.useDragAndDropX
|
|
501
|
-
|
|
502
|
-
const sortable = new Sortable(element, {
|
|
503
|
-
sort: props.useDragAndDropY,
|
|
504
|
-
|
|
505
|
-
...defaultSortableConfig,
|
|
506
|
-
|
|
507
|
-
...props.sortableConfig,
|
|
508
|
-
|
|
509
|
-
group: useOnlyDragAndDropY ? `column-${index}` : 'shared',
|
|
510
|
-
|
|
511
|
-
direction: useOnlyDragAndDropY ? 'vertical' : 'horizontal',
|
|
512
|
-
|
|
513
|
-
onStart: toggleIsDragging,
|
|
514
|
-
|
|
515
|
-
onAdd: event => onDropCard(event),
|
|
516
|
-
|
|
517
|
-
...(props.useDragAndDropY && {
|
|
518
|
-
onSort: event => onDropCard(event)
|
|
519
|
-
})
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
return sortable
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function toggleIsDragging () {
|
|
526
|
-
isDragging.value = !isDragging.value
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function onDropCard (event) {
|
|
530
|
-
onCancelDrop.value = () => cancelDrop(event)
|
|
531
|
-
|
|
532
|
-
onConfirmDrop.value = () => confirmDrop(event)
|
|
533
|
-
|
|
534
|
-
hasConfirmDialogProps.value
|
|
535
|
-
? openConfirmDialog()
|
|
536
|
-
: confirmDrop(event)
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function openConfirmDialog () {
|
|
540
|
-
showConfirmDialog.value = true
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function closeConfirmDialog () {
|
|
544
|
-
showConfirmDialog.value = false
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* @param {event} event
|
|
549
|
-
*/
|
|
550
|
-
function cancelDrop (event) {
|
|
551
|
-
/**
|
|
552
|
-
* Insere na posição antiga que pertencia (event.oldIndex) dentro do seu antigo pai (event.from)
|
|
553
|
-
*/
|
|
554
|
-
if (props.useDragAndDropX) event.from.insertBefore(event.item, event.from.children[event.oldIndex])
|
|
555
|
-
|
|
556
|
-
if (props.useDragAndDropY) {
|
|
557
|
-
const oldIndex = event.oldIndex
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Se oldIndex for 0, o targetIndex deverá ser 0, pois isso indica que se o item é o primeiro da lista, ele não será movido para outra posição.
|
|
561
|
-
*
|
|
562
|
-
* Caso o oldIndex for diferente, devo incrementar 1 para adicionar, pois isso permite que o item seja inserido logo após sua posição original.
|
|
563
|
-
*/
|
|
564
|
-
const targetIndex = oldIndex === 0 ? oldIndex : oldIndex + 1
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Verifica se o índice alvo é válido, caso contrário, define como o final
|
|
568
|
-
*/
|
|
569
|
-
const insertBeforeElement = targetIndex < event.from.children.length
|
|
570
|
-
? event.from.children[targetIndex]
|
|
571
|
-
: null
|
|
572
|
-
|
|
573
|
-
event.from.insertBefore(event.item, insertBeforeElement)
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (hasConfirmDialogProps.value) closeConfirmDialog()
|
|
577
|
-
|
|
578
|
-
toggleIsDragging()
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function confirmDrop (event) {
|
|
582
|
-
const { from, to, item: { id: itemId } } = event
|
|
583
|
-
|
|
584
|
-
const { headerKey: newHeaderKey } = to.dataset
|
|
585
|
-
const { headerKey: oldHeaderKey } = from.dataset
|
|
586
|
-
|
|
587
|
-
updatePosition({ newHeaderKey, oldHeaderKey, itemId, event })
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
*
|
|
592
|
-
* @param {{
|
|
593
|
-
* headerKey: string,
|
|
594
|
-
* itemId: string
|
|
595
|
-
* }}
|
|
596
|
-
*/
|
|
597
|
-
function removeItemFromList ({ headerKey, itemId }) {
|
|
598
|
-
/**
|
|
599
|
-
* Coluna referente ao model de resultado
|
|
600
|
-
*/
|
|
601
|
-
const columnItemList = columnsResultsModel.value[headerKey]
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Busca o item com base em seu ID na lista de itens da coluna
|
|
605
|
-
*/
|
|
606
|
-
const itemIndex = columnItemList.findIndex(itemContent => itemContent[props.itemIdKey] === itemId)
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Remove o item da listagem com base no index, sendo que preciso subtrair 1 para pegar o index correto
|
|
610
|
-
*/
|
|
611
|
-
columnItemList.splice(itemIndex, 1)
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Remove o item do count da coluna para não mostrar o botão de "Ver mais¨.
|
|
615
|
-
*/
|
|
616
|
-
columnsPagination.value[headerKey].count -= 1
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Descricao:
|
|
621
|
-
* Metodo que realiza a request de update
|
|
622
|
-
*
|
|
623
|
-
* @param {{
|
|
624
|
-
* newHeaderKey: string - ID da coluna de destino,
|
|
625
|
-
* oldHeaderKey: string - ID da antiga coluna,
|
|
626
|
-
* itemId: string - ID do meu item a ser movimento,
|
|
627
|
-
* event: event
|
|
628
|
-
* }}
|
|
629
|
-
*/
|
|
630
|
-
async function updatePosition ({ newHeaderKey, oldHeaderKey, itemId, event }) {
|
|
631
|
-
const params = {
|
|
632
|
-
[props.columnIdKey]: newHeaderKey,
|
|
633
|
-
...(props.useDragAndDropY && { newIndex: event.newIndex }),
|
|
634
|
-
...props.updatePositionParams
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const { data, error } = await promiseHandler(
|
|
638
|
-
axios.patch(`${props.updatePositionUrl}/${itemId}/update-position`, params),
|
|
639
|
-
{
|
|
640
|
-
errorMessage: 'Ocorreu um erro ao atualizar a posição de seu item.',
|
|
641
|
-
useLoading: false,
|
|
642
|
-
onLoading: value => {
|
|
643
|
-
isLoadingUpdatePosition.value = value
|
|
644
|
-
|
|
645
|
-
columnsLoading.value[newHeaderKey] = value
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
if (error) {
|
|
651
|
-
onCancelDrop.value()
|
|
652
|
-
|
|
653
|
-
return
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
removeItemFromList({ headerKey: oldHeaderKey, itemId })
|
|
657
|
-
|
|
658
|
-
setItemList({ headerKey: newHeaderKey, data: data.data, index: event.newIndex })
|
|
659
|
-
|
|
660
|
-
isUpdatingPosition.value = true
|
|
661
|
-
|
|
662
|
-
toggleIsDragging()
|
|
663
|
-
|
|
664
|
-
closeConfirmDialog()
|
|
665
|
-
|
|
666
|
-
emit('update-success')
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function setItemList ({ headerKey, data, index }) {
|
|
670
|
-
/**
|
|
671
|
-
* Coluna referente ao model de resultado
|
|
672
|
-
*/
|
|
673
|
-
const columnItemList = columnsResultsModel.value[headerKey]
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Adiciona o item na posição do event escolhido.
|
|
677
|
-
*/
|
|
678
|
-
columnItemList.splice(index, 0, data.result)
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function destroySortable () {
|
|
682
|
-
sortableInstances.value.forEach(sortable => sortable.destroy())
|
|
683
|
-
}
|
|
684
|
-
</script>
|
|
685
|
-
|
|
686
|
-
<style lang="scss">
|
|
687
|
-
.qas-board-generator {
|
|
688
|
-
max-height: 100vh;
|
|
689
|
-
|
|
690
|
-
&__column {
|
|
691
|
-
overflow-x: hidden;
|
|
692
|
-
scrollbar-width: none;
|
|
693
|
-
|
|
694
|
-
&:hover {
|
|
695
|
-
scrollbar-width: thin;
|
|
696
|
-
|
|
697
|
-
&::-webkit-scrollbar {
|
|
698
|
-
display: block;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
&::-webkit-scrollbar {
|
|
703
|
-
display: none;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// 60px é o valor do padding definido no container da column.
|
|
708
|
-
&__column-items {
|
|
709
|
-
height: calc(100% - 60px);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<qas-grabbable class="qas-board-generator" v-bind="grabbableProps">
|
|
3
|
+
<div class="no-wrap q-col-gutter-sm q-px-xl row">
|
|
4
|
+
<div v-for="(header, index) in headers" :key="index" class="q-mr-sm">
|
|
5
|
+
<qas-box class="q-mb-md" v-bind="headerBoxProps">
|
|
6
|
+
<slot :fields="getFieldsByHeader(header)" :header="header" :index="index" name="header-column" />
|
|
7
|
+
</qas-box>
|
|
8
|
+
|
|
9
|
+
<div ref="columnContainer" class="qas-board-generator__column secondary-scroll" :data-header-key="getKeyByHeader(header)" :style="containerStyle">
|
|
10
|
+
<div v-for="item in getItemsByHeader(header)" :id="item[props.itemIdKey]" :key="item[props.itemIdKey]" class="qas-board-generator__item">
|
|
11
|
+
<slot :column-index="index" :fields="getFieldsByHeader(header)" :item="item" name="column-item" />
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="full-width justify-center row">
|
|
15
|
+
<qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" label="Ver mais" :use-label-on-small-screen="false" variant="tertiary" @click="fetchColumn(header)" />
|
|
16
|
+
|
|
17
|
+
<q-spinner v-if="columnsLoading[getKeyByHeader(header)]" class="q-mb-md" color="grey-4" size="3em" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<qas-empty-result-text v-if="hasEmptyResultText(header)" />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<qas-dialog v-model="showConfirmDialog" v-bind="defaultConfirmDialogProps" />
|
|
26
|
+
</qas-grabbable>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import QasDialog from '../dialog/QasDialog.vue'
|
|
31
|
+
|
|
32
|
+
import { ref, watch, computed, onUnmounted, markRaw, inject, onMounted } from 'vue'
|
|
33
|
+
import promiseHandler from '../../helpers/promise-handler'
|
|
34
|
+
|
|
35
|
+
import Sortable from 'sortablejs'
|
|
36
|
+
|
|
37
|
+
defineOptions({ name: 'QasBoardGenerator' })
|
|
38
|
+
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
headers: {
|
|
41
|
+
type: Array,
|
|
42
|
+
default: () => []
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
results: {
|
|
46
|
+
type: Object,
|
|
47
|
+
default: () => ({})
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
headerBoxProps: {
|
|
51
|
+
type: Object,
|
|
52
|
+
default: () => ({})
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
columnIdKey: {
|
|
56
|
+
type: String,
|
|
57
|
+
required: true
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
columnParams: {
|
|
61
|
+
type: Object,
|
|
62
|
+
default: () => ({})
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
columnUrl: {
|
|
66
|
+
type: String,
|
|
67
|
+
required: true
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
confirmDialogProps: {
|
|
71
|
+
type: Object,
|
|
72
|
+
default: () => ({})
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
height: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: ''
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
itemIdKey: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: ''
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
limitPerColumn: {
|
|
86
|
+
type: Number,
|
|
87
|
+
default: 12
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
columnWidth: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: '300px'
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
sortableConfig: {
|
|
96
|
+
type: Object,
|
|
97
|
+
default: () => ({})
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
useMarkRaw: {
|
|
101
|
+
type: Boolean,
|
|
102
|
+
default: true
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
useDragAndDropX: {
|
|
106
|
+
type: Boolean
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
useDragAndDropY: {
|
|
110
|
+
type: Boolean
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
updatePositionUrl: {
|
|
114
|
+
type: String,
|
|
115
|
+
default: ''
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
updatePositionParams: {
|
|
119
|
+
type: Object,
|
|
120
|
+
default: () => ({})
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
lazyLoadingFieldsKeys: {
|
|
124
|
+
type: Array,
|
|
125
|
+
default: () => []
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const emit = defineEmits([
|
|
130
|
+
'update:results',
|
|
131
|
+
'fetch-column-success',
|
|
132
|
+
'fetch-column-error',
|
|
133
|
+
'fetch-columns-success',
|
|
134
|
+
'fetch-columns-error',
|
|
135
|
+
'update-success'
|
|
136
|
+
])
|
|
137
|
+
|
|
138
|
+
defineExpose({ fetchColumns, fetchColumn, reset })
|
|
139
|
+
|
|
140
|
+
// Inject
|
|
141
|
+
const axios = inject('axios')
|
|
142
|
+
|
|
143
|
+
const isFetchSuccessHeader = inject('isFetchListSucceeded', false)
|
|
144
|
+
|
|
145
|
+
const isInsideListView = inject('isListView', false)
|
|
146
|
+
|
|
147
|
+
// Refs
|
|
148
|
+
const columnContainer = ref(null)
|
|
149
|
+
const columnsPagination = ref({})
|
|
150
|
+
const columnsLoading = ref({})
|
|
151
|
+
const columnsFieldsModel = ref({})
|
|
152
|
+
const showConfirmDialog = ref(false)
|
|
153
|
+
const isDragging = ref(false)
|
|
154
|
+
const isLoadingUpdatePosition = ref(false)
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Instâncias do sortable, que são utilizadas para realizar o destroy ao sair da página
|
|
158
|
+
*/
|
|
159
|
+
const sortableInstances = ref([])
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Callbacks que recebe o event de movimentação
|
|
163
|
+
*/
|
|
164
|
+
const onCancelDrop = ref(() => {})
|
|
165
|
+
const onConfirmDrop = ref(() => {})
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Variável auxiliar que controla quando estou atualizando o header em caso de drag and drop
|
|
169
|
+
*/
|
|
170
|
+
const isUpdatingPosition = ref(false)
|
|
171
|
+
|
|
172
|
+
// Consts
|
|
173
|
+
const hasDragAndDrop = !!props.useDragAndDropX || !!props.useDragAndDropY
|
|
174
|
+
|
|
175
|
+
const grabbableProps = {
|
|
176
|
+
useScrollBar: true,
|
|
177
|
+
|
|
178
|
+
...(hasDragAndDrop && {
|
|
179
|
+
cancelMouseDownTarget: 'qas-board-generator__item'
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Watchers
|
|
184
|
+
watch(
|
|
185
|
+
() => isFetchSuccessHeader.value,
|
|
186
|
+
value => {
|
|
187
|
+
/**
|
|
188
|
+
* isFetchSuccessHeader é uma variavel que pego do listView por inject/provide, no qual caso eu faça request do header e dê sucesso, eu chamo as demais funções.
|
|
189
|
+
* Valido se não houve sucesso na requisição do header ou se não é uma atualização de posição, para assim não bater novamente nas colunas apenas no header.
|
|
190
|
+
*/
|
|
191
|
+
if (!value || isUpdatingPosition.value) return
|
|
192
|
+
|
|
193
|
+
fetchColumnsValues()
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
watch(
|
|
198
|
+
() => props.headers,
|
|
199
|
+
() => {
|
|
200
|
+
if (isUpdatingPosition.value) return
|
|
201
|
+
|
|
202
|
+
isUpdatingPosition.value = false
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
watch(columnContainer, setColumnHeightContainer)
|
|
207
|
+
|
|
208
|
+
// Lifecycles
|
|
209
|
+
onMounted(() => {
|
|
210
|
+
/**
|
|
211
|
+
* Caso eu use o listView (valor pego por provide), a request é feito pelo watch quando se ocorre o sucesso do `fetchList`
|
|
212
|
+
*/
|
|
213
|
+
if (isInsideListView) return
|
|
214
|
+
|
|
215
|
+
fetchColumnsValues()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
onUnmounted(destroySortable)
|
|
219
|
+
|
|
220
|
+
// Computeds
|
|
221
|
+
const columnsResultsModel = computed({
|
|
222
|
+
get () {
|
|
223
|
+
return props.results
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
set (newValues) {
|
|
227
|
+
emit('update:results', newValues)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const hasColumnsLength = computed(() => !!Object.keys(columnsResultsModel.value).length)
|
|
232
|
+
|
|
233
|
+
const containerStyle = computed(() => `width: ${props.columnWidth};`)
|
|
234
|
+
|
|
235
|
+
const hasConfirmDialogProps = computed(() => !!Object.keys(props.confirmDialogProps).length)
|
|
236
|
+
|
|
237
|
+
const defaultConfirmDialogProps = computed(() => {
|
|
238
|
+
const defaultProps = {
|
|
239
|
+
ok: {
|
|
240
|
+
label: 'Confirmar',
|
|
241
|
+
onClick: onConfirmDrop.value,
|
|
242
|
+
loading: isLoadingUpdatePosition.value
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
cancel: {
|
|
246
|
+
onClick: onCancelDrop.value
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
...props.confirmDialogProps,
|
|
252
|
+
...defaultProps
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// functions
|
|
257
|
+
/*
|
|
258
|
+
* Setar o tamanho do container do board, onde deverá ser a altura passada via prop, ou o default será ocupar o maximo
|
|
259
|
+
* de espaço que ele conseguir considerando a altura do container em relação ao topo.
|
|
260
|
+
*/
|
|
261
|
+
function setColumnHeightContainer () {
|
|
262
|
+
columnContainer.value?.forEach(columnElement => {
|
|
263
|
+
const heightToTop = columnElement?.getBoundingClientRect()?.top
|
|
264
|
+
const paddingSpacing = 60
|
|
265
|
+
const value = heightToTop + paddingSpacing
|
|
266
|
+
|
|
267
|
+
columnElement.style.setProperty('height', props.height ? props.height : `calc(100vh - ${value}px)`)
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/*
|
|
272
|
+
* Bater API pra cada header
|
|
273
|
+
*/
|
|
274
|
+
async function fetchColumns () {
|
|
275
|
+
const promises = props.headers.map(header => fetchColumn(header))
|
|
276
|
+
|
|
277
|
+
const { error } = await promiseHandler(promises, { useLoading: false })
|
|
278
|
+
|
|
279
|
+
if (error) {
|
|
280
|
+
emit('fetch-columns-error', error)
|
|
281
|
+
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
emit('fetch-columns-success')
|
|
286
|
+
|
|
287
|
+
if (hasDragAndDrop) handleElementsList()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/*
|
|
291
|
+
* Busca a coluna com base no header recebido.
|
|
292
|
+
*/
|
|
293
|
+
async function fetchColumn (header) {
|
|
294
|
+
const headerKey = getKeyByHeader(header)
|
|
295
|
+
const { limit, offset } = columnsPagination.value[headerKey] || {}
|
|
296
|
+
|
|
297
|
+
const { data: response, error } = await promiseHandler(
|
|
298
|
+
axios.get(`${props.columnUrl}/${headerKey}`, {
|
|
299
|
+
params: {
|
|
300
|
+
...props.columnParams,
|
|
301
|
+
limit,
|
|
302
|
+
offset
|
|
303
|
+
}
|
|
304
|
+
}),
|
|
305
|
+
{
|
|
306
|
+
onLoading: value => {
|
|
307
|
+
columnsLoading.value[headerKey] = value
|
|
308
|
+
},
|
|
309
|
+
useLoading: false,
|
|
310
|
+
errorMessage: 'Não conseguimos buscar as colunas do board. Por favor, tente novamente em alguns minutos.'
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if (error) {
|
|
315
|
+
emit('fetch-column-error', error)
|
|
316
|
+
|
|
317
|
+
throw error
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const newValues = response.data?.results || []
|
|
321
|
+
const resultsModel = columnsResultsModel.value[headerKey] || []
|
|
322
|
+
|
|
323
|
+
const newColumnValues = [
|
|
324
|
+
...resultsModel,
|
|
325
|
+
...newValues
|
|
326
|
+
]
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* exemplo de como columnsResultsModel irá ficar:
|
|
330
|
+
*
|
|
331
|
+
* {
|
|
332
|
+
* '2024-02-15': [...],
|
|
333
|
+
* '2024-02-16': [...]
|
|
334
|
+
* }
|
|
335
|
+
*
|
|
336
|
+
* onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsFieldsModel", "columnsLoading" e
|
|
337
|
+
* "columnPagination", organizando os fields, loadings e o controle de paginação por chave identificadora do header.
|
|
338
|
+
*/
|
|
339
|
+
columnsResultsModel.value[headerKey] = props.useMarkRaw ? markRaw(newColumnValues) : newColumnValues
|
|
340
|
+
|
|
341
|
+
/*
|
|
342
|
+
* Pode acontecer das options nos fields da segunda página serem diferentes da primeira página,
|
|
343
|
+
* portanto deve ocorrer o merge.
|
|
344
|
+
*/
|
|
345
|
+
if (response.data?.fields) {
|
|
346
|
+
columnsFieldsModel.value[headerKey] = markRaw(
|
|
347
|
+
getMergedFields(columnsFieldsModel.value[headerKey], response.data?.fields)
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
columnsPagination.value[headerKey].offset = columnsResultsModel.value[headerKey].length
|
|
352
|
+
columnsPagination.value[headerKey].count = response.data?.count
|
|
353
|
+
|
|
354
|
+
emit('fetch-column-success', { response, header })
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* Mergeia os options antigos com os novos de cada field.
|
|
359
|
+
*/
|
|
360
|
+
function getMergedFields (oldFields, newFields) {
|
|
361
|
+
// Primeira vez batendo a API, retorna os novos fields.
|
|
362
|
+
if (!oldFields || !props.lazyLoadingFieldsKeys.length) return newFields
|
|
363
|
+
|
|
364
|
+
// Caso bata a API e por algum motivo não venha fields, mantenha o antigos.
|
|
365
|
+
if (oldFields && !newFields) return oldFields
|
|
366
|
+
|
|
367
|
+
const mergedFields = { ...oldFields }
|
|
368
|
+
|
|
369
|
+
props.lazyLoadingFieldsKeys.forEach(fieldKey => {
|
|
370
|
+
mergedFields[fieldKey].options = getNonDuplicatedOptions(oldFields[fieldKey].options, newFields[fieldKey].options)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
return mergedFields
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/*
|
|
377
|
+
* Tratamento para fazer o merge e evitar options duplicados.
|
|
378
|
+
*/
|
|
379
|
+
function getNonDuplicatedOptions (oldOptions, newOptions) {
|
|
380
|
+
const options = [...oldOptions]
|
|
381
|
+
|
|
382
|
+
newOptions.forEach(item => {
|
|
383
|
+
const hasOption = options.find(option => option.value === item.value)
|
|
384
|
+
|
|
385
|
+
if (!hasOption) options.push(item)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
return options
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getItemsByHeader (header) {
|
|
392
|
+
return hasColumnsLength.value ? columnsResultsModel.value[getKeyByHeader(header)] : []
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Pegar key com base na chave identificador, exemplo:
|
|
397
|
+
* header -> { date: '2024-02-12', ... }
|
|
398
|
+
* columnIdKey -> 'date'
|
|
399
|
+
* retorno -> '2024-02-12'
|
|
400
|
+
*
|
|
401
|
+
* Onde esta chave será o "id" da coluna, sendo usado para bater a API, lidar com paginação, loading, etc.
|
|
402
|
+
*
|
|
403
|
+
* @example getKeyByHeader({ date: '2024-02-12', ... })
|
|
404
|
+
* @returns {string} // '2024-02-12'
|
|
405
|
+
*/
|
|
406
|
+
function getKeyByHeader (header = {}) {
|
|
407
|
+
return header[props.columnIdKey]
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/*
|
|
411
|
+
* Para cada header, irá ser criado um item com sua chave identificadora para lidar com paginação e loading.
|
|
412
|
+
* columnsPagination -> { '2024-02-15': { limit: 12, offset: 0 }, '2024-02-16': { limit: 12, offset: 0 }, ... }
|
|
413
|
+
* columnsLoading ->{ '2024-02-15': false, '2024-02-16': false, ... }
|
|
414
|
+
*/
|
|
415
|
+
function setColumnsPagination () {
|
|
416
|
+
columnsPagination.value = {}
|
|
417
|
+
columnsLoading.value = {}
|
|
418
|
+
|
|
419
|
+
props.headers.forEach(header => {
|
|
420
|
+
const headerKey = getKeyByHeader(header)
|
|
421
|
+
|
|
422
|
+
columnsPagination.value[headerKey] = { limit: props.limitPerColumn, offset: 0 }
|
|
423
|
+
columnsLoading.value[headerKey] = false
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function fetchColumnsValues () {
|
|
428
|
+
reset()
|
|
429
|
+
setColumnHeightContainer()
|
|
430
|
+
setColumnsPagination()
|
|
431
|
+
fetchColumns()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Descricao:
|
|
436
|
+
* Exibe o texto quando:
|
|
437
|
+
* - Nao esta carregando a coluna
|
|
438
|
+
* - Nao tem itens na coluna
|
|
439
|
+
* - Nao estou fazendo o drag and drop
|
|
440
|
+
*
|
|
441
|
+
* @param {Object} header
|
|
442
|
+
*/
|
|
443
|
+
function hasEmptyResultText (header) {
|
|
444
|
+
return !columnsLoading.value[getKeyByHeader(header)] && !getItemsByHeader(header)?.length && !isDragging.value
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* Valida se o tamanho dos itens da coluna é menor que o valor total de itens que o back retorna e
|
|
449
|
+
* se a coluna não está em carregamento.
|
|
450
|
+
*/
|
|
451
|
+
function hasSeeMore (header) {
|
|
452
|
+
const headerKey = getKeyByHeader(header)
|
|
453
|
+
const hasMorePagination = columnsResultsModel.value[headerKey]?.length < columnsPagination.value[headerKey]?.count
|
|
454
|
+
|
|
455
|
+
return hasMorePagination && !columnsLoading.value[headerKey]
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function reset () {
|
|
459
|
+
columnsResultsModel.value = {}
|
|
460
|
+
columnsPagination.value = {}
|
|
461
|
+
columnsLoading.value = {}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getFieldsByHeader (header) {
|
|
465
|
+
const headerKey = getKeyByHeader(header)
|
|
466
|
+
|
|
467
|
+
return columnsFieldsModel.value[headerKey] || {}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Loopa todos os itens da coluna com base no ref para pegar o elemento HTML e setar e instaciar o sortable.
|
|
472
|
+
*/
|
|
473
|
+
function handleElementsList () {
|
|
474
|
+
columnContainer.value.forEach((element, index) => {
|
|
475
|
+
const sortable = setSortable(element, index)
|
|
476
|
+
|
|
477
|
+
sortableInstances.value.push(sortable)
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Descricao:
|
|
483
|
+
* Seta a instancia do sortable, no qual varia de acordo com as props passadas.
|
|
484
|
+
*
|
|
485
|
+
* @param {HTMLElement} element
|
|
486
|
+
* @param {Number} index
|
|
487
|
+
*/
|
|
488
|
+
function setSortable (element, index) {
|
|
489
|
+
const defaultSortableConfig = {
|
|
490
|
+
animation: 500,
|
|
491
|
+
swapThreshold: 1,
|
|
492
|
+
delay: 50,
|
|
493
|
+
delayOnTouchOnly: true,
|
|
494
|
+
emptyInsertThreshold: 0
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Caso seja apenas drag and drop no eixo Y
|
|
499
|
+
*/
|
|
500
|
+
const useOnlyDragAndDropY = !!props.useDragAndDropY && !props.useDragAndDropX
|
|
501
|
+
|
|
502
|
+
const sortable = new Sortable(element, {
|
|
503
|
+
sort: props.useDragAndDropY,
|
|
504
|
+
|
|
505
|
+
...defaultSortableConfig,
|
|
506
|
+
|
|
507
|
+
...props.sortableConfig,
|
|
508
|
+
|
|
509
|
+
group: useOnlyDragAndDropY ? `column-${index}` : 'shared',
|
|
510
|
+
|
|
511
|
+
direction: useOnlyDragAndDropY ? 'vertical' : 'horizontal',
|
|
512
|
+
|
|
513
|
+
onStart: toggleIsDragging,
|
|
514
|
+
|
|
515
|
+
onAdd: event => onDropCard(event),
|
|
516
|
+
|
|
517
|
+
...(props.useDragAndDropY && {
|
|
518
|
+
onSort: event => onDropCard(event)
|
|
519
|
+
})
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
return sortable
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function toggleIsDragging () {
|
|
526
|
+
isDragging.value = !isDragging.value
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function onDropCard (event) {
|
|
530
|
+
onCancelDrop.value = () => cancelDrop(event)
|
|
531
|
+
|
|
532
|
+
onConfirmDrop.value = () => confirmDrop(event)
|
|
533
|
+
|
|
534
|
+
hasConfirmDialogProps.value
|
|
535
|
+
? openConfirmDialog()
|
|
536
|
+
: confirmDrop(event)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function openConfirmDialog () {
|
|
540
|
+
showConfirmDialog.value = true
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function closeConfirmDialog () {
|
|
544
|
+
showConfirmDialog.value = false
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* @param {event} event
|
|
549
|
+
*/
|
|
550
|
+
function cancelDrop (event) {
|
|
551
|
+
/**
|
|
552
|
+
* Insere na posição antiga que pertencia (event.oldIndex) dentro do seu antigo pai (event.from)
|
|
553
|
+
*/
|
|
554
|
+
if (props.useDragAndDropX) event.from.insertBefore(event.item, event.from.children[event.oldIndex])
|
|
555
|
+
|
|
556
|
+
if (props.useDragAndDropY) {
|
|
557
|
+
const oldIndex = event.oldIndex
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Se oldIndex for 0, o targetIndex deverá ser 0, pois isso indica que se o item é o primeiro da lista, ele não será movido para outra posição.
|
|
561
|
+
*
|
|
562
|
+
* Caso o oldIndex for diferente, devo incrementar 1 para adicionar, pois isso permite que o item seja inserido logo após sua posição original.
|
|
563
|
+
*/
|
|
564
|
+
const targetIndex = oldIndex === 0 ? oldIndex : oldIndex + 1
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Verifica se o índice alvo é válido, caso contrário, define como o final
|
|
568
|
+
*/
|
|
569
|
+
const insertBeforeElement = targetIndex < event.from.children.length
|
|
570
|
+
? event.from.children[targetIndex]
|
|
571
|
+
: null
|
|
572
|
+
|
|
573
|
+
event.from.insertBefore(event.item, insertBeforeElement)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (hasConfirmDialogProps.value) closeConfirmDialog()
|
|
577
|
+
|
|
578
|
+
toggleIsDragging()
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function confirmDrop (event) {
|
|
582
|
+
const { from, to, item: { id: itemId } } = event
|
|
583
|
+
|
|
584
|
+
const { headerKey: newHeaderKey } = to.dataset
|
|
585
|
+
const { headerKey: oldHeaderKey } = from.dataset
|
|
586
|
+
|
|
587
|
+
updatePosition({ newHeaderKey, oldHeaderKey, itemId, event })
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
*
|
|
592
|
+
* @param {{
|
|
593
|
+
* headerKey: string,
|
|
594
|
+
* itemId: string
|
|
595
|
+
* }}
|
|
596
|
+
*/
|
|
597
|
+
function removeItemFromList ({ headerKey, itemId }) {
|
|
598
|
+
/**
|
|
599
|
+
* Coluna referente ao model de resultado
|
|
600
|
+
*/
|
|
601
|
+
const columnItemList = columnsResultsModel.value[headerKey]
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Busca o item com base em seu ID na lista de itens da coluna
|
|
605
|
+
*/
|
|
606
|
+
const itemIndex = columnItemList.findIndex(itemContent => itemContent[props.itemIdKey] === itemId)
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Remove o item da listagem com base no index, sendo que preciso subtrair 1 para pegar o index correto
|
|
610
|
+
*/
|
|
611
|
+
columnItemList.splice(itemIndex, 1)
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Remove o item do count da coluna para não mostrar o botão de "Ver mais¨.
|
|
615
|
+
*/
|
|
616
|
+
columnsPagination.value[headerKey].count -= 1
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Descricao:
|
|
621
|
+
* Metodo que realiza a request de update
|
|
622
|
+
*
|
|
623
|
+
* @param {{
|
|
624
|
+
* newHeaderKey: string - ID da coluna de destino,
|
|
625
|
+
* oldHeaderKey: string - ID da antiga coluna,
|
|
626
|
+
* itemId: string - ID do meu item a ser movimento,
|
|
627
|
+
* event: event
|
|
628
|
+
* }}
|
|
629
|
+
*/
|
|
630
|
+
async function updatePosition ({ newHeaderKey, oldHeaderKey, itemId, event }) {
|
|
631
|
+
const params = {
|
|
632
|
+
[props.columnIdKey]: newHeaderKey,
|
|
633
|
+
...(props.useDragAndDropY && { newIndex: event.newIndex }),
|
|
634
|
+
...props.updatePositionParams
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const { data, error } = await promiseHandler(
|
|
638
|
+
axios.patch(`${props.updatePositionUrl}/${itemId}/update-position`, params),
|
|
639
|
+
{
|
|
640
|
+
errorMessage: 'Ocorreu um erro ao atualizar a posição de seu item.',
|
|
641
|
+
useLoading: false,
|
|
642
|
+
onLoading: value => {
|
|
643
|
+
isLoadingUpdatePosition.value = value
|
|
644
|
+
|
|
645
|
+
columnsLoading.value[newHeaderKey] = value
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
if (error) {
|
|
651
|
+
onCancelDrop.value()
|
|
652
|
+
|
|
653
|
+
return
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
removeItemFromList({ headerKey: oldHeaderKey, itemId })
|
|
657
|
+
|
|
658
|
+
setItemList({ headerKey: newHeaderKey, data: data.data, index: event.newIndex })
|
|
659
|
+
|
|
660
|
+
isUpdatingPosition.value = true
|
|
661
|
+
|
|
662
|
+
toggleIsDragging()
|
|
663
|
+
|
|
664
|
+
closeConfirmDialog()
|
|
665
|
+
|
|
666
|
+
emit('update-success')
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function setItemList ({ headerKey, data, index }) {
|
|
670
|
+
/**
|
|
671
|
+
* Coluna referente ao model de resultado
|
|
672
|
+
*/
|
|
673
|
+
const columnItemList = columnsResultsModel.value[headerKey]
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Adiciona o item na posição do event escolhido.
|
|
677
|
+
*/
|
|
678
|
+
columnItemList.splice(index, 0, data.result)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function destroySortable () {
|
|
682
|
+
sortableInstances.value.forEach(sortable => sortable.destroy())
|
|
683
|
+
}
|
|
684
|
+
</script>
|
|
685
|
+
|
|
686
|
+
<style lang="scss">
|
|
687
|
+
.qas-board-generator {
|
|
688
|
+
max-height: 100vh;
|
|
689
|
+
|
|
690
|
+
&__column {
|
|
691
|
+
overflow-x: hidden;
|
|
692
|
+
scrollbar-width: none;
|
|
693
|
+
|
|
694
|
+
&:hover {
|
|
695
|
+
scrollbar-width: thin;
|
|
696
|
+
|
|
697
|
+
&::-webkit-scrollbar {
|
|
698
|
+
display: block;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
&::-webkit-scrollbar {
|
|
703
|
+
display: none;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// 60px é o valor do padding definido no container da column.
|
|
708
|
+
&__column-items {
|
|
709
|
+
height: calc(100% - 60px);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
</style>
|