una-nuxt-module 1.0.2

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 (131) hide show
  1. package/README.md +84 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.mts +6 -0
  4. package/dist/module.d.ts +6 -0
  5. package/dist/module.json +9 -0
  6. package/dist/module.mjs +105 -0
  7. package/dist/runtime/assets/scss/styles.css +1249 -0
  8. package/dist/runtime/classes/FetchClient.d.ts +61 -0
  9. package/dist/runtime/classes/FetchClient.js +71 -0
  10. package/dist/runtime/components/layout/footer/Footer.vue +16 -0
  11. package/dist/runtime/components/layout/header/BtnExtendMenu.vue +29 -0
  12. package/dist/runtime/components/layout/header/Header.vue +7 -0
  13. package/dist/runtime/components/layout/header/HeaderMenu.vue +41 -0
  14. package/dist/runtime/components/layout/header/HeaderMenuTabs.vue +102 -0
  15. package/dist/runtime/components/layout/sidebar/NavCollapse.vue +38 -0
  16. package/dist/runtime/components/layout/sidebar/NavGroup.vue +9 -0
  17. package/dist/runtime/components/layout/sidebar/NavItem.vue +25 -0
  18. package/dist/runtime/components/layout/sidebar/SideBar.vue +74 -0
  19. package/dist/runtime/components/layout/sidebar/SideBarFooter.vue +69 -0
  20. package/dist/runtime/components/layout/sidebar/TopSideBarLogo.vue +25 -0
  21. package/dist/runtime/components/shared/authorization/AuthorizedRenderer.vue +41 -0
  22. package/dist/runtime/components/shared/buttons/BtnBack.vue +19 -0
  23. package/dist/runtime/components/shared/buttons/BtnCancel.vue +13 -0
  24. package/dist/runtime/components/shared/buttons/BtnConfirm.vue +14 -0
  25. package/dist/runtime/components/shared/containers/JsonViewer.vue +13 -0
  26. package/dist/runtime/components/shared/dates/DatePicker.vue +91 -0
  27. package/dist/runtime/components/shared/dialogs/DialogConfirmDelete.vue +32 -0
  28. package/dist/runtime/components/shared/dialogs/DialogExportTable.vue +42 -0
  29. package/dist/runtime/components/shared/feedback/LoadingSession.vue +17 -0
  30. package/dist/runtime/components/shared/feedback/SnackBar.vue +36 -0
  31. package/dist/runtime/components/shared/forms/FormBuilder/FieldBuilder.vue +251 -0
  32. package/dist/runtime/components/shared/forms/FormBuilder/FieldSlotMissed.vue +20 -0
  33. package/dist/runtime/components/shared/forms/FormBuilder/FormBuilder.vue +213 -0
  34. package/dist/runtime/components/shared/forms/FormBuilder/SteppersBuilder.vue +32 -0
  35. package/dist/runtime/components/shared/forms/FormBuilder/TabsBuilder.vue +30 -0
  36. package/dist/runtime/components/shared/forms/FormDialogWrapper.vue +48 -0
  37. package/dist/runtime/components/shared/forms/FormPageWrapper.vue +19 -0
  38. package/dist/runtime/components/shared/forms/FormSubmitSection.vue +48 -0
  39. package/dist/runtime/components/shared/navigation/BreadCrumbs.vue +21 -0
  40. package/dist/runtime/components/shared/tables/CustomTable.vue +261 -0
  41. package/dist/runtime/components/shared/tables/CustomTableHeader.vue +25 -0
  42. package/dist/runtime/components/shared/tables/NoDataMessage.vue +12 -0
  43. package/dist/runtime/components/shared/tables/TableSearchBar.vue +22 -0
  44. package/dist/runtime/components/shared/tables/buttons/BtnAdd.vue +25 -0
  45. package/dist/runtime/components/shared/tables/buttons/BtnDelete.vue +32 -0
  46. package/dist/runtime/components/shared/tables/buttons/BtnEdit.vue +30 -0
  47. package/dist/runtime/components/shared/tables/buttons/BtnExport.vue +17 -0
  48. package/dist/runtime/components/shared/tables/buttons/BtnFilter.vue +21 -0
  49. package/dist/runtime/components/shared/tables/pagination/ItemsPerPageCombo.vue +24 -0
  50. package/dist/runtime/components/shared/tables/pagination/ItemsPerPageLabel.vue +5 -0
  51. package/dist/runtime/components/shared/tables/pagination/PageSelector.vue +16 -0
  52. package/dist/runtime/components/shared/tables/pagination/PaginationInfo.vue +31 -0
  53. package/dist/runtime/composables/useAuthorization.d.ts +32 -0
  54. package/dist/runtime/composables/useAuthorization.js +95 -0
  55. package/dist/runtime/constants/form.d.ts +44 -0
  56. package/dist/runtime/constants/form.js +58 -0
  57. package/dist/runtime/constants/index.d.ts +4 -0
  58. package/dist/runtime/constants/index.js +4 -0
  59. package/dist/runtime/constants/pagination.d.ts +13 -0
  60. package/dist/runtime/constants/pagination.js +8 -0
  61. package/dist/runtime/constants/request.d.ts +5 -0
  62. package/dist/runtime/constants/request.js +6 -0
  63. package/dist/runtime/constants/tables.d.ts +4 -0
  64. package/dist/runtime/constants/tables.js +15 -0
  65. package/dist/runtime/enums/EAsyncDataRequestStatus.d.ts +9 -0
  66. package/dist/runtime/enums/EAsyncDataRequestStatus.js +7 -0
  67. package/dist/runtime/enums/EAuthorization.d.ts +8 -0
  68. package/dist/runtime/enums/EAuthorization.js +6 -0
  69. package/dist/runtime/enums/EFormField.d.ts +14 -0
  70. package/dist/runtime/enums/EFormField.js +12 -0
  71. package/dist/runtime/enums/EFormMode.d.ts +7 -0
  72. package/dist/runtime/enums/EFormMode.js +5 -0
  73. package/dist/runtime/enums/ERequestMethod.d.ts +9 -0
  74. package/dist/runtime/enums/ERequestMethod.js +7 -0
  75. package/dist/runtime/enums/ETheme.d.ts +7 -0
  76. package/dist/runtime/enums/ETheme.js +5 -0
  77. package/dist/runtime/enums/EVuetifyDateFormats.d.ts +32 -0
  78. package/dist/runtime/enums/EVuetifyDateFormats.js +30 -0
  79. package/dist/runtime/enums/index.d.ts +6 -0
  80. package/dist/runtime/enums/index.js +6 -0
  81. package/dist/runtime/i18n/config.d.ts +61 -0
  82. package/dist/runtime/i18n/config.js +10 -0
  83. package/dist/runtime/i18n/locales/es.json +55 -0
  84. package/dist/runtime/i18n/service.d.ts +72 -0
  85. package/dist/runtime/i18n/service.js +3 -0
  86. package/dist/runtime/i18n/vueI18n.d.ts +5 -0
  87. package/dist/runtime/i18n/vueI18n.js +3 -0
  88. package/dist/runtime/index.d.ts +9 -0
  89. package/dist/runtime/layouts/default.vue +31 -0
  90. package/dist/runtime/layouts/empty.vue +12 -0
  91. package/dist/runtime/middleware/authentication.d.ts +10 -0
  92. package/dist/runtime/middleware/authentication.js +30 -0
  93. package/dist/runtime/middleware/authorization.d.ts +7 -0
  94. package/dist/runtime/middleware/authorization.js +39 -0
  95. package/dist/runtime/pages/401.vue +34 -0
  96. package/dist/runtime/pages/403.vue +35 -0
  97. package/dist/runtime/pages/ssoCallback.vue +14 -0
  98. package/dist/runtime/plugins/auth.d.ts +12 -0
  99. package/dist/runtime/plugins/auth.js +83 -0
  100. package/dist/runtime/plugins/vue-json.d.ts +12 -0
  101. package/dist/runtime/plugins/vue-json.js +5 -0
  102. package/dist/runtime/public/images/logos/UNA_LogoMark_Black.png +0 -0
  103. package/dist/runtime/public/images/logos/UNA_LogoType_LogoMark_Red.png +0 -0
  104. package/dist/runtime/server/tsconfig.json +3 -0
  105. package/dist/runtime/stores/UiCustomizer.d.ts +22 -0
  106. package/dist/runtime/stores/UiCustomizer.js +34 -0
  107. package/dist/runtime/stores/appStatus.d.ts +63 -0
  108. package/dist/runtime/stores/appStatus.js +101 -0
  109. package/dist/runtime/stores/auth.d.ts +76 -0
  110. package/dist/runtime/stores/auth.js +66 -0
  111. package/dist/runtime/stores/formModeTracker.d.ts +14 -0
  112. package/dist/runtime/stores/formModeTracker.js +10 -0
  113. package/dist/runtime/types/index.d.ts +584 -0
  114. package/dist/runtime/types/index.js +1 -0
  115. package/dist/runtime/utils/buildSortQueryParams.d.ts +10 -0
  116. package/dist/runtime/utils/buildSortQueryParams.js +3 -0
  117. package/dist/runtime/utils/getCurrentPath.d.ts +7 -0
  118. package/dist/runtime/utils/getCurrentPath.js +4 -0
  119. package/dist/runtime/utils/getDateTimeInISO8601.d.ts +11 -0
  120. package/dist/runtime/utils/getDateTimeInISO8601.js +3 -0
  121. package/dist/runtime/utils/getFromLocalStorage.d.ts +9 -0
  122. package/dist/runtime/utils/getFromLocalStorage.js +6 -0
  123. package/dist/runtime/utils/isNumberInRange.d.ts +11 -0
  124. package/dist/runtime/utils/isNumberInRange.js +5 -0
  125. package/dist/runtime/utils/onlyNumbers.d.ts +9 -0
  126. package/dist/runtime/utils/onlyNumbers.js +3 -0
  127. package/dist/runtime/utils/stringToBoolean.d.ts +9 -0
  128. package/dist/runtime/utils/stringToBoolean.js +3 -0
  129. package/dist/types.d.mts +7 -0
  130. package/dist/types.d.ts +7 -0
  131. package/package.json +69 -0
@@ -0,0 +1,213 @@
1
+ <template>
2
+ <!-- #region TABS OR STEPPER VARIANTS -->
3
+ <template v-if="formConfig.tabs">
4
+ <TabsBuilder
5
+ v-if="formConfig.variant == 'TABS'"
6
+ v-model:form-config="formConfig"
7
+ v-model:tab-selected="tabSelected"
8
+ />
9
+ <SteppersBuilder
10
+ v-if="formConfig.variant == 'STEPPERS'"
11
+ v-model:form-config="formConfig"
12
+ v-model:tab-selected="tabSelected"
13
+ />
14
+
15
+ <v-window v-model="tabSelected">
16
+ <v-window-item
17
+ v-for="(tab, t) in formConfig.tabs"
18
+ :key="tab.value"
19
+ :value="tab.value"
20
+ reverse-transition="slide-x-transition"
21
+ transition="slide-x-transition"
22
+ >
23
+ <v-form
24
+ @submit.prevent="handleSubmitMultipleForms"
25
+ :ref="(element: TFormDomElement) => asignFormRef(tab.value, element)"
26
+ class="mt-2 mx-3"
27
+ >
28
+ <template v-for="(row, r) in tab.rows" :key="r">
29
+ <v-row v-if="row.section">
30
+ <v-label
31
+ :text="row.section.title"
32
+ class="font-weight-bold text-secondary mb-6"
33
+ />
34
+ <!-- <v-divider thickness="1" class="mb-6 mt-2" /> -->
35
+ </v-row>
36
+
37
+ <v-row class="mt-n3">
38
+ <template v-for="(field, f) in row.fields" :key="f">
39
+ <v-col :cols="field.size">
40
+ <slot
41
+ v-if="field.type == EFormField.SLOT"
42
+ :name="`field.${field.key}`"
43
+ :field="castToFieldSlot(field)"
44
+ >
45
+ <FieldSlotMissed :field="castToFieldSlot(field)" />
46
+ </slot>
47
+ <FieldBuilder v-else v-model:form-state="formState" :field />
48
+ </v-col>
49
+ </template>
50
+ </v-row>
51
+ </template>
52
+
53
+ <FormSubmitSection
54
+ :formMode="props.formMode"
55
+ :showBtnNextTab="tab.value < tabsCount"
56
+ :showBtnPrevTab="tab.value > 1"
57
+ @on-cancel="emit('on-cancel')"
58
+ @on-prev-tab="prevTab"
59
+ class="mt-8"
60
+ />
61
+ </v-form>
62
+ </v-window-item>
63
+ </v-window>
64
+ </template>
65
+ <!-- #endregion -->
66
+
67
+ <!-- #region SINGLE FORM VARIANT -->
68
+ <template v-else>
69
+ <v-form
70
+ @submit.prevent="handleSubmitSingleForm"
71
+ ref="singleForm"
72
+ class="mt-8"
73
+ >
74
+ <template v-for="(row, r) in formConfig.rows" :key="r">
75
+ <v-row v-if="row.section">
76
+ <v-label
77
+ :text="row.section.title"
78
+ class="font-weight-bold text-secondary mb-6"
79
+ />
80
+ <!-- <v-divider thickness="1" class="mb-6 mt-2" /> -->
81
+ </v-row>
82
+
83
+ <v-row class="mt-n3">
84
+ <template v-for="(field, f) in row.fields" :key="f">
85
+ <v-col :cols="field.size">
86
+ <slot
87
+ v-if="field.type == EFormField.SLOT"
88
+ :name="`field.${field.key}`"
89
+ :field="castToFieldSlot(field)"
90
+ >
91
+ <FieldSlotMissed :field="castToFieldSlot(field)" />
92
+ </slot>
93
+ <FieldBuilder v-else :field v-model:form-state="formState" />
94
+ </v-col>
95
+ </template>
96
+ </v-row>
97
+ </template>
98
+
99
+ <FormSubmitSection
100
+ :formMode="props.formMode"
101
+ @on-cancel="emit('on-cancel')"
102
+ />
103
+ </v-form>
104
+ </template>
105
+ <!-- #endregion -->
106
+ </template>
107
+
108
+ <script lang="ts" setup generic="T">
109
+ import {
110
+ computed,
111
+ ref,
112
+ type ComponentPublicInstance,
113
+ type PropType,
114
+ } from "vue";
115
+ import type {
116
+ IFormBuilderConfig,
117
+ IFormBuilderFieldSlot,
118
+ IFormBuilderTab,
119
+ TFormMode,
120
+ } from "../../../../types";
121
+ import { EFormField } from "../../../../enums";
122
+
123
+ const formState = defineModel<T>("formState", { required: true });
124
+ const formConfig = defineModel<IFormBuilderConfig>("formConfig", {
125
+ required: true,
126
+ });
127
+
128
+ const emit = defineEmits(["on-submit", "on-cancel"]);
129
+
130
+ const props = defineProps({
131
+ formMode: { type: String as PropType<TFormMode>, required: true },
132
+ wrapper: { type: String, required: true },
133
+ });
134
+
135
+ const tabsForms = ref({});
136
+ const singleForm = ref(null);
137
+ const tabSelected = ref(1);
138
+
139
+ type TFormDomElement = Element | ComponentPublicInstance | null;
140
+ const asignFormRef = (key: number, element: TFormDomElement) => {
141
+ // @ts-ignore
142
+ tabsForms.value[key] = element;
143
+ };
144
+
145
+ // #region HANDLE TABS LOGIC
146
+ const tabsCount = computed(() => {
147
+ return Number(formConfig.value.tabs?.length);
148
+ });
149
+
150
+ const changeTab = (newTab: number) => {
151
+ tabSelected.value = newTab;
152
+ };
153
+
154
+ const prevTab = () => {
155
+ changeTab(tabSelected.value - 1);
156
+ };
157
+
158
+ const nextTab = () => {
159
+ changeTab(tabSelected.value + 1);
160
+ };
161
+ // #endregion
162
+
163
+ // #region HANDLE SUBMIT FORMS
164
+ const handleSubmitSingleForm = async () => {
165
+ // @ts-ignore
166
+ const { valid } = await singleForm.value?.validate();
167
+ if (valid) {
168
+ emit("on-submit");
169
+ }
170
+ };
171
+
172
+ const handleSubmitMultipleForms = async () => {
173
+ if (!(await isCurrentFormValid())) {
174
+ return;
175
+ }
176
+
177
+ if (!isCurrentFormTheLast()) {
178
+ nextTab();
179
+ return;
180
+ }
181
+
182
+ if (await allFormsValid()) {
183
+ emit("on-submit");
184
+ }
185
+ };
186
+
187
+ const isCurrentFormTheLast = () => {
188
+ return tabSelected.value == tabsCount.value;
189
+ };
190
+
191
+ const isCurrentFormValid = async () => {
192
+ // @ts-ignore
193
+ const { valid } = await tabsForms.value[tabSelected.value]?.validate();
194
+ return valid;
195
+ };
196
+
197
+ const allFormsValid = async () => {
198
+ for (const tab of formConfig.value.tabs as IFormBuilderTab[]) {
199
+ // @ts-ignore
200
+ const { valid } = await tabsForms.value[tab.value]?.validate();
201
+ if (!valid) {
202
+ changeTab(tab.value);
203
+ return false;
204
+ }
205
+ }
206
+ return true;
207
+ };
208
+ // #endregion
209
+
210
+ const castToFieldSlot = (field: Object) => {
211
+ return field as IFormBuilderFieldSlot;
212
+ };
213
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <v-stepper
3
+ :alt-labels="formConfig.stepper?.LabelLocation == 'BOTTOM'"
4
+ class="my-8"
5
+ elevation="1"
6
+ v-model="tabSelected"
7
+ >
8
+ <v-stepper-header>
9
+ <template v-for="(tab, t) in formConfig.tabs" :key="t">
10
+ <v-stepper-item
11
+ :complete="tab.value < tabSelected"
12
+ :title="tab.title"
13
+ :value="tab.value"
14
+ color="primary"
15
+ v-model="tabSelected"
16
+ />
17
+ <v-divider v-if="tab.value != formConfig.tabs?.length" />
18
+ </template>
19
+ </v-stepper-header>
20
+ </v-stepper>
21
+ </template>
22
+
23
+ <script lang="ts" setup>
24
+ import type { IFormBuilderConfig } from "../../../../types";
25
+
26
+ const formConfig = defineModel<IFormBuilderConfig>("formConfig", {
27
+ required: true,
28
+ });
29
+ const tabSelected = defineModel<number>("tabSelected", {
30
+ required: true,
31
+ });
32
+ </script>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <v-row class="my-8 mx-0">
3
+ <v-tabs color="primary" grow v-model="tabSelected">
4
+ <template v-for="(tab, t) in formConfig.tabs" :key="t">
5
+ <v-tab
6
+ :class="tabSelected == tab.value ? 'activeTab' : ''"
7
+ :text="tab.title"
8
+ :value="tab.value"
9
+ color="primary"
10
+ variant="plain"
11
+ />
12
+ </template>
13
+ </v-tabs>
14
+ </v-row>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import type { IFormBuilderConfig } from "../../../../types";
19
+
20
+ const formConfig = defineModel<IFormBuilderConfig>("formConfig", {
21
+ required: true,
22
+ });
23
+ const tabSelected = defineModel<number>("tabSelected", {
24
+ required: true,
25
+ });
26
+ </script>
27
+
28
+ <style>
29
+ .activeTab{background-color:rgb(var(--v-theme-borderLight))}
30
+ </style>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <v-dialog v-model="openDialog" persistent :class="width">
3
+ <v-card elevation="2" rounded="lg">
4
+ <div class="d-flex align-center py-2 ml-10 mr-6">
5
+ <!-- Título del formulario -->
6
+ <span class="text-h6 text-primary font-weight-bold">
7
+ {{ props.title }}
8
+ </span>
9
+
10
+ <v-spacer />
11
+
12
+ <!-- Botón cancelar-->
13
+ <v-btn
14
+ @click="emit('on-cancel')"
15
+ elevation="0"
16
+ icon="mdi-close"
17
+ style="opacity: 0.8"
18
+ variant="text"
19
+ />
20
+ </div>
21
+
22
+ <v-divider :thickness="1" />
23
+
24
+ <!-- Slot para el formulario -->
25
+ <div class="mb-8 mx-13">
26
+ <slot name="form" wrapper="dialog" />
27
+ </div>
28
+ </v-card>
29
+ </v-dialog>
30
+ </template>
31
+
32
+ <script lang="ts" setup>
33
+ import type { PropType } from "vue";
34
+
35
+ type TFormWidth = "w-50" | "w-75" | "w-100";
36
+
37
+ const openDialog = defineModel<boolean>("openDialog", { required: true });
38
+
39
+ const emit = defineEmits(["on-cancel"]);
40
+
41
+ const props = defineProps({
42
+ title: { type: String, required: true },
43
+ width: {
44
+ type: String as PropType<TFormWidth>,
45
+ default: "w-50",
46
+ },
47
+ });
48
+ </script>
@@ -0,0 +1,19 @@
1
+ <template>
2
+ <div>
3
+ <!-- Título del formulario -->
4
+ <div class="pa-3 text-center">
5
+ <span class="text-body-1 text-uppercase text-primary font-weight-bold">
6
+ {{ props.title }}
7
+ </span>
8
+ </div>
9
+
10
+ <!-- Slot para el formulario -->
11
+ <slot name="form" wrapper="page" />
12
+ </div>
13
+ </template>
14
+
15
+ <script lang="ts" setup>
16
+ const props = defineProps({
17
+ title: { type: String, required: true },
18
+ });
19
+ </script>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <v-row align-content="end" class="mx-0 mb-0">
3
+ <!-- Btn Prev Tab -->
4
+ <v-btn
5
+ @click="emit('on-prev-tab')"
6
+ color="primary"
7
+ elevation="0"
8
+ text="Anterior"
9
+ v-if="props.showBtnPrevTab"
10
+ variant="text"
11
+ />
12
+
13
+ <v-spacer />
14
+
15
+ <!-- Btn Cancel -->
16
+ <BtnCancel @on-cancel="emit('on-cancel')" class="mr-6" />
17
+
18
+ <!-- Btn Submit or Next Tab -->
19
+ <v-btn
20
+ :text="textForBtnSubmit"
21
+ align-self="end"
22
+ color="primary"
23
+ density="comfortable"
24
+ elevation="0"
25
+ size="large"
26
+ type="submit"
27
+ variant="flat"
28
+ />
29
+ </v-row>
30
+ </template>
31
+
32
+ <script lang="ts" setup>
33
+ import { computed } from "vue";
34
+ import { EFormMode } from "../../../enums/EFormMode";
35
+
36
+ const emit = defineEmits(["on-cancel", "on-prev-tab"]);
37
+
38
+ const props = defineProps({
39
+ formMode: { type: String, required: true },
40
+ showBtnPrevTab: { type: Boolean, default: false },
41
+ showBtnNextTab: { type: Boolean, default: false },
42
+ });
43
+
44
+ const textForBtnSubmit = computed(() => {
45
+ if (props.showBtnNextTab) return "Siguiente";
46
+ return props.formMode == EFormMode.CREATE ? "Crear" : "Editar";
47
+ });
48
+ </script>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <v-row class="mt-n2">
3
+ <v-breadcrumbs :items="props.breadcrumbs">
4
+ <template v-slot:divider>
5
+ <v-icon icon="mdi-chevron-right" />
6
+ </template>
7
+ </v-breadcrumbs>
8
+ </v-row>
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import type { PropType } from "vue";
13
+
14
+ const props = defineProps({
15
+ breadcrumbs: {
16
+ type: Array as PropType<Array<any>>,
17
+ required: true,
18
+ default: [],
19
+ },
20
+ });
21
+ </script>
@@ -0,0 +1,261 @@
1
+ <template>
2
+ <v-data-table
3
+ :headers="getHeaders"
4
+ :items
5
+ :items-per-page="itemsPerPage.value"
6
+ :loading="appStatusStore.makingRequest"
7
+ :page
8
+ :sort-by="sortBy"
9
+ :sticky
10
+ @update:options="emit('on-refresh-table')"
11
+ @update:sortBy="() => {}"
12
+ class="CustomTable"
13
+ v-model:search="textToSearch"
14
+ >
15
+ <!-- Slot para headers de la tabla -->
16
+ <template
17
+ v-for="header in getHeaders"
18
+ #[`header.${header.key}`]="{ column }"
19
+ >
20
+ <slot :name="`header.${header.key}`" :column>
21
+ <v-hover>
22
+ <template v-slot:default="{ isHovering, props }">
23
+ <span
24
+ v-bind="props"
25
+ class="cursor-pointer"
26
+ @click="changeSort(column.key as string)"
27
+ >
28
+ {{ column.title }}
29
+ <v-icon
30
+ v-if="column.sortable"
31
+ :style="{ opacity: getOpacityForSortIcon(column.key as string, isHovering)}"
32
+ :icon="getSortIcon(column.key as string, isHovering)"
33
+ class="ml-2"
34
+ />
35
+ </span>
36
+ </template>
37
+ </v-hover>
38
+ </slot>
39
+ </template>
40
+
41
+ <!-- Slot para columnas de la tabla -->
42
+ <template v-for="header in headers" #[`item.${header.key}`]="{ item }">
43
+ <slot
44
+ v-if="header.key !== KEY_FOR_ACTIONS_COLUMN"
45
+ :name="`item.${header.key}`"
46
+ :item
47
+ >
48
+ {{ getPropertyToRender(item, header.key) }}
49
+ </slot>
50
+ </template>
51
+
52
+ <!-- Slot para columnas de acciones de edicion (editar/eliminar) -->
53
+ <template #item.actions="{ item }" v-if="renderActionsColumn">
54
+ <div>
55
+ <BtnEdit
56
+ @on-edit="emit('on-edit', item)"
57
+ :validateAuthorization="props.validateAuthorizationForEdit"
58
+ />
59
+ <BtnDelete
60
+ @on-delete="emit('on-delete', getIdToDelete(item))"
61
+ :validateAuthorization="props.validateAuthorizationForDelete"
62
+ />
63
+ </div>
64
+ </template>
65
+
66
+ <!-- Slot para el footer de la tabla (sección de paginación) -->
67
+ <template #bottom>
68
+ <div class="text-center pt-2 d-flex align-center">
69
+ <ItemsPerPageLabel />
70
+ <div class="d-flex">
71
+ <ItemsPerPageCombo
72
+ :items="itemsPerPageOptions"
73
+ v-model:items-per-page="itemsPerPage"
74
+ />
75
+ </div>
76
+ <v-spacer />
77
+ <PaginationInfo :paginationInfo />
78
+ <PageSelector :totalPages v-model:page="page" />
79
+ </div>
80
+ </template>
81
+
82
+ <!-- Slot para tabla cargando -->
83
+ <template #loading>
84
+ <v-skeleton-loader type="table-row@10" />
85
+ </template>
86
+
87
+ <!-- Slot para cuando la tabla no contiene datos -->
88
+ <template #no-data>
89
+ <NoDataMessage v-model:text-to-search="textToSearch" />
90
+ </template>
91
+ </v-data-table>
92
+ </template>
93
+
94
+ <script lang="ts" setup generic="T extends Object">
95
+ import {
96
+ PAGINATION_FIRST_PAGE,
97
+ KEY_FOR_ACTIONS_COLUMN,
98
+ PAGINATION_OPTIONS,
99
+ } from "../../../constants";
100
+ import { ref, computed, watch, type ComputedRef, type PropType } from "vue";
101
+ import { EAuthorization } from "../../../enums";
102
+ import { useAppStatusStore } from "../../../stores/appStatus";
103
+ import { useAuthorization } from "../../../composables/useAuthorization";
104
+ import type { IItemsPerPage, ISortFilter, ITableHeader } from "../../../types";
105
+
106
+ const authorization = useAuthorization();
107
+ const appStatusStore = useAppStatusStore();
108
+
109
+ const emit = defineEmits(["on-refresh-table", "on-delete", "on-edit"]);
110
+
111
+ const page = defineModel<number>("page", { required: true });
112
+ const totalPages = defineModel<number>("totalPages", { required: true });
113
+ const textToSearch = defineModel<string>("textToSearch", { required: true });
114
+ const totalElements = defineModel<number>("totalElements", { required: true });
115
+ const itemsPerPage = defineModel<IItemsPerPage>("itemsPerPage", {
116
+ required: true,
117
+ });
118
+ const sortOrder = defineModel<ISortFilter>("sortOrder", {
119
+ required: false,
120
+ });
121
+
122
+ const props = defineProps({
123
+ headers: {
124
+ type: Array as PropType<Array<ITableHeader>>,
125
+ required: true,
126
+ default: [],
127
+ },
128
+ idKey: { type: String, required: false, default: "id" },
129
+ items: { type: Array as PropType<Array<T>>, required: true, default: [] },
130
+ sticky: { type: Boolean, required: false, default: true },
131
+ validateAuthorizationForEdit: {
132
+ type: Boolean,
133
+ required: false,
134
+ default: true,
135
+ },
136
+ validateAuthorizationForDelete: {
137
+ type: Boolean,
138
+ required: false,
139
+ default: true,
140
+ },
141
+ });
142
+
143
+ const sortBy = ref([]);
144
+
145
+ const renderActionsColumn = computed(() => {
146
+ return props.headers.some((header) => header.key === KEY_FOR_ACTIONS_COLUMN);
147
+ });
148
+
149
+ const getHeaders = computed(() => {
150
+ const canEdit = props.validateAuthorizationForEdit
151
+ ? authorization.hasAuthorization(EAuthorization.EDIT)
152
+ : true;
153
+ const canDelete = props.validateAuthorizationForDelete
154
+ ? authorization.hasAuthorization(EAuthorization.DELETE)
155
+ : true;
156
+
157
+ if (canEdit || canDelete) return props.headers;
158
+
159
+ return props.headers.filter(
160
+ (header) => header.key !== KEY_FOR_ACTIONS_COLUMN
161
+ );
162
+ });
163
+
164
+ const itemsPerPageOptions: ComputedRef<IItemsPerPage[]> = computed(() => {
165
+ let options = PAGINATION_OPTIONS;
166
+ options[PAGINATION_OPTIONS.length - 1].value = totalElements.value;
167
+ return options;
168
+ });
169
+
170
+ const paginationInfo = computed(() => {
171
+ return {
172
+ page: page.value,
173
+ totalPages: totalPages.value,
174
+ totalElements: totalElements.value,
175
+ };
176
+ });
177
+
178
+ function getIdToDelete(item: T) {
179
+ if (item.hasOwnProperty(props.idKey)) {
180
+ return item[props.idKey as keyof T];
181
+ }
182
+ return null;
183
+ }
184
+
185
+ function getNestedProperty(object: any, path: string) {
186
+ try {
187
+ return path.split(".").reduce((current, key) => current[key], object);
188
+ } catch (err) {
189
+ console.log(err);
190
+ return null;
191
+ }
192
+ }
193
+
194
+ function getPropertyToRender(item: T, key: string) {
195
+ const isNestedKey = key.split(".").length > 1;
196
+ return isNestedKey ? getNestedProperty(item, key) : item[key as keyof T];
197
+ }
198
+
199
+ function changeSort(key: string) {
200
+ if (!sortOrder.value) {
201
+ return;
202
+ }
203
+
204
+ const currentSort = sortOrder.value[key];
205
+ switch (currentSort) {
206
+ case false:
207
+ sortOrder.value[key] = "desc";
208
+ break;
209
+ case "desc":
210
+ sortOrder.value[key] = "asc";
211
+ break;
212
+ case "asc":
213
+ sortOrder.value[key] = false;
214
+ break;
215
+ default:
216
+ break;
217
+ }
218
+
219
+ emit("on-refresh-table");
220
+ }
221
+
222
+ function getSortIcon(key: string, isHovering: boolean | null) {
223
+ if (!sortOrder.value) {
224
+ return;
225
+ }
226
+
227
+ const currentSort = sortOrder.value[key];
228
+ switch (currentSort) {
229
+ case false:
230
+ return isHovering ? "mdi-sort-descending" : "";
231
+ case "desc":
232
+ return "mdi-sort-descending";
233
+ case "asc":
234
+ return "mdi-sort-ascending";
235
+ default:
236
+ break;
237
+ }
238
+ }
239
+
240
+ function getOpacityForSortIcon(key: string, isHovering: boolean | null) {
241
+ if (!sortOrder.value) {
242
+ return;
243
+ }
244
+
245
+ const currentSort = sortOrder.value[key];
246
+ if (!currentSort && isHovering) {
247
+ return 0.4;
248
+ }
249
+ return 1;
250
+ }
251
+
252
+ // Cuando en la paginación se actualiza los elementos por página se debe resetear la página a la primera
253
+ watch(itemsPerPage, () => {
254
+ page.value = PAGINATION_FIRST_PAGE;
255
+ });
256
+
257
+ // Cuando en la paginación se actualiza la página se debe refresca la tabla
258
+ watch(page, () => {
259
+ emit("on-refresh-table");
260
+ });
261
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div class="pb-5 d-flex align-center justify-between">
3
+ <!-- Botón nuevo registro -->
4
+ <BtnAdd @on-create="emit('on-create')" />
5
+
6
+ <!-- Botón exportar tabla -->
7
+ <BtnExport @on-export="emit('on-export')" />
8
+
9
+ <v-spacer />
10
+
11
+ <!-- Barra de búsqueda de la tabla -->
12
+ <TableSearchBar
13
+ v-model:text-to-search="textToSearch"
14
+ @on-search="() => {}"
15
+ />
16
+
17
+ <!-- Slot para sección de filtros -->
18
+ <slot name="filter" />
19
+ </div>
20
+ </template>
21
+
22
+ <script lang="ts" setup>
23
+ const emit = defineEmits(["on-create", "on-export"]);
24
+ const textToSearch = defineModel<string>("textToSearch", { required: true });
25
+ </script>