@amirjalili1374/ui-kit 1.2.0
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 +275 -0
- package/dist/_redirects +1 -0
- package/dist/components/Loading.vue.d.ts +3 -0
- package/dist/components/Loading.vue.d.ts.map +1 -0
- package/dist/components/common/AppStepper.vue.d.ts +75 -0
- package/dist/components/common/AppStepper.vue.d.ts.map +1 -0
- package/dist/components/shared/BaseBreadcrumb.vue.d.ts +18 -0
- package/dist/components/shared/BaseBreadcrumb.vue.d.ts.map +1 -0
- package/dist/components/shared/BaseIcon.vue.d.ts +33 -0
- package/dist/components/shared/BaseIcon.vue.d.ts.map +1 -0
- package/dist/components/shared/ConfirmDialog.vue.d.ts +38 -0
- package/dist/components/shared/ConfirmDialog.vue.d.ts.map +1 -0
- package/dist/components/shared/CustomAutocomplete.vue.d.ts +81 -0
- package/dist/components/shared/CustomAutocomplete.vue.d.ts.map +1 -0
- package/dist/components/shared/CustomDataTable.vue.d.ts +59 -0
- package/dist/components/shared/CustomDataTable.vue.d.ts.map +1 -0
- package/dist/components/shared/DescriptionInput.vue.d.ts +34 -0
- package/dist/components/shared/DescriptionInput.vue.d.ts.map +1 -0
- package/dist/components/shared/DownloadButton.vue.d.ts +25 -0
- package/dist/components/shared/DownloadButton.vue.d.ts.map +1 -0
- package/dist/components/shared/MoneyInput.vue.d.ts +127 -0
- package/dist/components/shared/MoneyInput.vue.d.ts.map +1 -0
- package/dist/components/shared/PdfViewer.vue.d.ts +67 -0
- package/dist/components/shared/PdfViewer.vue.d.ts.map +1 -0
- package/dist/components/shared/ShamsiDatePicker.vue.d.ts +48 -0
- package/dist/components/shared/ShamsiDatePicker.vue.d.ts.map +1 -0
- package/dist/components/shared/UiChildCard.vue.d.ts +14 -0
- package/dist/components/shared/UiChildCard.vue.d.ts.map +1 -0
- package/dist/components/shared/UiParentCard.vue.d.ts +18 -0
- package/dist/components/shared/UiParentCard.vue.d.ts.map +1 -0
- package/dist/components/shared/VPriceTextField.vue.d.ts +30 -0
- package/dist/components/shared/VPriceTextField.vue.d.ts.map +1 -0
- package/dist/composables/useDataTable.d.ts +36 -0
- package/dist/composables/useDataTable.d.ts.map +1 -0
- package/dist/composables/useTableActions.d.ts +294 -0
- package/dist/composables/useTableActions.d.ts.map +1 -0
- package/dist/composables/useTableHeaders.d.ts +80 -0
- package/dist/composables/useTableHeaders.d.ts.map +1 -0
- package/dist/composables/useTableSelection.d.ts +32 -0
- package/dist/composables/useTableSelection.d.ts.map +1 -0
- package/dist/constants/enums/booleanEnum.d.ts +13 -0
- package/dist/constants/enums/booleanEnum.d.ts.map +1 -0
- package/dist/directives/v-digit-limit.d.ts +6 -0
- package/dist/directives/v-digit-limit.d.ts.map +1 -0
- package/dist/directives/v-permission.d.ts +3 -0
- package/dist/directives/v-permission.d.ts.map +1 -0
- package/dist/favicon.svg +13 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/persian.json +1 -0
- package/dist/plugins/mdi-icon.d.ts +16 -0
- package/dist/plugins/mdi-icon.d.ts.map +1 -0
- package/dist/services/apiService.d.ts +21 -0
- package/dist/services/apiService.d.ts.map +1 -0
- package/dist/services/axiosInstance.d.ts +11 -0
- package/dist/services/axiosInstance.d.ts.map +1 -0
- package/dist/stores/customizer.d.ts +47 -0
- package/dist/stores/customizer.d.ts.map +1 -0
- package/dist/stores/permissions.d.ts +47 -0
- package/dist/stores/permissions.d.ts.map +1 -0
- package/dist/style.css +5 -0
- package/dist/types/componentTypes/DataTableType.d.ts +14 -0
- package/dist/types/componentTypes/DataTableType.d.ts.map +1 -0
- package/dist/types/componentTypes/DataTableTypes.d.ts +120 -0
- package/dist/types/componentTypes/DataTableTypes.d.ts.map +1 -0
- package/dist/ui-kit.cjs.js +2 -0
- package/dist/ui-kit.cjs.js.map +1 -0
- package/dist/ui-kit.es.js +36840 -0
- package/dist/ui-kit.es.js.map +1 -0
- package/dist/utils/NationalCodeValidator.d.ts +4 -0
- package/dist/utils/NationalCodeValidator.d.ts.map +1 -0
- package/dist/utils/date-convertor.d.ts +5 -0
- package/dist/utils/date-convertor.d.ts.map +1 -0
- package/dist/utils/greetingUtils.d.ts +35 -0
- package/dist/utils/greetingUtils.d.ts.map +1 -0
- package/dist/utils/helpers/fetch-wrapper.d.ts +23 -0
- package/dist/utils/helpers/fetch-wrapper.d.ts.map +1 -0
- package/dist/utils/number-formatter.d.ts +16 -0
- package/dist/utils/number-formatter.d.ts.map +1 -0
- package/dist/validators/nationalCodeRule.d.ts +2 -0
- package/dist/validators/nationalCodeRule.d.ts.map +1 -0
- package/package.json +134 -0
- package/src/assets/fonts/A Massir Spray.ttf +0 -0
- package/src/assets/fonts/BYekan.ttf +0 -0
- package/src/assets/fonts/BYekan.woff +0 -0
- package/src/assets/fonts/BYekan.woff2 +0 -0
- package/src/assets/fonts/Dima Shekasteh 2 Free.ttf +0 -0
- package/src/assets/fonts/Dima Shekasteh Free Regular.ttf +0 -0
- package/src/assets/fonts/IRANSansWeb.ts +1 -0
- package/src/assets/fonts/IRANSansWeb.ttf +0 -0
- package/src/assets/fonts/IRANSansXBlack.ttf +0 -0
- package/src/assets/fonts/IRANSansXBold.ttf +0 -0
- package/src/assets/fonts/IRANSansXDemiBold.ttf +0 -0
- package/src/assets/fonts/IRANSansXExtraBold.ttf +0 -0
- package/src/assets/fonts/IRANSansXLight.ttf +0 -0
- package/src/assets/fonts/IRANSansXMedium.ttf +0 -0
- package/src/assets/fonts/IRANSansXRegular.ttf +0 -0
- package/src/assets/fonts/IRANSansXThin.ttf +0 -0
- package/src/assets/fonts/IRANSansXUltraLight.ttf +0 -0
- package/src/assets/fonts/IranNastaliq.ttf +0 -0
- package/src/assets/fonts/Vazir-Medium-FD.ttf +0 -0
- package/src/assets/fonts/Vazir-Medium-FD.woff +0 -0
- package/src/assets/fonts/Vazir-Medium-FD.woff2 +0 -0
- package/src/assets/fonts/Vazir-Regular-FD.eot +0 -0
- package/src/assets/fonts/kalamehBold.woff +0 -0
- package/src/assets/fonts/kalamehBold.woff2 +0 -0
- package/src/assets/fonts/kalamehHeavy.woff +0 -0
- package/src/assets/fonts/kalamehHeavy.woff2 +0 -0
- package/src/assets/fonts/kalamehLight.woff +0 -0
- package/src/assets/fonts/kalamehLight.woff2 +0 -0
- package/src/assets/fonts/kalamehRegular.woff +0 -0
- package/src/assets/fonts/kalamehRegular.woff2 +0 -0
- package/src/assets/images/auth/social-google.svg +6 -0
- package/src/assets/images/favicon.svg +18 -0
- package/src/assets/images/icons/icon-card.svg +5 -0
- package/src/assets/images/logos/logo.svg +12 -0
- package/src/assets/images/logos/logolight.svg +12 -0
- package/src/assets/images/maintenance/img-error-bg.svg +34 -0
- package/src/assets/images/maintenance/img-error-blue.svg +43 -0
- package/src/assets/images/maintenance/img-error-purple.svg +42 -0
- package/src/assets/images/maintenance/img-error-text.svg +27 -0
- package/src/assets/images/profile/profile-user-account-svgrepo-com.svg +12 -0
- package/src/assets/images/profile/user-round.svg +15 -0
- package/src/assets/images/template/template-01.ts +1 -0
- package/src/assets/images/vectors/colorized-bg.svg +40 -0
- package/src/assets/images/vectors/logo_stroke_1px.svg +26 -0
- package/src/assets/images/vectors/logo_stroke_2px.svg +26 -0
- package/src/assets/scss/components/_approval-sections.scss +75 -0
- package/src/assets/styles/fonts.scss +77 -0
- package/src/components/Loading.vue +88 -0
- package/src/components/common/AppStepper.vue +139 -0
- package/src/components/shared/BaseBreadcrumb.vue +55 -0
- package/src/components/shared/BaseIcon.vue +27 -0
- package/src/components/shared/ConfirmDialog.vue +72 -0
- package/src/components/shared/CustomAutocomplete.vue +306 -0
- package/src/components/shared/CustomDataTable.vue +1859 -0
- package/src/components/shared/DescriptionInput.vue +204 -0
- package/src/components/shared/DownloadButton.vue +169 -0
- package/src/components/shared/MoneyInput.vue +105 -0
- package/src/components/shared/PdfViewer.vue +645 -0
- package/src/components/shared/ShamsiDatePicker.vue +444 -0
- package/src/components/shared/UiChildCard.vue +17 -0
- package/src/components/shared/UiParentCard.vue +21 -0
- package/src/components/shared/VPriceTextField.vue +136 -0
- package/src/composables/useDataTable.ts +152 -0
- package/src/composables/usePermissions.ts +90 -0
- package/src/composables/useRouteGuard.ts +36 -0
- package/src/composables/useTableActions.ts +207 -0
- package/src/composables/useTableHeaders.ts +172 -0
- package/src/composables/useTableSelection.ts +201 -0
- package/src/constants/enums/approval.ts +13 -0
- package/src/constants/enums/booleanEnum.ts +11 -0
- package/src/constants/enums/contractType.ts +11 -0
- package/src/constants/enums/lcProductType.ts +21 -0
- package/src/constants/enums/repaymentType.ts +11 -0
- package/src/directives/v-digit-limit.ts +15 -0
- package/src/directives/v-permission.ts +31 -0
- package/src/features/index.ts +48 -0
- package/src/index.ts +119 -0
- package/src/plugins/key-clock.ts +39 -0
- package/src/plugins/mdi-icon.ts +31 -0
- package/src/plugins/vuetify.ts +74 -0
- package/src/scss/_override.scss +72 -0
- package/src/scss/_variables.scss +124 -0
- package/src/scss/components/_VButtons.scss +23 -0
- package/src/scss/components/_VCard.scss +20 -0
- package/src/scss/components/_VCustomDataTable.scss +282 -0
- package/src/scss/components/_VField.scss +9 -0
- package/src/scss/components/_VInput.scss +17 -0
- package/src/scss/components/_VNavigationDrawer.scss +3 -0
- package/src/scss/components/_VShadow.scss +3 -0
- package/src/scss/components/_VStepper.scss +235 -0
- package/src/scss/components/_VTabs.scss +11 -0
- package/src/scss/components/_VTextField.scss +40 -0
- package/src/scss/components/_approval.scss +128 -0
- package/src/scss/layout/_container.scss +147 -0
- package/src/scss/layout/_sidebar.scss +138 -0
- package/src/scss/layout/_topbar.scss +39 -0
- package/src/scss/pages/_dashboards.scss +97 -0
- package/src/scss/style.scss +21 -0
- package/src/services/apiService.ts +59 -0
- package/src/services/axiosInstance.ts +14 -0
- package/src/stores/customizer.ts +55 -0
- package/src/stores/permissions.ts +237 -0
- package/src/theme/darkThemes/DarkModernTheme.ts +54 -0
- package/src/theme/darkThemes/DarkOrangeTheme.ts +53 -0
- package/src/theme/darkThemes/DarkPurpleTheme.ts +54 -0
- package/src/theme/darkThemes/DarkRedTheme.ts +54 -0
- package/src/theme/darkThemes/DarkSilverTheme.ts +53 -0
- package/src/theme/darkThemes/DarkSteelTealGreen.ts +53 -0
- package/src/theme/darkThemes/DarkTealTheme.ts +52 -0
- package/src/theme/lightThemes/ModernTheme.ts +55 -0
- package/src/theme/lightThemes/OrangeTheme.ts +54 -0
- package/src/theme/lightThemes/PurpleTheme.ts +54 -0
- package/src/theme/lightThemes/RedTheme.ts +55 -0
- package/src/theme/lightThemes/SilverTheme.ts +55 -0
- package/src/theme/lightThemes/SteelTealGreen.ts +54 -0
- package/src/theme/lightThemes/TealTheme.ts +54 -0
- package/src/types/approval/approvalType.ts +473 -0
- package/src/types/cartable/cartableTypes.ts +169 -0
- package/src/types/componentTypes/DataTableType.ts +14 -0
- package/src/types/componentTypes/DataTableTypes.ts +130 -0
- package/src/types/enums/global.ts +267 -0
- package/src/types/jalaali-js.d.ts +6 -0
- package/src/types/models/Base.ts +4 -0
- package/src/types/models/env.d.ts +10 -0
- package/src/types/models/person.ts +13 -0
- package/src/types/models/userInfo.ts +29 -0
- package/src/types/preApproval/preApprovalTypes.ts +67 -0
- package/src/types/shims-tabler-icons.d.ts +58 -0
- package/src/types/themeTypes/ThemeType.ts +47 -0
- package/src/types/vue-apexcharts.d.ts +1 -0
- package/src/types/vue3-print-nb.d.ts +1 -0
- package/src/types/vue_tabler_icon.d.ts +10 -0
- package/src/utils/NationalCodeValidator.ts +33 -0
- package/src/utils/date-convertor.ts +40 -0
- package/src/utils/greetingUtils.ts +97 -0
- package/src/utils/helpers/fake-backend.ts +68 -0
- package/src/utils/helpers/fetch-wrapper.ts +86 -0
- package/src/utils/number-formatter.ts +33 -0
- package/src/validators/nationalCodeRule.ts +6 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { ref, computed, watch } from 'vue';
|
|
2
|
+
import axiosInstance from '@/services/axiosInstance';
|
|
3
|
+
import { useDebounceFn } from '@vueuse/core';
|
|
4
|
+
import type { Ref } from 'vue';
|
|
5
|
+
|
|
6
|
+
export interface DataTableOptions {
|
|
7
|
+
apiResource: string;
|
|
8
|
+
queryParams?: Record<string, any>;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
autoFetch?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PaginationState {
|
|
14
|
+
currentPage: number;
|
|
15
|
+
itemsPerPage: number;
|
|
16
|
+
totalItems: number;
|
|
17
|
+
totalPages: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useDataTable<T = any>(options: DataTableOptions) {
|
|
21
|
+
const items = ref<T[]>([]) as Ref<T[]>;
|
|
22
|
+
const loading = ref(false);
|
|
23
|
+
const error = ref<string | null>(null);
|
|
24
|
+
const hasMore = ref(true);
|
|
25
|
+
const isLoadingMore = ref(false);
|
|
26
|
+
|
|
27
|
+
const pagination = ref<PaginationState>({
|
|
28
|
+
currentPage: 1,
|
|
29
|
+
itemsPerPage: options.pageSize || 10,
|
|
30
|
+
totalItems: 0,
|
|
31
|
+
totalPages: 1
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const fetchData = async (params: Record<string, any> = {}) => {
|
|
35
|
+
if (loading.value) return;
|
|
36
|
+
|
|
37
|
+
loading.value = true;
|
|
38
|
+
error.value = null;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const response = await axiosInstance.get(options.apiResource, {
|
|
42
|
+
params: {
|
|
43
|
+
page: pagination.value.currentPage,
|
|
44
|
+
per_page: pagination.value.itemsPerPage,
|
|
45
|
+
...options.queryParams,
|
|
46
|
+
...params
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { data, meta } = response.data;
|
|
51
|
+
|
|
52
|
+
if (meta) {
|
|
53
|
+
items.value = data;
|
|
54
|
+
pagination.value = {
|
|
55
|
+
currentPage: meta.current_page || 1,
|
|
56
|
+
itemsPerPage: meta.per_page || options.pageSize || 10,
|
|
57
|
+
totalItems: meta.total || 0,
|
|
58
|
+
totalPages: meta.last_page || 1
|
|
59
|
+
};
|
|
60
|
+
hasMore.value = meta.current_page < meta.last_page;
|
|
61
|
+
} else {
|
|
62
|
+
items.value = data;
|
|
63
|
+
hasMore.value = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return data;
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
error.value = err.response?.data?.message || 'Failed to fetch data';
|
|
69
|
+
console.error('Error fetching data:', err);
|
|
70
|
+
throw err;
|
|
71
|
+
} finally {
|
|
72
|
+
loading.value = false;
|
|
73
|
+
isLoadingMore.value = false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const debouncedFetchData = useDebounceFn(fetchData, 300);
|
|
78
|
+
|
|
79
|
+
const loadMore = async () => {
|
|
80
|
+
if (loading.value || !hasMore.value) return;
|
|
81
|
+
|
|
82
|
+
isLoadingMore.value = true;
|
|
83
|
+
pagination.value.currentPage += 1;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const response = await axiosInstance.get(options.apiResource, {
|
|
87
|
+
params: {
|
|
88
|
+
page: pagination.value.currentPage,
|
|
89
|
+
per_page: pagination.value.itemsPerPage,
|
|
90
|
+
...options.queryParams
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const { data, meta } = response.data;
|
|
95
|
+
|
|
96
|
+
if (meta) {
|
|
97
|
+
items.value = [...items.value, ...data];
|
|
98
|
+
hasMore.value = meta.current_page < meta.last_page;
|
|
99
|
+
} else {
|
|
100
|
+
items.value = [...items.value, ...data];
|
|
101
|
+
hasMore.value = false;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error('Error loading more data:', err);
|
|
105
|
+
pagination.value.currentPage -= 1;
|
|
106
|
+
throw err;
|
|
107
|
+
} finally {
|
|
108
|
+
isLoadingMore.value = false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const setPage = (page: number) => {
|
|
113
|
+
if (page < 1 || page > pagination.value.totalPages) return;
|
|
114
|
+
pagination.value.currentPage = page;
|
|
115
|
+
fetchData();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const setItemsPerPage = (size: number) => {
|
|
119
|
+
pagination.value.itemsPerPage = size;
|
|
120
|
+
pagination.value.currentPage = 1;
|
|
121
|
+
fetchData();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Watch for changes in query params
|
|
125
|
+
watch(
|
|
126
|
+
() => options.queryParams,
|
|
127
|
+
(newParams) => {
|
|
128
|
+
if (options.autoFetch !== false) {
|
|
129
|
+
debouncedFetchData(newParams);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{ deep: true }
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Initial fetch
|
|
136
|
+
if (options.autoFetch !== false) {
|
|
137
|
+
fetchData();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
items,
|
|
142
|
+
loading,
|
|
143
|
+
error,
|
|
144
|
+
hasMore,
|
|
145
|
+
isLoadingMore,
|
|
146
|
+
pagination,
|
|
147
|
+
fetchData,
|
|
148
|
+
loadMore,
|
|
149
|
+
setPage,
|
|
150
|
+
setItemsPerPage
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
import { usePermissionsStore } from '@/stores/permissions';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Note: This composable is app-specific and includes business logic.
|
|
6
|
+
* It's not exported from the library. Consuming apps should create
|
|
7
|
+
* their own usePermissions composable or use usePermissionsStore directly.
|
|
8
|
+
*/
|
|
9
|
+
function useCustomerInfoStore() {
|
|
10
|
+
// Stub - this composable is not exported from library
|
|
11
|
+
// Consuming apps should implement their own customer info store
|
|
12
|
+
return {
|
|
13
|
+
isUserInfoLoaded: false,
|
|
14
|
+
getUserRoles: [] as string[],
|
|
15
|
+
getUserLotusRoles: [] as string[],
|
|
16
|
+
hasRole: () => false,
|
|
17
|
+
hasLotusRole: () => false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function usePermissions() {
|
|
22
|
+
const permissionsStore = usePermissionsStore();
|
|
23
|
+
const customerInfo = useCustomerInfoStore();
|
|
24
|
+
|
|
25
|
+
// Check if user has permission for a specific menu
|
|
26
|
+
const hasMenuPermission = (menuKey: string) => {
|
|
27
|
+
return permissionsStore.hasMenuPermission(menuKey);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Check if user has any of the specified roles
|
|
31
|
+
const hasAnyRole = (roles: string[]) => {
|
|
32
|
+
return permissionsStore.hasAnyRole(roles);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Check if user has any of the specified lotus roles
|
|
36
|
+
const hasAnyLotusRole = (roles: string[]) => {
|
|
37
|
+
return permissionsStore.hasAnyLotusRole(roles);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Check if user has a specific role
|
|
41
|
+
const hasRole = (role: string) => {
|
|
42
|
+
return customerInfo.hasRole(role);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Check if user has a specific lotus role
|
|
46
|
+
const hasLotusRole = (role: string) => {
|
|
47
|
+
return customerInfo.hasLotusRole(role);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Get user's roles
|
|
51
|
+
const userRoles = computed(() => customerInfo.getUserRoles);
|
|
52
|
+
|
|
53
|
+
// Get user's lotus roles
|
|
54
|
+
const userLotusRoles = computed(() => customerInfo.getLotusRoles);
|
|
55
|
+
|
|
56
|
+
// Check if user info is loaded
|
|
57
|
+
const isUserInfoLoaded = computed(() => customerInfo.isUserInfoLoaded);
|
|
58
|
+
|
|
59
|
+
// Common permission checks
|
|
60
|
+
const canCreateApproval = computed(() => hasRole('smp_create_approval'));
|
|
61
|
+
const canEditApproval = computed(() => hasRole('smp_edit_approval'));
|
|
62
|
+
const canViewCartable = computed(() => hasRole('SMP_VIEW_CARTABLE'));
|
|
63
|
+
const canOperateCartable = computed(() => hasRole('SMP_CARTABLE_OPERATION'));
|
|
64
|
+
const canViewCartableHistory = computed(() => hasRole('SMP_CARTABLE_HIST'));
|
|
65
|
+
const canViewApprovalHistory = computed(() => hasRole('SMP_APPROVAL_HIST'));
|
|
66
|
+
const canCreateFlowManagement = computed(() => hasRole('SMP_CREATE_FLOW_MNG'));
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
// Permission checking methods
|
|
70
|
+
hasMenuPermission,
|
|
71
|
+
hasAnyRole,
|
|
72
|
+
hasAnyLotusRole,
|
|
73
|
+
hasRole,
|
|
74
|
+
hasLotusRole,
|
|
75
|
+
|
|
76
|
+
// Computed properties
|
|
77
|
+
userRoles,
|
|
78
|
+
userLotusRoles,
|
|
79
|
+
isUserInfoLoaded,
|
|
80
|
+
|
|
81
|
+
// Common permission checks
|
|
82
|
+
canCreateApproval,
|
|
83
|
+
canEditApproval,
|
|
84
|
+
canViewCartable,
|
|
85
|
+
canOperateCartable,
|
|
86
|
+
canViewCartableHistory,
|
|
87
|
+
canViewApprovalHistory,
|
|
88
|
+
canCreateFlowManagement
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { usePermissionsStore } from '@/stores/permissions';
|
|
2
|
+
import { useRouter, useRoute } from 'vue-router';
|
|
3
|
+
|
|
4
|
+
export function useRouteGuard() {
|
|
5
|
+
const permissionsStore = usePermissionsStore();
|
|
6
|
+
const router = useRouter();
|
|
7
|
+
const route = useRoute();
|
|
8
|
+
|
|
9
|
+
// Check if user can access a specific route by inspecting meta.permission
|
|
10
|
+
const canAccessRoute = (routePath: string): boolean => {
|
|
11
|
+
// Fallback: allow; guard will block at navigation time if needed
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Navigate to route if user has permission, otherwise redirect to 403
|
|
16
|
+
const navigateWithPermission = (routePath: string) => {
|
|
17
|
+
router.push(routePath);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Check permission and redirect if not authorized
|
|
21
|
+
const requirePermission = (permissionKey?: string, redirectTo: string = '/error/403') => {
|
|
22
|
+
const key = permissionKey ?? (route.meta?.permission as string | undefined);
|
|
23
|
+
if (!key) return true;
|
|
24
|
+
if (!permissionsStore.hasMenuPermission(key)) {
|
|
25
|
+
router.push(redirectTo);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
canAccessRoute,
|
|
33
|
+
navigateWithPermission,
|
|
34
|
+
requirePermission
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { ref, computed } from 'vue';
|
|
2
|
+
import type { Component } from 'vue';
|
|
3
|
+
import { icons } from '@/plugins/mdi-icon';
|
|
4
|
+
|
|
5
|
+
export interface CustomAction {
|
|
6
|
+
title: string;
|
|
7
|
+
component: Component;
|
|
8
|
+
condition?: (item: any) => boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CustomButtonAction {
|
|
12
|
+
label: string;
|
|
13
|
+
color?: string;
|
|
14
|
+
onClick: (item: any) => void;
|
|
15
|
+
disabled?: boolean | ((item: any) => boolean);
|
|
16
|
+
icon?: string;
|
|
17
|
+
outlined?: boolean;
|
|
18
|
+
rounded?: boolean;
|
|
19
|
+
class?: string;
|
|
20
|
+
tooltip?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ActionOptions {
|
|
24
|
+
actions?: ('create' | 'edit' | 'delete' | 'view')[];
|
|
25
|
+
routes?: Record<string, string>;
|
|
26
|
+
downloadLink?: Record<string, string>;
|
|
27
|
+
customActions?: CustomAction[];
|
|
28
|
+
customButtons?: CustomButtonAction[];
|
|
29
|
+
customButtonsFn?: (item: any) => CustomButtonAction[];
|
|
30
|
+
showRefreshButton?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useTableActions(options: ActionOptions = {}) {
|
|
34
|
+
const dialog = ref(false);
|
|
35
|
+
const deleteDialog = ref(false);
|
|
36
|
+
const actionDialog = ref(false);
|
|
37
|
+
const isEditing = ref(false);
|
|
38
|
+
const editedItem = ref<Record<string, any> | null>(null);
|
|
39
|
+
const formModel = ref<Record<string, any>>({});
|
|
40
|
+
const itemToDelete = ref<Record<string, any> | null>(null);
|
|
41
|
+
const currentAction = ref<CustomAction | null>(null);
|
|
42
|
+
|
|
43
|
+
// Open dialog for create/edit
|
|
44
|
+
const openDialog = (item?: any) => {
|
|
45
|
+
isEditing.value = !!item;
|
|
46
|
+
editedItem.value = item ? { ...item } : {};
|
|
47
|
+
formModel.value = item ? { ...item } : {};
|
|
48
|
+
dialog.value = true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Open delete confirmation dialog
|
|
52
|
+
const openDeleteDialog = (item: any) => {
|
|
53
|
+
itemToDelete.value = item;
|
|
54
|
+
deleteDialog.value = true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Open custom action dialog
|
|
58
|
+
const openActionDialog = (action: CustomAction, item: any) => {
|
|
59
|
+
currentAction.value = action;
|
|
60
|
+
editedItem.value = item ? { ...item } : {};
|
|
61
|
+
formModel.value = item ? { ...item } : {};
|
|
62
|
+
actionDialog.value = true;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Reset all dialogs and form
|
|
66
|
+
const resetDialogs = () => {
|
|
67
|
+
dialog.value = false;
|
|
68
|
+
deleteDialog.value = false;
|
|
69
|
+
actionDialog.value = false;
|
|
70
|
+
isEditing.value = false;
|
|
71
|
+
editedItem.value = null;
|
|
72
|
+
itemToDelete.value = null;
|
|
73
|
+
currentAction.value = null;
|
|
74
|
+
formModel.value = {};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Check if action should be shown for an item
|
|
78
|
+
const shouldShowAction = (action: CustomAction, item: any): boolean => {
|
|
79
|
+
if (!action.condition) return true;
|
|
80
|
+
return action.condition(item);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Get all available actions for an item
|
|
84
|
+
const getItemActions = (item: any): CustomButtonAction[] => {
|
|
85
|
+
const actions: CustomButtonAction[] = [];
|
|
86
|
+
|
|
87
|
+
// Add CRUD actions
|
|
88
|
+
if (options.actions) {
|
|
89
|
+
if (options.actions.includes('view')) {
|
|
90
|
+
actions.push({
|
|
91
|
+
label: 'View',
|
|
92
|
+
icon: icons.eye,
|
|
93
|
+
color: 'info',
|
|
94
|
+
outlined: true,
|
|
95
|
+
onClick: () => openDialog(item)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (options.actions.includes('edit')) {
|
|
100
|
+
actions.push({
|
|
101
|
+
label: 'Edit',
|
|
102
|
+
icon: icons.pencil,
|
|
103
|
+
color: 'primary',
|
|
104
|
+
outlined: true,
|
|
105
|
+
onClick: () => openDialog(item)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (options.actions.includes('delete')) {
|
|
110
|
+
actions.push({
|
|
111
|
+
label: 'Delete',
|
|
112
|
+
icon: icons.delete,
|
|
113
|
+
color: 'error',
|
|
114
|
+
outlined: true,
|
|
115
|
+
onClick: () => openDeleteDialog(item)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add route actions
|
|
121
|
+
if (options.routes) {
|
|
122
|
+
Object.entries(options.routes).forEach(([key, route]) => {
|
|
123
|
+
actions.push({
|
|
124
|
+
label: key.charAt(0).toUpperCase() + key.slice(1),
|
|
125
|
+
icon: icons.arrowRight,
|
|
126
|
+
color: 'secondary',
|
|
127
|
+
outlined: true,
|
|
128
|
+
onClick: () => {
|
|
129
|
+
// This will be handled by the parent component
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add download actions
|
|
136
|
+
if (options.downloadLink) {
|
|
137
|
+
Object.entries(options.downloadLink).forEach(([key, url]) => {
|
|
138
|
+
actions.push({
|
|
139
|
+
label: `Download ${key}`,
|
|
140
|
+
icon: icons.download,
|
|
141
|
+
color: 'success',
|
|
142
|
+
outlined: true,
|
|
143
|
+
onClick: () => {
|
|
144
|
+
// This will be handled by the parent component
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add custom actions
|
|
151
|
+
if (options.customActions) {
|
|
152
|
+
options.customActions.forEach(action => {
|
|
153
|
+
if (shouldShowAction(action, item)) {
|
|
154
|
+
actions.push({
|
|
155
|
+
label: action.title,
|
|
156
|
+
icon: icons.cog,
|
|
157
|
+
color: 'primary',
|
|
158
|
+
outlined: true,
|
|
159
|
+
onClick: () => openActionDialog(action, item)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Add dynamic custom buttons
|
|
166
|
+
if (options.customButtonsFn) {
|
|
167
|
+
const dynamicButtons = options.customButtonsFn(item);
|
|
168
|
+
actions.push(...dynamicButtons);
|
|
169
|
+
} else if (options.customButtons) {
|
|
170
|
+
actions.push(...options.customButtons);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return actions;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Check if any actions are available
|
|
177
|
+
const hasAnyActions = computed(() => {
|
|
178
|
+
const hasCrudActions = Array.isArray(options.actions) && options.actions.length > 0;
|
|
179
|
+
const hasRoutes = !!options.routes && Object.keys(options.routes).length > 0;
|
|
180
|
+
const hasDownloadLinks = !!options.downloadLink && Object.keys(options.downloadLink).length > 0;
|
|
181
|
+
const hasCustomActions = Array.isArray(options.customActions) && options.customActions.length > 0;
|
|
182
|
+
const hasCustomButtons = (Array.isArray(options.customButtons) && options.customButtons.length > 0) || !!options.customButtonsFn;
|
|
183
|
+
|
|
184
|
+
return hasCrudActions || hasRoutes || hasDownloadLinks || hasCustomActions || hasCustomButtons;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
// State
|
|
189
|
+
dialog,
|
|
190
|
+
deleteDialog,
|
|
191
|
+
actionDialog,
|
|
192
|
+
isEditing,
|
|
193
|
+
editedItem,
|
|
194
|
+
formModel,
|
|
195
|
+
itemToDelete,
|
|
196
|
+
currentAction,
|
|
197
|
+
hasAnyActions,
|
|
198
|
+
|
|
199
|
+
// Methods
|
|
200
|
+
openDialog,
|
|
201
|
+
openDeleteDialog,
|
|
202
|
+
openActionDialog,
|
|
203
|
+
resetDialogs,
|
|
204
|
+
getItemActions,
|
|
205
|
+
shouldShowAction
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
|
|
3
|
+
export interface TableHeader {
|
|
4
|
+
title?: string;
|
|
5
|
+
key?: string;
|
|
6
|
+
sortable?: boolean;
|
|
7
|
+
editable?: boolean;
|
|
8
|
+
width?: number | string;
|
|
9
|
+
minWidth?: number | string;
|
|
10
|
+
maxWidth?: number | string;
|
|
11
|
+
align?: 'start' | 'center' | 'end';
|
|
12
|
+
fixed?: boolean | 'left' | 'right';
|
|
13
|
+
resizable?: boolean;
|
|
14
|
+
filterable?: boolean;
|
|
15
|
+
filterOptions?: Array<{ text: string; value: any }>;
|
|
16
|
+
filterMultiple?: boolean;
|
|
17
|
+
filterMethod?: (value: any, row: any, column: any) => boolean;
|
|
18
|
+
sortMethod?: (a: any, b: any) => number;
|
|
19
|
+
sortBy?: string | ((row: any) => any) | (string | ((row: any) => any))[];
|
|
20
|
+
sortOrders?: ('ascending' | 'descending')[];
|
|
21
|
+
formatter?: (row: any, column: any, cellValue: any, index: number) => any;
|
|
22
|
+
className?: string | ((data: { row: any; rowIndex: number; column: any; columnIndex: number }) => string);
|
|
23
|
+
headerClassName?: string | ((data: { row: any; rowIndex: number; column: any; columnIndex: number }) => string);
|
|
24
|
+
showOverflowTooltip?: boolean | { [key: string]: any };
|
|
25
|
+
renderHeader?: (data: { column: any; $index: number }) => any;
|
|
26
|
+
render?: (data: { row: any; column: any; $index: number }) => any;
|
|
27
|
+
children?: TableHeader[];
|
|
28
|
+
type?: 'selection' | 'index' | 'expand';
|
|
29
|
+
index?: number | ((index: number) => number);
|
|
30
|
+
selectable?: (row: any, index: number) => boolean;
|
|
31
|
+
reserveSelection?: boolean;
|
|
32
|
+
filters?: { text: string; value: any }[];
|
|
33
|
+
filterPlacement?: string;
|
|
34
|
+
filteredValue?: any[];
|
|
35
|
+
columnKey?: string;
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface HeaderOptions {
|
|
40
|
+
selectable?: boolean;
|
|
41
|
+
hasActions?: boolean;
|
|
42
|
+
actionsWidth?: number | string;
|
|
43
|
+
showSelection?: boolean;
|
|
44
|
+
showIndex?: boolean;
|
|
45
|
+
indexLabel?: string;
|
|
46
|
+
indexMethod?: (index: number) => number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useTableHeaders(headers: TableHeader[], options: HeaderOptions = {}) {
|
|
50
|
+
const {
|
|
51
|
+
selectable = false,
|
|
52
|
+
hasActions = false,
|
|
53
|
+
actionsWidth = '200px',
|
|
54
|
+
showSelection = true,
|
|
55
|
+
showIndex = false,
|
|
56
|
+
indexLabel = '#',
|
|
57
|
+
indexMethod
|
|
58
|
+
} = options;
|
|
59
|
+
|
|
60
|
+
// Add selection column if needed
|
|
61
|
+
const withSelection = computed<TableHeader[]>(() => {
|
|
62
|
+
if (!selectable || !showSelection) return headers;
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
type: 'selection',
|
|
67
|
+
width: '50px',
|
|
68
|
+
align: 'center',
|
|
69
|
+
fixed: 'left',
|
|
70
|
+
selectable: (row: any, index: number) => true
|
|
71
|
+
},
|
|
72
|
+
...headers
|
|
73
|
+
];
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Add index column if needed
|
|
77
|
+
const withIndex = computed<TableHeader[]>(() => {
|
|
78
|
+
if (!showIndex) return withSelection.value;
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
type: 'index',
|
|
83
|
+
label: indexLabel,
|
|
84
|
+
width: '60px',
|
|
85
|
+
align: 'center',
|
|
86
|
+
fixed: 'left',
|
|
87
|
+
index: indexMethod || ((index: number) => index + 1)
|
|
88
|
+
},
|
|
89
|
+
...withSelection.value
|
|
90
|
+
];
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Add actions column if needed
|
|
94
|
+
const withActions = computed<TableHeader[]>(() => {
|
|
95
|
+
if (!hasActions) return withIndex.value;
|
|
96
|
+
|
|
97
|
+
return [
|
|
98
|
+
...withIndex.value,
|
|
99
|
+
{
|
|
100
|
+
label: 'Actions',
|
|
101
|
+
key: 'actions',
|
|
102
|
+
width: actionsWidth,
|
|
103
|
+
fixed: 'right',
|
|
104
|
+
align: 'center',
|
|
105
|
+
render: (data: { row: any, column: any, $index: number }) => {
|
|
106
|
+
// This will be handled by the parent component
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Final headers with all transformations applied
|
|
114
|
+
const processedHeaders = computed<TableHeader[]>(() => {
|
|
115
|
+
return withActions.value.map(header => ({
|
|
116
|
+
sortable: false,
|
|
117
|
+
resizable: true,
|
|
118
|
+
minWidth: 80,
|
|
119
|
+
align: 'start',
|
|
120
|
+
showOverflowTooltip: true,
|
|
121
|
+
...header,
|
|
122
|
+
// Ensure width is always a string with px if it's a number
|
|
123
|
+
width: typeof header.width === 'number' ? `${header.width}px` : header.width
|
|
124
|
+
}));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Get column by key
|
|
128
|
+
const getColumnByKey = (key: string): TableHeader | undefined => {
|
|
129
|
+
return processedHeaders.value.find(header => header.key === key);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Get column index by key
|
|
133
|
+
const getColumnIndex = (key: string): number => {
|
|
134
|
+
return processedHeaders.value.findIndex(header => header.key === key);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Get visible columns
|
|
138
|
+
const visibleColumns = computed(() => {
|
|
139
|
+
return processedHeaders.value.filter(header => header.hidden !== true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Get fixed columns
|
|
143
|
+
const fixedColumns = computed(() => {
|
|
144
|
+
return processedHeaders.value.filter(header => header.fixed === true || header.fixed === 'left' || header.fixed === 'right');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Get left fixed columns
|
|
148
|
+
const leftFixedColumns = computed(() => {
|
|
149
|
+
return processedHeaders.value.filter(header => header.fixed === 'left');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Get right fixed columns
|
|
153
|
+
const rightFixedColumns = computed(() => {
|
|
154
|
+
return processedHeaders.value.filter(header => header.fixed === 'right');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Get scrollable columns (not fixed)
|
|
158
|
+
const scrollableColumns = computed(() => {
|
|
159
|
+
return processedHeaders.value.filter(header => !header.fixed);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
headers: processedHeaders,
|
|
164
|
+
visibleColumns,
|
|
165
|
+
fixedColumns,
|
|
166
|
+
leftFixedColumns,
|
|
167
|
+
rightFixedColumns,
|
|
168
|
+
scrollableColumns,
|
|
169
|
+
getColumnByKey,
|
|
170
|
+
getColumnIndex
|
|
171
|
+
};
|
|
172
|
+
}
|