@bildvitta/quasar-ui-asteroid 3.17.0-beta.21 → 3.17.0-beta.22

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.
Files changed (314) hide show
  1. package/package.json +1 -1
  2. package/src/asteroid.js +4 -4
  3. package/src/components/actions/QasActions.vue +81 -81
  4. package/src/components/actions/QasActions.yml +34 -34
  5. package/src/components/actions-menu/QasActionsMenu.vue +251 -251
  6. package/src/components/actions-menu/QasActionsMenu.yml +89 -89
  7. package/src/components/actions-menu/composables/use-delete.js +30 -30
  8. package/src/components/actions-menu/composables/use-double-split-actions.js +42 -42
  9. package/src/components/actions-menu/composables/use-options-actions.js +27 -27
  10. package/src/components/actions-menu/composables/use-single-action.js +17 -17
  11. package/src/components/actions-menu/composables/use-single-split-actions.js +35 -35
  12. package/src/components/actions-menu/composables/use-tooltips.js +43 -43
  13. package/src/components/actions-menu/utils/get-label.js +3 -3
  14. package/src/components/actions-menu/utils/set-click-handler.js +6 -6
  15. package/src/components/alert/QasAlert.test.js +43 -43
  16. package/src/components/alert/QasAlert.vue +110 -110
  17. package/src/components/alert/QasAlert.yml +39 -39
  18. package/src/components/app-bar/QasAppBar.vue +119 -119
  19. package/src/components/app-bar/QasAppBar.yml +44 -44
  20. package/src/components/app-menu/QasAppMenu.vue +392 -392
  21. package/src/components/app-menu/QasAppMenu.yml +73 -73
  22. package/src/components/app-menu/composables/use-app-menu-dropdown.js +71 -71
  23. package/src/components/app-menu/composables/use-app-user.js +49 -49
  24. package/src/components/app-menu/composables/use-development-badge.js +23 -23
  25. package/src/components/app-menu/private/PvAppMenuDropdown.vue +71 -71
  26. package/src/components/app-menu/private/PvAppMenuHelpChat.vue +222 -222
  27. package/src/components/app-user/QasAppUser.vue +281 -281
  28. package/src/components/app-user/QasAppUser.yml +56 -56
  29. package/src/components/avatar/QasAvatar.vue +95 -95
  30. package/src/components/avatar/QasAvatar.yml +38 -38
  31. package/src/components/avatar/enums/AvatarColors.js +10 -10
  32. package/src/components/badge/QasBadge.vue +87 -87
  33. package/src/components/badge/QasBadge.yml +55 -55
  34. package/src/components/board-generator/QasBoardGenerator.vue +712 -712
  35. package/src/components/board-generator/QasBoardGenerator.yml +167 -167
  36. package/src/components/box/QasBox.vue +51 -51
  37. package/src/components/box/QasBox.yml +29 -29
  38. package/src/components/breakline/QasBreakline.vue +38 -38
  39. package/src/components/breakline/QasBreakline.yml +25 -25
  40. package/src/components/btn/QasBtn.vue +148 -148
  41. package/src/components/btn/QasBtn.yml +48 -48
  42. package/src/components/btn-dropdown/QasBtnDropdown.vue +146 -146
  43. package/src/components/btn-dropdown/QasBtnDropdown.yml +59 -59
  44. package/src/components/card/QasCard.vue +132 -132
  45. package/src/components/card/QasCard.yml +44 -44
  46. package/src/components/card-image/QasCardImage.vue +95 -95
  47. package/src/components/card-image/QasCardImage.yml +48 -48
  48. package/src/components/chart-view/QasChartView.vue +441 -441
  49. package/src/components/chart-view/QasChartView.yml +109 -109
  50. package/src/components/chart-view/config/charts/bar.js +45 -45
  51. package/src/components/chart-view/config/charts/doughnut.js +43 -43
  52. package/src/components/chart-view/config/charts/index.js +9 -9
  53. package/src/components/chart-view/config/charts/line.js +57 -57
  54. package/src/components/chart-view/config/defaults/colors.js +19 -19
  55. package/src/components/chart-view/config/defaults/font.js +5 -5
  56. package/src/components/chart-view/config/defaults/index.js +2 -2
  57. package/src/components/chart-view/config/index.js +3 -3
  58. package/src/components/chart-view/config/plugins/index.js +3 -3
  59. package/src/components/chart-view/config/plugins/legend.js +9 -9
  60. package/src/components/chart-view/config/plugins/tooltip.js +15 -15
  61. package/src/components/chart-view/config/plugins/zoom.js +31 -31
  62. package/src/components/checkbox/QasCheckbox.vue +150 -150
  63. package/src/components/checkbox/QasCheckbox.yml +36 -36
  64. package/src/components/copy/QasCopy.vue +46 -46
  65. package/src/components/copy/QasCopy.yml +23 -23
  66. package/src/components/date/QasDate.vue +600 -600
  67. package/src/components/date/QasDate.yml +69 -69
  68. package/src/components/date/enums/DateMaskOptions.js +6 -6
  69. package/src/components/date-time-input/QasDateTimeInput.vue +269 -269
  70. package/src/components/date-time-input/QasDateTimeInput.yml +64 -64
  71. package/src/components/debugger/QasDebugger.vue +31 -31
  72. package/src/components/debugger/QasDebugger.yml +10 -10
  73. package/src/components/delete/QasDelete.vue +100 -100
  74. package/src/components/delete/QasDelete.yml +60 -60
  75. package/src/components/dialog/QasDialog.vue +217 -217
  76. package/src/components/dialog/QasDialog.yml +117 -117
  77. package/src/components/dialog/composables/use-cancel.js +40 -40
  78. package/src/components/dialog/composables/use-dynamic-components.js +86 -86
  79. package/src/components/dialog/composables/use-ok.js +45 -45
  80. package/src/components/dialog-router/QasDialogRouter.vue +86 -86
  81. package/src/components/dialog-router/QasDialogRouter.yml +23 -23
  82. package/src/components/drawer/QasDrawer.vue +118 -118
  83. package/src/components/drawer/QasDrawer.yml +58 -58
  84. package/src/components/empty-result-text/QasEmptyResultText.vue +18 -18
  85. package/src/components/empty-result-text/QasEmptyResultText.yml +14 -14
  86. package/src/components/expansion-item/QasExpansionItem.vue +291 -291
  87. package/src/components/expansion-item/QasExpansionItem.yml +76 -76
  88. package/src/components/field/QasField.vue +220 -220
  89. package/src/components/field/QasField.yml +36 -36
  90. package/src/components/filters/QasFilters.vue +472 -472
  91. package/src/components/filters/QasFilters.yml +171 -171
  92. package/src/components/filters/private/PvFiltersButton.vue +101 -101
  93. package/src/components/form-generator/QasFormGenerator.vue +274 -274
  94. package/src/components/form-generator/QasFormGenerator.yml +109 -109
  95. package/src/components/form-view/QasFormView.vue +484 -484
  96. package/src/components/form-view/QasFormView.yml +253 -253
  97. package/src/components/gallery/QasGallery.vue +243 -243
  98. package/src/components/gallery/QasGallery.yml +188 -188
  99. package/src/components/gallery/composables/use-delete.js +54 -54
  100. package/src/components/gallery/private/PvGalleryCarouselDialog.vue +92 -92
  101. package/src/components/gallery/private/PvGalleryDeleteDialog.vue +54 -54
  102. package/src/components/gallery-card/QasGalleryCard.vue +153 -153
  103. package/src/components/gallery-card/QasGalleryCard.yml +61 -61
  104. package/src/components/grabbable/QasGrabbable.vue +185 -185
  105. package/src/components/grabbable/QasGrabbable.yml +26 -26
  106. package/src/components/grid-generator/QasGridGenerator.vue +210 -210
  107. package/src/components/grid-generator/QasGridGenerator.yml +127 -127
  108. package/src/components/grid-item/QasGridItem.vue +89 -89
  109. package/src/components/grid-item/QasGridItem.yml +22 -22
  110. package/src/components/header/QasHeader.vue +152 -152
  111. package/src/components/header/QasHeader.yml +50 -50
  112. package/src/components/infinite-scroll/QasInfiniteScroll.vue +168 -168
  113. package/src/components/infinite-scroll/QasInfiniteScroll.yml +78 -78
  114. package/src/components/info/QasInfo.vue +155 -155
  115. package/src/components/info/QasInfo.yml +34 -34
  116. package/src/components/input/QasInput.vue +252 -252
  117. package/src/components/input/QasInput.yml +65 -65
  118. package/src/components/label/QasLabel.vue +61 -61
  119. package/src/components/label/QasLabel.yml +44 -44
  120. package/src/components/layout/QasLayout.vue +101 -101
  121. package/src/components/layout/QasLayout.yml +46 -46
  122. package/src/components/layout/private/PvLayoutNotificationCard.vue +86 -86
  123. package/src/components/layout/private/PvLayoutNotificationsDrawer.vue +140 -140
  124. package/src/components/list-items/QasListItems.vue +125 -125
  125. package/src/components/list-items/QasListItems.yml +71 -71
  126. package/src/components/list-view/QasListView.vue +302 -302
  127. package/src/components/list-view/QasListView.yml +169 -169
  128. package/src/components/map/QasMap.vue +75 -75
  129. package/src/components/map/QasMap.yml +33 -33
  130. package/src/components/nested-fields/QasNestedFields.vue +584 -584
  131. package/src/components/nested-fields/QasNestedFields.yml +296 -296
  132. package/src/components/numeric-input/QasNumericInput.vue +206 -206
  133. package/src/components/numeric-input/QasNumericInput.yml +85 -85
  134. package/src/components/option-group/QasOptionGroup.vue +54 -54
  135. package/src/components/option-group/QasOptionGroup.yml +30 -30
  136. package/src/components/page-header/QasPageHeader.vue +143 -143
  137. package/src/components/page-header/QasPageHeader.yml +42 -42
  138. package/src/components/pagination/QasPagination.vue +71 -71
  139. package/src/components/pagination/QasPagination.yml +4 -4
  140. package/src/components/password-input/QasPasswordInput.vue +110 -110
  141. package/src/components/password-input/QasPasswordInput.yml +111 -111
  142. package/src/components/password-strength-checker/QasPasswordStrengthChecker.vue +71 -71
  143. package/src/components/password-strength-checker/QasPasswordStrengthChecker.yml +103 -103
  144. package/src/components/profile/QasProfile.vue +98 -98
  145. package/src/components/profile/QasProfile.yml +60 -60
  146. package/src/components/radio/QasRadio.vue +62 -62
  147. package/src/components/radio/QasRadio.yml +11 -11
  148. package/src/components/resizer/QasResizer.vue +90 -90
  149. package/src/components/resizer/QasResizer.yml +24 -24
  150. package/src/components/search-box/QasSearchBox.vue +294 -294
  151. package/src/components/search-box/QasSearchBox.yml +154 -154
  152. package/src/components/search-input/QasSearchInput.vue +106 -106
  153. package/src/components/search-input/QasSearchInput.yml +48 -48
  154. package/src/components/select/QasSelect.vue +514 -514
  155. package/src/components/select/QasSelect.yml +157 -157
  156. package/src/components/select-list/QasSelectList.vue +238 -238
  157. package/src/components/select-list/QasSelectList.yml +108 -108
  158. package/src/components/select-list/private/PvSelectListCheckbox.vue +31 -31
  159. package/src/components/select-list-dialog/QasSelectListDialog.vue +395 -395
  160. package/src/components/select-list-dialog/QasSelectListDialog.yml +108 -108
  161. package/src/components/signature-pad/QasSignaturePad.vue +122 -122
  162. package/src/components/signature-pad/QasSignaturePad.yml +53 -53
  163. package/src/components/signature-uploader/QasSignatureUploader.vue +164 -164
  164. package/src/components/signature-uploader/QasSignatureUploader.yml +37 -37
  165. package/src/components/single-view/QasSingleView.vue +175 -175
  166. package/src/components/single-view/QasSingleView.yml +131 -131
  167. package/src/components/sortable/QasSortable.vue +151 -151
  168. package/src/components/sortable/QasSortable.yml +65 -65
  169. package/src/components/status/QasStatus.test.js +11 -11
  170. package/src/components/status/QasStatus.vue +29 -29
  171. package/src/components/status/QasStatus.yml +10 -10
  172. package/src/components/stepper/QasStepper.vue +199 -152
  173. package/src/components/stepper/QasStepper.yml +35 -35
  174. package/src/components/stepper-form-view/QasStepperFormView.vue +86 -84
  175. package/src/components/stepper-form-view/QasStepperFormView.yml +58 -58
  176. package/src/components/table-generator/QasTableGenerator.vue +416 -416
  177. package/src/components/table-generator/QasTableGenerator.yml +94 -94
  178. package/src/components/tabs-generator/QasTabsGenerator.vue +196 -196
  179. package/src/components/tabs-generator/QasTabsGenerator.yml +53 -53
  180. package/src/components/text-truncate/QasTextTruncate.vue +274 -274
  181. package/src/components/text-truncate/QasTextTruncate.yml +60 -60
  182. package/src/components/timeline/QasTimeline.vue +140 -140
  183. package/src/components/timeline/QasTimeline.yml +36 -36
  184. package/src/components/toggle/QasToggle.vue +14 -14
  185. package/src/components/toggle/QasToggle.yml +5 -5
  186. package/src/components/toggle-visibility/QasToggleVisibility.vue +79 -79
  187. package/src/components/toggle-visibility/QasToggleVisibility.yml +30 -30
  188. package/src/components/transfer/QasTransfer.vue +239 -239
  189. package/src/components/transfer/QasTransfer.yml +60 -60
  190. package/src/components/tree-generator/QasTreeForm.vue +62 -62
  191. package/src/components/tree-generator/QasTreeGenerator.vue +488 -488
  192. package/src/components/tree-generator/QasTreeGenerator.yml +81 -81
  193. package/src/components/uploader/QasUploader.vue +691 -645
  194. package/src/components/uploader/QasUploader.yml +206 -192
  195. package/src/components/uploader/private/PvUploaderGalleryCard.vue +357 -357
  196. package/src/components/welcome/QasWelcome.vue +125 -125
  197. package/src/components/welcome/QasWelcome.yml +23 -23
  198. package/src/components/welcome/private/PvWelcomeShortcutCard.vue +58 -58
  199. package/src/components/whatsapp-link/QasWhatsappLink.vue +34 -34
  200. package/src/components/whatsapp-link/QasWhatsappLink.yml +18 -18
  201. package/src/composables/index.js +8 -8
  202. package/src/composables/private/index.js +3 -3
  203. package/src/composables/private/use-generator.js +174 -174
  204. package/src/composables/private/use-toggle-visibility.js +48 -48
  205. package/src/composables/private/use-view.js +186 -186
  206. package/src/composables/use-context.js +15 -15
  207. package/src/composables/use-form.js +32 -32
  208. package/src/composables/use-history.js +46 -46
  209. package/src/composables/use-notifications.js +128 -128
  210. package/src/composables/use-query-cache.js +60 -60
  211. package/src/composables/use-screen.js +42 -42
  212. package/src/css/base/font-face.scss +28 -28
  213. package/src/css/base/index.scss +1 -1
  214. package/src/css/components/base.scss +12 -12
  215. package/src/css/components/button.scss +116 -116
  216. package/src/css/components/checkbox.scss +14 -14
  217. package/src/css/components/editor.scss +7 -7
  218. package/src/css/components/field.scss +88 -88
  219. package/src/css/components/index.scss +10 -10
  220. package/src/css/components/item.scss +52 -52
  221. package/src/css/components/radio.scss +18 -18
  222. package/src/css/components/scrollbar.scss +20 -20
  223. package/src/css/components/tabs.scss +3 -3
  224. package/src/css/components/toggle.scss +13 -13
  225. package/src/css/mixins/index.scss +3 -3
  226. package/src/css/mixins/set-brand.scss +15 -15
  227. package/src/css/mixins/set-button.scss +26 -26
  228. package/src/css/mixins/set-typography.scss +8 -8
  229. package/src/css/plugins/index.scss +2 -2
  230. package/src/css/plugins/loading.scss +5 -5
  231. package/src/css/plugins/notify.scss +100 -100
  232. package/src/css/utils/background.scss +34 -34
  233. package/src/css/utils/border-radius.scss +29 -29
  234. package/src/css/utils/border.scss +42 -42
  235. package/src/css/utils/container.scss +36 -36
  236. package/src/css/utils/fonts.scss +5 -5
  237. package/src/css/utils/index.scss +10 -10
  238. package/src/css/utils/line-height.scss +19 -19
  239. package/src/css/utils/opacity.scss +5 -5
  240. package/src/css/utils/scroll.scss +21 -21
  241. package/src/css/utils/text.scss +15 -15
  242. package/src/css/utils/unset.scss +8 -8
  243. package/src/css/variables/button.scss +4 -4
  244. package/src/css/variables/index.scss +6 -6
  245. package/src/css/variables/scrollbar.scss +11 -11
  246. package/src/css/variables/separator.scss +2 -2
  247. package/src/css/variables/shadow.scss +3 -3
  248. package/src/css/variables/spacing.scss +98 -98
  249. package/src/css/variables/typography.scss +149 -149
  250. package/src/directives/Test.js +13 -13
  251. package/src/enums/Align.js +7 -7
  252. package/src/enums/Spacing.js +98 -98
  253. package/src/enums/Status.js +33 -33
  254. package/src/helpers/add-counter-suffix.js +3 -3
  255. package/src/helpers/base-64-to-blob.js +21 -21
  256. package/src/helpers/camelize-fields-name.js +15 -15
  257. package/src/helpers/construct-object.js +29 -29
  258. package/src/helpers/copy-to-clipboard.js +15 -15
  259. package/src/helpers/destroy-nested-children-by-key.js +33 -33
  260. package/src/helpers/download-file.js +21 -21
  261. package/src/helpers/filter-list-by-handle.js +31 -31
  262. package/src/helpers/filter-object-to-array.js +29 -29
  263. package/src/helpers/filter-object.js +34 -34
  264. package/src/helpers/filters.js +163 -163
  265. package/src/helpers/find-children-by-key.js +14 -14
  266. package/src/helpers/get-greatest-common-divisor.js +16 -16
  267. package/src/helpers/get-normalized-options.js +20 -20
  268. package/src/helpers/get-placeholder.js +19 -19
  269. package/src/helpers/get-required-label.js +3 -3
  270. package/src/helpers/get-slot-children-text.js +15 -15
  271. package/src/helpers/handle-process.js +13 -13
  272. package/src/helpers/images.js +28 -28
  273. package/src/helpers/index.js +25 -25
  274. package/src/helpers/is-empty.js +3 -3
  275. package/src/helpers/is-local-development.js +3 -3
  276. package/src/helpers/private/gutter-validator.js +15 -15
  277. package/src/helpers/private/has-parent-by-class-name.js +15 -15
  278. package/src/helpers/promise-handler.js +40 -40
  279. package/src/helpers/rules.js +7 -7
  280. package/src/helpers/set-scroll-on-grab.js +128 -128
  281. package/src/index.cjs.js +1 -1
  282. package/src/index.esm.js +4 -4
  283. package/src/index.scss +38 -38
  284. package/src/index.umd.js +2 -2
  285. package/src/mixins/context.js +8 -8
  286. package/src/mixins/delete.js +48 -48
  287. package/src/mixins/dialog-router.js +17 -17
  288. package/src/mixins/form.js +11 -11
  289. package/src/mixins/generator.js +94 -94
  290. package/src/mixins/index.js +17 -17
  291. package/src/mixins/password.js +86 -86
  292. package/src/mixins/search-filter.js +354 -354
  293. package/src/mixins/view.js +156 -156
  294. package/src/pages/ErrorComponent.vue +74 -74
  295. package/src/pages/Forbidden.vue +29 -29
  296. package/src/pages/NotFound.vue +29 -29
  297. package/src/pages/ServerError.vue +29 -29
  298. package/src/pages/Unauthorized.vue +29 -29
  299. package/src/plugins/delete/Delete.js +107 -107
  300. package/src/plugins/delete/Delete.yml +75 -75
  301. package/src/plugins/dialog/Dialog.js +14 -14
  302. package/src/plugins/dialog/Dialog.yml +10 -10
  303. package/src/plugins/index.js +13 -13
  304. package/src/plugins/notify-error/NotifyError.js +13 -13
  305. package/src/plugins/notify-error/NotifyError.yml +11 -11
  306. package/src/plugins/notify-success/NotifySuccess.js +13 -13
  307. package/src/plugins/notify-success/NotifySuccess.yml +11 -11
  308. package/src/plugins/screen/Screen.js +35 -35
  309. package/src/plugins/screen/Screen.yml +16 -16
  310. package/src/shared/date-config.js +26 -26
  311. package/src/shared/fuse-config.js +4 -4
  312. package/src/shared/notify-config.js +7 -7
  313. package/src/vue-plugin/third-party-component-handler.js +29 -29
  314. 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>