@solidxai/core-ui 0.1.3 → 0.1.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/auth/SolidInitialLoginOtp.d.ts.map +1 -1
- package/dist/components/auth/SolidInitialLoginOtp.js +0 -5
- package/dist/components/auth/SolidInitialLoginOtp.js.map +1 -1
- package/dist/components/auth/SolidInitialLoginOtp.tsx +0 -5
- package/dist/components/auth/SolidLogin.d.ts.map +1 -1
- package/dist/components/auth/SolidLogin.js +7 -5
- package/dist/components/auth/SolidLogin.js.map +1 -1
- package/dist/components/auth/SolidLogin.tsx +10 -8
- package/dist/components/common/GeneralSettings.d.ts.map +1 -1
- package/dist/components/common/GeneralSettings.js +48 -47
- package/dist/components/common/GeneralSettings.js.map +1 -1
- package/dist/components/common/GeneralSettings.tsx +41 -10
- package/dist/components/core/common/FilterComponent.js.map +1 -1
- package/dist/components/core/common/FilterComponent.tsx +1 -1
- package/dist/components/core/common/GroupingComponent.d.ts +54 -0
- package/dist/components/core/common/GroupingComponent.d.ts.map +1 -0
- package/dist/components/core/common/GroupingComponent.js +196 -0
- package/dist/components/core/common/GroupingComponent.js.map +1 -0
- package/dist/components/core/common/GroupingComponent.tsx +452 -0
- package/dist/components/core/common/SolidGlobalSearchElement.d.ts +18 -1
- package/dist/components/core/common/SolidGlobalSearchElement.d.ts.map +1 -1
- package/dist/components/core/common/SolidGlobalSearchElement.js +152 -52
- package/dist/components/core/common/SolidGlobalSearchElement.js.map +1 -1
- package/dist/components/core/common/SolidGlobalSearchElement.tsx +212 -35
- package/dist/components/core/extension/solid-core/modelSequence/modelSequenceFormViewChangeHandler.d.ts +19 -0
- package/dist/components/core/extension/solid-core/modelSequence/modelSequenceFormViewChangeHandler.d.ts.map +1 -0
- package/dist/components/core/extension/solid-core/modelSequence/modelSequenceFormViewChangeHandler.js +90 -0
- package/dist/components/core/extension/solid-core/modelSequence/modelSequenceFormViewChangeHandler.js.map +1 -0
- package/dist/components/core/extension/solid-core/modelSequence/modelSequenceFormViewChangeHandler.tsx +59 -0
- package/dist/components/core/extension/solid-core/roleMetadata/RolePermissionsManyToManyFieldWidget.d.ts.map +1 -1
- package/dist/components/core/extension/solid-core/roleMetadata/RolePermissionsManyToManyFieldWidget.js +17 -28
- package/dist/components/core/extension/solid-core/roleMetadata/RolePermissionsManyToManyFieldWidget.js.map +1 -1
- package/dist/components/core/extension/solid-core/roleMetadata/RolePermissionsManyToManyFieldWidget.tsx +71 -56
- package/dist/components/core/filter/SolidOneToManyFilterElement.d.ts +2 -0
- package/dist/components/core/filter/SolidOneToManyFilterElement.d.ts.map +1 -0
- package/dist/components/core/filter/SolidOneToManyFilterElement.js +86 -0
- package/dist/components/core/filter/SolidOneToManyFilterElement.js.map +1 -0
- package/dist/components/core/filter/SolidOneToManyFilterElement.tsx +62 -0
- package/dist/components/core/filter/SolidVarInputsFilterElement.d.ts +1 -0
- package/dist/components/core/filter/SolidVarInputsFilterElement.d.ts.map +1 -1
- package/dist/components/core/filter/SolidVarInputsFilterElement.js +4 -1
- package/dist/components/core/filter/SolidVarInputsFilterElement.js.map +1 -1
- package/dist/components/core/filter/SolidVarInputsFilterElement.tsx +10 -0
- package/dist/components/core/filter/fields/SolidRelationField.d.ts.map +1 -1
- package/dist/components/core/filter/fields/SolidRelationField.js +4 -2
- package/dist/components/core/filter/fields/SolidRelationField.js.map +1 -1
- package/dist/components/core/filter/fields/SolidRelationField.tsx +4 -2
- package/dist/components/core/filter/fields/relations/SolidRelationOneToManyField.d.ts +4 -0
- package/dist/components/core/filter/fields/relations/SolidRelationOneToManyField.d.ts.map +1 -0
- package/dist/components/core/filter/fields/relations/SolidRelationOneToManyField.js +25 -0
- package/dist/components/core/filter/fields/relations/SolidRelationOneToManyField.js.map +1 -0
- package/dist/components/core/filter/fields/relations/SolidRelationOneToManyField.tsx +60 -0
- package/dist/components/core/form/SolidFormFooter.js +4 -4
- package/dist/components/core/form/SolidFormFooter.js.map +1 -1
- package/dist/components/core/form/SolidFormFooter.tsx +4 -4
- package/dist/components/core/form/fields/SolidBooleanField.d.ts.map +1 -1
- package/dist/components/core/form/fields/SolidBooleanField.js +11 -8
- package/dist/components/core/form/fields/SolidBooleanField.js.map +1 -1
- package/dist/components/core/form/fields/SolidBooleanField.tsx +20 -8
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.d.ts +40 -0
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.js +317 -157
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.js.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationManyToManyField.tsx +463 -243
- package/dist/components/core/form/fields/relations/SolidRelationOneToManyField.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationOneToManyField.js +46 -95
- package/dist/components/core/form/fields/relations/SolidRelationOneToManyField.js.map +1 -1
- package/dist/components/core/form/fields/relations/SolidRelationOneToManyField.tsx +57 -113
- package/dist/components/core/form/fields/relations/widgets/helpers/useRelationEntityHandler.d.ts +15 -4
- package/dist/components/core/form/fields/relations/widgets/helpers/useRelationEntityHandler.d.ts.map +1 -1
- package/dist/components/core/form/fields/relations/widgets/helpers/useRelationEntityHandler.js +220 -33
- package/dist/components/core/form/fields/relations/widgets/helpers/useRelationEntityHandler.js.map +1 -1
- package/dist/components/core/form/fields/relations/widgets/helpers/useRelationEntityHandler.ts +167 -36
- package/dist/components/core/kanban/SolidKanbanView.d.ts.map +1 -1
- package/dist/components/core/kanban/SolidKanbanView.js +13 -12
- package/dist/components/core/kanban/SolidKanbanView.js.map +1 -1
- package/dist/components/core/kanban/SolidKanbanView.tsx +8 -7
- package/dist/components/core/list/SolidListView.d.ts +18 -10
- package/dist/components/core/list/SolidListView.d.ts.map +1 -1
- package/dist/components/core/list/SolidListView.js +176 -177
- package/dist/components/core/list/SolidListView.js.map +1 -1
- package/dist/components/core/list/SolidListView.tsx +130 -143
- package/dist/components/core/list/SolidListViewConfigure.d.ts +7 -0
- package/dist/components/core/list/SolidListViewConfigure.d.ts.map +1 -1
- package/dist/components/core/list/SolidListViewConfigure.js +6 -5
- package/dist/components/core/list/SolidListViewConfigure.js.map +1 -1
- package/dist/components/core/list/SolidListViewConfigure.tsx +21 -12
- package/dist/components/core/list/columns/SolidShortTextColumn.d.ts.map +1 -1
- package/dist/components/core/list/columns/SolidShortTextColumn.js +1 -37
- package/dist/components/core/list/columns/SolidShortTextColumn.js.map +1 -1
- package/dist/components/core/list/columns/SolidShortTextColumn.tsx +0 -41
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.d.ts.map +1 -1
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.js +9 -5
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.js.map +1 -1
- package/dist/components/core/list/columns/relations/SolidRelationManyToOneColumn.tsx +14 -3
- package/dist/components/core/list/listViewRegistry.js.map +1 -1
- package/dist/components/core/list/listViewRegistry.ts +1 -2
- package/dist/components/core/tree/SolidTreeView.d.ts +38 -0
- package/dist/components/core/tree/SolidTreeView.d.ts.map +1 -0
- package/dist/components/core/tree/SolidTreeView.js +1179 -0
- package/dist/components/core/tree/SolidTreeView.js.map +1 -0
- package/dist/components/core/tree/SolidTreeView.tsx +1637 -0
- package/dist/components/core/tree/treeViewRegistry.d.ts +7 -0
- package/dist/components/core/tree/treeViewRegistry.d.ts.map +1 -0
- package/dist/components/core/tree/treeViewRegistry.js +17 -0
- package/dist/components/core/tree/treeViewRegistry.js.map +1 -0
- package/dist/components/core/tree/treeViewRegistry.ts +23 -0
- package/dist/components/core/users/CreateUser.d.ts.map +1 -1
- package/dist/components/core/users/CreateUser.js +19 -6
- package/dist/components/core/users/CreateUser.js.map +1 -1
- package/dist/components/core/users/CreateUser.tsx +39 -0
- package/dist/helpers/helpers.d.ts +2 -0
- package/dist/helpers/helpers.d.ts.map +1 -1
- package/dist/helpers/helpers.js +3 -1
- package/dist/helpers/helpers.js.map +1 -1
- package/dist/helpers/helpers.ts +4 -1
- package/dist/helpers/registry.d.ts.map +1 -1
- package/dist/helpers/registry.js +5 -1
- package/dist/helpers/registry.js.map +1 -1
- package/dist/helpers/registry.ts +7 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.ts +6 -1
- package/dist/resources/globals.css +32 -4
- package/dist/routes/pages/admin/core/ListPage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/ListPage.js +2 -2
- package/dist/routes/pages/admin/core/ListPage.js.map +1 -1
- package/dist/routes/pages/admin/core/ListPage.tsx +3 -2
- package/dist/routes/pages/admin/core/ModuleHomePage.d.ts.map +1 -1
- package/dist/routes/pages/admin/core/ModuleHomePage.js +4 -15
- package/dist/routes/pages/admin/core/ModuleHomePage.js.map +1 -1
- package/dist/routes/pages/admin/core/ModuleHomePage.tsx +4 -3
- package/dist/routes/pages/admin/core/TreePage.d.ts +2 -0
- package/dist/routes/pages/admin/core/TreePage.d.ts.map +1 -0
- package/dist/routes/pages/admin/core/TreePage.js +37 -0
- package/dist/routes/pages/admin/core/TreePage.js.map +1 -0
- package/dist/routes/pages/admin/core/TreePage.tsx +30 -0
- package/dist/routes/solidRoutes.d.ts.map +1 -1
- package/dist/routes/solidRoutes.js +2 -0
- package/dist/routes/solidRoutes.js.map +1 -1
- package/dist/routes/solidRoutes.tsx +3 -1
- package/dist/routes/types.d.ts +1 -1
- package/dist/routes/types.d.ts.map +1 -1
- package/dist/routes/types.js.map +1 -1
- package/dist/routes/types.ts +1 -0
- package/dist/types/index.d.ts +8 -2
- package/dist/types/solid-core.d.ts +40 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1637 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useImperativeHandle,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { Toast } from "primereact/toast";
|
|
10
|
+
import { useDispatch, useSelector } from "react-redux";
|
|
11
|
+
import { showNavbar, toggleNavbar } from "../../../redux/features/navbarSlice";
|
|
12
|
+
import { useGetSolidViewLayoutQuery } from "../../../redux/api/solidViewApi";
|
|
13
|
+
import qs from "qs";
|
|
14
|
+
import { useSearchParams } from "../../../hooks/useSearchParams";
|
|
15
|
+
import { SolidGlobalSearchElement } from "../common/SolidGlobalSearchElement";
|
|
16
|
+
import { Button } from "primereact/button";
|
|
17
|
+
import { permissionExpression } from "../../../helpers/permissions";
|
|
18
|
+
import { SolidCreateButton } from "../common/SolidCreateButton";
|
|
19
|
+
import { Dialog } from "primereact/dialog";
|
|
20
|
+
import { createSolidEntityApi } from "../../../redux/api/solidEntityApi";
|
|
21
|
+
import { AggregationRule, GroupingRule } from "../common/GroupingComponent";
|
|
22
|
+
import { TreeTable } from "primereact/treetable";
|
|
23
|
+
import { Dropdown } from "primereact/dropdown";
|
|
24
|
+
import type { TreeNode } from "primereact/treenode";
|
|
25
|
+
import { Column } from "primereact/column";
|
|
26
|
+
import { SolidListViewColumn } from "../list/SolidListViewColumn";
|
|
27
|
+
import { hasAnyRole } from "../../../helpers/rolesHelper";
|
|
28
|
+
import { useSession } from "../../../hooks/useSession";
|
|
29
|
+
import { useLazyCheckIfPermissionExistsQuery } from "../../../redux/api/userApi";
|
|
30
|
+
import { SolidListViewConfigure } from "../list/SolidListViewConfigure";
|
|
31
|
+
import CompactImage from '../../../resources/images/layout/images/compact.png';
|
|
32
|
+
import CozyImage from '../../../resources/images/layout/images/cozy.png';
|
|
33
|
+
import ComfortableImage from '../../../resources/images/layout/images/comfortable.png';
|
|
34
|
+
import { Divider } from "primereact/divider";
|
|
35
|
+
import { ERROR_MESSAGES } from "../../../constants/error-messages";
|
|
36
|
+
import { getSingularAndPlural } from "../../../helpers/helpers";
|
|
37
|
+
import { getFilterObjectFromLocalStorage, setFilterObjectToLocalStorage } from "../list/SolidListView";
|
|
38
|
+
import { HomePageModuleSvg } from "../../Svg/HomePageModuleSvg";
|
|
39
|
+
import { SolidBeforeTreeNodeLoad } from "../../../types";
|
|
40
|
+
import { getExtensionFunction } from "../../../helpers/registry";
|
|
41
|
+
import { SolidTreeLoad, SolidTreeUiEventResponse } from "../../../types/solid-core";
|
|
42
|
+
import { Tooltip } from "primereact/tooltip";
|
|
43
|
+
|
|
44
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
type SolidTreeViewParams = {
|
|
47
|
+
moduleName: string;
|
|
48
|
+
modelName: string;
|
|
49
|
+
inlineCreate?: boolean;
|
|
50
|
+
handlePopUpOpen?: any;
|
|
51
|
+
embeded?: boolean;
|
|
52
|
+
customLayout?: any;
|
|
53
|
+
customFilter?: any;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type SolidTreeViewHandle = {
|
|
57
|
+
refresh: () => void;
|
|
58
|
+
clearFilters: () => void;
|
|
59
|
+
applyFilter: (filter: {
|
|
60
|
+
custom_filter_predicate?: any;
|
|
61
|
+
search_predicate?: any;
|
|
62
|
+
saved_filter_predicate?: any;
|
|
63
|
+
predefined_search_predicate?: any;
|
|
64
|
+
}) => void;
|
|
65
|
+
setPagination: (nextFirst: number, nextRows: number) => void;
|
|
66
|
+
setSort: (nextSortField: string, nextSortOrder: 1 | -1 | 0) => void;
|
|
67
|
+
setShowArchived: (value: boolean) => void;
|
|
68
|
+
getState: () => {
|
|
69
|
+
first: number;
|
|
70
|
+
rows: number;
|
|
71
|
+
sortField: string;
|
|
72
|
+
sortOrder: 1 | -1 | 0;
|
|
73
|
+
showArchived: boolean;
|
|
74
|
+
filters: any;
|
|
75
|
+
filterPredicates: any;
|
|
76
|
+
listData: any[];
|
|
77
|
+
totalRecords: number;
|
|
78
|
+
loading: boolean;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type GroupPathItem = {
|
|
83
|
+
ruleIndex: number;
|
|
84
|
+
fieldName: string;
|
|
85
|
+
filterField: string;
|
|
86
|
+
value: any;
|
|
87
|
+
dateGranularity?: string | null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type NodeMeta = {
|
|
91
|
+
nodeType: "group" | "record";
|
|
92
|
+
ruleIndex: number;
|
|
93
|
+
groupLabel?: string;
|
|
94
|
+
idCount?: number | string;
|
|
95
|
+
aggregates?: Record<string, any>;
|
|
96
|
+
groupPath: GroupPathItem[];
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
type TreeRowData = Record<string, any> & {
|
|
100
|
+
__treeMeta?: NodeMeta;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pagination entry stored per node key.
|
|
105
|
+
* - "root" is used for the top-level group list.
|
|
106
|
+
* - Any other node.key is used for its children (groups or records).
|
|
107
|
+
*/
|
|
108
|
+
type PaginationEntry = {
|
|
109
|
+
offset: number; // current page start
|
|
110
|
+
limit: number; // page size
|
|
111
|
+
total: number; // total items returned from API (used to determine hasNext)
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
115
|
+
|
|
116
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
export const SolidTreeView = forwardRef<SolidTreeViewHandle, SolidTreeViewParams>((params, ref) => {
|
|
119
|
+
const toast = useRef<Toast>(null);
|
|
120
|
+
const dispatch = useDispatch();
|
|
121
|
+
const visibleNavbar = useSelector((state: any) => state.navbarState?.visibleNavbar);
|
|
122
|
+
const searchParams = useSearchParams();
|
|
123
|
+
|
|
124
|
+
const session = useSession();
|
|
125
|
+
const user = session?.data?.user;
|
|
126
|
+
|
|
127
|
+
const solidGlobalSearchElementRef = useRef<any>(null);
|
|
128
|
+
|
|
129
|
+
const [showSaveFilterPopup, setShowSaveFilterPopup] = useState<boolean>(false);
|
|
130
|
+
const [showGlobalSearchElement, setShowGlobalSearchElement] = useState<boolean>(false);
|
|
131
|
+
const [toPopulate, setToPopulate] = useState<string[]>([]);
|
|
132
|
+
const [toPopulateMedia, setToPopulateMedia] = useState<string[]>([]);
|
|
133
|
+
const [actionsAllowed, setActionsAllowed] = useState<string[]>([]);
|
|
134
|
+
const [createButtonUrl, setCreateButtonUrl] = useState<string>();
|
|
135
|
+
const [createActionQueryParams, setCreateActionQueryParams] = useState<Record<string, string>>({});
|
|
136
|
+
const [selectedRecords, setSelectedRecords] = useState<any[]>([]);
|
|
137
|
+
const [selectedRecoverRecords, setSelectedRecoverRecords] = useState<any[]>([]);
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
const [groupingRules, setGroupingRules] = useState<GroupingRule[]>([]);
|
|
141
|
+
const [aggregationRules, setAggregationRules] = useState<AggregationRule[]>([]);
|
|
142
|
+
|
|
143
|
+
const [treeNodes, setTreeNodes] = useState<TreeNode[]>([]);
|
|
144
|
+
const [selectedNodeKeys, setSelectedNodeKeys] = useState<Record<string, any>>({});
|
|
145
|
+
const [expandedKeys, setExpandedKeys] = useState<any>({});
|
|
146
|
+
const [treeLoading, setTreeLoading] = useState<boolean>(false);
|
|
147
|
+
|
|
148
|
+
const [sortField, setSortField] = useState<string>("");
|
|
149
|
+
const [sortOrder, setSortOrder] = useState<number>(0);
|
|
150
|
+
|
|
151
|
+
const [pageSizeOptions, setPageSizeOptions] = useState<number[]>([10, 25, 50]);
|
|
152
|
+
const [globalLimit, setGlobalLimit] = useState<number>(25);
|
|
153
|
+
|
|
154
|
+
const [isDeleteRecordsDialogVisible, setDeleteRecordsDialogVisible] = useState(false);
|
|
155
|
+
const [isRecoverDialogVisible, setRecoverDialogVisible] = useState(false);
|
|
156
|
+
const [showArchived, setShowArchived] = useState(false);
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
const sizeOptions = [
|
|
160
|
+
{ label: "Compact", value: "small", image: CompactImage },
|
|
161
|
+
{ label: "Cozy", value: "normal", image: CozyImage },
|
|
162
|
+
{ label: "Comfortable", value: "large", image: ComfortableImage },
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const [size, setSize] = useState<string | any>(sizeOptions[1].value);
|
|
166
|
+
const [viewModes, setViewModes] = useState<any>([]);
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
// ── Pagination state ──────────────────────────────────────────────────────
|
|
170
|
+
/**
|
|
171
|
+
* Key: "root" for top-level group list; node.key (string) for any other node.
|
|
172
|
+
* Value: { offset, limit, total }
|
|
173
|
+
*/
|
|
174
|
+
const [paginationMap, setPaginationMap] = useState<Record<string, PaginationEntry>>({});
|
|
175
|
+
|
|
176
|
+
const getPagination = (key: string): PaginationEntry =>
|
|
177
|
+
paginationMap[key] ?? { offset: 0, limit: globalLimit, total: 0 };
|
|
178
|
+
|
|
179
|
+
const setPagination = (key: string, entry: Partial<PaginationEntry>) =>
|
|
180
|
+
setPaginationMap((prev) => ({
|
|
181
|
+
...prev,
|
|
182
|
+
[key]: { ...getPagination(key), ...entry },
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
// ── Pagination helpers ────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
const hasPrev = (key: string) => getPagination(key).offset > 0;
|
|
188
|
+
|
|
189
|
+
const hasNext = (key: string) => {
|
|
190
|
+
const { offset, limit, total } = getPagination(key);
|
|
191
|
+
// total here is the count of records returned by the last fetch for that node.
|
|
192
|
+
// We consider "has next" when we received a full page (i.e. there might be more).
|
|
193
|
+
return offset + limit < total;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
const menuItemId = searchParams.get("menuItemId");
|
|
199
|
+
const menuItemName = searchParams.get("menuItemName");
|
|
200
|
+
const actionId = searchParams.get("actionId");
|
|
201
|
+
const actionName = searchParams.get("actionName");
|
|
202
|
+
|
|
203
|
+
const entityApi = createSolidEntityApi(params.modelName);
|
|
204
|
+
const {
|
|
205
|
+
useCreateSolidEntityMutation,
|
|
206
|
+
useDeleteMultipleSolidEntitiesMutation,
|
|
207
|
+
useDeleteSolidEntityMutation,
|
|
208
|
+
useGetSolidEntitiesQuery,
|
|
209
|
+
useGetSolidEntityByIdQuery,
|
|
210
|
+
useLazyGetSolidEntitiesQuery,
|
|
211
|
+
useLazyGetSolidEntityByIdQuery,
|
|
212
|
+
usePrefetch,
|
|
213
|
+
useUpdateSolidEntityMutation,
|
|
214
|
+
useRecoverSolidEntityByIdQuery,
|
|
215
|
+
useLazyRecoverSolidEntityByIdQuery,
|
|
216
|
+
useRecoverSolidEntityMutation,
|
|
217
|
+
} = entityApi;
|
|
218
|
+
|
|
219
|
+
const [triggerGetSolidEntities] = useLazyGetSolidEntitiesQuery();
|
|
220
|
+
|
|
221
|
+
const [
|
|
222
|
+
triggerRecoverSolidEntitiesById,
|
|
223
|
+
{
|
|
224
|
+
data: recoverByIdData,
|
|
225
|
+
isLoading: recoverByIdIsLoading,
|
|
226
|
+
error: recoverByIdError,
|
|
227
|
+
isError: recoverByIdIsError,
|
|
228
|
+
isSuccess: recoverByIdIsSuccess,
|
|
229
|
+
},
|
|
230
|
+
] = useLazyRecoverSolidEntityByIdQuery();
|
|
231
|
+
|
|
232
|
+
const [
|
|
233
|
+
triggerRecoverSolidEntities,
|
|
234
|
+
{
|
|
235
|
+
data: recoverByData,
|
|
236
|
+
isLoading: recoverByIsLoading,
|
|
237
|
+
error: recoverError,
|
|
238
|
+
isError: recoverIsError,
|
|
239
|
+
isSuccess: recoverByIsSuccess,
|
|
240
|
+
},
|
|
241
|
+
] = useRecoverSolidEntityMutation();
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
const treeViewMetaDataQs = qs.stringify(
|
|
246
|
+
{
|
|
247
|
+
modelName: params.modelName,
|
|
248
|
+
moduleName: params.moduleName,
|
|
249
|
+
viewType: "list",
|
|
250
|
+
menuItemId,
|
|
251
|
+
menuItemName,
|
|
252
|
+
actionId,
|
|
253
|
+
actionName,
|
|
254
|
+
},
|
|
255
|
+
{ encodeValuesOnly: true }
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const { data: solidTreeViewMetaData } = useGetSolidViewLayoutQuery(treeViewMetaDataQs);
|
|
259
|
+
const solidTreeViewLayout = params.customLayout || solidTreeViewMetaData?.data?.solidView?.layout;
|
|
260
|
+
|
|
261
|
+
const [triggerCheckIfPermissionExists] = useLazyCheckIfPermissionExistsQuery();
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
const fetchPermissions = async () => {
|
|
265
|
+
if (params.modelName) {
|
|
266
|
+
const permissionNames = [
|
|
267
|
+
permissionExpression(params.modelName, 'create'),
|
|
268
|
+
permissionExpression(params.modelName, 'delete'),
|
|
269
|
+
permissionExpression(params.modelName, 'update'),
|
|
270
|
+
permissionExpression(params.modelName, 'deleteMany'),
|
|
271
|
+
permissionExpression(params.modelName, 'findOne'),
|
|
272
|
+
permissionExpression(params.modelName, 'findMany'),
|
|
273
|
+
permissionExpression(params.modelName, 'insertMany'),
|
|
274
|
+
permissionExpression('importTransaction', 'create'),
|
|
275
|
+
permissionExpression('exportTransaction', 'create'),
|
|
276
|
+
permissionExpression('userViewMetadata', 'create'),
|
|
277
|
+
permissionExpression('savedFilters', 'create')
|
|
278
|
+
];
|
|
279
|
+
const queryData = {
|
|
280
|
+
permissionNames: permissionNames,
|
|
281
|
+
};
|
|
282
|
+
const queryString = qs.stringify(queryData, {
|
|
283
|
+
encodeValuesOnly: true,
|
|
284
|
+
});
|
|
285
|
+
const response = await triggerCheckIfPermissionExists(queryString);
|
|
286
|
+
setActionsAllowed(response.data.data);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
fetchPermissions();
|
|
290
|
+
}, [params.modelName]);
|
|
291
|
+
|
|
292
|
+
const [deleteManySolidEntities] = useDeleteMultipleSolidEntitiesMutation();
|
|
293
|
+
|
|
294
|
+
const treeViewTitle = solidTreeViewMetaData?.data?.solidView?.displayName;
|
|
295
|
+
|
|
296
|
+
const toggleBothSidebars = () => {
|
|
297
|
+
if (visibleNavbar) {
|
|
298
|
+
dispatch(toggleNavbar());
|
|
299
|
+
} else {
|
|
300
|
+
dispatch(showNavbar());
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const [filters, setFilters] = useState<any>(null);
|
|
305
|
+
const [filterPredicates, setFilterPredicates] = useState<any>(null);
|
|
306
|
+
|
|
307
|
+
const latestFiltersRef = useRef<any>(filters);
|
|
308
|
+
const latestFilterPredicatesRef = useRef<any>(filterPredicates);
|
|
309
|
+
|
|
310
|
+
const activeGroupingRules = useMemo(
|
|
311
|
+
() => (groupingRules || []).filter((rule) => !!rule?.fieldName),
|
|
312
|
+
[groupingRules]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
useEffect(() => { latestFiltersRef.current = filters; }, [filters]);
|
|
316
|
+
useEffect(() => { latestFilterPredicatesRef.current = filterPredicates; }, [filterPredicates]);
|
|
317
|
+
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (solidTreeViewMetaData) {
|
|
320
|
+
setViewModes(solidTreeViewMetaData?.data?.viewModes);
|
|
321
|
+
}
|
|
322
|
+
}, [solidTreeViewMetaData]);
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
const solidFieldsMetadata = solidTreeViewMetaData?.data?.solidFieldsMetadata;
|
|
326
|
+
const queryObject = getFilterObjectFromLocalStorage();
|
|
327
|
+
|
|
328
|
+
const layoutPageSizeOptions = solidTreeViewMetaData?.data?.solidView?.layout?.attrs?.pageSizeOptions;
|
|
329
|
+
const currentOptions = (Array.isArray(layoutPageSizeOptions) && layoutPageSizeOptions.length > 0)
|
|
330
|
+
? layoutPageSizeOptions
|
|
331
|
+
: [15, 25, 50];
|
|
332
|
+
|
|
333
|
+
setPageSizeOptions(currentOptions);
|
|
334
|
+
|
|
335
|
+
if (queryObject) {
|
|
336
|
+
setToPopulate(queryObject.populate || []);
|
|
337
|
+
setToPopulateMedia(queryObject.populateMedia || []);
|
|
338
|
+
setSortField(queryObject.sortField || "");
|
|
339
|
+
setSortOrder(queryObject.sortOrder || 0);
|
|
340
|
+
|
|
341
|
+
const savedLimit = queryObject.limit;
|
|
342
|
+
if (savedLimit && currentOptions.includes(savedLimit)) {
|
|
343
|
+
setGlobalLimit(savedLimit);
|
|
344
|
+
} else {
|
|
345
|
+
setGlobalLimit(currentOptions[0]);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
if (!solidTreeViewLayout || !solidFieldsMetadata) {
|
|
350
|
+
setToPopulate([]);
|
|
351
|
+
setToPopulateMedia([]);
|
|
352
|
+
setGlobalLimit(currentOptions[0]);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const nextPopulate: string[] = [];
|
|
357
|
+
const nextPopulateMedia: string[] = [];
|
|
358
|
+
|
|
359
|
+
for (const column of solidTreeViewLayout?.children || []) {
|
|
360
|
+
const fieldMetadata = solidFieldsMetadata?.[column?.attrs?.name];
|
|
361
|
+
if (!fieldMetadata) continue;
|
|
362
|
+
|
|
363
|
+
if (fieldMetadata.type === "relation" && fieldMetadata.relationType === "many-to-one") {
|
|
364
|
+
if (!nextPopulate.includes(fieldMetadata.name)) nextPopulate.push(fieldMetadata.name);
|
|
365
|
+
}
|
|
366
|
+
if (fieldMetadata.type === "mediaSingle" || fieldMetadata.type === "mediaMultiple") {
|
|
367
|
+
if (!nextPopulateMedia.includes(fieldMetadata.name)) nextPopulateMedia.push(fieldMetadata.name);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
setToPopulate(nextPopulate);
|
|
372
|
+
setToPopulateMedia(nextPopulateMedia);
|
|
373
|
+
setGlobalLimit(currentOptions[0]);
|
|
374
|
+
}
|
|
375
|
+
}, [solidTreeViewLayout, solidTreeViewMetaData]);
|
|
376
|
+
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
|
|
379
|
+
// event invocation is not tested
|
|
380
|
+
if (solidTreeViewMetaData && solidTreeViewMetaData?.data) {
|
|
381
|
+
const handleDynamicFunction = async () => {
|
|
382
|
+
const dynamicHeader = solidTreeViewMetaData?.data?.solidView?.layout?.onTreeLoad;
|
|
383
|
+
let dynamicExtensionFunction = null;
|
|
384
|
+
let treeLayout = solidTreeViewMetaData?.data?.solidView?.layout;
|
|
385
|
+
let treeViewNodes = treeNodes;
|
|
386
|
+
if (params.customLayout) {
|
|
387
|
+
treeLayout = params.customLayout;
|
|
388
|
+
}
|
|
389
|
+
const event: SolidTreeLoad = {
|
|
390
|
+
fieldsMetadata: solidTreeViewMetaData?.data?.solidFieldsMetadata,
|
|
391
|
+
type: "onTreeLoad",
|
|
392
|
+
nodes: treeViewNodes,
|
|
393
|
+
viewMetadata: solidTreeViewMetaData?.data?.solidView,
|
|
394
|
+
treeViewLayout: treeLayout,
|
|
395
|
+
queryParams: {
|
|
396
|
+
menuItemId: menuItemId,
|
|
397
|
+
menuItemName: menuItemName,
|
|
398
|
+
actionId: actionId,
|
|
399
|
+
actionName: actionName,
|
|
400
|
+
},
|
|
401
|
+
user: user,
|
|
402
|
+
session: session.data,
|
|
403
|
+
params: params
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
if (dynamicHeader) {
|
|
407
|
+
dynamicExtensionFunction = getExtensionFunction(dynamicHeader);
|
|
408
|
+
if (dynamicExtensionFunction) {
|
|
409
|
+
const updatedListData: SolidTreeUiEventResponse = await dynamicExtensionFunction(event);
|
|
410
|
+
|
|
411
|
+
if (updatedListData && updatedListData?.dataChanged && updatedListData?.newNodes) {
|
|
412
|
+
treeViewNodes = updatedListData.newNodes;
|
|
413
|
+
}
|
|
414
|
+
if (updatedListData && updatedListData?.layoutChanged && updatedListData?.newLayout) {
|
|
415
|
+
treeLayout = updatedListData.newLayout;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (treeViewNodes) {
|
|
419
|
+
setTreeNodes(treeViewNodes);
|
|
420
|
+
}
|
|
421
|
+
if (treeLayout) {
|
|
422
|
+
solidTreeViewLayout(treeLayout);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
handleDynamicFunction();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
}, [treeNodes]);
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
// ─── Field / filter helpers ───────────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
const getFieldMetadata = (fieldName: string) =>
|
|
435
|
+
solidTreeViewMetaData?.data?.solidFieldsMetadata?.[fieldName];
|
|
436
|
+
|
|
437
|
+
const getResolvedGroupField = (fieldName: string) => {
|
|
438
|
+
const fieldMetadata = getFieldMetadata(fieldName);
|
|
439
|
+
if (
|
|
440
|
+
fieldMetadata?.type === "relation" &&
|
|
441
|
+
fieldMetadata?.relationType === "many-to-one" &&
|
|
442
|
+
fieldMetadata?.relationModel?.userKeyField?.name
|
|
443
|
+
) {
|
|
444
|
+
return `${fieldName}.${fieldMetadata.relationModel.userKeyField.name}`;
|
|
445
|
+
}
|
|
446
|
+
return fieldName;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const toGroupByParam = (rule: GroupingRule) => {
|
|
450
|
+
const fieldName = String(rule.fieldName);
|
|
451
|
+
const resolvedField = getResolvedGroupField(fieldName);
|
|
452
|
+
if (!rule.dateGrouping) return resolvedField;
|
|
453
|
+
if (rule.dateGrouping === "YYYY") return `${resolvedField}:year:YYYY`;
|
|
454
|
+
if (rule.dateGrouping === "MMM") return `${resolvedField}:month:MMM`;
|
|
455
|
+
if (rule.dateGrouping === "YYYY-MM") return `${resolvedField}:month:YYYY-MM`;
|
|
456
|
+
if (rule.dateGrouping === "YYYY-MM-DD") return `${resolvedField}:day:YYYY-MM-DD`;
|
|
457
|
+
return resolvedField;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// const dateTimeImplicitFilter = (rule: GroupingRule) => {
|
|
461
|
+
// const fieldMetadata = getFieldMetadata(String(rule.fieldName));
|
|
462
|
+
// return !!rule.dateGrouping && ["date", "datetime"].includes(fieldMetadata?.type);
|
|
463
|
+
// };
|
|
464
|
+
|
|
465
|
+
const getDateGranularity = (rule: GroupingRule) => {
|
|
466
|
+
const fieldMetadata = getFieldMetadata(String(rule.fieldName));
|
|
467
|
+
if (rule.dateGrouping && ["date", "datetime"].includes(fieldMetadata?.type)) {
|
|
468
|
+
switch (rule.dateGrouping) {
|
|
469
|
+
case "YYYY":
|
|
470
|
+
return "year";
|
|
471
|
+
case "MMM":
|
|
472
|
+
return "month";
|
|
473
|
+
case "YYYY-MM":
|
|
474
|
+
return "month";
|
|
475
|
+
case "YYYY-MM-DD":
|
|
476
|
+
return "day";
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const buildNestedEqCondition = (fieldPath: string, value: any, dateGranularity: any) => {
|
|
483
|
+
const parts = fieldPath.split(".").filter(Boolean);
|
|
484
|
+
if (parts.length === 0) return {};
|
|
485
|
+
if (dateGranularity) {
|
|
486
|
+
return { [`${parts[0]}:${dateGranularity}`]: { $eq: value } };
|
|
487
|
+
}
|
|
488
|
+
return parts.reduceRight((acc: any, part: string) => ({ [part]: acc }), { $eq: value });
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const buildImplicitFiltersFromPath = (groupPath: GroupPathItem[]) =>
|
|
492
|
+
groupPath
|
|
493
|
+
// .filter((item) => !item.skipImplicitFilter)
|
|
494
|
+
.map((item) => buildNestedEqCondition(item.filterField, item.value, item.dateGranularity));
|
|
495
|
+
|
|
496
|
+
const mergeFiltersWithImplicit = (implicitFilters: any[]) => {
|
|
497
|
+
const baseFilters = latestFiltersRef.current;
|
|
498
|
+
const hasBaseFilters = baseFilters && Object.keys(baseFilters).length > 0;
|
|
499
|
+
if (!hasBaseFilters && implicitFilters.length === 0) return null;
|
|
500
|
+
const merged = hasBaseFilters ? structuredClone(baseFilters) : { $and: [] };
|
|
501
|
+
if (implicitFilters.length === 0) return merged;
|
|
502
|
+
if (Array.isArray(merged.$and)) {
|
|
503
|
+
merged.$and.push(...implicitFilters);
|
|
504
|
+
return merged;
|
|
505
|
+
}
|
|
506
|
+
return { $and: [merged, ...implicitFilters] };
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const buildAggregates = () => {
|
|
510
|
+
const derivedAggregates = (aggregationRules || [])
|
|
511
|
+
.filter((rule) => !!rule?.fieldName && !!rule?.operator)
|
|
512
|
+
.map((rule) => `${rule.fieldName}:${rule.operator}`);
|
|
513
|
+
return derivedAggregates.length > 0 ? derivedAggregates : ["id:count"];
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const extractGroupCount = (groupMeta: any) => {
|
|
517
|
+
if (groupMeta?.id_count !== undefined) return groupMeta.id_count;
|
|
518
|
+
const countKey = Object.keys(groupMeta || {}).find((key) => key.endsWith("_count"));
|
|
519
|
+
if (countKey) return groupMeta[countKey];
|
|
520
|
+
return undefined;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
const normalizeRecord = (record: any) => {
|
|
524
|
+
const newRecord = { ...record };
|
|
525
|
+
Object.entries(newRecord).forEach(([key, value]) => {
|
|
526
|
+
if (typeof value === "string") {
|
|
527
|
+
try {
|
|
528
|
+
const parsed = JSON.parse(value);
|
|
529
|
+
if (Array.isArray(parsed)) newRecord[key] = parsed.join(", ");
|
|
530
|
+
} catch {
|
|
531
|
+
if (/^\[.*\]$/.test(value)) newRecord[key] = value.replace(/[\[\]"]+/g, "");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
return newRecord;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const formatHeader = (key: string) => {
|
|
539
|
+
return key
|
|
540
|
+
.split("_")
|
|
541
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
542
|
+
.join(" ");
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const getSortParam = (ruleIndex: number) => {
|
|
546
|
+
if (!sortField || !sortOrder) return null;
|
|
547
|
+
|
|
548
|
+
const dir = sortOrder === 1 ? "ASC" : "DESC";
|
|
549
|
+
|
|
550
|
+
// 1. Sorting by the "Group" column
|
|
551
|
+
if (sortField === "__group") {
|
|
552
|
+
const rule = activeGroupingRules[ruleIndex];
|
|
553
|
+
if (!rule) return null;
|
|
554
|
+
// At this level, sort by the field we are grouping by
|
|
555
|
+
return `${toGroupByParam(rule)}:${dir}`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 2. Sorting by an aggregate column
|
|
559
|
+
const isAggregate = aggregationRules.some((rule) => {
|
|
560
|
+
const responseKey = `${rule.fieldName}:${rule.operator}`;
|
|
561
|
+
return responseKey === sortField || `${rule.fieldName}_${rule.operator}` === sortField;
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (isAggregate) {
|
|
565
|
+
const [field, op] = sortField.includes(":") ? sortField.split(":") : sortField.split("_");
|
|
566
|
+
|
|
567
|
+
// For leaf level: only field:DIR
|
|
568
|
+
if (ruleIndex >= activeGroupingRules.length) {
|
|
569
|
+
return `${field}:${dir}`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// For group levels: field:operator:DIR
|
|
573
|
+
return `${field}:${op}:${dir}`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 3. Sorting by a leaf column (regular record field)
|
|
577
|
+
return `${sortField}:${dir}`;
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// ─── API fetch helpers (now accept offset + limit) ────────────────────────
|
|
581
|
+
|
|
582
|
+
const runGroupedQuery = async (
|
|
583
|
+
ruleIndex: number,
|
|
584
|
+
groupPath: GroupPathItem[],
|
|
585
|
+
offset: number,
|
|
586
|
+
limit: number
|
|
587
|
+
) => {
|
|
588
|
+
const rule = activeGroupingRules[ruleIndex];
|
|
589
|
+
if (!rule?.fieldName) return { response: null };
|
|
590
|
+
|
|
591
|
+
let queryData: any = {
|
|
592
|
+
offset,
|
|
593
|
+
limit,
|
|
594
|
+
groupBy: [toGroupByParam(rule)],
|
|
595
|
+
aggregates: buildAggregates(),
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const sortParam = getSortParam(ruleIndex);
|
|
599
|
+
if (sortParam) queryData.sort = [sortParam];
|
|
600
|
+
|
|
601
|
+
const implicitFilters = buildImplicitFiltersFromPath(groupPath);
|
|
602
|
+
const mergedFilters = mergeFiltersWithImplicit(implicitFilters);
|
|
603
|
+
if (mergedFilters) queryData.filters = mergedFilters;
|
|
604
|
+
|
|
605
|
+
// event invocation is not tested
|
|
606
|
+
const dynamicHeader = solidTreeViewMetaData?.data?.solidView?.layout?.onBeforeTreeDataLoad;
|
|
607
|
+
let dynamicExtensionFunction = null;
|
|
608
|
+
const event: SolidBeforeTreeNodeLoad = {
|
|
609
|
+
type: "onBeforeTreeDataLoad",
|
|
610
|
+
level: ruleIndex,
|
|
611
|
+
levelFieldName: rule.fieldName,
|
|
612
|
+
fieldsMetadata: solidTreeViewMetaData?.data?.solidFieldsMetadata,
|
|
613
|
+
viewMetadata: solidTreeViewMetaData?.data?.solidView,
|
|
614
|
+
treeViewLayout: solidTreeViewMetaData?.data.solidView.layout,
|
|
615
|
+
filter: structuredClone(queryData),
|
|
616
|
+
queryParams: {
|
|
617
|
+
menuItemId: menuItemId,
|
|
618
|
+
menuItemName: menuItemName,
|
|
619
|
+
actionId: actionId,
|
|
620
|
+
actionName: actionName,
|
|
621
|
+
},
|
|
622
|
+
user: user,
|
|
623
|
+
session: session.data,
|
|
624
|
+
params: params
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
if (dynamicHeader) {
|
|
628
|
+
dynamicExtensionFunction = getExtensionFunction(dynamicHeader);
|
|
629
|
+
if (dynamicExtensionFunction) {
|
|
630
|
+
try {
|
|
631
|
+
const updatedListData: SolidTreeUiEventResponse = await dynamicExtensionFunction(event);
|
|
632
|
+
if (updatedListData && updatedListData?.filterApplied && updatedListData?.newFilter) {
|
|
633
|
+
queryData = updatedListData?.newFilter;
|
|
634
|
+
}
|
|
635
|
+
} catch (err) {
|
|
636
|
+
console.error("Error executing onBeforeTreeDataLoad extension:", err);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const queryString = qs.stringify(queryData, { encodeValuesOnly: true });
|
|
642
|
+
|
|
643
|
+
const response = await triggerGetSolidEntities(queryString).unwrap();
|
|
644
|
+
return { queryData, queryString, response };
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const runLeafQuery = async (
|
|
648
|
+
groupPath: GroupPathItem[],
|
|
649
|
+
offset: number,
|
|
650
|
+
limit: number
|
|
651
|
+
) => {
|
|
652
|
+
let queryData: any = {
|
|
653
|
+
offset,
|
|
654
|
+
limit,
|
|
655
|
+
sort: getSortParam(groupPath.length) ? [getSortParam(groupPath.length)] : ["id:desc"],
|
|
656
|
+
populate: toPopulate,
|
|
657
|
+
populateMedia: toPopulateMedia,
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const implicitFilters = buildImplicitFiltersFromPath(groupPath);
|
|
661
|
+
const mergedFilters = mergeFiltersWithImplicit(implicitFilters);
|
|
662
|
+
if (mergedFilters) queryData.filters = mergedFilters;
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
// event invocation is not tested
|
|
666
|
+
const dynamicHeader = solidTreeViewMetaData?.data?.solidView?.layout?.onBeforeTreeDataLoad;
|
|
667
|
+
let dynamicExtensionFunction = null;
|
|
668
|
+
const event: SolidBeforeTreeNodeLoad = {
|
|
669
|
+
type: "onBeforeTreeDataLoad",
|
|
670
|
+
level: groupPath.length,
|
|
671
|
+
levelFieldName: groupPath[groupPath.length - 1].fieldName,
|
|
672
|
+
fieldsMetadata: solidTreeViewMetaData?.data?.solidFieldsMetadata,
|
|
673
|
+
viewMetadata: solidTreeViewMetaData?.data?.solidView,
|
|
674
|
+
treeViewLayout: solidTreeViewMetaData?.data.solidView.layout,
|
|
675
|
+
filter: structuredClone(queryData),
|
|
676
|
+
queryParams: {
|
|
677
|
+
menuItemId: menuItemId,
|
|
678
|
+
menuItemName: menuItemName,
|
|
679
|
+
actionId: actionId,
|
|
680
|
+
actionName: actionName,
|
|
681
|
+
},
|
|
682
|
+
user: user,
|
|
683
|
+
session: session.data,
|
|
684
|
+
params: params
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
if (dynamicHeader) {
|
|
688
|
+
dynamicExtensionFunction = getExtensionFunction(dynamicHeader);
|
|
689
|
+
if (dynamicExtensionFunction) {
|
|
690
|
+
try {
|
|
691
|
+
const updatedListData: SolidTreeUiEventResponse = await dynamicExtensionFunction(event);
|
|
692
|
+
if (updatedListData && updatedListData?.filterApplied && updatedListData?.newFilter) {
|
|
693
|
+
queryData = updatedListData?.newFilter;
|
|
694
|
+
}
|
|
695
|
+
} catch (err) {
|
|
696
|
+
console.error("Error executing onBeforeTreeDataLoad extension:", err);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
const queryString = qs.stringify(queryData, { encodeValuesOnly: true });
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
const response = await triggerGetSolidEntities(queryString).unwrap();
|
|
709
|
+
return { queryData, queryString, response };
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// ─── Node builders ────────────────────────────────────────────────────────
|
|
713
|
+
|
|
714
|
+
const buildGroupNodes = (
|
|
715
|
+
groupMetaRows: any[],
|
|
716
|
+
ruleIndex: number,
|
|
717
|
+
parentPath: GroupPathItem[],
|
|
718
|
+
parentKey: string
|
|
719
|
+
): TreeNode[] => {
|
|
720
|
+
const rule = activeGroupingRules[ruleIndex];
|
|
721
|
+
if (!rule?.fieldName) return [];
|
|
722
|
+
|
|
723
|
+
const fieldName = String(rule.fieldName);
|
|
724
|
+
const filterField = getResolvedGroupField(fieldName);
|
|
725
|
+
// const dateGranularity = dateTimeImplicitFilter(rule);
|
|
726
|
+
const dateGranularity = getDateGranularity(rule);
|
|
727
|
+
return (groupMetaRows || []).map((groupMeta, index) => {
|
|
728
|
+
const groupLabel = groupMeta?.groupName ?? "(empty)";
|
|
729
|
+
const groupValue = groupMeta?.groupValue ?? "(empty)";
|
|
730
|
+
const idCount = extractGroupCount(groupMeta);
|
|
731
|
+
|
|
732
|
+
const groupPath: GroupPathItem[] = [
|
|
733
|
+
...parentPath,
|
|
734
|
+
{ ruleIndex, fieldName, filterField, value: groupValue, dateGranularity },
|
|
735
|
+
];
|
|
736
|
+
|
|
737
|
+
return {
|
|
738
|
+
key: `${parentKey}-g-${ruleIndex}-${index}`,
|
|
739
|
+
data: {
|
|
740
|
+
__treeMeta: {
|
|
741
|
+
nodeType: "group",
|
|
742
|
+
ruleIndex,
|
|
743
|
+
groupLabel,
|
|
744
|
+
idCount,
|
|
745
|
+
aggregates: groupMeta,
|
|
746
|
+
groupPath,
|
|
747
|
+
},
|
|
748
|
+
} as TreeRowData,
|
|
749
|
+
children: [],
|
|
750
|
+
leaf: false,
|
|
751
|
+
};
|
|
752
|
+
});
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
const buildRecordNodes = (
|
|
756
|
+
records: any[],
|
|
757
|
+
parentKey: string,
|
|
758
|
+
groupPath: GroupPathItem[]
|
|
759
|
+
): TreeNode[] => {
|
|
760
|
+
return (records || []).map((record: any, index: number) => {
|
|
761
|
+
const normalizedRecord = normalizeRecord(record);
|
|
762
|
+
return {
|
|
763
|
+
key: `${parentKey}-r-${record?.id ?? index}`,
|
|
764
|
+
data: {
|
|
765
|
+
...normalizedRecord,
|
|
766
|
+
__treeMeta: {
|
|
767
|
+
nodeType: "record",
|
|
768
|
+
ruleIndex: groupPath.length,
|
|
769
|
+
groupPath,
|
|
770
|
+
},
|
|
771
|
+
} as TreeRowData,
|
|
772
|
+
leaf: true,
|
|
773
|
+
};
|
|
774
|
+
});
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const updateNodeChildren = (
|
|
778
|
+
nodes: TreeNode[],
|
|
779
|
+
targetKey: string | number,
|
|
780
|
+
children: TreeNode[]
|
|
781
|
+
): TreeNode[] => {
|
|
782
|
+
return nodes.map((node) => {
|
|
783
|
+
if (node.key === targetKey) {
|
|
784
|
+
return { ...node, children, leaf: children.length === 0 };
|
|
785
|
+
}
|
|
786
|
+
if (node.children && node.children.length > 0) {
|
|
787
|
+
return { ...node, children: updateNodeChildren(node.children, targetKey, children) };
|
|
788
|
+
}
|
|
789
|
+
return node;
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// ─── Root group load / paginate ───────────────────────────────────────────
|
|
794
|
+
|
|
795
|
+
const loadRootGroups = async (offset = 0) => {
|
|
796
|
+
if (!solidTreeViewMetaData || activeGroupingRules.length === 0) {
|
|
797
|
+
setTreeNodes([]);
|
|
798
|
+
setExpandedKeys({});
|
|
799
|
+
|
|
800
|
+
const queryObject = getFilterObjectFromLocalStorage();
|
|
801
|
+
if (queryObject) {
|
|
802
|
+
delete queryObject.grouping_rules;
|
|
803
|
+
delete queryObject.aggregation_rules;
|
|
804
|
+
setFilterObjectToLocalStorage(queryObject);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const limit = globalLimit || DEFAULT_PAGE_SIZE;
|
|
811
|
+
setTreeLoading(true);
|
|
812
|
+
try {
|
|
813
|
+
const { response } = await runGroupedQuery(0, [], offset, limit);
|
|
814
|
+
const rootNodes = buildGroupNodes(response?.groupMeta || [], 0, [], "root");
|
|
815
|
+
setTreeNodes(rootNodes);
|
|
816
|
+
setExpandedKeys({});
|
|
817
|
+
// Collapse expanded keys since data changed
|
|
818
|
+
const total = response?.meta.totalRecords ?? 0;
|
|
819
|
+
setPagination("root", { offset, total: total });
|
|
820
|
+
|
|
821
|
+
if (latestFilterPredicatesRef.current && latestFilterPredicatesRef.current.persistFilter) {
|
|
822
|
+
let queryData: any = {
|
|
823
|
+
offset: offset,
|
|
824
|
+
limit: limit,
|
|
825
|
+
populate: toPopulate,
|
|
826
|
+
populateMedia: toPopulateMedia,
|
|
827
|
+
sortField: sortField,
|
|
828
|
+
sortOrder: sortOrder,
|
|
829
|
+
custom_filter_predicate: latestFilterPredicatesRef.current.custom_filter_predicate || null,
|
|
830
|
+
search_predicate: latestFilterPredicatesRef.current.search_predicate || null,
|
|
831
|
+
saved_filter_predicate: latestFilterPredicatesRef.current.saved_filter_predicate || null,
|
|
832
|
+
predefined_search_predicate: latestFilterPredicatesRef.current.predefined_search_predicate || null,
|
|
833
|
+
grouping_rules: latestFilterPredicatesRef.current.grouping_rules || null,
|
|
834
|
+
aggregation_rules: latestFilterPredicatesRef.current.aggregation_rules || null,
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
setFilterObjectToLocalStorage(queryData);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
} catch (error: any) {
|
|
841
|
+
setTreeNodes([]);
|
|
842
|
+
toast.current?.show({
|
|
843
|
+
severity: "error",
|
|
844
|
+
summary: "Failed to load tree",
|
|
845
|
+
detail: error?.data?.message || error?.message || "Unable to load grouped data",
|
|
846
|
+
life: 4000,
|
|
847
|
+
});
|
|
848
|
+
} finally {
|
|
849
|
+
setTreeLoading(false);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
useEffect(() => {
|
|
854
|
+
// Reset root pagination on data dependencies change
|
|
855
|
+
setPaginationMap({});
|
|
856
|
+
if (filters && filterPredicates) {
|
|
857
|
+
loadRootGroups(0);
|
|
858
|
+
}
|
|
859
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
860
|
+
}, [solidTreeViewMetaData, params.modelName, activeGroupingRules, aggregationRules, filters, sortField, sortOrder, globalLimit]);
|
|
861
|
+
|
|
862
|
+
// ─── Expand handler ───────────────────────────────────────────────────────
|
|
863
|
+
|
|
864
|
+
const loadNodeChildren = async (node: TreeNode, offset: number) => {
|
|
865
|
+
const meta = (node.data as TreeRowData)?.__treeMeta;
|
|
866
|
+
if (!meta || meta.nodeType !== "group") return;
|
|
867
|
+
|
|
868
|
+
const nodeKey = String(node.key);
|
|
869
|
+
const limit = getPagination(nodeKey).limit || DEFAULT_PAGE_SIZE;
|
|
870
|
+
const nextRuleIndex = meta.ruleIndex + 1;
|
|
871
|
+
|
|
872
|
+
setTreeLoading(true);
|
|
873
|
+
try {
|
|
874
|
+
let children: TreeNode[] = [];
|
|
875
|
+
let total = 0;
|
|
876
|
+
|
|
877
|
+
if (nextRuleIndex < activeGroupingRules.length) {
|
|
878
|
+
|
|
879
|
+
const { response } = await runGroupedQuery(
|
|
880
|
+
nextRuleIndex,
|
|
881
|
+
meta.groupPath || [],
|
|
882
|
+
offset,
|
|
883
|
+
limit
|
|
884
|
+
);
|
|
885
|
+
children = buildGroupNodes(
|
|
886
|
+
response?.groupMeta || [],
|
|
887
|
+
nextRuleIndex,
|
|
888
|
+
meta.groupPath || [],
|
|
889
|
+
nodeKey
|
|
890
|
+
);
|
|
891
|
+
total = response?.meta.totalRecords ?? 0;
|
|
892
|
+
} else {
|
|
893
|
+
const { response } = await runLeafQuery(meta.groupPath || [], offset, limit);
|
|
894
|
+
children = buildRecordNodes(response?.records || [], nodeKey, meta.groupPath || []);
|
|
895
|
+
total = response?.meta.totalRecords ?? 0;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
setTreeNodes((prev) => updateNodeChildren(prev, nodeKey, children));
|
|
899
|
+
setPagination(nodeKey, { offset, total });
|
|
900
|
+
|
|
901
|
+
// Collapse all immediate children's expanded state so stale
|
|
902
|
+
// sub-trees don't appear open with no data after pagination.
|
|
903
|
+
if (offset !== 0 || children.length > 0) {
|
|
904
|
+
setExpandedKeys((prevKeys: any) => {
|
|
905
|
+
const next = { ...prevKeys };
|
|
906
|
+
Object.keys(next).forEach((k) => {
|
|
907
|
+
if (k !== nodeKey && k.startsWith(`${nodeKey}-`)) {
|
|
908
|
+
delete next[k];
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
return next;
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
// Also wipe pagination state for all descendant keys so page
|
|
915
|
+
// counters don't carry over to the newly loaded children.
|
|
916
|
+
setPaginationMap((prevMap) => {
|
|
917
|
+
const next = { ...prevMap };
|
|
918
|
+
Object.keys(next).forEach((k) => {
|
|
919
|
+
if (k !== nodeKey && k.startsWith(`${nodeKey}-`)) {
|
|
920
|
+
delete next[k];
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
return next;
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
} catch (error: any) {
|
|
927
|
+
toast.current?.show({
|
|
928
|
+
severity: "error",
|
|
929
|
+
summary: "Failed to expand node",
|
|
930
|
+
detail: error?.data?.message || error?.message || "Unable to load children",
|
|
931
|
+
life: 4000,
|
|
932
|
+
});
|
|
933
|
+
setTreeNodes((prev) => updateNodeChildren(prev, nodeKey, []));
|
|
934
|
+
} finally {
|
|
935
|
+
setTreeLoading(false);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
const handleNodeExpand = async (event: any) => {
|
|
940
|
+
const node: TreeNode | undefined = event?.node;
|
|
941
|
+
if (!node) return;
|
|
942
|
+
const nodeKey = String(node.key);
|
|
943
|
+
|
|
944
|
+
// If already loaded (has children) don't re-fetch, just expand
|
|
945
|
+
const alreadyLoaded = node.children && node.children.length > 0;
|
|
946
|
+
|
|
947
|
+
if (!alreadyLoaded) {
|
|
948
|
+
await loadNodeChildren(node, 0);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
// If this node is checked, propagate selection to its freshly loaded children.
|
|
953
|
+
const isChecked = selectedNodeKeys?.[nodeKey]?.checked === true;
|
|
954
|
+
if (!isChecked) return;
|
|
955
|
+
|
|
956
|
+
// Use setTreeNodes callback to read the latest tree state after loadNodeChildren
|
|
957
|
+
// has updated it, then select all immediate children.
|
|
958
|
+
setTreeNodes((currentNodes) => {
|
|
959
|
+
const parentNode = findNodeByKey(currentNodes, nodeKey);
|
|
960
|
+
if (!parentNode?.children?.length) return currentNodes;
|
|
961
|
+
|
|
962
|
+
setSelectedNodeKeys((prevKeys: any) => {
|
|
963
|
+
const next = { ...prevKeys };
|
|
964
|
+
parentNode.children!.forEach((child) => {
|
|
965
|
+
next[String(child.key)] = {
|
|
966
|
+
checked: true,
|
|
967
|
+
partialChecked: false,
|
|
968
|
+
nodeType: child.data?.__treeMeta?.nodeType, // already on the node
|
|
969
|
+
};
|
|
970
|
+
});
|
|
971
|
+
return next;
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
return currentNodes; // tree shape unchanged, we only need the read
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// ─── Pagination click handlers ────────────────────────────────────────────
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Navigate root-level groups to next/prev page.
|
|
984
|
+
*/
|
|
985
|
+
const handleRootPageChange = async (direction: "prev" | "next") => {
|
|
986
|
+
const { offset, limit } = getPagination("root");
|
|
987
|
+
const nextOffset = direction === "prev"
|
|
988
|
+
? Math.max(0, offset - limit)
|
|
989
|
+
: offset + limit;
|
|
990
|
+
await loadRootGroups(nextOffset);
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Navigate a node's children to next/prev page.
|
|
995
|
+
* We need to find the node in the tree by key to call loadNodeChildren.
|
|
996
|
+
*/
|
|
997
|
+
const findNodeByKey = (nodes: TreeNode[], key: string): TreeNode | null => {
|
|
998
|
+
for (const node of nodes) {
|
|
999
|
+
if (node.key === key) return node;
|
|
1000
|
+
if (node.children) {
|
|
1001
|
+
const found = findNodeByKey(node.children, key);
|
|
1002
|
+
if (found) return found;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const handleNodePageChange = async (nodeKey: string, direction: "prev" | "next") => {
|
|
1009
|
+
const { offset, limit } = getPagination(nodeKey);
|
|
1010
|
+
const nextOffset = direction === "prev"
|
|
1011
|
+
? Math.max(0, offset - limit)
|
|
1012
|
+
: offset + limit;
|
|
1013
|
+
|
|
1014
|
+
const node = findNodeByKey(treeNodes, nodeKey);
|
|
1015
|
+
if (!node) return;
|
|
1016
|
+
|
|
1017
|
+
await loadNodeChildren(node, nextOffset);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
// ─── Filter handler ───────────────────────────────────────────────────────
|
|
1021
|
+
|
|
1022
|
+
const handleApplyCustomFilter = (nextFilterPredicates: any, persistFilter = false) => {
|
|
1023
|
+
const queryfilter = structuredClone(params.customFilter) || { $and: [] };
|
|
1024
|
+
|
|
1025
|
+
if (nextFilterPredicates?.custom_filter_predicate) queryfilter.$and.push(nextFilterPredicates.custom_filter_predicate);
|
|
1026
|
+
if (nextFilterPredicates?.search_predicate) queryfilter.$and.push(nextFilterPredicates.search_predicate);
|
|
1027
|
+
if (nextFilterPredicates?.saved_filter_predicate) queryfilter.$and.push(nextFilterPredicates.saved_filter_predicate);
|
|
1028
|
+
if (nextFilterPredicates?.predefined_search_predicate) queryfilter.$and.push(nextFilterPredicates.predefined_search_predicate);
|
|
1029
|
+
|
|
1030
|
+
latestFiltersRef.current = queryfilter;
|
|
1031
|
+
|
|
1032
|
+
const updatedFilterPredicates = structuredClone(nextFilterPredicates || {});
|
|
1033
|
+
updatedFilterPredicates.persistFilter = persistFilter;
|
|
1034
|
+
latestFilterPredicatesRef.current = updatedFilterPredicates;
|
|
1035
|
+
|
|
1036
|
+
setFilters(queryfilter);
|
|
1037
|
+
setFilterPredicates(updatedFilterPredicates);
|
|
1038
|
+
|
|
1039
|
+
const grouping_rules = updatedFilterPredicates.grouping_rules;
|
|
1040
|
+
const aggregation_rules = updatedFilterPredicates.aggregation_rules;
|
|
1041
|
+
|
|
1042
|
+
setGroupingRules(Array.isArray(grouping_rules) ? grouping_rules : []);
|
|
1043
|
+
setAggregationRules(Array.isArray(aggregation_rules) ? aggregation_rules : []);
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// ─── Bulk delete ──────────────────────────────────────────────────────────
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
// handle bulk deletion
|
|
1050
|
+
const deleteBulk = () => {
|
|
1051
|
+
let deleteList: any = [];
|
|
1052
|
+
selectedRecords.forEach((element: any) => {
|
|
1053
|
+
deleteList.push(element.id);
|
|
1054
|
+
});
|
|
1055
|
+
deleteManySolidEntities(deleteList)
|
|
1056
|
+
.unwrap()
|
|
1057
|
+
.then(() => {
|
|
1058
|
+
toast.current?.show({
|
|
1059
|
+
severity: 'success',
|
|
1060
|
+
summary: 'Deleted',
|
|
1061
|
+
detail: ERROR_MESSAGES.RECORD_DELETE,
|
|
1062
|
+
life: 3000
|
|
1063
|
+
});
|
|
1064
|
+
setDeleteRecordsDialogVisible(false);
|
|
1065
|
+
})
|
|
1066
|
+
.catch((error) => {
|
|
1067
|
+
toast.current?.show({
|
|
1068
|
+
severity: 'error',
|
|
1069
|
+
summary: 'Delete Failed',
|
|
1070
|
+
detail: error?.data?.message,
|
|
1071
|
+
life: 4000
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// handle closing of the delete dialog...
|
|
1077
|
+
const onDeleteClose = () => {
|
|
1078
|
+
setDeleteRecordsDialogVisible(false);
|
|
1079
|
+
setSelectedRecords([]);
|
|
1080
|
+
setSelectedRecoverRecords([]);
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
const recoverAll = () => {
|
|
1085
|
+
let recoverList: any = [];
|
|
1086
|
+
selectedRecoverRecords.forEach((element: any) => {
|
|
1087
|
+
recoverList.push(element.id);
|
|
1088
|
+
});
|
|
1089
|
+
triggerRecoverSolidEntities(recoverList);
|
|
1090
|
+
setRecoverDialogVisible(false);
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
const handleFetchUpdatedRecords = () => {
|
|
1095
|
+
// setQueryString();
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
// ─── Column rendering ─────────────────────────────────────────────────────
|
|
1099
|
+
|
|
1100
|
+
const renderColumnsDynamically = () => {
|
|
1101
|
+
if (!solidTreeViewMetaData?.data || !solidTreeViewLayout) return null;
|
|
1102
|
+
|
|
1103
|
+
const solidFieldsMetadata = solidTreeViewMetaData.data.solidFieldsMetadata;
|
|
1104
|
+
if (!solidFieldsMetadata) return null;
|
|
1105
|
+
|
|
1106
|
+
return solidTreeViewLayout.children?.map((column: any) => {
|
|
1107
|
+
const fieldMetadata = solidFieldsMetadata[column.attrs.name];
|
|
1108
|
+
if (!fieldMetadata) return null;
|
|
1109
|
+
|
|
1110
|
+
const visibleToRole = column?.attrs?.roles || [];
|
|
1111
|
+
if (visibleToRole.length > 0 && !hasAnyRole(user?.roles, visibleToRole)) return null;
|
|
1112
|
+
|
|
1113
|
+
const listColumn = SolidListViewColumn({
|
|
1114
|
+
solidListViewMetaData: solidTreeViewMetaData,
|
|
1115
|
+
fieldMetadata,
|
|
1116
|
+
column,
|
|
1117
|
+
setLightboxUrls: () => { },
|
|
1118
|
+
setOpenLightbox: () => { },
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
if (!React.isValidElement(listColumn)) return null;
|
|
1122
|
+
|
|
1123
|
+
const originalBody = (listColumn as any)?.props?.body;
|
|
1124
|
+
const originalProps = (listColumn as any)?.props || {};
|
|
1125
|
+
const mergedColumnStyle = {
|
|
1126
|
+
minWidth: "12rem",
|
|
1127
|
+
...(originalProps.style || {}),
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
return (
|
|
1131
|
+
<Column
|
|
1132
|
+
key={`tree-col-${fieldMetadata.name}`}
|
|
1133
|
+
field={originalProps.field ?? fieldMetadata.name}
|
|
1134
|
+
header={originalProps.header}
|
|
1135
|
+
// sortable
|
|
1136
|
+
style={mergedColumnStyle}
|
|
1137
|
+
className={originalProps.className}
|
|
1138
|
+
headerClassName={originalProps.headerClassName}
|
|
1139
|
+
bodyClassName={originalProps.bodyClassName}
|
|
1140
|
+
align={originalProps.align}
|
|
1141
|
+
alignHeader={originalProps.alignHeader}
|
|
1142
|
+
body={(node: any, options: any) => {
|
|
1143
|
+
const rowData = node?.data ?? node;
|
|
1144
|
+
const nodeMeta = rowData?.__treeMeta;
|
|
1145
|
+
|
|
1146
|
+
if (nodeMeta?.nodeType === "group") return <span> </span>;
|
|
1147
|
+
|
|
1148
|
+
if (typeof originalBody === "function") return originalBody(rowData, options);
|
|
1149
|
+
|
|
1150
|
+
return rowData?.[fieldMetadata.name] ?? <span> </span>;
|
|
1151
|
+
}}
|
|
1152
|
+
/>
|
|
1153
|
+
);
|
|
1154
|
+
});
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const renderAggregateColumns = () => {
|
|
1158
|
+
if (activeGroupingRules.length === 0) return null;
|
|
1159
|
+
|
|
1160
|
+
const derivedAggregates = buildAggregates();
|
|
1161
|
+
// derivedAggregates is an array like ["id:count", "price:sum"]
|
|
1162
|
+
// We want to render columns for each of these.
|
|
1163
|
+
|
|
1164
|
+
return derivedAggregates.map((agg) => {
|
|
1165
|
+
const [field, operator] = agg.split(":");
|
|
1166
|
+
const responseKey = `${field}_${operator}`;
|
|
1167
|
+
const header = formatHeader(responseKey);
|
|
1168
|
+
|
|
1169
|
+
return (
|
|
1170
|
+
<Column
|
|
1171
|
+
key={`agg-col-${agg}`}
|
|
1172
|
+
field={responseKey}
|
|
1173
|
+
header={header}
|
|
1174
|
+
sortable
|
|
1175
|
+
style={{ minWidth: "8rem" }}
|
|
1176
|
+
body={(node: any) => {
|
|
1177
|
+
const rowData = node?.data ?? node;
|
|
1178
|
+
const nodeMeta = rowData?.__treeMeta;
|
|
1179
|
+
|
|
1180
|
+
if (nodeMeta?.nodeType !== "group") return <span> </span>;
|
|
1181
|
+
|
|
1182
|
+
const value = nodeMeta?.aggregates?.[responseKey];
|
|
1183
|
+
return <span>{value ?? 0}</span>;
|
|
1184
|
+
}}
|
|
1185
|
+
/>
|
|
1186
|
+
);
|
|
1187
|
+
});
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// ─── Group column body: label + child pagination controls ─────────────────
|
|
1191
|
+
|
|
1192
|
+
const groupColumnBody = (node: TreeNode) => {
|
|
1193
|
+
const rowData = node?.data as TreeRowData;
|
|
1194
|
+
const nodeMeta = rowData?.__treeMeta;
|
|
1195
|
+
|
|
1196
|
+
if (nodeMeta?.nodeType !== "group") return <span> </span>;
|
|
1197
|
+
|
|
1198
|
+
const label = nodeMeta.groupLabel ?? "";
|
|
1199
|
+
const truncateAfter = 30;
|
|
1200
|
+
return (
|
|
1201
|
+
<div className="flex align-items-center">
|
|
1202
|
+
<div
|
|
1203
|
+
className="solid-table-row"
|
|
1204
|
+
style={{ maxWidth: `${truncateAfter}ch` }}
|
|
1205
|
+
// title={truncateAfter ? displayValue : undefined}
|
|
1206
|
+
>
|
|
1207
|
+
<span className="font-semibold">{label}</span>
|
|
1208
|
+
</div>
|
|
1209
|
+
{truncateAfter && label.length > truncateAfter &&
|
|
1210
|
+
<>
|
|
1211
|
+
<Tooltip target=".solid-field-tooltip-icon" />
|
|
1212
|
+
<i className="pi pi-info-circle solid-field-tooltip-icon"
|
|
1213
|
+
data-pr-tooltip={label}
|
|
1214
|
+
/>
|
|
1215
|
+
</>
|
|
1216
|
+
}
|
|
1217
|
+
</div>
|
|
1218
|
+
);
|
|
1219
|
+
};
|
|
1220
|
+
|
|
1221
|
+
// ─── Root pagination bar ──────────────────────────────────────────────────
|
|
1222
|
+
|
|
1223
|
+
const RootPaginationBar = () => {
|
|
1224
|
+
if (activeGroupingRules.length === 0) return null;
|
|
1225
|
+
|
|
1226
|
+
const { offset, total } = getPagination("root");
|
|
1227
|
+
const currentPage = Math.floor(offset / globalLimit) + 1;
|
|
1228
|
+
const rootHasPrev = hasPrev("root");
|
|
1229
|
+
const rootHasNext = hasNext("root");
|
|
1230
|
+
|
|
1231
|
+
if (!rootHasPrev && !rootHasNext) return null;
|
|
1232
|
+
|
|
1233
|
+
return (
|
|
1234
|
+
<div style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", borderTop: "1px solid var(--surface-border)" }}>
|
|
1235
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.5rem 0.75rem" }}>
|
|
1236
|
+
<span className="text-sm text-color-secondary">Items per page</span>
|
|
1237
|
+
<Dropdown
|
|
1238
|
+
value={globalLimit}
|
|
1239
|
+
options={pageSizeOptions}
|
|
1240
|
+
onChange={(e) => {
|
|
1241
|
+
setGlobalLimit(e.value);
|
|
1242
|
+
}}
|
|
1243
|
+
className="solid-page-size-dropdown"
|
|
1244
|
+
style={{ height: '2rem', display: 'flex', alignItems: 'center' }}
|
|
1245
|
+
/>
|
|
1246
|
+
</div>
|
|
1247
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.5rem 0.75rem" }}>
|
|
1248
|
+
<span className="text-sm text-color-secondary">{offset + 1}–{Math.min(offset + globalLimit, total)} of {total}</span>
|
|
1249
|
+
<Button
|
|
1250
|
+
type="button"
|
|
1251
|
+
icon="pi pi-angle-left"
|
|
1252
|
+
size="small"
|
|
1253
|
+
outlined
|
|
1254
|
+
severity="secondary"
|
|
1255
|
+
disabled={!rootHasPrev || treeLoading}
|
|
1256
|
+
onClick={() => handleRootPageChange("prev")}
|
|
1257
|
+
style={{ padding: 0, border: "none", width: "2rem" }}
|
|
1258
|
+
className="small-button"
|
|
1259
|
+
/>
|
|
1260
|
+
<Button
|
|
1261
|
+
type="button"
|
|
1262
|
+
icon="pi pi-angle-right"
|
|
1263
|
+
iconPos="right"
|
|
1264
|
+
size="small"
|
|
1265
|
+
outlined
|
|
1266
|
+
severity="secondary"
|
|
1267
|
+
disabled={!rootHasNext || treeLoading}
|
|
1268
|
+
onClick={() => handleRootPageChange("next")}
|
|
1269
|
+
style={{ padding: 0, border: "none", width: "2rem" }}
|
|
1270
|
+
className="small-button"
|
|
1271
|
+
/>
|
|
1272
|
+
</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
);
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
// ─── Imperative handle ────────────────────────────────────────────────────
|
|
1278
|
+
|
|
1279
|
+
useImperativeHandle(ref, () => ({
|
|
1280
|
+
refresh: () => { void loadRootGroups(getPagination("root").offset); },
|
|
1281
|
+
clearFilters: () => {
|
|
1282
|
+
setFilters(params.customFilter || { $and: [] });
|
|
1283
|
+
solidGlobalSearchElementRef.current?.clearFilter?.();
|
|
1284
|
+
},
|
|
1285
|
+
applyFilter: (filter) => { handleApplyCustomFilter(filter); },
|
|
1286
|
+
setPagination: () => { /* pagination wired via paginationMap */ },
|
|
1287
|
+
setSort: (nextSortField: string, nextSortOrder: 1 | -1 | 0) => {
|
|
1288
|
+
setSortField(nextSortField);
|
|
1289
|
+
setSortOrder(nextSortOrder);
|
|
1290
|
+
},
|
|
1291
|
+
setShowArchived: () => { /* archived toggle for grouped tree will be wired later */ },
|
|
1292
|
+
getState: () => ({
|
|
1293
|
+
first: getPagination("root").offset,
|
|
1294
|
+
rows: getPagination("root").limit,
|
|
1295
|
+
sortField,
|
|
1296
|
+
sortOrder: sortOrder as any,
|
|
1297
|
+
showArchived: false,
|
|
1298
|
+
filters,
|
|
1299
|
+
filterPredicates,
|
|
1300
|
+
listData: treeNodes,
|
|
1301
|
+
totalRecords: getPagination("root").total,
|
|
1302
|
+
loading: treeLoading,
|
|
1303
|
+
}),
|
|
1304
|
+
}), [filters, filterPredicates, params.customFilter, treeLoading, treeNodes, paginationMap]);
|
|
1305
|
+
|
|
1306
|
+
// ─── Render ───────────────────────────────────────────────────────────────
|
|
1307
|
+
|
|
1308
|
+
return (
|
|
1309
|
+
<div className="page-parent-wrapper">
|
|
1310
|
+
<Toast ref={toast} />
|
|
1311
|
+
|
|
1312
|
+
{/* ── Header ── */}
|
|
1313
|
+
<div className="page-header flex-column lg:flex-row">
|
|
1314
|
+
<div className="flex justify-content-between w-full">
|
|
1315
|
+
<div className="flex align-items-center solid-header-buttons-wrapper">
|
|
1316
|
+
{params.embeded !== true && (
|
|
1317
|
+
<div className="apps-icon block md:hidden cursor-pointer" onClick={toggleBothSidebars}>
|
|
1318
|
+
<i className="pi pi-th-large"></i>
|
|
1319
|
+
</div>
|
|
1320
|
+
)}
|
|
1321
|
+
|
|
1322
|
+
<p className="m-0 view-title solid-text-wrapper">{treeViewTitle}</p>
|
|
1323
|
+
|
|
1324
|
+
{solidTreeViewMetaData?.data?.solidView?.layout?.attrs.enableGlobalSearch === true && (
|
|
1325
|
+
<div className="hidden lg:flex">
|
|
1326
|
+
<SolidGlobalSearchElement
|
|
1327
|
+
viewType="tree"
|
|
1328
|
+
showSaveFilterPopup={showSaveFilterPopup}
|
|
1329
|
+
setShowSaveFilterPopup={setShowSaveFilterPopup}
|
|
1330
|
+
ref={solidGlobalSearchElementRef}
|
|
1331
|
+
viewData={solidTreeViewMetaData}
|
|
1332
|
+
handleApplyCustomFilter={handleApplyCustomFilter}
|
|
1333
|
+
/>
|
|
1334
|
+
</div>
|
|
1335
|
+
)}
|
|
1336
|
+
</div>
|
|
1337
|
+
|
|
1338
|
+
<div className="flex align-items-center solid-header-buttons-wrapper">
|
|
1339
|
+
{solidTreeViewMetaData?.data?.solidView?.layout?.attrs.enableGlobalSearch === true && (
|
|
1340
|
+
<div className="flex lg:hidden">
|
|
1341
|
+
<Button
|
|
1342
|
+
type="button"
|
|
1343
|
+
size="small"
|
|
1344
|
+
icon="pi pi-search"
|
|
1345
|
+
severity="secondary"
|
|
1346
|
+
outlined
|
|
1347
|
+
className="solid-icon-button"
|
|
1348
|
+
onClick={() => setShowGlobalSearchElement(!showGlobalSearchElement)}
|
|
1349
|
+
/>
|
|
1350
|
+
</div>
|
|
1351
|
+
)}
|
|
1352
|
+
|
|
1353
|
+
{actionsAllowed.includes(`${permissionExpression(params.modelName, "create")}`) &&
|
|
1354
|
+
solidTreeViewMetaData?.data?.solidView?.layout?.attrs.create !== false && (
|
|
1355
|
+
<SolidCreateButton
|
|
1356
|
+
createButtonUrl={createButtonUrl}
|
|
1357
|
+
createActionQueryParams={createActionQueryParams}
|
|
1358
|
+
responsiveIconOnly={true}
|
|
1359
|
+
/>
|
|
1360
|
+
)}
|
|
1361
|
+
|
|
1362
|
+
{actionsAllowed.includes(`${permissionExpression(params.modelName, "delete")}`) &&
|
|
1363
|
+
solidTreeViewMetaData?.data?.solidView?.layout?.attrs.delete !== false &&
|
|
1364
|
+
selectedRecords.length > 0 && (
|
|
1365
|
+
<Button
|
|
1366
|
+
type="button"
|
|
1367
|
+
label="Delete"
|
|
1368
|
+
size="small"
|
|
1369
|
+
onClick={() => setDeleteRecordsDialogVisible(true)}
|
|
1370
|
+
className="small-button"
|
|
1371
|
+
severity="danger"
|
|
1372
|
+
/>
|
|
1373
|
+
)}
|
|
1374
|
+
|
|
1375
|
+
<Button
|
|
1376
|
+
type="button"
|
|
1377
|
+
size="small"
|
|
1378
|
+
icon="pi pi-refresh"
|
|
1379
|
+
severity="secondary"
|
|
1380
|
+
className="solid-icon-button"
|
|
1381
|
+
outlined
|
|
1382
|
+
onClick={() => { void loadRootGroups(getPagination("root").offset); }}
|
|
1383
|
+
/>
|
|
1384
|
+
{showArchived && (
|
|
1385
|
+
<Button
|
|
1386
|
+
type="button"
|
|
1387
|
+
icon="pi pi-refresh"
|
|
1388
|
+
label="Recover"
|
|
1389
|
+
size="small"
|
|
1390
|
+
severity="secondary"
|
|
1391
|
+
className="hidden lg:flex solid-icon-button "
|
|
1392
|
+
onClick={() => setRecoverDialogVisible(true)}
|
|
1393
|
+
></Button>
|
|
1394
|
+
)}
|
|
1395
|
+
|
|
1396
|
+
{params.embeded === false &&
|
|
1397
|
+
solidTreeViewLayout?.attrs?.configureView !== false && (
|
|
1398
|
+
<SolidListViewConfigure
|
|
1399
|
+
listViewMetaData={solidTreeViewMetaData}
|
|
1400
|
+
solidListViewLayout={solidTreeViewLayout}
|
|
1401
|
+
setShowArchived={setShowArchived}
|
|
1402
|
+
showArchived={showArchived}
|
|
1403
|
+
viewData={solidTreeViewMetaData}
|
|
1404
|
+
sizeOptions={sizeOptions}
|
|
1405
|
+
setSize={setSize}
|
|
1406
|
+
size={size}
|
|
1407
|
+
viewModes={viewModes}
|
|
1408
|
+
params={params}
|
|
1409
|
+
actionsAllowed={actionsAllowed}
|
|
1410
|
+
selectedRecords={selectedRecords}
|
|
1411
|
+
setDialogVisible={setDeleteRecordsDialogVisible}
|
|
1412
|
+
setShowSaveFilterPopup={setShowSaveFilterPopup}
|
|
1413
|
+
filters={filters}
|
|
1414
|
+
handleFetchUpdatedRecords={handleFetchUpdatedRecords}
|
|
1415
|
+
setRecoverDialogVisible={setRecoverDialogVisible}
|
|
1416
|
+
/>
|
|
1417
|
+
)}
|
|
1418
|
+
</div>
|
|
1419
|
+
</div>
|
|
1420
|
+
|
|
1421
|
+
{solidTreeViewMetaData?.data?.solidView?.layout?.attrs.enableGlobalSearch === true &&
|
|
1422
|
+
showGlobalSearchElement && (
|
|
1423
|
+
<div className="flex lg:hidden">
|
|
1424
|
+
<SolidGlobalSearchElement
|
|
1425
|
+
viewType="tree"
|
|
1426
|
+
showSaveFilterPopup={showSaveFilterPopup}
|
|
1427
|
+
setShowSaveFilterPopup={setShowSaveFilterPopup}
|
|
1428
|
+
ref={solidGlobalSearchElementRef}
|
|
1429
|
+
viewData={solidTreeViewMetaData}
|
|
1430
|
+
handleApplyCustomFilter={handleApplyCustomFilter}
|
|
1431
|
+
/>
|
|
1432
|
+
</div>
|
|
1433
|
+
)}
|
|
1434
|
+
</div>
|
|
1435
|
+
|
|
1436
|
+
<style>{`
|
|
1437
|
+
|
|
1438
|
+
`}</style>
|
|
1439
|
+
|
|
1440
|
+
{/* ── Tree table ── */}
|
|
1441
|
+
<div className="solid-datatable-wrapper solid-treetable-wrapper flex-1 min-h-0 overflow-auto">
|
|
1442
|
+
{activeGroupingRules.length === 0 ? (
|
|
1443
|
+
<div className="flex flex-column align-items-center justify-content-center h-full p-6 text-center">
|
|
1444
|
+
<div className="mb-4" style={{ opacity: 0.1 }}>
|
|
1445
|
+
<HomePageModuleSvg />
|
|
1446
|
+
</div>
|
|
1447
|
+
<h3 className="m-0 mb-2" style={{ color: "var(--solid-dark-title)", fontWeight: 700, fontSize: '1.5rem' }}>
|
|
1448
|
+
Tree View
|
|
1449
|
+
</h3>
|
|
1450
|
+
<p className="m-0 text-sl" style={{ maxWidth: '35rem', lineHeight: '1.5', color: 'var(--text-color)' }}>
|
|
1451
|
+
To visualize your data in a hierarchical structure, please apply a <strong>Grouping Rule</strong> from the Global Search bar above.
|
|
1452
|
+
</p>
|
|
1453
|
+
</div>
|
|
1454
|
+
|
|
1455
|
+
) : (
|
|
1456
|
+
<TreeTable
|
|
1457
|
+
value={treeNodes}
|
|
1458
|
+
lazy
|
|
1459
|
+
loading={treeLoading}
|
|
1460
|
+
expandedKeys={expandedKeys}
|
|
1461
|
+
onToggle={(event: any) => setExpandedKeys(event.value)}
|
|
1462
|
+
onExpand={handleNodeExpand}
|
|
1463
|
+
scrollable
|
|
1464
|
+
tableStyle={{ minWidth: "max-content" }}
|
|
1465
|
+
tableClassName="solid-data-table"
|
|
1466
|
+
resizableColumns
|
|
1467
|
+
columnResizeMode="expand"
|
|
1468
|
+
selectionMode="checkbox"
|
|
1469
|
+
selectionKeys={selectedNodeKeys}
|
|
1470
|
+
sortField={sortField}
|
|
1471
|
+
sortOrder={sortOrder as any}
|
|
1472
|
+
removableSort
|
|
1473
|
+
onSort={(e) => {
|
|
1474
|
+
setSortField(e.sortField);
|
|
1475
|
+
setSortOrder(e.sortOrder as any);
|
|
1476
|
+
}}
|
|
1477
|
+
onSelectionChange={(e) => {
|
|
1478
|
+
const incoming = e.value as Record<string, any>;
|
|
1479
|
+
|
|
1480
|
+
setSelectedNodeKeys((prev: any) => {
|
|
1481
|
+
const next: Record<string, any> = {};
|
|
1482
|
+
|
|
1483
|
+
Object.keys(incoming).forEach((key) => {
|
|
1484
|
+
// Find the node to get its type
|
|
1485
|
+
const node = findNodeByKey(treeNodes, key);
|
|
1486
|
+
next[key] = {
|
|
1487
|
+
...incoming[key], // checked, partialChecked from PrimeReact
|
|
1488
|
+
nodeType: node?.data?.__treeMeta?.nodeType // add type from tree
|
|
1489
|
+
?? prev[key]?.nodeType, // fallback to prev if node not found
|
|
1490
|
+
};
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
return next;
|
|
1494
|
+
});
|
|
1495
|
+
}}
|
|
1496
|
+
|
|
1497
|
+
>
|
|
1498
|
+
<Column
|
|
1499
|
+
key="tree-group-column"
|
|
1500
|
+
field="__group"
|
|
1501
|
+
header="Group"
|
|
1502
|
+
sortable
|
|
1503
|
+
expander={(node: any) => node?.data?.__treeMeta?.nodeType === "group"}
|
|
1504
|
+
body={groupColumnBody}
|
|
1505
|
+
|
|
1506
|
+
style={{ minWidth: "18rem", display: "flex", alignItems: "center" }}
|
|
1507
|
+
/>
|
|
1508
|
+
|
|
1509
|
+
{renderColumnsDynamically()}
|
|
1510
|
+
{renderAggregateColumns()}
|
|
1511
|
+
|
|
1512
|
+
<Column
|
|
1513
|
+
key="tree-last-frozen-column"
|
|
1514
|
+
header=""
|
|
1515
|
+
style={{ width: "20rem" }}
|
|
1516
|
+
body={(node: any) => {
|
|
1517
|
+
const rowData = node?.data as TreeRowData;
|
|
1518
|
+
const nodeMeta = rowData?.__treeMeta;
|
|
1519
|
+
if (nodeMeta?.nodeType !== "group") return <span> </span>;
|
|
1520
|
+
|
|
1521
|
+
const nodeKey = String(node.key);
|
|
1522
|
+
const isExpanded = expandedKeys[nodeKey];
|
|
1523
|
+
const childrenLoaded = isExpanded && node.children && node.children.length > 0;
|
|
1524
|
+
if (!childrenLoaded) return <span> </span>;
|
|
1525
|
+
|
|
1526
|
+
const pagEntry = getPagination(nodeKey);
|
|
1527
|
+
const canPrev = hasPrev(nodeKey);
|
|
1528
|
+
const canNext = hasNext(nodeKey);
|
|
1529
|
+
|
|
1530
|
+
// "in Jharkhand" — this node's own group label
|
|
1531
|
+
const inLabel = nodeMeta.groupLabel ?? "";
|
|
1532
|
+
|
|
1533
|
+
// "of cities" — what the children represent
|
|
1534
|
+
// nextRuleIndex = nodeMeta.ruleIndex + 1
|
|
1535
|
+
const nextRuleIndex = nodeMeta.ruleIndex + 1;
|
|
1536
|
+
const isLeafLevel = nextRuleIndex >= activeGroupingRules.length;
|
|
1537
|
+
const ofLabel = isLeafLevel
|
|
1538
|
+
? solidTreeViewMetaData?.data?.solidView?.model?.displayName // leaf → model name
|
|
1539
|
+
: (() => {
|
|
1540
|
+
const nextRule = activeGroupingRules[nextRuleIndex];
|
|
1541
|
+
const fieldName = String(nextRule?.fieldName ?? "");
|
|
1542
|
+
const fieldMeta = getFieldMetadata(fieldName);
|
|
1543
|
+
// Use displayName if available, fallback to fieldName
|
|
1544
|
+
return fieldMeta?.displayName ?? fieldMeta?.name ?? fieldName;
|
|
1545
|
+
})();
|
|
1546
|
+
|
|
1547
|
+
return (
|
|
1548
|
+
<div
|
|
1549
|
+
style={{ display: "flex", alignItems: "center", gap: "0.2rem", justifyContent: "flex-end" }}
|
|
1550
|
+
onClick={(e) => e.stopPropagation()}
|
|
1551
|
+
>
|
|
1552
|
+
{/* <span style={{ fontSize: "0.9rem", color: "var(--text-color-secondary)", whiteSpace: "nowrap" }}>
|
|
1553
|
+
{pagEntry.offset + 1}–{Math.min(pagEntry.offset + pagEntry.limit, pagEntry.total)} of {pagEntry.total} {getSingularAndPlural(ofLabel).toPlural ?? ofLabel} in {inLabel}
|
|
1554
|
+
</span> */}
|
|
1555
|
+
<Button
|
|
1556
|
+
type="button"
|
|
1557
|
+
icon="pi pi-angle-left"
|
|
1558
|
+
size="small"
|
|
1559
|
+
rounded
|
|
1560
|
+
outlined
|
|
1561
|
+
disabled={!canPrev || treeLoading}
|
|
1562
|
+
style={{ padding: 0, border: "none", width: "2rem" }}
|
|
1563
|
+
className="small-button"
|
|
1564
|
+
onClick={() => handleNodePageChange(nodeKey, "prev")}
|
|
1565
|
+
/>
|
|
1566
|
+
<Button
|
|
1567
|
+
type="button"
|
|
1568
|
+
icon="pi pi-angle-right"
|
|
1569
|
+
size="small"
|
|
1570
|
+
rounded
|
|
1571
|
+
outlined
|
|
1572
|
+
disabled={!canNext || treeLoading}
|
|
1573
|
+
style={{ padding: 0, border: "none", width: "2rem" }}
|
|
1574
|
+
className="small-button"
|
|
1575
|
+
onClick={() => handleNodePageChange(nodeKey, "next")}
|
|
1576
|
+
/>
|
|
1577
|
+
</div>
|
|
1578
|
+
);
|
|
1579
|
+
}}
|
|
1580
|
+
/>
|
|
1581
|
+
</TreeTable>
|
|
1582
|
+
)}
|
|
1583
|
+
</div>
|
|
1584
|
+
|
|
1585
|
+
{/* ── Root-level pagination bar ── */}
|
|
1586
|
+
<RootPaginationBar />
|
|
1587
|
+
|
|
1588
|
+
{/* ── Delete dialog ── */}
|
|
1589
|
+
<Dialog
|
|
1590
|
+
visible={isDeleteRecordsDialogVisible}
|
|
1591
|
+
header="Confirm Delete"
|
|
1592
|
+
onHide={() => setDeleteRecordsDialogVisible(false)}
|
|
1593
|
+
headerClassName="py-2"
|
|
1594
|
+
contentClassName="px-0 pb-0"
|
|
1595
|
+
// style={{ width: '20vw' }}
|
|
1596
|
+
breakpoints={{ '1199px': '30rem', '550px': '85vw' }}
|
|
1597
|
+
>
|
|
1598
|
+
<Divider className="m-0" />
|
|
1599
|
+
<div className="p-4">
|
|
1600
|
+
<p className="m-0 solid-primary-title" style={{ fontSize: 16 }}>Are you sure you want to delete the selected records?</p>
|
|
1601
|
+
<div className="flex align-items-center gap-2 mt-3">
|
|
1602
|
+
<Button label="Delete" severity="danger" size="small" autoFocus onClick={deleteBulk} />
|
|
1603
|
+
<Button label="Cancel" size="small" onClick={onDeleteClose} outlined className='bg-primary-reverse' />
|
|
1604
|
+
</div>
|
|
1605
|
+
</div>
|
|
1606
|
+
</Dialog>
|
|
1607
|
+
<Dialog
|
|
1608
|
+
visible={isRecoverDialogVisible}
|
|
1609
|
+
header="Confirm Recover"
|
|
1610
|
+
modal
|
|
1611
|
+
className="solid-confirm-dialog"
|
|
1612
|
+
footer={() => (
|
|
1613
|
+
<div className="flex justify-content-center">
|
|
1614
|
+
<Button
|
|
1615
|
+
label="Yes"
|
|
1616
|
+
icon="pi pi-check"
|
|
1617
|
+
severity="danger"
|
|
1618
|
+
autoFocus
|
|
1619
|
+
onClick={recoverAll}
|
|
1620
|
+
/>
|
|
1621
|
+
<Button
|
|
1622
|
+
label="No"
|
|
1623
|
+
icon="pi pi-times"
|
|
1624
|
+
onClick={() => setRecoverDialogVisible(false)}
|
|
1625
|
+
/>
|
|
1626
|
+
</div>
|
|
1627
|
+
)}
|
|
1628
|
+
onHide={() => setRecoverDialogVisible(false)}
|
|
1629
|
+
>
|
|
1630
|
+
<p>Are you sure you want to recover all records?</p>
|
|
1631
|
+
</Dialog>
|
|
1632
|
+
|
|
1633
|
+
</div>
|
|
1634
|
+
);
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
SolidTreeView.displayName = "SolidTreeView";
|