@asaleh37/ui-base 26.2.16 → 27.5.26

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 (176) hide show
  1. package/.env.development +1 -1
  2. package/.github/workflows/publish-npm.yml +49 -49
  3. package/README.md +51 -51
  4. package/__ODockerfile +14 -14
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +1 -1
  8. package/dist/index.mjs.map +1 -1
  9. package/eslint.config.js +29 -29
  10. package/index.html +20 -20
  11. package/package.json +122 -122
  12. package/public/manifest.json +21 -21
  13. package/rollup.config-1748377725725.cjs +50 -50
  14. package/rollup.config.js +45 -45
  15. package/src/components/App.tsx +155 -155
  16. package/src/components/BaseApp.tsx +75 -75
  17. package/src/components/ExampleTrial.tsx +24 -24
  18. package/src/components/administration/admin/ChangePasswordPanel.tsx +128 -128
  19. package/src/components/administration/admin/CustomPersonGrid.tsx +361 -361
  20. package/src/components/administration/admin/OrgMemberRoleForm.tsx +83 -83
  21. package/src/components/administration/admin/OrgProvidedPersonGrid.tsx +347 -347
  22. package/src/components/administration/admin/OrganizationApplicationModuleGrid.tsx +107 -107
  23. package/src/components/administration/admin/OrganizationGrid.tsx +118 -118
  24. package/src/components/administration/admin/OrganizationMemberGrid.tsx +190 -190
  25. package/src/components/administration/admin/OrganizationMemberRoleGrid.tsx +87 -87
  26. package/src/components/administration/admin/OrganizationRankGrid.tsx +133 -133
  27. package/src/components/administration/admin/OrganizationUnitGrid.tsx +143 -143
  28. package/src/components/administration/admin/OrganizationUnitTypeGrid.tsx +108 -108
  29. package/src/components/administration/admin/PersonGrid.tsx +27 -27
  30. package/src/components/administration/admin/RoleAuthoritiesForm.tsx +82 -82
  31. package/src/components/administration/admin/SystemApplicationAuthorityGrid.tsx +117 -117
  32. package/src/components/administration/admin/SystemApplicationGrid.tsx +83 -83
  33. package/src/components/administration/admin/SystemApplicationModuleGrid.tsx +96 -96
  34. package/src/components/administration/admin/SystemApplicationRoleAuthorityGrid.tsx +75 -75
  35. package/src/components/administration/admin/SystemApplicationRoleGrid.tsx +116 -116
  36. package/src/components/administration/dev/AttachmentConfigGrid.tsx +224 -224
  37. package/src/components/administration/dev/AttachmentGrid.tsx +172 -172
  38. package/src/components/administration/dev/BluePrintGrid.tsx +129 -129
  39. package/src/components/administration/dev/DashboardGrid.tsx +173 -173
  40. package/src/components/administration/dev/DashboardWidgetGrid.tsx +164 -164
  41. package/src/components/administration/dev/DataQueryGrid.tsx +216 -216
  42. package/src/components/administration/dev/DataQueryParameterGrid.tsx +191 -191
  43. package/src/components/administration/dev/DataQueryParametersForm.tsx +84 -84
  44. package/src/components/administration/dev/DatasourceConnectionGrid.tsx +151 -151
  45. package/src/components/administration/dev/EntityParameterGrid.tsx +322 -322
  46. package/src/components/administration/dev/LookupGrid.tsx +120 -120
  47. package/src/components/administration/dev/MailAttachmentGrid.tsx +172 -172
  48. package/src/components/administration/dev/MailBodyGrid.tsx +216 -216
  49. package/src/components/administration/dev/MailNotificationQueueGrid.tsx +245 -245
  50. package/src/components/administration/dev/MailRecipientGrid.tsx +170 -170
  51. package/src/components/administration/dev/MailSenderConfigGrid.tsx +486 -486
  52. package/src/components/administration/dev/MailTemplateGrid.tsx +385 -385
  53. package/src/components/administration/dev/NotificationGrid.tsx +435 -435
  54. package/src/components/administration/dev/NotificationQueueGrid.tsx +222 -222
  55. package/src/components/administration/dev/ReportGrid.tsx +503 -503
  56. package/src/components/administration/dev/ReportParameterGrid.tsx +186 -186
  57. package/src/components/administration/dev/ReportParametersForm.tsx +84 -84
  58. package/src/components/administration/dev/WidgetGrid.tsx +380 -380
  59. package/src/components/administration/dev/WorkflowDocumentActionGrid.tsx +264 -264
  60. package/src/components/administration/dev/WorkflowDocumentActionHistoryGrid.tsx +172 -172
  61. package/src/components/administration/dev/WorkflowDocumentActionMailGrid.tsx +161 -161
  62. package/src/components/administration/dev/WorkflowDocumentGrid.tsx +357 -357
  63. package/src/components/administration/dev/WorkflowDocumentMailLogGrid.tsx +218 -218
  64. package/src/components/administration/dev/WorkflowDocumentStatusGrid.tsx +243 -243
  65. package/src/components/common/AzureLogin.tsx +222 -222
  66. package/src/components/common/ChangeOrgForm.tsx +85 -85
  67. package/src/components/common/Home.tsx +43 -43
  68. package/src/components/common/LanguageSwitcher.tsx +25 -25
  69. package/src/components/common/LayoutHandlers.tsx +11 -11
  70. package/src/components/common/LoadingMask.tsx +24 -24
  71. package/src/components/common/Login.tsx +268 -268
  72. package/src/components/common/MobileLogin.tsx +229 -229
  73. package/src/components/common/MyNotificationsPanel.tsx +109 -109
  74. package/src/components/common/NoLicenseComponent.tsx +79 -79
  75. package/src/components/common/NotificationItem.tsx +138 -138
  76. package/src/components/index.ts +10 -10
  77. package/src/components/msalConfig.ts +11 -11
  78. package/src/components/templates/DataEntryTemplates/DataEntryTypes.ts +361 -361
  79. package/src/components/templates/DataEntryTemplates/DataEntryUtil.ts +297 -297
  80. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormAction.tsx +60 -60
  81. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormElementField.tsx +238 -238
  82. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormElementGroup.tsx +108 -108
  83. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/CheckBox.tsx +66 -66
  84. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/ComboBox.tsx +164 -164
  85. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/Datefield.tsx +70 -70
  86. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/DatetimeField.tsx +71 -71
  87. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/FiltersPanel.tsx +237 -237
  88. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/SystemLookupCombobox.tsx +56 -56
  89. package/src/components/templates/DataEntryTemplates/TemplateDataForm/FormFields/TemplateTextField.tsx +20 -20
  90. package/src/components/templates/DataEntryTemplates/TemplateDataForm/TemplateForm.tsx +431 -431
  91. package/src/components/templates/DataEntryTemplates/TemplateDataGrid/DataGridColumnsUtil.tsx +197 -197
  92. package/src/components/templates/DataEntryTemplates/TemplateDataGrid/TemplateGrid.tsx +1044 -1044
  93. package/src/components/templates/DataEntryTemplates/TemplateDataGrid/TemplateGridMultiRecordAction.tsx +89 -89
  94. package/src/components/templates/DataEntryTemplates/TemplateDataGrid/TemplateGridRecordAction.tsx +95 -95
  95. package/src/components/templates/DataEntryTemplates/TemplateDataGrid/TemplateGridTopBar.tsx +251 -251
  96. package/src/components/templates/TransferList.tsx +256 -256
  97. package/src/components/templates/Window/ConfirmationWindow.tsx +55 -55
  98. package/src/components/templates/attachment/AttachmentCard.tsx +141 -141
  99. package/src/components/templates/attachment/AttachmentImageViewer.tsx +85 -85
  100. package/src/components/templates/attachment/AttachmentPanel.tsx +285 -285
  101. package/src/components/templates/index.ts +35 -35
  102. package/src/components/templates/report/ExcelReportViewer.tsx +71 -71
  103. package/src/components/templates/report/ReportViewer.tsx +261 -261
  104. package/src/components/templates/report/ReportViewerRoute.tsx +9 -9
  105. package/src/components/templates/visuals/DashboardRouteView.tsx +9 -9
  106. package/src/components/templates/visuals/DashboardViewer.tsx +192 -192
  107. package/src/components/templates/visuals/WidgetViewer.tsx +208 -208
  108. package/src/components/templates/visuals/charts/TemplateBarChart.tsx +23 -23
  109. package/src/components/templates/visuals/charts/TemplateDataCard.tsx +35 -35
  110. package/src/components/templates/visuals/charts/TemplateGauge.tsx +21 -21
  111. package/src/components/templates/visuals/charts/TemplateLineChart.tsx +22 -22
  112. package/src/components/templates/visuals/charts/TemplateLineProgress.tsx +42 -42
  113. package/src/components/templates/visuals/charts/TemplatePieChart.tsx +24 -24
  114. package/src/components/templates/workflow/WorkflowDocumentPanel.tsx +611 -611
  115. package/src/components/templates/workflow/WorkflowDocumentTimeLine.tsx +145 -145
  116. package/src/components/templates/workflow/WorkflowRouteComponent.tsx +14 -14
  117. package/src/examples/ExampleGrid.tsx +134 -134
  118. package/src/hooks/UseConfirmationWindow.tsx +56 -56
  119. package/src/hooks/UseMobile.tsx +13 -13
  120. package/src/hooks/UseSession.tsx +59 -59
  121. package/src/hooks/UseWindow.tsx +111 -111
  122. package/src/hooks/index.ts +22 -22
  123. package/src/hooks/useApiActions.ts +124 -124
  124. package/src/hooks/useAxios.tsx +340 -340
  125. package/src/hooks/useCommonStore.tsx +29 -29
  126. package/src/hooks/useInterval.tsx +23 -23
  127. package/src/hooks/useLoadingMask.tsx +16 -16
  128. package/src/hooks/useLookupGridColumn.tsx +35 -35
  129. package/src/hooks/useParameterPanel.tsx +171 -171
  130. package/src/index.ts +4 -4
  131. package/src/layout/DrawerHeader.tsx +10 -10
  132. package/src/layout/Layout.tsx +102 -102
  133. package/src/layout/MainContent.tsx +115 -115
  134. package/src/layout/MobileDrawer.tsx +103 -103
  135. package/src/layout/NavigationTree.tsx +360 -360
  136. package/src/layout/NotificationButton.tsx +207 -207
  137. package/src/layout/RouteWrapper.tsx +63 -63
  138. package/src/layout/SideBar.tsx +85 -85
  139. package/src/layout/TopBar.tsx +317 -317
  140. package/src/locales/arabic/adminLocalsAr.json +94 -94
  141. package/src/locales/arabic/common.json +44 -44
  142. package/src/locales/arabic/devLocalsAr.json +317 -317
  143. package/src/locales/arabic/index.ts +9 -9
  144. package/src/locales/english/adminLocalsEn.json +97 -97
  145. package/src/locales/english/common.json +43 -43
  146. package/src/locales/english/devLocalsEn.json +318 -318
  147. package/src/locales/english/index.ts +9 -9
  148. package/src/locales/i18n.ts +8 -8
  149. package/src/locales/index.ts +9 -9
  150. package/src/main.tsx +41 -41
  151. package/src/navigationItems/Administration/adminNavigationItems.tsx +231 -231
  152. package/src/navigationItems/Administration/index.tsx +16 -16
  153. package/src/navigationItems/common/CommonNavigationItems.tsx +12 -12
  154. package/src/navigationItems/common/index.tsx +7 -7
  155. package/src/navigationItems/index.tsx +35 -35
  156. package/src/redux/features/administration/AdministrationStoresMetaData.ts +164 -164
  157. package/src/redux/features/common/AppInfoSlice.ts +95 -95
  158. package/src/redux/features/common/AppLayoutSlice.ts +29 -29
  159. package/src/redux/features/common/CommonStoreSlice.ts +44 -44
  160. package/src/redux/features/common/LoadingMaskSlice.ts +30 -30
  161. package/src/redux/features/common/SideBarSlice.ts +27 -27
  162. package/src/redux/features/common/UserSessionSlice.ts +54 -54
  163. package/src/redux/store.ts +29 -29
  164. package/src/routes/administration/adminRoutes.tsx +99 -99
  165. package/src/routes/administration/devRoutes.tsx +129 -129
  166. package/src/routes/administration/index.ts +10 -10
  167. package/src/routes/index.ts +5 -5
  168. package/src/routes/types/index.ts +6 -6
  169. package/src/styles/index.css +19 -19
  170. package/src/types/index.ts +8 -8
  171. package/src/util/AppUtils.ts +73 -73
  172. package/src/util/constants.ts +6 -6
  173. package/src/util/index.ts +5 -5
  174. package/tsconfig.json +135 -135
  175. package/vite.config.ts +24 -24
  176. package/package-lock.json/342/200/216 +0 -9040
@@ -1,1044 +1,1044 @@
1
- import {
2
- DataGridPremium,
3
- GridActionsCellItem,
4
- GridActionsCellItemProps,
5
- GridColumnOrderChangeParams,
6
- GridColumnResizeParams,
7
- GridColumnVisibilityModel,
8
- GridEventListener,
9
- GridExpandMoreIcon,
10
- GridPinnedColumnFields,
11
- GridRowGroupingModel,
12
- GridRowModes,
13
- GridRowModesModel,
14
- GridRowParams,
15
- GridRowSelectionModel,
16
- } from "@mui/x-data-grid-premium";
17
- import { TemplateGridColDef, TemplateGridProps } from "../DataEntryTypes";
18
- import TemplateGridTopBar from "./TemplateGridTopBar";
19
- import { useNavigate } from "react-router-dom";
20
- import React, { useEffect, useState } from "react";
21
- import { z } from "zod";
22
- import {
23
- constructGridColumnsFromFields,
24
- constructValidationSchema,
25
- getAllFields,
26
- } from "../DataEntryUtil";
27
- import { toast } from "react-toastify";
28
- import {
29
- Accordion,
30
- AccordionDetails,
31
- AccordionSummary,
32
- Box,
33
- Divider,
34
- IconButton,
35
- Tooltip,
36
- Typography,
37
- } from "@mui/material";
38
- import { capitalizeFirstLetter, isNumber } from "../../../../util/AppUtils";
39
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
40
- import TemplateGridRecordAction from "./TemplateGridRecordAction";
41
- import TemplateForm from "../TemplateDataForm/TemplateForm";
42
- import { useSelector } from "react-redux";
43
- import { useTranslation } from "react-i18next";
44
- import useSession from "../../../../hooks/UseSession";
45
- import { useConfirmationWindow } from "../../../../hooks/UseConfirmationWindow";
46
- import { useWindow } from "../../../../hooks/UseWindow";
47
- import { useAxios } from "../../../../hooks";
48
- import useLookupGridColumn from "../../../../hooks/useLookupGridColumn";
49
- import { ContinuousColorLegend } from "@mui/x-charts";
50
- import AttachmentPanel from "../../attachment/AttachmentPanel";
51
- import WorkflowDocumentPanel from "../../workflow/WorkflowDocumentPanel";
52
- let currentNewRecordIndex = -1;
53
-
54
- interface GridState {
55
- columnVisibilityModel?: GridColumnVisibilityModel;
56
- columnOrder?: string[];
57
- columnWidths?: { [key: string]: number };
58
- columnGroupingModel?: GridRowGroupingModel;
59
- pinnedColumns?: GridPinnedColumnFields;
60
- }
61
-
62
- const loadGridState = (gridStateKey: string): GridState => {
63
- try {
64
- const saved = localStorage.getItem(gridStateKey);
65
- return saved ? JSON.parse(saved) : {};
66
- } catch (e) {
67
- console.error("Failed to load grid state", e);
68
- return {};
69
- }
70
- };
71
-
72
- const saveGridState = (gridStateKey: string, statePart: GridState) => {
73
- const current = loadGridState(gridStateKey);
74
- localStorage.setItem(
75
- gridStateKey,
76
- JSON.stringify({ ...current, ...statePart })
77
- );
78
- };
79
-
80
- const PIN_FIXED_COLUMNS = ["__check__", "actions"];
81
-
82
- const TemplateGrid: React.FC<TemplateGridProps> = (props) => {
83
- const { t } = useTranslation();
84
- const { handleGetRequest } = useAxios();
85
- const stores = useSelector((state: any) => state.commonStores.stores);
86
- const AppLayout = useSelector((state: any) => state.AppLayout);
87
- const [selectedRecord, setSelectedRecord] = useState<any>({});
88
- const [attachmentPanelEnabledForRecord, setAttachmentPanelEnabledForRecord] =
89
- useState<boolean>(true);
90
- const { getLookupOptions } = useLookupGridColumn();
91
- const { Window: AttachmentWindow, setWindowState: setAttachmentWindowState } =
92
- useWindow({
93
- windowTitle: t(props.gridTitle) + " Attachments",
94
- windowIcon: "paperclip",
95
- width: "fit-content",
96
- height: "fit-content",
97
- minHeight: 500,
98
- minWidth: "50%",
99
- onCloseCallBack: () => {
100
- props?.apiActions?.reloadData(props.gridLoadParametersValues);
101
- },
102
- });
103
- const { Window: WorkFlowWindow, setWindowState: setWorkFlowWindowState } =
104
- useWindow({
105
- windowTitle: t(props.gridTitle) + " Approvals",
106
- windowIcon: "stamp",
107
- height: "fit-content",
108
- minHeight: 500,
109
- width: "fit-content",
110
- // width:1100,
111
- onCloseCallBack: () => {
112
- props?.apiActions?.reloadData(props.gridLoadParametersValues);
113
- },
114
- });
115
- const [generatedColumns, setGeneratedColumns] = useState<
116
- Array<TemplateGridColDef>
117
- >([]);
118
- const fields = getAllFields(props.formElements);
119
- const hiddenFields = [];
120
- const savedState = React.useMemo<GridState>(
121
- () => (props?.gridStateKey ? loadGridState(props.gridStateKey) : {}),
122
- []
123
- );
124
- for (const field of fields) {
125
- if (field?.gridProps?.hidden === true) {
126
- hiddenFields.push(field.fieldName);
127
- }
128
- }
129
- let initialVisibilityState: GridColumnVisibilityModel = {};
130
- if (savedState?.columnVisibilityModel) {
131
- initialVisibilityState = savedState.columnVisibilityModel;
132
- const existingFields = Object.keys(initialVisibilityState);
133
- for (const field of hiddenFields) {
134
- if (!existingFields.includes(field)) {
135
- initialVisibilityState[field] = false;
136
- }
137
- }
138
- } else {
139
- for (const field of hiddenFields) {
140
- initialVisibilityState[field] = false;
141
- }
142
- }
143
-
144
- const themeDirection = useSelector(
145
- (state: any) => state.AppLayout.appDirection
146
- );
147
-
148
- const clearGridState = () => {
149
- if (props?.gridStateKey) {
150
- localStorage.removeItem(props.gridStateKey);
151
- }
152
- setColumnVisibilityModel({});
153
- setColumnOrder([]);
154
- setColumnWidths({});
155
- setGridRowGroupingModel([]);
156
- setPinnedColumns({
157
- left: [...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : [])],
158
- right: [...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : [])],
159
- });
160
- };
161
- const [columnVisibilityModel, setColumnVisibilityModel] =
162
- React.useState<GridColumnVisibilityModel>(initialVisibilityState);
163
- const [columnOrder, setColumnOrder] = React.useState<string[]>(
164
- savedState.columnOrder || []
165
- );
166
- const [columnWidths, setColumnWidths] = React.useState<{
167
- [key: string]: number;
168
- }>(savedState.columnWidths || {});
169
- const [gridRowGroupingModel, setGridRowGroupingModel] =
170
- useState<GridRowGroupingModel>(savedState.columnGroupingModel || []);
171
- let newLeft = savedState?.pinnedColumns?.left || [];
172
- newLeft = newLeft.filter(
173
- (record: any) => !PIN_FIXED_COLUMNS.includes(record)
174
- );
175
- let newRight = savedState?.pinnedColumns?.right || [] || [];
176
- newRight = newRight.filter(
177
- (record: any) => !PIN_FIXED_COLUMNS.includes(record)
178
- );
179
-
180
- const [pinnedColumns, setPinnedColumns] = useState<GridPinnedColumnFields>({
181
- left: [...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : []), ...newLeft],
182
- right: [
183
- ...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : []),
184
- ...newRight,
185
- ],
186
- });
187
-
188
- const session = useSession();
189
- const navigate = useNavigate();
190
- const [recordToDelete, setRecordToDelete] = useState<any>(null);
191
- const [recordToEdit, setRecordToEdit] = useState<any>(null);
192
- const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
193
- const loadComboboxData: (
194
- storeUrl: any,
195
- storeLoadParam: any,
196
- dataQueryId: any
197
- ) => Promise<Array<any>> = async (storeUrl, storeLoadParam, dataQueryId) => {
198
- let comboboxData: Array<any> = [];
199
- if (storeUrl) {
200
- await handleGetRequest({
201
- endPointURI: storeUrl,
202
- showMask: false,
203
- parameters: storeLoadParam,
204
- successCallBkFn: (response) => {
205
- comboboxData = response.data;
206
- },
207
- });
208
- } else if (dataQueryId) {
209
- await handleGetRequest({
210
- endPointURI: "api/v1/dev/query/result",
211
- showMask: false,
212
- parameters: { queryId: dataQueryId, ...storeLoadParam },
213
- successCallBkFn: (response) => {
214
- comboboxData = response.data;
215
- },
216
- });
217
- }
218
- return comboboxData;
219
- };
220
- const adjustGridColumns = async () => {
221
- let gridColumns = constructGridColumnsFromFields(
222
- fields,
223
- props?.editMode?.editMode === "row" || false,
224
- t
225
- );
226
- for (let gridColumn of gridColumns) {
227
- if (gridColumn?.lookupType) {
228
- gridColumn.displayField =
229
- AppLayout.appDirection === "ltr"
230
- ? "lookupEnDisplay"
231
- : "lookupArDisplay";
232
- gridColumn.options = await getLookupOptions(gridColumn.lookupType);
233
- gridColumn.valueField = "lookupValue";
234
- }
235
- if (gridColumn.type === "custom") {
236
- if (gridColumn?.options) {
237
- continue;
238
- } else if (gridColumn?.commonStoreKey) {
239
- gridColumn.options = stores[gridColumn.commonStoreKey]?.data || [];
240
- } else {
241
- gridColumn.options = await loadComboboxData(
242
- gridColumn?.storeUrl,
243
- gridColumn?.storeLoadParam,
244
- gridColumn?.dataQueryId
245
- );
246
- }
247
- }
248
- }
249
- setGeneratedColumns(gridColumns);
250
- };
251
- const [rowSelectionModel, setRowSelectionModel] =
252
- useState<GridRowSelectionModel>({ ids: new Set(), type: "include" });
253
- let keyColumnName: string = "id";
254
- if (props?.keyColumnName) {
255
- keyColumnName = props?.keyColumnName;
256
- }
257
- const setData = props.setData;
258
- let validationSchema = null;
259
- if (props?.validationSchema) {
260
- validationSchema = props.validationSchema;
261
- } else {
262
- validationSchema = z.object(constructValidationSchema(fields));
263
- }
264
-
265
- let isEditAllowed = true;
266
- if (props?.editAction?.authority) {
267
- isEditAllowed = session.isUserAuthorized(props.editAction.authority);
268
- }
269
- let isDeleteAllowed = true;
270
- if (props?.deleteAction?.authority) {
271
- isDeleteAllowed = session.isUserAuthorized(props.deleteAction.authority);
272
- }
273
-
274
- const handleRowModesModelChange = (newModel: GridRowModesModel) => {
275
- setRowModesModel(newModel);
276
- };
277
-
278
- const handlePinnedColumnsChange = (newModel: GridPinnedColumnFields) => {
279
- let newLeft = newModel?.left || [];
280
- newLeft = newLeft.filter(
281
- (record: any) => !PIN_FIXED_COLUMNS.includes(record)
282
- );
283
- let newRight = newModel?.right || [];
284
- newRight = newRight.filter(
285
- (record: any) => !PIN_FIXED_COLUMNS.includes(record)
286
- );
287
- const newPinedColumns: GridPinnedColumnFields = {
288
- left: [
289
- ...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : []),
290
- ...newLeft,
291
- ],
292
- right: [
293
- ...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : []),
294
- ...newRight,
295
- ],
296
- };
297
- setPinnedColumns(newPinedColumns);
298
- if (props?.gridStateKey) {
299
- saveGridState(props.gridStateKey, { pinnedColumns: newPinedColumns });
300
- }
301
- };
302
-
303
- const handleDeleteRecord = async () => {
304
- let result = true;
305
- if (!(recordToDelete && recordToDelete?.isNew === true)) {
306
- result = await props.apiActions.deleteRecordById(
307
- recordToDelete[keyColumnName]
308
- );
309
- }
310
- if (result) {
311
- if (props?.deleteAction?.postActionCallBack) {
312
- await props.deleteAction.postActionCallBack(recordToDelete);
313
- }
314
-
315
- setData((oldData: any) => {
316
- const newData = oldData.filter(
317
- (record: any) =>
318
- record[keyColumnName] != recordToDelete[keyColumnName]
319
- );
320
- return newData;
321
- });
322
- }
323
- };
324
-
325
- const { ConfirmationWindow, setOpen: setConfirmationWindowOpened } =
326
- useConfirmationWindow({
327
- title: "Confirmation",
328
- body: "Are you sure you want to delete this record ?",
329
- onConfirmationCallBk: handleDeleteRecord,
330
- });
331
-
332
- let formModalHeight = undefined;
333
- let formModalWidth = undefined;
334
- let formModalMinHeight = undefined;
335
- let formModalMinWidth = undefined;
336
- let formModalIcon = undefined;
337
- let formModalTitle = undefined;
338
-
339
- if (props?.editMode?.editMode === "modal") {
340
- formModalHeight = props?.editMode?.specs?.modalHeight || "fit-content";
341
- formModalWidth = props?.editMode?.specs?.modalWidth || "300";
342
- formModalMinHeight = props?.editMode?.specs?.modalMinHeight;
343
- formModalMinWidth = props?.editMode?.specs?.modalMinWidth;
344
- formModalIcon = props?.editMode?.specs?.modalIcon || "window";
345
- formModalTitle = props?.editMode?.specs?.modalTitle || "Record Form";
346
- }
347
-
348
- const { Window: FormWindow, setWindowState: setFormWindowState } = useWindow({
349
- height: formModalHeight,
350
- minHeight: formModalMinHeight,
351
- minWidth: formModalMinWidth,
352
- onCloseCallBack: () => {
353
- props.apiActions.reloadData(props?.gridLoadParametersValues);
354
- },
355
- width: formModalWidth,
356
- windowIcon: formModalIcon,
357
- windowTitle: formModalTitle,
358
- });
359
-
360
- const handleCreateNewRecord = () => {
361
- if (props?.editMode?.editMode === "form") {
362
- navigate(props.editMode.specs.formRoute);
363
- } else if (props?.editMode?.editMode === "modal") {
364
- setRecordToEdit(null);
365
- setFormWindowState(true);
366
- } else if (props?.editMode?.editMode === "row") {
367
- currentNewRecordIndex = currentNewRecordIndex - 1;
368
- const newRecord: any = {};
369
- newRecord[keyColumnName] = currentNewRecordIndex;
370
- newRecord.isNew = true;
371
- for (const gridColumn of generatedColumns) {
372
- if (
373
- gridColumn?.type != "actions" &&
374
- gridColumn?.field != keyColumnName
375
- ) {
376
- if (gridColumn?.field) {
377
- newRecord[gridColumn.field] = null;
378
- }
379
- }
380
- }
381
- setData((oldRows: any) => [newRecord, ...oldRows]);
382
- setRowModesModel((oldModel: any) => ({
383
- ...oldModel,
384
- [currentNewRecordIndex]: { mode: GridRowModes.Edit },
385
- }));
386
- }
387
- };
388
-
389
- const handleEditRecord = async (record: any) => {
390
- if (record) {
391
- setRecordToEdit(record);
392
- if (props.editMode.editMode === "form") {
393
- navigate(props.editMode.specs.formRoute + "/" + record[keyColumnName]);
394
- } else if (props.editMode.editMode === "modal") {
395
- setFormWindowState(true);
396
- } else if (props?.editMode?.editMode === "row") {
397
- const id = record[keyColumnName];
398
- setRowModesModel({
399
- ...rowModesModel,
400
- [id]: { mode: GridRowModes.Edit },
401
- });
402
- }
403
- }
404
- };
405
-
406
- const validateRecord = (record: any) => {
407
- try {
408
- validationSchema.parse(record);
409
- } catch (err) {
410
- console.log("validateRecord err", err);
411
- let errorMessage: any = null;
412
- if (err instanceof z.ZodError) {
413
- errorMessage = err.errors
414
- .map(
415
- (error) => "Error in field (" + error.path + ") : " + error.message
416
- )
417
- .join(",");
418
- } else {
419
- errorMessage = "invalid record data";
420
- }
421
- return errorMessage;
422
- }
423
- };
424
-
425
- const handleSaveRowClick = (record: any) => {
426
- const id = record[keyColumnName];
427
- setRowModesModel({
428
- ...rowModesModel,
429
- [id]: { mode: GridRowModes.View },
430
- });
431
- };
432
-
433
- const processRowUpdate = async (record: any) => {
434
- if (props.editMode.editMode === "row") {
435
- let savedRecord: any = null;
436
- const errorMessage = validateRecord(record);
437
- if (errorMessage) {
438
- const errors = errorMessage.split(",");
439
- toast.error(
440
- <div style={{}}>
441
- {errors.map((error: any) => (
442
- <>
443
- <div>{error}</div>
444
- <Divider />
445
- </>
446
- ))}
447
- </div>
448
- );
449
- throw new Error(errorMessage);
450
- }
451
- if (props?.editAction?.preActionValidation) {
452
- if (!props.editAction.preActionValidation(record)) {
453
- throw new Error("error on the configured presave validation");
454
- }
455
- }
456
- const requestObject: any = { ...record };
457
- if (
458
- record[keyColumnName] &&
459
- isNumber(record[keyColumnName]) &&
460
- Number(record[keyColumnName]) < 0
461
- ) {
462
- requestObject[keyColumnName] = null;
463
- }
464
- savedRecord = await props.apiActions.saveRecord(requestObject);
465
- if (savedRecord == null) {
466
- throw new Error(
467
- "Failed to process your request, contact your administrator"
468
- );
469
- }
470
- if (props?.editAction?.postActionCallBack) {
471
- await props.editAction.postActionCallBack(record);
472
- }
473
- if (record?.isNew === true) {
474
- setData((oldData: any) => {
475
- const newData = oldData.filter(
476
- (x: any) => x[keyColumnName] !== record[keyColumnName]
477
- );
478
- return [savedRecord, ...newData];
479
- });
480
- }
481
- if (
482
- props?.editMode?.reloadAfterSave === true &&
483
- props?.apiActions?.reloadData
484
- ) {
485
- props?.apiActions?.reloadData(props?.gridLoadParametersValues);
486
- }
487
- return savedRecord;
488
- }
489
- };
490
-
491
- const handleCancelRowEditClick = (record: any) => {
492
- const id = record[keyColumnName];
493
- if (id && isNumber(id) && id < 0) {
494
- setRowModesModel({
495
- ...rowModesModel,
496
- [id]: { mode: GridRowModes.View, ignoreModifications: true },
497
- });
498
- setData((oldData: any) => {
499
- const newData = oldData.filter(
500
- (record: any) => record[keyColumnName] != id
501
- );
502
- return newData;
503
- });
504
- } else {
505
- setRowModesModel({
506
- ...rowModesModel,
507
- [id]: { mode: GridRowModes.View, ignoreModifications: true },
508
- });
509
- }
510
- };
511
-
512
- const getActionColumnActions = (params: GridRowParams<any>) => {
513
- const record: any = params.row;
514
- const actions: Array<React.ReactElement<GridActionsCellItemProps>> = [];
515
- if (props?.editMode?.editMode != "none") {
516
- if (
517
- props?.disableDefaultAction === undefined ||
518
- !props.disableDefaultAction
519
- ) {
520
- const isInEditMode: boolean =
521
- rowModesModel[record[keyColumnName]]?.mode === GridRowModes.Edit;
522
- if (props.editMode.editMode === "row" && isInEditMode) {
523
- if (isEditAllowed) {
524
- actions.push(
525
- <GridActionsCellItem
526
- icon={<FontAwesomeIcon icon="save" />}
527
- label={t("SAVE_BTN_LABEL")}
528
- onClick={() => {
529
- handleSaveRowClick(record);
530
- }}
531
- />
532
- );
533
- actions.push(
534
- <GridActionsCellItem
535
- icon={<FontAwesomeIcon icon="cancel" />}
536
- label={t("CANCEL_BTN_LABEL")}
537
- onClick={() => {
538
- handleCancelRowEditClick(record);
539
- }}
540
- color="inherit"
541
- />
542
- );
543
- }
544
- } else {
545
- if (props?.editAction && props?.editAction?.isEnabled === true) {
546
- let isEditActionVisibleForRecord = true;
547
- if (props?.editAction?.isActionVisibleForRecord) {
548
- isEditActionVisibleForRecord =
549
- props?.editAction?.isActionVisibleForRecord(record);
550
- }
551
- let isActionEditDisabledForRecord = false;
552
- if (props?.editAction?.isActionDisabledForRecord) {
553
- isActionEditDisabledForRecord =
554
- props?.editAction?.isActionDisabledForRecord(record);
555
- }
556
- if (isEditAllowed && isEditActionVisibleForRecord) {
557
- actions.push(
558
- <GridActionsCellItem
559
- disabled={isActionEditDisabledForRecord}
560
- icon={
561
- <Tooltip title={t("EDIT_BTN_LABEL")}>
562
- <FontAwesomeIcon
563
- icon={"edit"}
564
- style={{
565
- color: isActionEditDisabledForRecord
566
- ? "gray"
567
- : undefined,
568
- }}
569
- />
570
- </Tooltip>
571
- }
572
- showInMenu={
573
- props?.editAction?.gridActionProps?.showInMenu || false
574
- }
575
- label={t("EDIT_BTN_LABEL")}
576
- className="textPrimary"
577
- color="inherit"
578
- onClick={() => {
579
- if (isEditAllowed && !isActionEditDisabledForRecord) {
580
- handleEditRecord(record);
581
- }
582
- }}
583
- />
584
- );
585
- }
586
- }
587
- if (props?.deleteAction && props?.deleteAction?.isEnabled === true) {
588
- let isDeleteActionVisibleForRecord = true;
589
- if (props?.deleteAction?.isActionVisibleForRecord) {
590
- isDeleteActionVisibleForRecord =
591
- props?.deleteAction?.isActionVisibleForRecord(record);
592
- }
593
- let isDeleteActionDisabledForRecord = false;
594
- if (props?.deleteAction?.isActionDisabledForRecord) {
595
- isDeleteActionDisabledForRecord =
596
- props?.deleteAction?.isActionDisabledForRecord(record);
597
- }
598
- if (isDeleteAllowed && isDeleteActionVisibleForRecord) {
599
- actions.push(
600
- <GridActionsCellItem
601
- disabled={isDeleteActionDisabledForRecord}
602
- icon={
603
- <Tooltip title={t("DELETE_BTN_LABEL")}>
604
- <FontAwesomeIcon
605
- icon={"trash"}
606
- style={{
607
- color: isDeleteActionDisabledForRecord
608
- ? "gray"
609
- : undefined,
610
- }}
611
- />
612
- </Tooltip>
613
- }
614
- showInMenu={
615
- props?.deleteAction?.gridActionProps?.showInMenu || false
616
- }
617
- label={t("DELETE_BTN_LABEL")}
618
- className="textPrimary"
619
- color="inherit"
620
- onClick={() => {
621
- if (isDeleteAllowed && !isDeleteActionDisabledForRecord) {
622
- if (props?.deleteAction?.preActionValidation) {
623
- if (!props.deleteAction.preActionValidation(record)) {
624
- return;
625
- }
626
- }
627
- setRecordToDelete(record);
628
- setConfirmationWindowOpened(true);
629
- }
630
- }}
631
- />
632
- );
633
- }
634
- }
635
- }
636
- }
637
- }
638
- if (
639
- (record[props?.keyColumnName] || (record["id"] && record["id"] > 0)) &&
640
- props?.attachment
641
- ) {
642
- actions?.push(
643
- <GridActionsCellItem
644
- icon={
645
- <Tooltip title={"Attachments"}>
646
- <FontAwesomeIcon icon={"paperclip"} />
647
- </Tooltip>
648
- }
649
- label={"Attachments"}
650
- className="textPrimary"
651
- color="inherit"
652
- onClick={() => {
653
- setSelectedRecord(record);
654
- if (props?.attachment?.enableAttachFn) {
655
- setAttachmentPanelEnabledForRecord(
656
- props.attachment.enableAttachFn(record)
657
- );
658
- } else {
659
- setAttachmentPanelEnabledForRecord(true);
660
- }
661
- setAttachmentWindowState(true);
662
- }}
663
- />
664
- );
665
- }
666
- if (
667
- (record[props?.keyColumnName] || (record["id"] && record["id"] > 0)) &&
668
- props?.workFlowDocumentCode
669
- ) {
670
- actions?.push(
671
- <GridActionsCellItem
672
- icon={
673
- <Tooltip title={"Approvals"}>
674
- <FontAwesomeIcon icon={"stamp"} />
675
- </Tooltip>
676
- }
677
- label={"Approvals"}
678
- className="textPrimary"
679
- color="inherit"
680
- onClick={() => {
681
- setSelectedRecord(record);
682
- setWorkFlowWindowState(true);
683
- }}
684
- />
685
- );
686
- }
687
- if (
688
- record[props?.keyColumnName || "id"] &&
689
- record[props?.keyColumnName || "id"] > 0 &&
690
- props?.rowActions
691
- ) {
692
- for (const rowAction of props.rowActions) {
693
- if (
694
- !(
695
- rowAction?.gridActionProps?.multiRecord &&
696
- rowAction?.gridActionProps?.multiRecord === true
697
- )
698
- ) {
699
- actions.push(
700
- <TemplateGridRecordAction
701
- {...rowAction}
702
- record={record}
703
- reloadData={async () => {
704
- props.apiActions.reloadData(props?.gridLoadParametersValues);
705
- }}
706
- />
707
- );
708
- }
709
- }
710
- }
711
-
712
- return actions;
713
- };
714
-
715
- const actionColumn: TemplateGridColDef = {
716
- type: "actions",
717
- field: "actions",
718
- headerName: "",
719
- headerAlign: "center",
720
- width:
721
- (props?.rowActions ? props.rowActions.length * 30 : 0) +
722
- (props?.editAction && props?.editAction?.isEnabled ? 30 : 0) +
723
- (props?.deleteAction && props?.deleteAction?.isEnabled ? 30 : 0) +
724
- (props?.attachment ? 30 : 0) +
725
- (props?.workFlowDocumentCode ? 30 : 0),
726
- getActions: getActionColumnActions,
727
- };
728
-
729
- let structuredColumns: Array<TemplateGridColDef> = [];
730
- if (
731
- props?.disableDefaultAction === undefined ||
732
- !props.disableDefaultAction ||
733
- (props?.rowActions && props?.rowActions.length > 0)
734
- ) {
735
- structuredColumns.push(actionColumn);
736
- }
737
-
738
- structuredColumns = [...structuredColumns, ...generatedColumns];
739
- const handleRowSelection = (gridSelectionModel: GridRowSelectionModel) => {
740
- setRowSelectionModel(gridSelectionModel);
741
- };
742
- useEffect(() => {
743
- if (props?.autoLoad === undefined || props.autoLoad === true) {
744
- props.apiActions.reloadData(props?.gridLoadParametersValues);
745
- }
746
- adjustGridColumns();
747
- }, []);
748
-
749
- useEffect(() => {
750
- props.apiActions.reloadData(props?.gridLoadParametersValues);
751
- }, [session.UserInfo?.currentOrganization]);
752
-
753
- useEffect(() => {
754
- adjustGridColumns();
755
- }, [themeDirection, props.formElements]);
756
-
757
- const handleColumnVisibilityChange = (model: GridColumnVisibilityModel) => {
758
- setColumnVisibilityModel(model);
759
- if (props?.gridStateKey) {
760
- saveGridState(props.gridStateKey, { columnVisibilityModel: model });
761
- }
762
- };
763
-
764
- const handleColumnOrderChange: GridEventListener<"columnOrderChange"> = (
765
- params: GridColumnOrderChangeParams
766
- ) => {
767
- const { column, targetIndex } = params;
768
- setColumnOrder((prevOrder) => {
769
- const currentOrder = prevOrder.length
770
- ? [...prevOrder]
771
- : structuredColumns.map((col) => col.field);
772
- const fromIndex = currentOrder.indexOf(column.field);
773
- if (fromIndex === -1) return currentOrder;
774
-
775
- currentOrder.splice(fromIndex, 1); // remove
776
- currentOrder.splice(targetIndex, 0, column.field); // insert at new index
777
-
778
- if (props?.gridStateKey) {
779
- saveGridState(props.gridStateKey, { columnOrder: currentOrder });
780
- }
781
-
782
- return currentOrder;
783
- });
784
- };
785
-
786
- const handleRowGroupChange = (model: GridRowGroupingModel) => {
787
- setGridRowGroupingModel(model);
788
- if (props?.gridStateKey) {
789
- saveGridState(props.gridStateKey, { columnGroupingModel: model });
790
- }
791
- };
792
-
793
- // const handleColumnWidthChange: GridEventListener<"columnWidthChange"> = (
794
- // params: GridColumnResizeParams
795
- // ) => {
796
- // const updatedWidths = {
797
- // ...columnWidths,
798
- // [params.colDef.field]: params.width,
799
- // };
800
- // setColumnWidths(updatedWidths);
801
- // if (props?.gridStateKey) {
802
- // saveGridState(props.gridStateKey, { columnWidths: updatedWidths });
803
- // }
804
- // };
805
-
806
- const adjustedColumns: Array<TemplateGridColDef> = React.useMemo(() => {
807
- const baseCols = structuredColumns.map((col) => ({
808
- ...col,
809
- }));
810
-
811
- // Reorder based on saved columnOrder
812
- if (columnOrder.length) {
813
- const fieldToCol = new Map(baseCols.map((col) => [col.field, col]));
814
- return columnOrder.map((field) => fieldToCol.get(field)!).filter(Boolean);
815
- }
816
- return baseCols;
817
- }, [columnOrder, columnWidths, structuredColumns]);
818
-
819
- return (
820
- <>
821
- <ConfirmationWindow />
822
- {props?.editMode?.editMode === "modal" ? (
823
- <FormWindow>
824
- {props?.editMode?.specs?.formComponent ? (
825
- <props.editMode.specs.formComponent
826
- recordIdToEdit={
827
- recordToEdit ? recordToEdit[keyColumnName] : undefined
828
- }
829
- formCloseCallBk={() => {
830
- setFormWindowState(false);
831
- props.apiActions.reloadData(props?.gridLoadParametersValues);
832
- }}
833
- />
834
- ) : (
835
- <TemplateForm
836
- keyColumnName={props.keyColumnName}
837
- attachment={props.attachment}
838
- formValuesChangeCallBk={props?.formProps?.formValuesChangeCallBk}
839
- recordIdToEdit={
840
- recordToEdit ? recordToEdit[keyColumnName] : undefined
841
- }
842
- formCloseCallBk={() => {
843
- setFormWindowState(false);
844
- props.apiActions.reloadData(props?.gridLoadParametersValues);
845
- }}
846
- elements={props.formElements}
847
- apiActions={props.apiActions}
848
- editAuthorityKey={props?.editAction?.authority}
849
- formSavedSuccessfullyCallBk={
850
- props?.editAction?.postActionCallBack
851
- }
852
- preSaveValidation={props?.editAction?.preActionValidation}
853
- actions={props?.rowActions}
854
- />
855
- )}
856
- </FormWindow>
857
- ) : (
858
- <></>
859
- )}
860
- {props?.hideInfoBar === undefined && !props?.hideInfoBar ? (
861
- <Box sx={{ display: "flex" }}>
862
- {props?.hideBackButton === undefined && !props?.hideBackButton ? (
863
- <IconButton
864
- onClick={() => {
865
- navigate(-1, { replace: true });
866
- }}
867
- >
868
- <FontAwesomeIcon
869
- icon={themeDirection === "ltr" ? "arrow-left" : "arrow-right"}
870
- />
871
- </IconButton>
872
- ) : (
873
- <></>
874
- )}
875
- <Box
876
- sx={{
877
- flex: 1,
878
- display: "flex",
879
- alignItems: "center",
880
- justifyContent: "center",
881
- }}
882
- >
883
- {props?.girdIcon ? (
884
- <FontAwesomeIcon
885
- icon={props.girdIcon}
886
- style={{ marginRight: 5, marginLeft: 5 }}
887
- />
888
- ) : (
889
- <></>
890
- )}
891
- <Typography variant="h5">
892
- {props?.gridTitle
893
- ? capitalizeFirstLetter(t(props?.gridTitle))
894
- : ""}
895
- </Typography>
896
- </Box>
897
- </Box>
898
- ) : (
899
- <></>
900
- )}
901
-
902
- {props?.gridLoadParameters &&
903
- props?.gridLoadParameters.length > 0 &&
904
- props?.gridLoadParametersValues &&
905
- props?.setGridLoadParametersValues ? (
906
- <Accordion defaultExpanded sx={{ width: "100%" }}>
907
- <AccordionSummary expandIcon={<GridExpandMoreIcon />}>
908
- <Box
909
- sx={{
910
- display: "flex",
911
- alignItems: "center",
912
- justifyContent: "center",
913
- }}
914
- >
915
- <FontAwesomeIcon
916
- style={{ marginLeft: 5, marginRight: 5 }}
917
- icon="search"
918
- />
919
- <Typography component="span">Filters</Typography>
920
- </Box>
921
- </AccordionSummary>
922
- <AccordionDetails>
923
- <Box>
924
- <TemplateForm
925
- saveButtonSpecs={{
926
- label: t("SEARCH_BTN_LABEL"),
927
- icon: "search",
928
- actionButtonVariant: "outlined",
929
- actionButtonColor: "success",
930
- }}
931
- cancelButtonSpecs={{
932
- label: t("RESET_BTN_LABEL"),
933
- icon: "eraser",
934
- actionButtonVariant: "outlined",
935
- actionButtonColor: "error",
936
- }}
937
- apiActions={{
938
- deleteRecordById: async () => {
939
- return true;
940
- },
941
- saveRecord: async (params) => {
942
- if (params != undefined) {
943
- props.setGridLoadParametersValues(params);
944
- } else {
945
- props.setGridLoadParametersValues({});
946
- }
947
- props.apiActions.reloadData(params);
948
- },
949
- reloadData: async () => {},
950
- loadRecordById: async () => {},
951
- }}
952
- elements={props.gridLoadParameters}
953
- />
954
- </Box>
955
- </AccordionDetails>
956
- </Accordion>
957
- ) : (
958
- <></>
959
- )}
960
- {props?.attachment ? (
961
- <AttachmentWindow>
962
- <AttachmentPanel
963
- attachmentCode={props.attachment.attachmentCode}
964
- refKey={selectedRecord[props?.keyColumnName || "id"]}
965
- enableAttachment={attachmentPanelEnabledForRecord}
966
- />
967
- </AttachmentWindow>
968
- ) : (
969
- <></>
970
- )}
971
- {props?.workFlowDocumentCode ? (
972
- <WorkFlowWindow>
973
- <WorkflowDocumentPanel
974
- workFlowDocumentCode={props.workFlowDocumentCode}
975
- refDocumentId={selectedRecord[props?.keyColumnName || "id"]}
976
- postActionCallBk={() => {
977
- setWorkFlowWindowState(false);
978
- props.apiActions.reloadData(props.gridLoadParametersValues);
979
- }}
980
- cancelActionCallBk={() => {
981
- setWorkFlowWindowState(false);
982
- props.apiActions.reloadData(props.gridLoadParametersValues);
983
- }}
984
- />
985
- </WorkFlowWindow>
986
- ) : (
987
- <></>
988
- )}
989
- <DataGridPremium
990
- {...props?.muiProps}
991
- slots={{ toolbar: TemplateGridTopBar }}
992
- slotProps={{
993
- toolbar: {
994
- templateProps: {
995
- ...props,
996
- rowSelectionModel: rowSelectionModel,
997
- data: props?.data,
998
- },
999
- handleCreateNewRecord,
1000
- clearGridState,
1001
- } as any,
1002
- }}
1003
- getRowId={(record: any) => {
1004
- return record[keyColumnName];
1005
- }}
1006
- showToolbar={true}
1007
- rows={props?.data}
1008
- columns={adjustedColumns}
1009
- checkboxSelection
1010
- editMode="row"
1011
- onRowEditStop={(params, event) => {
1012
- if (params.reason === "rowFocusOut") {
1013
- event.defaultMuiPrevented = true;
1014
- }
1015
- }}
1016
- rowModesModel={
1017
- props.editMode.editMode == "row" ? rowModesModel : undefined
1018
- }
1019
- onRowModesModelChange={
1020
- props.editMode.editMode == "row"
1021
- ? handleRowModesModelChange
1022
- : undefined
1023
- }
1024
- rowGroupingColumnMode="multiple"
1025
- processRowUpdate={processRowUpdate}
1026
- rowSelectionModel={rowSelectionModel}
1027
- onRowSelectionModelChange={handleRowSelection}
1028
- columnVisibilityModel={columnVisibilityModel}
1029
- onColumnVisibilityModelChange={handleColumnVisibilityChange}
1030
- onColumnOrderChange={handleColumnOrderChange}
1031
- // onColumnWidthChange={handleColumnWidthChange}
1032
- rowGroupingModel={gridRowGroupingModel}
1033
- onRowGroupingModelChange={(model: GridRowGroupingModel) => {
1034
- handleRowGroupChange(model);
1035
- }}
1036
- // pinnedColumns={pinnedColumns}
1037
- // onPinnedColumnsChange={handlePinnedColumnsChange}
1038
- sx={{ width: "100%" }}
1039
- />
1040
- </>
1041
- );
1042
- };
1043
-
1044
- export default TemplateGrid;
1
+ import {
2
+ DataGridPremium,
3
+ GridActionsCellItem,
4
+ GridActionsCellItemProps,
5
+ GridColumnOrderChangeParams,
6
+ GridColumnResizeParams,
7
+ GridColumnVisibilityModel,
8
+ GridEventListener,
9
+ GridExpandMoreIcon,
10
+ GridPinnedColumnFields,
11
+ GridRowGroupingModel,
12
+ GridRowModes,
13
+ GridRowModesModel,
14
+ GridRowParams,
15
+ GridRowSelectionModel,
16
+ } from "@mui/x-data-grid-premium";
17
+ import { TemplateGridColDef, TemplateGridProps } from "../DataEntryTypes";
18
+ import TemplateGridTopBar from "./TemplateGridTopBar";
19
+ import { useNavigate } from "react-router-dom";
20
+ import React, { useEffect, useState } from "react";
21
+ import { z } from "zod";
22
+ import {
23
+ constructGridColumnsFromFields,
24
+ constructValidationSchema,
25
+ getAllFields,
26
+ } from "../DataEntryUtil";
27
+ import { toast } from "react-toastify";
28
+ import {
29
+ Accordion,
30
+ AccordionDetails,
31
+ AccordionSummary,
32
+ Box,
33
+ Divider,
34
+ IconButton,
35
+ Tooltip,
36
+ Typography,
37
+ } from "@mui/material";
38
+ import { capitalizeFirstLetter, isNumber } from "../../../../util/AppUtils";
39
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
40
+ import TemplateGridRecordAction from "./TemplateGridRecordAction";
41
+ import TemplateForm from "../TemplateDataForm/TemplateForm";
42
+ import { useSelector } from "react-redux";
43
+ import { useTranslation } from "react-i18next";
44
+ import useSession from "../../../../hooks/UseSession";
45
+ import { useConfirmationWindow } from "../../../../hooks/UseConfirmationWindow";
46
+ import { useWindow } from "../../../../hooks/UseWindow";
47
+ import { useAxios } from "../../../../hooks";
48
+ import useLookupGridColumn from "../../../../hooks/useLookupGridColumn";
49
+ import { ContinuousColorLegend } from "@mui/x-charts";
50
+ import AttachmentPanel from "../../attachment/AttachmentPanel";
51
+ import WorkflowDocumentPanel from "../../workflow/WorkflowDocumentPanel";
52
+ let currentNewRecordIndex = -1;
53
+
54
+ interface GridState {
55
+ columnVisibilityModel?: GridColumnVisibilityModel;
56
+ columnOrder?: string[];
57
+ columnWidths?: { [key: string]: number };
58
+ columnGroupingModel?: GridRowGroupingModel;
59
+ pinnedColumns?: GridPinnedColumnFields;
60
+ }
61
+
62
+ const loadGridState = (gridStateKey: string): GridState => {
63
+ try {
64
+ const saved = localStorage.getItem(gridStateKey);
65
+ return saved ? JSON.parse(saved) : {};
66
+ } catch (e) {
67
+ console.error("Failed to load grid state", e);
68
+ return {};
69
+ }
70
+ };
71
+
72
+ const saveGridState = (gridStateKey: string, statePart: GridState) => {
73
+ const current = loadGridState(gridStateKey);
74
+ localStorage.setItem(
75
+ gridStateKey,
76
+ JSON.stringify({ ...current, ...statePart })
77
+ );
78
+ };
79
+
80
+ const PIN_FIXED_COLUMNS = ["__check__", "actions"];
81
+
82
+ const TemplateGrid: React.FC<TemplateGridProps> = (props) => {
83
+ const { t } = useTranslation();
84
+ const { handleGetRequest } = useAxios();
85
+ const stores = useSelector((state: any) => state.commonStores.stores);
86
+ const AppLayout = useSelector((state: any) => state.AppLayout);
87
+ const [selectedRecord, setSelectedRecord] = useState<any>({});
88
+ const [attachmentPanelEnabledForRecord, setAttachmentPanelEnabledForRecord] =
89
+ useState<boolean>(true);
90
+ const { getLookupOptions } = useLookupGridColumn();
91
+ const { Window: AttachmentWindow, setWindowState: setAttachmentWindowState } =
92
+ useWindow({
93
+ windowTitle: t(props.gridTitle) + " Attachments",
94
+ windowIcon: "paperclip",
95
+ width: "fit-content",
96
+ height: "fit-content",
97
+ minHeight: 500,
98
+ minWidth: "50%",
99
+ onCloseCallBack: () => {
100
+ props?.apiActions?.reloadData(props.gridLoadParametersValues);
101
+ },
102
+ });
103
+ const { Window: WorkFlowWindow, setWindowState: setWorkFlowWindowState } =
104
+ useWindow({
105
+ windowTitle: t(props.gridTitle) + " Approvals",
106
+ windowIcon: "stamp",
107
+ height: "fit-content",
108
+ minHeight: 500,
109
+ width: "fit-content",
110
+ // width:1100,
111
+ onCloseCallBack: () => {
112
+ props?.apiActions?.reloadData(props.gridLoadParametersValues);
113
+ },
114
+ });
115
+ const [generatedColumns, setGeneratedColumns] = useState<
116
+ Array<TemplateGridColDef>
117
+ >([]);
118
+ const fields = getAllFields(props.formElements);
119
+ const hiddenFields = [];
120
+ const savedState = React.useMemo<GridState>(
121
+ () => (props?.gridStateKey ? loadGridState(props.gridStateKey) : {}),
122
+ []
123
+ );
124
+ for (const field of fields) {
125
+ if (field?.gridProps?.hidden === true) {
126
+ hiddenFields.push(field.fieldName);
127
+ }
128
+ }
129
+ let initialVisibilityState: GridColumnVisibilityModel = {};
130
+ if (savedState?.columnVisibilityModel) {
131
+ initialVisibilityState = savedState.columnVisibilityModel;
132
+ const existingFields = Object.keys(initialVisibilityState);
133
+ for (const field of hiddenFields) {
134
+ if (!existingFields.includes(field)) {
135
+ initialVisibilityState[field] = false;
136
+ }
137
+ }
138
+ } else {
139
+ for (const field of hiddenFields) {
140
+ initialVisibilityState[field] = false;
141
+ }
142
+ }
143
+
144
+ const themeDirection = useSelector(
145
+ (state: any) => state.AppLayout.appDirection
146
+ );
147
+
148
+ const clearGridState = () => {
149
+ if (props?.gridStateKey) {
150
+ localStorage.removeItem(props.gridStateKey);
151
+ }
152
+ setColumnVisibilityModel({});
153
+ setColumnOrder([]);
154
+ setColumnWidths({});
155
+ setGridRowGroupingModel([]);
156
+ setPinnedColumns({
157
+ left: [...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : [])],
158
+ right: [...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : [])],
159
+ });
160
+ };
161
+ const [columnVisibilityModel, setColumnVisibilityModel] =
162
+ React.useState<GridColumnVisibilityModel>(initialVisibilityState);
163
+ const [columnOrder, setColumnOrder] = React.useState<string[]>(
164
+ savedState.columnOrder || []
165
+ );
166
+ const [columnWidths, setColumnWidths] = React.useState<{
167
+ [key: string]: number;
168
+ }>(savedState.columnWidths || {});
169
+ const [gridRowGroupingModel, setGridRowGroupingModel] =
170
+ useState<GridRowGroupingModel>(savedState.columnGroupingModel || []);
171
+ let newLeft = savedState?.pinnedColumns?.left || [];
172
+ newLeft = newLeft.filter(
173
+ (record: any) => !PIN_FIXED_COLUMNS.includes(record)
174
+ );
175
+ let newRight = savedState?.pinnedColumns?.right || [] || [];
176
+ newRight = newRight.filter(
177
+ (record: any) => !PIN_FIXED_COLUMNS.includes(record)
178
+ );
179
+
180
+ const [pinnedColumns, setPinnedColumns] = useState<GridPinnedColumnFields>({
181
+ left: [...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : []), ...newLeft],
182
+ right: [
183
+ ...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : []),
184
+ ...newRight,
185
+ ],
186
+ });
187
+
188
+ const session = useSession();
189
+ const navigate = useNavigate();
190
+ const [recordToDelete, setRecordToDelete] = useState<any>(null);
191
+ const [recordToEdit, setRecordToEdit] = useState<any>(null);
192
+ const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
193
+ const loadComboboxData: (
194
+ storeUrl: any,
195
+ storeLoadParam: any,
196
+ dataQueryId: any
197
+ ) => Promise<Array<any>> = async (storeUrl, storeLoadParam, dataQueryId) => {
198
+ let comboboxData: Array<any> = [];
199
+ if (storeUrl) {
200
+ await handleGetRequest({
201
+ endPointURI: storeUrl,
202
+ showMask: false,
203
+ parameters: storeLoadParam,
204
+ successCallBkFn: (response) => {
205
+ comboboxData = response.data;
206
+ },
207
+ });
208
+ } else if (dataQueryId) {
209
+ await handleGetRequest({
210
+ endPointURI: "api/v1/dev/query/result",
211
+ showMask: false,
212
+ parameters: { queryId: dataQueryId, ...storeLoadParam },
213
+ successCallBkFn: (response) => {
214
+ comboboxData = response.data;
215
+ },
216
+ });
217
+ }
218
+ return comboboxData;
219
+ };
220
+ const adjustGridColumns = async () => {
221
+ let gridColumns = constructGridColumnsFromFields(
222
+ fields,
223
+ props?.editMode?.editMode === "row" || false,
224
+ t
225
+ );
226
+ for (let gridColumn of gridColumns) {
227
+ if (gridColumn?.lookupType) {
228
+ gridColumn.displayField =
229
+ AppLayout.appDirection === "ltr"
230
+ ? "lookupEnDisplay"
231
+ : "lookupArDisplay";
232
+ gridColumn.options = await getLookupOptions(gridColumn.lookupType);
233
+ gridColumn.valueField = "lookupValue";
234
+ }
235
+ if (gridColumn.type === "custom") {
236
+ if (gridColumn?.options) {
237
+ continue;
238
+ } else if (gridColumn?.commonStoreKey) {
239
+ gridColumn.options = stores[gridColumn.commonStoreKey]?.data || [];
240
+ } else {
241
+ gridColumn.options = await loadComboboxData(
242
+ gridColumn?.storeUrl,
243
+ gridColumn?.storeLoadParam,
244
+ gridColumn?.dataQueryId
245
+ );
246
+ }
247
+ }
248
+ }
249
+ setGeneratedColumns(gridColumns);
250
+ };
251
+ const [rowSelectionModel, setRowSelectionModel] =
252
+ useState<GridRowSelectionModel>({ ids: new Set(), type: "include" });
253
+ let keyColumnName: string = "id";
254
+ if (props?.keyColumnName) {
255
+ keyColumnName = props?.keyColumnName;
256
+ }
257
+ const setData = props.setData;
258
+ let validationSchema = null;
259
+ if (props?.validationSchema) {
260
+ validationSchema = props.validationSchema;
261
+ } else {
262
+ validationSchema = z.object(constructValidationSchema(fields));
263
+ }
264
+
265
+ let isEditAllowed = true;
266
+ if (props?.editAction?.authority) {
267
+ isEditAllowed = session.isUserAuthorized(props.editAction.authority);
268
+ }
269
+ let isDeleteAllowed = true;
270
+ if (props?.deleteAction?.authority) {
271
+ isDeleteAllowed = session.isUserAuthorized(props.deleteAction.authority);
272
+ }
273
+
274
+ const handleRowModesModelChange = (newModel: GridRowModesModel) => {
275
+ setRowModesModel(newModel);
276
+ };
277
+
278
+ const handlePinnedColumnsChange = (newModel: GridPinnedColumnFields) => {
279
+ let newLeft = newModel?.left || [];
280
+ newLeft = newLeft.filter(
281
+ (record: any) => !PIN_FIXED_COLUMNS.includes(record)
282
+ );
283
+ let newRight = newModel?.right || [];
284
+ newRight = newRight.filter(
285
+ (record: any) => !PIN_FIXED_COLUMNS.includes(record)
286
+ );
287
+ const newPinedColumns: GridPinnedColumnFields = {
288
+ left: [
289
+ ...(themeDirection === "ltr" ? PIN_FIXED_COLUMNS : []),
290
+ ...newLeft,
291
+ ],
292
+ right: [
293
+ ...(themeDirection === "rtl" ? PIN_FIXED_COLUMNS : []),
294
+ ...newRight,
295
+ ],
296
+ };
297
+ setPinnedColumns(newPinedColumns);
298
+ if (props?.gridStateKey) {
299
+ saveGridState(props.gridStateKey, { pinnedColumns: newPinedColumns });
300
+ }
301
+ };
302
+
303
+ const handleDeleteRecord = async () => {
304
+ let result = true;
305
+ if (!(recordToDelete && recordToDelete?.isNew === true)) {
306
+ result = await props.apiActions.deleteRecordById(
307
+ recordToDelete[keyColumnName]
308
+ );
309
+ }
310
+ if (result) {
311
+ if (props?.deleteAction?.postActionCallBack) {
312
+ await props.deleteAction.postActionCallBack(recordToDelete);
313
+ }
314
+
315
+ setData((oldData: any) => {
316
+ const newData = oldData.filter(
317
+ (record: any) =>
318
+ record[keyColumnName] != recordToDelete[keyColumnName]
319
+ );
320
+ return newData;
321
+ });
322
+ }
323
+ };
324
+
325
+ const { ConfirmationWindow, setOpen: setConfirmationWindowOpened } =
326
+ useConfirmationWindow({
327
+ title: "Confirmation",
328
+ body: "Are you sure you want to delete this record ?",
329
+ onConfirmationCallBk: handleDeleteRecord,
330
+ });
331
+
332
+ let formModalHeight = undefined;
333
+ let formModalWidth = undefined;
334
+ let formModalMinHeight = undefined;
335
+ let formModalMinWidth = undefined;
336
+ let formModalIcon = undefined;
337
+ let formModalTitle = undefined;
338
+
339
+ if (props?.editMode?.editMode === "modal") {
340
+ formModalHeight = props?.editMode?.specs?.modalHeight || "fit-content";
341
+ formModalWidth = props?.editMode?.specs?.modalWidth || "300";
342
+ formModalMinHeight = props?.editMode?.specs?.modalMinHeight;
343
+ formModalMinWidth = props?.editMode?.specs?.modalMinWidth;
344
+ formModalIcon = props?.editMode?.specs?.modalIcon || "window";
345
+ formModalTitle = props?.editMode?.specs?.modalTitle || "Record Form";
346
+ }
347
+
348
+ const { Window: FormWindow, setWindowState: setFormWindowState } = useWindow({
349
+ height: formModalHeight,
350
+ minHeight: formModalMinHeight,
351
+ minWidth: formModalMinWidth,
352
+ onCloseCallBack: () => {
353
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
354
+ },
355
+ width: formModalWidth,
356
+ windowIcon: formModalIcon,
357
+ windowTitle: formModalTitle,
358
+ });
359
+
360
+ const handleCreateNewRecord = () => {
361
+ if (props?.editMode?.editMode === "form") {
362
+ navigate(props.editMode.specs.formRoute);
363
+ } else if (props?.editMode?.editMode === "modal") {
364
+ setRecordToEdit(null);
365
+ setFormWindowState(true);
366
+ } else if (props?.editMode?.editMode === "row") {
367
+ currentNewRecordIndex = currentNewRecordIndex - 1;
368
+ const newRecord: any = {};
369
+ newRecord[keyColumnName] = currentNewRecordIndex;
370
+ newRecord.isNew = true;
371
+ for (const gridColumn of generatedColumns) {
372
+ if (
373
+ gridColumn?.type != "actions" &&
374
+ gridColumn?.field != keyColumnName
375
+ ) {
376
+ if (gridColumn?.field) {
377
+ newRecord[gridColumn.field] = null;
378
+ }
379
+ }
380
+ }
381
+ setData((oldRows: any) => [newRecord, ...oldRows]);
382
+ setRowModesModel((oldModel: any) => ({
383
+ ...oldModel,
384
+ [currentNewRecordIndex]: { mode: GridRowModes.Edit },
385
+ }));
386
+ }
387
+ };
388
+
389
+ const handleEditRecord = async (record: any) => {
390
+ if (record) {
391
+ setRecordToEdit(record);
392
+ if (props.editMode.editMode === "form") {
393
+ navigate(props.editMode.specs.formRoute + "/" + record[keyColumnName]);
394
+ } else if (props.editMode.editMode === "modal") {
395
+ setFormWindowState(true);
396
+ } else if (props?.editMode?.editMode === "row") {
397
+ const id = record[keyColumnName];
398
+ setRowModesModel({
399
+ ...rowModesModel,
400
+ [id]: { mode: GridRowModes.Edit },
401
+ });
402
+ }
403
+ }
404
+ };
405
+
406
+ const validateRecord = (record: any) => {
407
+ try {
408
+ validationSchema.parse(record);
409
+ } catch (err) {
410
+ console.log("validateRecord err", err);
411
+ let errorMessage: any = null;
412
+ if (err instanceof z.ZodError) {
413
+ errorMessage = err.errors
414
+ .map(
415
+ (error) => "Error in field (" + error.path + ") : " + error.message
416
+ )
417
+ .join(",");
418
+ } else {
419
+ errorMessage = "invalid record data";
420
+ }
421
+ return errorMessage;
422
+ }
423
+ };
424
+
425
+ const handleSaveRowClick = (record: any) => {
426
+ const id = record[keyColumnName];
427
+ setRowModesModel({
428
+ ...rowModesModel,
429
+ [id]: { mode: GridRowModes.View },
430
+ });
431
+ };
432
+
433
+ const processRowUpdate = async (record: any) => {
434
+ if (props.editMode.editMode === "row") {
435
+ let savedRecord: any = null;
436
+ const errorMessage = validateRecord(record);
437
+ if (errorMessage) {
438
+ const errors = errorMessage.split(",");
439
+ toast.error(
440
+ <div style={{}}>
441
+ {errors.map((error: any) => (
442
+ <>
443
+ <div>{error}</div>
444
+ <Divider />
445
+ </>
446
+ ))}
447
+ </div>
448
+ );
449
+ throw new Error(errorMessage);
450
+ }
451
+ if (props?.editAction?.preActionValidation) {
452
+ if (!props.editAction.preActionValidation(record)) {
453
+ throw new Error("error on the configured presave validation");
454
+ }
455
+ }
456
+ const requestObject: any = { ...record };
457
+ if (
458
+ record[keyColumnName] &&
459
+ isNumber(record[keyColumnName]) &&
460
+ Number(record[keyColumnName]) < 0
461
+ ) {
462
+ requestObject[keyColumnName] = null;
463
+ }
464
+ savedRecord = await props.apiActions.saveRecord(requestObject);
465
+ if (savedRecord == null) {
466
+ throw new Error(
467
+ "Failed to process your request, contact your administrator"
468
+ );
469
+ }
470
+ if (props?.editAction?.postActionCallBack) {
471
+ await props.editAction.postActionCallBack(record);
472
+ }
473
+ if (record?.isNew === true) {
474
+ setData((oldData: any) => {
475
+ const newData = oldData.filter(
476
+ (x: any) => x[keyColumnName] !== record[keyColumnName]
477
+ );
478
+ return [savedRecord, ...newData];
479
+ });
480
+ }
481
+ if (
482
+ props?.editMode?.reloadAfterSave === true &&
483
+ props?.apiActions?.reloadData
484
+ ) {
485
+ props?.apiActions?.reloadData(props?.gridLoadParametersValues);
486
+ }
487
+ return savedRecord;
488
+ }
489
+ };
490
+
491
+ const handleCancelRowEditClick = (record: any) => {
492
+ const id = record[keyColumnName];
493
+ if (id && isNumber(id) && id < 0) {
494
+ setRowModesModel({
495
+ ...rowModesModel,
496
+ [id]: { mode: GridRowModes.View, ignoreModifications: true },
497
+ });
498
+ setData((oldData: any) => {
499
+ const newData = oldData.filter(
500
+ (record: any) => record[keyColumnName] != id
501
+ );
502
+ return newData;
503
+ });
504
+ } else {
505
+ setRowModesModel({
506
+ ...rowModesModel,
507
+ [id]: { mode: GridRowModes.View, ignoreModifications: true },
508
+ });
509
+ }
510
+ };
511
+
512
+ const getActionColumnActions = (params: GridRowParams<any>) => {
513
+ const record: any = params.row;
514
+ const actions: Array<React.ReactElement<GridActionsCellItemProps>> = [];
515
+ if (props?.editMode?.editMode != "none") {
516
+ if (
517
+ props?.disableDefaultAction === undefined ||
518
+ !props.disableDefaultAction
519
+ ) {
520
+ const isInEditMode: boolean =
521
+ rowModesModel[record[keyColumnName]]?.mode === GridRowModes.Edit;
522
+ if (props.editMode.editMode === "row" && isInEditMode) {
523
+ if (isEditAllowed) {
524
+ actions.push(
525
+ <GridActionsCellItem
526
+ icon={<FontAwesomeIcon icon="save" />}
527
+ label={t("SAVE_BTN_LABEL")}
528
+ onClick={() => {
529
+ handleSaveRowClick(record);
530
+ }}
531
+ />
532
+ );
533
+ actions.push(
534
+ <GridActionsCellItem
535
+ icon={<FontAwesomeIcon icon="cancel" />}
536
+ label={t("CANCEL_BTN_LABEL")}
537
+ onClick={() => {
538
+ handleCancelRowEditClick(record);
539
+ }}
540
+ color="inherit"
541
+ />
542
+ );
543
+ }
544
+ } else {
545
+ if (props?.editAction && props?.editAction?.isEnabled === true) {
546
+ let isEditActionVisibleForRecord = true;
547
+ if (props?.editAction?.isActionVisibleForRecord) {
548
+ isEditActionVisibleForRecord =
549
+ props?.editAction?.isActionVisibleForRecord(record);
550
+ }
551
+ let isActionEditDisabledForRecord = false;
552
+ if (props?.editAction?.isActionDisabledForRecord) {
553
+ isActionEditDisabledForRecord =
554
+ props?.editAction?.isActionDisabledForRecord(record);
555
+ }
556
+ if (isEditAllowed && isEditActionVisibleForRecord) {
557
+ actions.push(
558
+ <GridActionsCellItem
559
+ disabled={isActionEditDisabledForRecord}
560
+ icon={
561
+ <Tooltip title={t("EDIT_BTN_LABEL")}>
562
+ <FontAwesomeIcon
563
+ icon={"edit"}
564
+ style={{
565
+ color: isActionEditDisabledForRecord
566
+ ? "gray"
567
+ : undefined,
568
+ }}
569
+ />
570
+ </Tooltip>
571
+ }
572
+ showInMenu={
573
+ props?.editAction?.gridActionProps?.showInMenu || false
574
+ }
575
+ label={t("EDIT_BTN_LABEL")}
576
+ className="textPrimary"
577
+ color="inherit"
578
+ onClick={() => {
579
+ if (isEditAllowed && !isActionEditDisabledForRecord) {
580
+ handleEditRecord(record);
581
+ }
582
+ }}
583
+ />
584
+ );
585
+ }
586
+ }
587
+ if (props?.deleteAction && props?.deleteAction?.isEnabled === true) {
588
+ let isDeleteActionVisibleForRecord = true;
589
+ if (props?.deleteAction?.isActionVisibleForRecord) {
590
+ isDeleteActionVisibleForRecord =
591
+ props?.deleteAction?.isActionVisibleForRecord(record);
592
+ }
593
+ let isDeleteActionDisabledForRecord = false;
594
+ if (props?.deleteAction?.isActionDisabledForRecord) {
595
+ isDeleteActionDisabledForRecord =
596
+ props?.deleteAction?.isActionDisabledForRecord(record);
597
+ }
598
+ if (isDeleteAllowed && isDeleteActionVisibleForRecord) {
599
+ actions.push(
600
+ <GridActionsCellItem
601
+ disabled={isDeleteActionDisabledForRecord}
602
+ icon={
603
+ <Tooltip title={t("DELETE_BTN_LABEL")}>
604
+ <FontAwesomeIcon
605
+ icon={"trash"}
606
+ style={{
607
+ color: isDeleteActionDisabledForRecord
608
+ ? "gray"
609
+ : undefined,
610
+ }}
611
+ />
612
+ </Tooltip>
613
+ }
614
+ showInMenu={
615
+ props?.deleteAction?.gridActionProps?.showInMenu || false
616
+ }
617
+ label={t("DELETE_BTN_LABEL")}
618
+ className="textPrimary"
619
+ color="inherit"
620
+ onClick={() => {
621
+ if (isDeleteAllowed && !isDeleteActionDisabledForRecord) {
622
+ if (props?.deleteAction?.preActionValidation) {
623
+ if (!props.deleteAction.preActionValidation(record)) {
624
+ return;
625
+ }
626
+ }
627
+ setRecordToDelete(record);
628
+ setConfirmationWindowOpened(true);
629
+ }
630
+ }}
631
+ />
632
+ );
633
+ }
634
+ }
635
+ }
636
+ }
637
+ }
638
+ if (
639
+ (record[props?.keyColumnName] || (record["id"] && record["id"] > 0)) &&
640
+ props?.attachment
641
+ ) {
642
+ actions?.push(
643
+ <GridActionsCellItem
644
+ icon={
645
+ <Tooltip title={"Attachments"}>
646
+ <FontAwesomeIcon icon={"paperclip"} />
647
+ </Tooltip>
648
+ }
649
+ label={"Attachments"}
650
+ className="textPrimary"
651
+ color="inherit"
652
+ onClick={() => {
653
+ setSelectedRecord(record);
654
+ if (props?.attachment?.enableAttachFn) {
655
+ setAttachmentPanelEnabledForRecord(
656
+ props.attachment.enableAttachFn(record)
657
+ );
658
+ } else {
659
+ setAttachmentPanelEnabledForRecord(true);
660
+ }
661
+ setAttachmentWindowState(true);
662
+ }}
663
+ />
664
+ );
665
+ }
666
+ if (
667
+ (record[props?.keyColumnName] || (record["id"] && record["id"] > 0)) &&
668
+ props?.workFlowDocumentCode
669
+ ) {
670
+ actions?.push(
671
+ <GridActionsCellItem
672
+ icon={
673
+ <Tooltip title={"Approvals"}>
674
+ <FontAwesomeIcon icon={"stamp"} />
675
+ </Tooltip>
676
+ }
677
+ label={"Approvals"}
678
+ className="textPrimary"
679
+ color="inherit"
680
+ onClick={() => {
681
+ setSelectedRecord(record);
682
+ setWorkFlowWindowState(true);
683
+ }}
684
+ />
685
+ );
686
+ }
687
+ if (
688
+ record[props?.keyColumnName || "id"] &&
689
+ record[props?.keyColumnName || "id"] > 0 &&
690
+ props?.rowActions
691
+ ) {
692
+ for (const rowAction of props.rowActions) {
693
+ if (
694
+ !(
695
+ rowAction?.gridActionProps?.multiRecord &&
696
+ rowAction?.gridActionProps?.multiRecord === true
697
+ )
698
+ ) {
699
+ actions.push(
700
+ <TemplateGridRecordAction
701
+ {...rowAction}
702
+ record={record}
703
+ reloadData={async () => {
704
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
705
+ }}
706
+ />
707
+ );
708
+ }
709
+ }
710
+ }
711
+
712
+ return actions;
713
+ };
714
+
715
+ const actionColumn: TemplateGridColDef = {
716
+ type: "actions",
717
+ field: "actions",
718
+ headerName: "",
719
+ headerAlign: "center",
720
+ width:
721
+ (props?.rowActions ? props.rowActions.length * 30 : 0) +
722
+ (props?.editAction && props?.editAction?.isEnabled ? 30 : 0) +
723
+ (props?.deleteAction && props?.deleteAction?.isEnabled ? 30 : 0) +
724
+ (props?.attachment ? 30 : 0) +
725
+ (props?.workFlowDocumentCode ? 30 : 0),
726
+ getActions: getActionColumnActions,
727
+ };
728
+
729
+ let structuredColumns: Array<TemplateGridColDef> = [];
730
+ if (
731
+ props?.disableDefaultAction === undefined ||
732
+ !props.disableDefaultAction ||
733
+ (props?.rowActions && props?.rowActions.length > 0)
734
+ ) {
735
+ structuredColumns.push(actionColumn);
736
+ }
737
+
738
+ structuredColumns = [...structuredColumns, ...generatedColumns];
739
+ const handleRowSelection = (gridSelectionModel: GridRowSelectionModel) => {
740
+ setRowSelectionModel(gridSelectionModel);
741
+ };
742
+ useEffect(() => {
743
+ if (props?.autoLoad === undefined || props.autoLoad === true) {
744
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
745
+ }
746
+ adjustGridColumns();
747
+ }, []);
748
+
749
+ useEffect(() => {
750
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
751
+ }, [session.UserInfo?.currentOrganization]);
752
+
753
+ useEffect(() => {
754
+ adjustGridColumns();
755
+ }, [themeDirection, props.formElements]);
756
+
757
+ const handleColumnVisibilityChange = (model: GridColumnVisibilityModel) => {
758
+ setColumnVisibilityModel(model);
759
+ if (props?.gridStateKey) {
760
+ saveGridState(props.gridStateKey, { columnVisibilityModel: model });
761
+ }
762
+ };
763
+
764
+ const handleColumnOrderChange: GridEventListener<"columnOrderChange"> = (
765
+ params: GridColumnOrderChangeParams
766
+ ) => {
767
+ const { column, targetIndex } = params;
768
+ setColumnOrder((prevOrder) => {
769
+ const currentOrder = prevOrder.length
770
+ ? [...prevOrder]
771
+ : structuredColumns.map((col) => col.field);
772
+ const fromIndex = currentOrder.indexOf(column.field);
773
+ if (fromIndex === -1) return currentOrder;
774
+
775
+ currentOrder.splice(fromIndex, 1); // remove
776
+ currentOrder.splice(targetIndex, 0, column.field); // insert at new index
777
+
778
+ if (props?.gridStateKey) {
779
+ saveGridState(props.gridStateKey, { columnOrder: currentOrder });
780
+ }
781
+
782
+ return currentOrder;
783
+ });
784
+ };
785
+
786
+ const handleRowGroupChange = (model: GridRowGroupingModel) => {
787
+ setGridRowGroupingModel(model);
788
+ if (props?.gridStateKey) {
789
+ saveGridState(props.gridStateKey, { columnGroupingModel: model });
790
+ }
791
+ };
792
+
793
+ // const handleColumnWidthChange: GridEventListener<"columnWidthChange"> = (
794
+ // params: GridColumnResizeParams
795
+ // ) => {
796
+ // const updatedWidths = {
797
+ // ...columnWidths,
798
+ // [params.colDef.field]: params.width,
799
+ // };
800
+ // setColumnWidths(updatedWidths);
801
+ // if (props?.gridStateKey) {
802
+ // saveGridState(props.gridStateKey, { columnWidths: updatedWidths });
803
+ // }
804
+ // };
805
+
806
+ const adjustedColumns: Array<TemplateGridColDef> = React.useMemo(() => {
807
+ const baseCols = structuredColumns.map((col) => ({
808
+ ...col,
809
+ }));
810
+
811
+ // Reorder based on saved columnOrder
812
+ if (columnOrder.length) {
813
+ const fieldToCol = new Map(baseCols.map((col) => [col.field, col]));
814
+ return columnOrder.map((field) => fieldToCol.get(field)!).filter(Boolean);
815
+ }
816
+ return baseCols;
817
+ }, [columnOrder, columnWidths, structuredColumns]);
818
+
819
+ return (
820
+ <>
821
+ <ConfirmationWindow />
822
+ {props?.editMode?.editMode === "modal" ? (
823
+ <FormWindow>
824
+ {props?.editMode?.specs?.formComponent ? (
825
+ <props.editMode.specs.formComponent
826
+ recordIdToEdit={
827
+ recordToEdit ? recordToEdit[keyColumnName] : undefined
828
+ }
829
+ formCloseCallBk={() => {
830
+ setFormWindowState(false);
831
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
832
+ }}
833
+ />
834
+ ) : (
835
+ <TemplateForm
836
+ keyColumnName={props.keyColumnName}
837
+ attachment={props.attachment}
838
+ formValuesChangeCallBk={props?.formProps?.formValuesChangeCallBk}
839
+ recordIdToEdit={
840
+ recordToEdit ? recordToEdit[keyColumnName] : undefined
841
+ }
842
+ formCloseCallBk={() => {
843
+ setFormWindowState(false);
844
+ props.apiActions.reloadData(props?.gridLoadParametersValues);
845
+ }}
846
+ elements={props.formElements}
847
+ apiActions={props.apiActions}
848
+ editAuthorityKey={props?.editAction?.authority}
849
+ formSavedSuccessfullyCallBk={
850
+ props?.editAction?.postActionCallBack
851
+ }
852
+ preSaveValidation={props?.editAction?.preActionValidation}
853
+ actions={props?.rowActions}
854
+ />
855
+ )}
856
+ </FormWindow>
857
+ ) : (
858
+ <></>
859
+ )}
860
+ {props?.hideInfoBar === undefined && !props?.hideInfoBar ? (
861
+ <Box sx={{ display: "flex" }}>
862
+ {props?.hideBackButton === undefined && !props?.hideBackButton ? (
863
+ <IconButton
864
+ onClick={() => {
865
+ navigate(-1, { replace: true });
866
+ }}
867
+ >
868
+ <FontAwesomeIcon
869
+ icon={themeDirection === "ltr" ? "arrow-left" : "arrow-right"}
870
+ />
871
+ </IconButton>
872
+ ) : (
873
+ <></>
874
+ )}
875
+ <Box
876
+ sx={{
877
+ flex: 1,
878
+ display: "flex",
879
+ alignItems: "center",
880
+ justifyContent: "center",
881
+ }}
882
+ >
883
+ {props?.girdIcon ? (
884
+ <FontAwesomeIcon
885
+ icon={props.girdIcon}
886
+ style={{ marginRight: 5, marginLeft: 5 }}
887
+ />
888
+ ) : (
889
+ <></>
890
+ )}
891
+ <Typography variant="h5">
892
+ {props?.gridTitle
893
+ ? capitalizeFirstLetter(t(props?.gridTitle))
894
+ : ""}
895
+ </Typography>
896
+ </Box>
897
+ </Box>
898
+ ) : (
899
+ <></>
900
+ )}
901
+
902
+ {props?.gridLoadParameters &&
903
+ props?.gridLoadParameters.length > 0 &&
904
+ props?.gridLoadParametersValues &&
905
+ props?.setGridLoadParametersValues ? (
906
+ <Accordion defaultExpanded sx={{ width: "100%" }}>
907
+ <AccordionSummary expandIcon={<GridExpandMoreIcon />}>
908
+ <Box
909
+ sx={{
910
+ display: "flex",
911
+ alignItems: "center",
912
+ justifyContent: "center",
913
+ }}
914
+ >
915
+ <FontAwesomeIcon
916
+ style={{ marginLeft: 5, marginRight: 5 }}
917
+ icon="search"
918
+ />
919
+ <Typography component="span">Filters</Typography>
920
+ </Box>
921
+ </AccordionSummary>
922
+ <AccordionDetails>
923
+ <Box>
924
+ <TemplateForm
925
+ saveButtonSpecs={{
926
+ label: t("SEARCH_BTN_LABEL"),
927
+ icon: "search",
928
+ actionButtonVariant: "outlined",
929
+ actionButtonColor: "success",
930
+ }}
931
+ cancelButtonSpecs={{
932
+ label: t("RESET_BTN_LABEL"),
933
+ icon: "eraser",
934
+ actionButtonVariant: "outlined",
935
+ actionButtonColor: "error",
936
+ }}
937
+ apiActions={{
938
+ deleteRecordById: async () => {
939
+ return true;
940
+ },
941
+ saveRecord: async (params) => {
942
+ if (params != undefined) {
943
+ props.setGridLoadParametersValues(params);
944
+ } else {
945
+ props.setGridLoadParametersValues({});
946
+ }
947
+ props.apiActions.reloadData(params);
948
+ },
949
+ reloadData: async () => {},
950
+ loadRecordById: async () => {},
951
+ }}
952
+ elements={props.gridLoadParameters}
953
+ />
954
+ </Box>
955
+ </AccordionDetails>
956
+ </Accordion>
957
+ ) : (
958
+ <></>
959
+ )}
960
+ {props?.attachment ? (
961
+ <AttachmentWindow>
962
+ <AttachmentPanel
963
+ attachmentCode={props.attachment.attachmentCode}
964
+ refKey={selectedRecord[props?.keyColumnName || "id"]}
965
+ enableAttachment={attachmentPanelEnabledForRecord}
966
+ />
967
+ </AttachmentWindow>
968
+ ) : (
969
+ <></>
970
+ )}
971
+ {props?.workFlowDocumentCode ? (
972
+ <WorkFlowWindow>
973
+ <WorkflowDocumentPanel
974
+ workFlowDocumentCode={props.workFlowDocumentCode}
975
+ refDocumentId={selectedRecord[props?.keyColumnName || "id"]}
976
+ postActionCallBk={() => {
977
+ setWorkFlowWindowState(false);
978
+ props.apiActions.reloadData(props.gridLoadParametersValues);
979
+ }}
980
+ cancelActionCallBk={() => {
981
+ setWorkFlowWindowState(false);
982
+ props.apiActions.reloadData(props.gridLoadParametersValues);
983
+ }}
984
+ />
985
+ </WorkFlowWindow>
986
+ ) : (
987
+ <></>
988
+ )}
989
+ <DataGridPremium
990
+ {...props?.muiProps}
991
+ slots={{ toolbar: TemplateGridTopBar }}
992
+ slotProps={{
993
+ toolbar: {
994
+ templateProps: {
995
+ ...props,
996
+ rowSelectionModel: rowSelectionModel,
997
+ data: props?.data,
998
+ },
999
+ handleCreateNewRecord,
1000
+ clearGridState,
1001
+ } as any,
1002
+ }}
1003
+ getRowId={(record: any) => {
1004
+ return record[keyColumnName];
1005
+ }}
1006
+ showToolbar={true}
1007
+ rows={props?.data}
1008
+ columns={adjustedColumns}
1009
+ checkboxSelection
1010
+ editMode="row"
1011
+ onRowEditStop={(params, event) => {
1012
+ if (params.reason === "rowFocusOut") {
1013
+ event.defaultMuiPrevented = true;
1014
+ }
1015
+ }}
1016
+ rowModesModel={
1017
+ props.editMode.editMode == "row" ? rowModesModel : undefined
1018
+ }
1019
+ onRowModesModelChange={
1020
+ props.editMode.editMode == "row"
1021
+ ? handleRowModesModelChange
1022
+ : undefined
1023
+ }
1024
+ rowGroupingColumnMode="multiple"
1025
+ processRowUpdate={processRowUpdate}
1026
+ rowSelectionModel={rowSelectionModel}
1027
+ onRowSelectionModelChange={handleRowSelection}
1028
+ columnVisibilityModel={columnVisibilityModel}
1029
+ onColumnVisibilityModelChange={handleColumnVisibilityChange}
1030
+ onColumnOrderChange={handleColumnOrderChange}
1031
+ // onColumnWidthChange={handleColumnWidthChange}
1032
+ rowGroupingModel={gridRowGroupingModel}
1033
+ onRowGroupingModelChange={(model: GridRowGroupingModel) => {
1034
+ handleRowGroupChange(model);
1035
+ }}
1036
+ // pinnedColumns={pinnedColumns}
1037
+ // onPinnedColumnsChange={handlePinnedColumnsChange}
1038
+ sx={{ width: "100%" }}
1039
+ />
1040
+ </>
1041
+ );
1042
+ };
1043
+
1044
+ export default TemplateGrid;