@asaleh37/ui-base 1.0.9 → 1.1.0

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