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.
- package/README.md +84 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +105 -0
- package/dist/runtime/assets/scss/styles.css +1249 -0
- package/dist/runtime/classes/FetchClient.d.ts +61 -0
- package/dist/runtime/classes/FetchClient.js +71 -0
- package/dist/runtime/components/layout/footer/Footer.vue +16 -0
- package/dist/runtime/components/layout/header/BtnExtendMenu.vue +29 -0
- package/dist/runtime/components/layout/header/Header.vue +7 -0
- package/dist/runtime/components/layout/header/HeaderMenu.vue +41 -0
- package/dist/runtime/components/layout/header/HeaderMenuTabs.vue +102 -0
- package/dist/runtime/components/layout/sidebar/NavCollapse.vue +38 -0
- package/dist/runtime/components/layout/sidebar/NavGroup.vue +9 -0
- package/dist/runtime/components/layout/sidebar/NavItem.vue +25 -0
- package/dist/runtime/components/layout/sidebar/SideBar.vue +74 -0
- package/dist/runtime/components/layout/sidebar/SideBarFooter.vue +69 -0
- package/dist/runtime/components/layout/sidebar/TopSideBarLogo.vue +25 -0
- package/dist/runtime/components/shared/authorization/AuthorizedRenderer.vue +41 -0
- package/dist/runtime/components/shared/buttons/BtnBack.vue +19 -0
- package/dist/runtime/components/shared/buttons/BtnCancel.vue +13 -0
- package/dist/runtime/components/shared/buttons/BtnConfirm.vue +14 -0
- package/dist/runtime/components/shared/containers/JsonViewer.vue +13 -0
- package/dist/runtime/components/shared/dates/DatePicker.vue +91 -0
- package/dist/runtime/components/shared/dialogs/DialogConfirmDelete.vue +32 -0
- package/dist/runtime/components/shared/dialogs/DialogExportTable.vue +42 -0
- package/dist/runtime/components/shared/feedback/LoadingSession.vue +17 -0
- package/dist/runtime/components/shared/feedback/SnackBar.vue +36 -0
- package/dist/runtime/components/shared/forms/FormBuilder/FieldBuilder.vue +251 -0
- package/dist/runtime/components/shared/forms/FormBuilder/FieldSlotMissed.vue +20 -0
- package/dist/runtime/components/shared/forms/FormBuilder/FormBuilder.vue +213 -0
- package/dist/runtime/components/shared/forms/FormBuilder/SteppersBuilder.vue +32 -0
- package/dist/runtime/components/shared/forms/FormBuilder/TabsBuilder.vue +30 -0
- package/dist/runtime/components/shared/forms/FormDialogWrapper.vue +48 -0
- package/dist/runtime/components/shared/forms/FormPageWrapper.vue +19 -0
- package/dist/runtime/components/shared/forms/FormSubmitSection.vue +48 -0
- package/dist/runtime/components/shared/navigation/BreadCrumbs.vue +21 -0
- package/dist/runtime/components/shared/tables/CustomTable.vue +261 -0
- package/dist/runtime/components/shared/tables/CustomTableHeader.vue +25 -0
- package/dist/runtime/components/shared/tables/NoDataMessage.vue +12 -0
- package/dist/runtime/components/shared/tables/TableSearchBar.vue +22 -0
- package/dist/runtime/components/shared/tables/buttons/BtnAdd.vue +25 -0
- package/dist/runtime/components/shared/tables/buttons/BtnDelete.vue +32 -0
- package/dist/runtime/components/shared/tables/buttons/BtnEdit.vue +30 -0
- package/dist/runtime/components/shared/tables/buttons/BtnExport.vue +17 -0
- package/dist/runtime/components/shared/tables/buttons/BtnFilter.vue +21 -0
- package/dist/runtime/components/shared/tables/pagination/ItemsPerPageCombo.vue +24 -0
- package/dist/runtime/components/shared/tables/pagination/ItemsPerPageLabel.vue +5 -0
- package/dist/runtime/components/shared/tables/pagination/PageSelector.vue +16 -0
- package/dist/runtime/components/shared/tables/pagination/PaginationInfo.vue +31 -0
- package/dist/runtime/composables/useAuthorization.d.ts +32 -0
- package/dist/runtime/composables/useAuthorization.js +95 -0
- package/dist/runtime/constants/form.d.ts +44 -0
- package/dist/runtime/constants/form.js +58 -0
- package/dist/runtime/constants/index.d.ts +4 -0
- package/dist/runtime/constants/index.js +4 -0
- package/dist/runtime/constants/pagination.d.ts +13 -0
- package/dist/runtime/constants/pagination.js +8 -0
- package/dist/runtime/constants/request.d.ts +5 -0
- package/dist/runtime/constants/request.js +6 -0
- package/dist/runtime/constants/tables.d.ts +4 -0
- package/dist/runtime/constants/tables.js +15 -0
- package/dist/runtime/enums/EAsyncDataRequestStatus.d.ts +9 -0
- package/dist/runtime/enums/EAsyncDataRequestStatus.js +7 -0
- package/dist/runtime/enums/EAuthorization.d.ts +8 -0
- package/dist/runtime/enums/EAuthorization.js +6 -0
- package/dist/runtime/enums/EFormField.d.ts +14 -0
- package/dist/runtime/enums/EFormField.js +12 -0
- package/dist/runtime/enums/EFormMode.d.ts +7 -0
- package/dist/runtime/enums/EFormMode.js +5 -0
- package/dist/runtime/enums/ERequestMethod.d.ts +9 -0
- package/dist/runtime/enums/ERequestMethod.js +7 -0
- package/dist/runtime/enums/ETheme.d.ts +7 -0
- package/dist/runtime/enums/ETheme.js +5 -0
- package/dist/runtime/enums/EVuetifyDateFormats.d.ts +32 -0
- package/dist/runtime/enums/EVuetifyDateFormats.js +30 -0
- package/dist/runtime/enums/index.d.ts +6 -0
- package/dist/runtime/enums/index.js +6 -0
- package/dist/runtime/i18n/config.d.ts +61 -0
- package/dist/runtime/i18n/config.js +10 -0
- package/dist/runtime/i18n/locales/es.json +55 -0
- package/dist/runtime/i18n/service.d.ts +72 -0
- package/dist/runtime/i18n/service.js +3 -0
- package/dist/runtime/i18n/vueI18n.d.ts +5 -0
- package/dist/runtime/i18n/vueI18n.js +3 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/layouts/default.vue +31 -0
- package/dist/runtime/layouts/empty.vue +12 -0
- package/dist/runtime/middleware/authentication.d.ts +10 -0
- package/dist/runtime/middleware/authentication.js +30 -0
- package/dist/runtime/middleware/authorization.d.ts +7 -0
- package/dist/runtime/middleware/authorization.js +39 -0
- package/dist/runtime/pages/401.vue +34 -0
- package/dist/runtime/pages/403.vue +35 -0
- package/dist/runtime/pages/ssoCallback.vue +14 -0
- package/dist/runtime/plugins/auth.d.ts +12 -0
- package/dist/runtime/plugins/auth.js +83 -0
- package/dist/runtime/plugins/vue-json.d.ts +12 -0
- package/dist/runtime/plugins/vue-json.js +5 -0
- package/dist/runtime/public/images/logos/UNA_LogoMark_Black.png +0 -0
- package/dist/runtime/public/images/logos/UNA_LogoType_LogoMark_Red.png +0 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/stores/UiCustomizer.d.ts +22 -0
- package/dist/runtime/stores/UiCustomizer.js +34 -0
- package/dist/runtime/stores/appStatus.d.ts +63 -0
- package/dist/runtime/stores/appStatus.js +101 -0
- package/dist/runtime/stores/auth.d.ts +76 -0
- package/dist/runtime/stores/auth.js +66 -0
- package/dist/runtime/stores/formModeTracker.d.ts +14 -0
- package/dist/runtime/stores/formModeTracker.js +10 -0
- package/dist/runtime/types/index.d.ts +584 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/utils/buildSortQueryParams.d.ts +10 -0
- package/dist/runtime/utils/buildSortQueryParams.js +3 -0
- package/dist/runtime/utils/getCurrentPath.d.ts +7 -0
- package/dist/runtime/utils/getCurrentPath.js +4 -0
- package/dist/runtime/utils/getDateTimeInISO8601.d.ts +11 -0
- package/dist/runtime/utils/getDateTimeInISO8601.js +3 -0
- package/dist/runtime/utils/getFromLocalStorage.d.ts +9 -0
- package/dist/runtime/utils/getFromLocalStorage.js +6 -0
- package/dist/runtime/utils/isNumberInRange.d.ts +11 -0
- package/dist/runtime/utils/isNumberInRange.js +5 -0
- package/dist/runtime/utils/onlyNumbers.d.ts +9 -0
- package/dist/runtime/utils/onlyNumbers.js +3 -0
- package/dist/runtime/utils/stringToBoolean.d.ts +9 -0
- package/dist/runtime/utils/stringToBoolean.js +3 -0
- package/dist/types.d.mts +7 -0
- package/dist/types.d.ts +7 -0
- 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>
|