@asaleh37/ui-base 25.8.1-4 → 25.8.1-7

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