@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.
Files changed (221) hide show
  1. package/README.md +275 -0
  2. package/dist/_redirects +1 -0
  3. package/dist/components/Loading.vue.d.ts +3 -0
  4. package/dist/components/Loading.vue.d.ts.map +1 -0
  5. package/dist/components/common/AppStepper.vue.d.ts +75 -0
  6. package/dist/components/common/AppStepper.vue.d.ts.map +1 -0
  7. package/dist/components/shared/BaseBreadcrumb.vue.d.ts +18 -0
  8. package/dist/components/shared/BaseBreadcrumb.vue.d.ts.map +1 -0
  9. package/dist/components/shared/BaseIcon.vue.d.ts +33 -0
  10. package/dist/components/shared/BaseIcon.vue.d.ts.map +1 -0
  11. package/dist/components/shared/ConfirmDialog.vue.d.ts +38 -0
  12. package/dist/components/shared/ConfirmDialog.vue.d.ts.map +1 -0
  13. package/dist/components/shared/CustomAutocomplete.vue.d.ts +81 -0
  14. package/dist/components/shared/CustomAutocomplete.vue.d.ts.map +1 -0
  15. package/dist/components/shared/CustomDataTable.vue.d.ts +59 -0
  16. package/dist/components/shared/CustomDataTable.vue.d.ts.map +1 -0
  17. package/dist/components/shared/DescriptionInput.vue.d.ts +34 -0
  18. package/dist/components/shared/DescriptionInput.vue.d.ts.map +1 -0
  19. package/dist/components/shared/DownloadButton.vue.d.ts +25 -0
  20. package/dist/components/shared/DownloadButton.vue.d.ts.map +1 -0
  21. package/dist/components/shared/MoneyInput.vue.d.ts +127 -0
  22. package/dist/components/shared/MoneyInput.vue.d.ts.map +1 -0
  23. package/dist/components/shared/PdfViewer.vue.d.ts +67 -0
  24. package/dist/components/shared/PdfViewer.vue.d.ts.map +1 -0
  25. package/dist/components/shared/ShamsiDatePicker.vue.d.ts +48 -0
  26. package/dist/components/shared/ShamsiDatePicker.vue.d.ts.map +1 -0
  27. package/dist/components/shared/UiChildCard.vue.d.ts +14 -0
  28. package/dist/components/shared/UiChildCard.vue.d.ts.map +1 -0
  29. package/dist/components/shared/UiParentCard.vue.d.ts +18 -0
  30. package/dist/components/shared/UiParentCard.vue.d.ts.map +1 -0
  31. package/dist/components/shared/VPriceTextField.vue.d.ts +30 -0
  32. package/dist/components/shared/VPriceTextField.vue.d.ts.map +1 -0
  33. package/dist/composables/useDataTable.d.ts +36 -0
  34. package/dist/composables/useDataTable.d.ts.map +1 -0
  35. package/dist/composables/useTableActions.d.ts +294 -0
  36. package/dist/composables/useTableActions.d.ts.map +1 -0
  37. package/dist/composables/useTableHeaders.d.ts +80 -0
  38. package/dist/composables/useTableHeaders.d.ts.map +1 -0
  39. package/dist/composables/useTableSelection.d.ts +32 -0
  40. package/dist/composables/useTableSelection.d.ts.map +1 -0
  41. package/dist/constants/enums/booleanEnum.d.ts +13 -0
  42. package/dist/constants/enums/booleanEnum.d.ts.map +1 -0
  43. package/dist/directives/v-digit-limit.d.ts +6 -0
  44. package/dist/directives/v-digit-limit.d.ts.map +1 -0
  45. package/dist/directives/v-permission.d.ts +3 -0
  46. package/dist/directives/v-permission.d.ts.map +1 -0
  47. package/dist/favicon.svg +13 -0
  48. package/dist/index.d.ts +53 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/persian.json +1 -0
  51. package/dist/plugins/mdi-icon.d.ts +16 -0
  52. package/dist/plugins/mdi-icon.d.ts.map +1 -0
  53. package/dist/services/apiService.d.ts +21 -0
  54. package/dist/services/apiService.d.ts.map +1 -0
  55. package/dist/services/axiosInstance.d.ts +11 -0
  56. package/dist/services/axiosInstance.d.ts.map +1 -0
  57. package/dist/stores/customizer.d.ts +47 -0
  58. package/dist/stores/customizer.d.ts.map +1 -0
  59. package/dist/stores/permissions.d.ts +47 -0
  60. package/dist/stores/permissions.d.ts.map +1 -0
  61. package/dist/style.css +5 -0
  62. package/dist/types/componentTypes/DataTableType.d.ts +14 -0
  63. package/dist/types/componentTypes/DataTableType.d.ts.map +1 -0
  64. package/dist/types/componentTypes/DataTableTypes.d.ts +120 -0
  65. package/dist/types/componentTypes/DataTableTypes.d.ts.map +1 -0
  66. package/dist/ui-kit.cjs.js +2 -0
  67. package/dist/ui-kit.cjs.js.map +1 -0
  68. package/dist/ui-kit.es.js +36840 -0
  69. package/dist/ui-kit.es.js.map +1 -0
  70. package/dist/utils/NationalCodeValidator.d.ts +4 -0
  71. package/dist/utils/NationalCodeValidator.d.ts.map +1 -0
  72. package/dist/utils/date-convertor.d.ts +5 -0
  73. package/dist/utils/date-convertor.d.ts.map +1 -0
  74. package/dist/utils/greetingUtils.d.ts +35 -0
  75. package/dist/utils/greetingUtils.d.ts.map +1 -0
  76. package/dist/utils/helpers/fetch-wrapper.d.ts +23 -0
  77. package/dist/utils/helpers/fetch-wrapper.d.ts.map +1 -0
  78. package/dist/utils/number-formatter.d.ts +16 -0
  79. package/dist/utils/number-formatter.d.ts.map +1 -0
  80. package/dist/validators/nationalCodeRule.d.ts +2 -0
  81. package/dist/validators/nationalCodeRule.d.ts.map +1 -0
  82. package/package.json +134 -0
  83. package/src/assets/fonts/A Massir Spray.ttf +0 -0
  84. package/src/assets/fonts/BYekan.ttf +0 -0
  85. package/src/assets/fonts/BYekan.woff +0 -0
  86. package/src/assets/fonts/BYekan.woff2 +0 -0
  87. package/src/assets/fonts/Dima Shekasteh 2 Free.ttf +0 -0
  88. package/src/assets/fonts/Dima Shekasteh Free Regular.ttf +0 -0
  89. package/src/assets/fonts/IRANSansWeb.ts +1 -0
  90. package/src/assets/fonts/IRANSansWeb.ttf +0 -0
  91. package/src/assets/fonts/IRANSansXBlack.ttf +0 -0
  92. package/src/assets/fonts/IRANSansXBold.ttf +0 -0
  93. package/src/assets/fonts/IRANSansXDemiBold.ttf +0 -0
  94. package/src/assets/fonts/IRANSansXExtraBold.ttf +0 -0
  95. package/src/assets/fonts/IRANSansXLight.ttf +0 -0
  96. package/src/assets/fonts/IRANSansXMedium.ttf +0 -0
  97. package/src/assets/fonts/IRANSansXRegular.ttf +0 -0
  98. package/src/assets/fonts/IRANSansXThin.ttf +0 -0
  99. package/src/assets/fonts/IRANSansXUltraLight.ttf +0 -0
  100. package/src/assets/fonts/IranNastaliq.ttf +0 -0
  101. package/src/assets/fonts/Vazir-Medium-FD.ttf +0 -0
  102. package/src/assets/fonts/Vazir-Medium-FD.woff +0 -0
  103. package/src/assets/fonts/Vazir-Medium-FD.woff2 +0 -0
  104. package/src/assets/fonts/Vazir-Regular-FD.eot +0 -0
  105. package/src/assets/fonts/kalamehBold.woff +0 -0
  106. package/src/assets/fonts/kalamehBold.woff2 +0 -0
  107. package/src/assets/fonts/kalamehHeavy.woff +0 -0
  108. package/src/assets/fonts/kalamehHeavy.woff2 +0 -0
  109. package/src/assets/fonts/kalamehLight.woff +0 -0
  110. package/src/assets/fonts/kalamehLight.woff2 +0 -0
  111. package/src/assets/fonts/kalamehRegular.woff +0 -0
  112. package/src/assets/fonts/kalamehRegular.woff2 +0 -0
  113. package/src/assets/images/auth/social-google.svg +6 -0
  114. package/src/assets/images/favicon.svg +18 -0
  115. package/src/assets/images/icons/icon-card.svg +5 -0
  116. package/src/assets/images/logos/logo.svg +12 -0
  117. package/src/assets/images/logos/logolight.svg +12 -0
  118. package/src/assets/images/maintenance/img-error-bg.svg +34 -0
  119. package/src/assets/images/maintenance/img-error-blue.svg +43 -0
  120. package/src/assets/images/maintenance/img-error-purple.svg +42 -0
  121. package/src/assets/images/maintenance/img-error-text.svg +27 -0
  122. package/src/assets/images/profile/profile-user-account-svgrepo-com.svg +12 -0
  123. package/src/assets/images/profile/user-round.svg +15 -0
  124. package/src/assets/images/template/template-01.ts +1 -0
  125. package/src/assets/images/vectors/colorized-bg.svg +40 -0
  126. package/src/assets/images/vectors/logo_stroke_1px.svg +26 -0
  127. package/src/assets/images/vectors/logo_stroke_2px.svg +26 -0
  128. package/src/assets/scss/components/_approval-sections.scss +75 -0
  129. package/src/assets/styles/fonts.scss +77 -0
  130. package/src/components/Loading.vue +88 -0
  131. package/src/components/common/AppStepper.vue +139 -0
  132. package/src/components/shared/BaseBreadcrumb.vue +55 -0
  133. package/src/components/shared/BaseIcon.vue +27 -0
  134. package/src/components/shared/ConfirmDialog.vue +72 -0
  135. package/src/components/shared/CustomAutocomplete.vue +306 -0
  136. package/src/components/shared/CustomDataTable.vue +1859 -0
  137. package/src/components/shared/DescriptionInput.vue +204 -0
  138. package/src/components/shared/DownloadButton.vue +169 -0
  139. package/src/components/shared/MoneyInput.vue +105 -0
  140. package/src/components/shared/PdfViewer.vue +645 -0
  141. package/src/components/shared/ShamsiDatePicker.vue +444 -0
  142. package/src/components/shared/UiChildCard.vue +17 -0
  143. package/src/components/shared/UiParentCard.vue +21 -0
  144. package/src/components/shared/VPriceTextField.vue +136 -0
  145. package/src/composables/useDataTable.ts +152 -0
  146. package/src/composables/usePermissions.ts +90 -0
  147. package/src/composables/useRouteGuard.ts +36 -0
  148. package/src/composables/useTableActions.ts +207 -0
  149. package/src/composables/useTableHeaders.ts +172 -0
  150. package/src/composables/useTableSelection.ts +201 -0
  151. package/src/constants/enums/approval.ts +13 -0
  152. package/src/constants/enums/booleanEnum.ts +11 -0
  153. package/src/constants/enums/contractType.ts +11 -0
  154. package/src/constants/enums/lcProductType.ts +21 -0
  155. package/src/constants/enums/repaymentType.ts +11 -0
  156. package/src/directives/v-digit-limit.ts +15 -0
  157. package/src/directives/v-permission.ts +31 -0
  158. package/src/features/index.ts +48 -0
  159. package/src/index.ts +119 -0
  160. package/src/plugins/key-clock.ts +39 -0
  161. package/src/plugins/mdi-icon.ts +31 -0
  162. package/src/plugins/vuetify.ts +74 -0
  163. package/src/scss/_override.scss +72 -0
  164. package/src/scss/_variables.scss +124 -0
  165. package/src/scss/components/_VButtons.scss +23 -0
  166. package/src/scss/components/_VCard.scss +20 -0
  167. package/src/scss/components/_VCustomDataTable.scss +282 -0
  168. package/src/scss/components/_VField.scss +9 -0
  169. package/src/scss/components/_VInput.scss +17 -0
  170. package/src/scss/components/_VNavigationDrawer.scss +3 -0
  171. package/src/scss/components/_VShadow.scss +3 -0
  172. package/src/scss/components/_VStepper.scss +235 -0
  173. package/src/scss/components/_VTabs.scss +11 -0
  174. package/src/scss/components/_VTextField.scss +40 -0
  175. package/src/scss/components/_approval.scss +128 -0
  176. package/src/scss/layout/_container.scss +147 -0
  177. package/src/scss/layout/_sidebar.scss +138 -0
  178. package/src/scss/layout/_topbar.scss +39 -0
  179. package/src/scss/pages/_dashboards.scss +97 -0
  180. package/src/scss/style.scss +21 -0
  181. package/src/services/apiService.ts +59 -0
  182. package/src/services/axiosInstance.ts +14 -0
  183. package/src/stores/customizer.ts +55 -0
  184. package/src/stores/permissions.ts +237 -0
  185. package/src/theme/darkThemes/DarkModernTheme.ts +54 -0
  186. package/src/theme/darkThemes/DarkOrangeTheme.ts +53 -0
  187. package/src/theme/darkThemes/DarkPurpleTheme.ts +54 -0
  188. package/src/theme/darkThemes/DarkRedTheme.ts +54 -0
  189. package/src/theme/darkThemes/DarkSilverTheme.ts +53 -0
  190. package/src/theme/darkThemes/DarkSteelTealGreen.ts +53 -0
  191. package/src/theme/darkThemes/DarkTealTheme.ts +52 -0
  192. package/src/theme/lightThemes/ModernTheme.ts +55 -0
  193. package/src/theme/lightThemes/OrangeTheme.ts +54 -0
  194. package/src/theme/lightThemes/PurpleTheme.ts +54 -0
  195. package/src/theme/lightThemes/RedTheme.ts +55 -0
  196. package/src/theme/lightThemes/SilverTheme.ts +55 -0
  197. package/src/theme/lightThemes/SteelTealGreen.ts +54 -0
  198. package/src/theme/lightThemes/TealTheme.ts +54 -0
  199. package/src/types/approval/approvalType.ts +473 -0
  200. package/src/types/cartable/cartableTypes.ts +169 -0
  201. package/src/types/componentTypes/DataTableType.ts +14 -0
  202. package/src/types/componentTypes/DataTableTypes.ts +130 -0
  203. package/src/types/enums/global.ts +267 -0
  204. package/src/types/jalaali-js.d.ts +6 -0
  205. package/src/types/models/Base.ts +4 -0
  206. package/src/types/models/env.d.ts +10 -0
  207. package/src/types/models/person.ts +13 -0
  208. package/src/types/models/userInfo.ts +29 -0
  209. package/src/types/preApproval/preApprovalTypes.ts +67 -0
  210. package/src/types/shims-tabler-icons.d.ts +58 -0
  211. package/src/types/themeTypes/ThemeType.ts +47 -0
  212. package/src/types/vue-apexcharts.d.ts +1 -0
  213. package/src/types/vue3-print-nb.d.ts +1 -0
  214. package/src/types/vue_tabler_icon.d.ts +10 -0
  215. package/src/utils/NationalCodeValidator.ts +33 -0
  216. package/src/utils/date-convertor.ts +40 -0
  217. package/src/utils/greetingUtils.ts +97 -0
  218. package/src/utils/helpers/fake-backend.ts +68 -0
  219. package/src/utils/helpers/fetch-wrapper.ts +86 -0
  220. package/src/utils/number-formatter.ts +33 -0
  221. 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
+ }