diff --git a/src/actionbar/ShareUrlButton.tsx b/src/actionbar/ShareUrlButton.tsx
index 67b70a4cb..b9cf437e5 100644
--- a/src/actionbar/ShareUrlButton.tsx
+++ b/src/actionbar/ShareUrlButton.tsx
@@ -1,4 +1,4 @@
-import { useState, useCallback } from "react";
+import { useState, useCallback, useRef } from "react";
import { Button, Input, message, Space, Popover, theme } from "antd";
import { CopyOutlined, CheckOutlined } from "@ant-design/icons";
import { useLocale } from "@gisce/react-formiga-components";
@@ -15,6 +15,7 @@ export type ShareUrlButtonProps = {
};
export function ShareUrlButton({ res_id, searchParams }: ShareUrlButtonProps) {
+ const buttonRef = useRef(null);
const { currentView } = useActionViewContext();
const initialView = {
id: currentView.view_id,
@@ -118,12 +119,14 @@ export function ShareUrlButton({ res_id, searchParams }: ShareUrlButtonProps) {
return (
- }
- disabled={moreDataNeededForCopying}
- tooltip={t("share")}
- />
+
+
}
+ disabled={moreDataNeededForCopying}
+ tooltip={t("share")}
+ />
+
);
diff --git a/src/actionbar/TreeActionBar.tsx b/src/actionbar/TreeActionBar.tsx
index e01f5cd3f..4d790ceae 100644
--- a/src/actionbar/TreeActionBar.tsx
+++ b/src/actionbar/TreeActionBar.tsx
@@ -79,7 +79,7 @@ function TreeActionBarComponent({
limit,
totalItems,
isActive,
- isInfiniteTree,
+ treeType,
} = useContext(ActionViewContext) as ActionViewContextType;
const advancedExportEnabled = useFeatureIsEnabled(
@@ -183,12 +183,12 @@ function TreeActionBarComponent({
setSearchTreeNameSearch?.(searchString);
} else {
setSearchTreeNameSearch?.(undefined);
- if (!isInfiniteTree) {
+ if (treeType !== "infinite") {
searchTreeRef?.current?.refreshResults();
}
}
},
- [isInfiniteTree, searchTreeRef, setSearchTreeNameSearch],
+ [treeType, searchTreeRef, setSearchTreeNameSearch],
);
const handleExportAction = useCallback(
@@ -220,14 +220,14 @@ function TreeActionBarComponent({
);
useEffect(() => {
- if (isInfiniteTree && searchTreeNameSearch === undefined) {
+ if (treeType === "infinite" && searchTreeNameSearch === undefined) {
if (isFirstMount.current) {
isFirstMount.current = false;
return;
}
searchTreeRef?.current?.refreshResults();
}
- }, [isInfiniteTree, searchTreeNameSearch, searchTreeRef]);
+ }, [treeType, searchTreeNameSearch, searchTreeRef]);
useHotkeys(
"ctrl+l,command+l",
diff --git a/src/actionbar/useNextPrevious.ts b/src/actionbar/useNextPrevious.ts
index 4dceffa30..b198e7d62 100644
--- a/src/actionbar/useNextPrevious.ts
+++ b/src/actionbar/useNextPrevious.ts
@@ -6,7 +6,7 @@ import { useShowErrorDialog } from "@/ui/GenericErrorDialog";
export const useNextPrevious = () => {
const {
- isInfiniteTree,
+ treeType,
totalItems,
currentItemIndex,
setCurrentId,
@@ -109,20 +109,20 @@ export const useNextPrevious = () => {
);
const onNextClick = useCallback(() => {
- if (isInfiniteTree) {
+ if (treeType === "infinite") {
handleInfiniteNavigation("next");
} else {
handleFiniteNavigation("next");
}
- }, [isInfiniteTree, handleInfiniteNavigation, handleFiniteNavigation]);
+ }, [treeType, handleInfiniteNavigation, handleFiniteNavigation]);
const onPreviousClick = useCallback(() => {
- if (isInfiniteTree) {
+ if (treeType === "infinite") {
handleInfiniteNavigation("previous");
} else {
handleFiniteNavigation("previous");
}
- }, [isInfiniteTree, handleInfiniteNavigation, handleFiniteNavigation]);
+ }, [treeType, handleInfiniteNavigation, handleFiniteNavigation]);
return {
onNextClick,
diff --git a/src/context/ActionViewContext.tsx b/src/context/ActionViewContext.tsx
index fc09645d6..e9839d373 100644
--- a/src/context/ActionViewContext.tsx
+++ b/src/context/ActionViewContext.tsx
@@ -1,6 +1,10 @@
import { convertParamsToValues } from "@/helpers/searchHelper";
import { DEFAULT_SEARCH_LIMIT } from "@/models/constants";
import { TreeView, View } from "@/types";
+import {
+ DEFAULT_TREE_TYPE,
+ TreeType,
+} from "@/views/actionViews/TreeActionView";
import { ColumnState } from "@gisce/react-formiga-table";
import { createContext, useContext, useEffect, useState } from "react";
@@ -24,7 +28,7 @@ type ActionViewProviderProps = {
totalItems: number;
setTotalItems: (totalItems: number) => void;
selectedRowItems?: any[];
- setSelectedRowItems: (value: any[]) => void;
+ setSelectedRowItems: (value: any[] | ((prevValue: any[]) => any[])) => void;
setSearchTreeNameSearch: (searchString?: string) => void;
searchTreeNameSearch?: string;
goToResourceId: (ids: number[], openInSameTab?: boolean) => Promise;
@@ -32,6 +36,8 @@ type ActionViewProviderProps = {
isActive: boolean;
children: React.ReactNode;
initialSearchParams?: any[];
+ initialCurrentPage?: number;
+ initialOrder?: any[];
};
export type ActionViewContextType = Omit<
@@ -66,13 +72,17 @@ export type ActionViewContextType = Omit<
setLimit?: (value: number) => void;
setTitle?: (value: string) => void;
treeFirstVisibleRow: number;
- setTreeFirstVisibleRow: (totalItems: number) => void;
+ setTreeFirstVisibleRow: (value: number) => void;
+ treeFirstVisibleColumn: string | undefined;
+ setTreeFirstVisibleColumn: (value: string | undefined) => void;
searchQuery?: SearchQueryParams;
setSearchQuery?: (value: SearchQueryParams) => void;
- isInfiniteTree?: boolean;
- setIsInfiniteTree?: (value: boolean) => void;
- sortState?: ColumnState[];
- setSortState?: (value: ColumnState[] | undefined) => void;
+ treeType?: TreeType;
+ setTreeType?: (value: TreeType) => void;
+ order?: ColumnState[];
+ setOrder?: (value: ColumnState[] | undefined) => void;
+ currentPage?: number;
+ setCurrentPage?: (value: number) => void;
};
export const ActionViewContext = createContext(
@@ -116,6 +126,8 @@ const ActionViewProvider = (props: ActionViewProviderProps): any => {
limit: limitProps,
isActive,
initialSearchParams,
+ initialCurrentPage,
+ initialOrder,
} = props;
const [formIsSaving, setFormIsSaving] = useState(false);
@@ -138,20 +150,30 @@ const ActionViewProvider = (props: ActionViewProviderProps): any => {
),
);
const [treeFirstVisibleRow, setTreeFirstVisibleRow] = useState(0);
+ const [treeFirstVisibleColumn, setTreeFirstVisibleColumn] = useState<
+ string | undefined
+ >(undefined);
const [searchQuery, setSearchQuery] = useState();
- const [isInfiniteTree, setIsInfiniteTree] = useState(false);
- const [sortState, setSortState] = useState();
+ const [treeType, setTreeType] = useState(DEFAULT_TREE_TYPE);
+ const [order, setOrder] = useState(
+ initialOrder as ColumnState[] | undefined,
+ );
const [limit, setLimit] = useState(
limitProps !== undefined ? limitProps : DEFAULT_SEARCH_LIMIT,
);
const [title, setTitle] = useState(titleProps);
+ const [currentPage, setCurrentPage] = useState(
+ initialCurrentPage || 1,
+ );
+
useEffect(() => {
if (results && results.length > 0 && !currentItemIndex) {
setCurrentItemIndex?.(0);
setCurrentId?.(results[0].id);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [results]);
useEffect(() => {
@@ -244,12 +266,16 @@ const ActionViewProvider = (props: ActionViewProviderProps): any => {
isActive,
setTreeFirstVisibleRow,
treeFirstVisibleRow,
+ treeFirstVisibleColumn,
+ setTreeFirstVisibleColumn,
searchQuery,
setSearchQuery,
- isInfiniteTree,
- setIsInfiniteTree,
- sortState,
- setSortState,
+ treeType,
+ setTreeType,
+ order,
+ setOrder,
+ currentPage,
+ setCurrentPage,
}}
>
{children}
@@ -322,12 +348,16 @@ export const useActionViewContext = () => {
setTitle: () => {},
treeFirstVisibleRow: 0,
setTreeFirstVisibleRow: () => {},
+ treeFirstVisibleColumn: undefined,
+ setTreeFirstVisibleColumn: () => {},
searchQuery: undefined,
setSearchQuery: () => {},
- isInfiniteTree: false,
- setIsInfiniteTree: () => {},
- sortState: undefined,
- setSortState: () => {},
+ treeType: DEFAULT_TREE_TYPE,
+ setTreeType: () => {},
+ order: undefined,
+ setOrder: () => {},
+ currentPage: 1,
+ setCurrentPage: () => {},
};
}
diff --git a/src/helpers/treeHelper.tsx b/src/helpers/treeHelper.tsx
index d48faad53..82dfd878a 100644
--- a/src/helpers/treeHelper.tsx
+++ b/src/helpers/treeHelper.tsx
@@ -31,6 +31,7 @@ const getTableColumns = (
tree: TreeOoui,
components: any,
context: any,
+ treeType: "infinite" | "paginated" | "legacy",
): Column[] => {
const tableColumns = tree.columns.map((column) => {
const type = column.type;
@@ -59,6 +60,15 @@ const getTableColumns = (
};
}
+ let isSortable = true;
+
+ if (treeType === "legacy") {
+ isSortable = type !== "one2many";
+ } else {
+ isSortable =
+ type !== "one2many" && type !== "many2one" && !column.isFunction;
+ }
+
return {
key,
dataIndex: key,
@@ -77,8 +87,7 @@ const getTableColumns = (
if (aItem > bItem) return 1;
return 0;
},
- isSortable:
- type !== "one2many" && type !== "many2one" && !column.isFunction,
+ isSortable,
};
});
return tableColumns;
diff --git a/src/hooks/useSearchTreeState.ts b/src/hooks/useSearchTreeState.ts
index f02e4243a..a3594cbea 100644
--- a/src/hooks/useSearchTreeState.ts
+++ b/src/hooks/useSearchTreeState.ts
@@ -5,6 +5,12 @@ import {
useIsUnderActionViewContext,
} from "@/context/ActionViewContext";
import { ColumnState } from "@gisce/react-formiga-table";
+import { DEFAULT_PAGE_SIZE } from "@/widgets/views/Tree/Paginated/hooks/usePaginatedSearch";
+import { DEFAULT_SEARCH_LIMIT } from "@/models/constants";
+import {
+ DEFAULT_TREE_TYPE,
+ TreeType,
+} from "@/views/actionViews/TreeActionView";
export type SearchTreeState = {
treeIsLoading: boolean;
@@ -12,9 +18,11 @@ export type SearchTreeState = {
searchVisible: boolean;
setSearchVisible: (value: boolean) => void;
selectedRowItems: any[];
- setSelectedRowItems: (value: any[]) => void;
+ setSelectedRowItems: (value: any[] | ((prevValue: any[]) => any[])) => void;
treeFirstVisibleRow: number;
setTreeFirstVisibleRow: (value: number) => void;
+ treeFirstVisibleColumn: string | undefined;
+ setTreeFirstVisibleColumn: (value: string | undefined) => void;
searchParams: any[];
setSearchParams: (value: any[]) => void;
searchValues: any;
@@ -28,8 +36,14 @@ export type SearchTreeState = {
totalItems: number;
setTotalItems: (value: number) => void;
isActive?: boolean;
- sortState?: ColumnState[];
- setSortState: (value: ColumnState[] | undefined) => void;
+ order?: ColumnState[];
+ setOrder: (value: ColumnState[] | undefined) => void;
+ currentPage: number;
+ setCurrentPage: (value: number) => void;
+ treeType: TreeType;
+ setTreeType: (value: TreeType) => void;
+ limit: number;
+ setLimit: (value: number) => void;
};
export function useSearchTreeState({
@@ -46,6 +60,8 @@ export function useSearchTreeState({
const [localSearchVisible, setLocalSearchVisible] = useState(false);
const [localSelectedRowItems, setLocalSelectedRowItems] = useState([]);
const [localTreeFirstVisibleRow, setLocalTreeFirstVisibleRow] = useState(0);
+ const [localTreeFirstVisibleColumn, setLocalTreeFirstVisibleColumn] =
+ useState(undefined);
const [localSearchParams, setLocalSearchParams] = useState([]);
const [localSearchValues, setLocalSearchValues] = useState({});
const [localSearchTreeNameSearch, setLocalSearchTreeNameSearch] =
@@ -53,9 +69,11 @@ export function useSearchTreeState({
const [localResults, setLocalResults] = useState([]);
const [localSearchQuery, setLocalSearchQuery] = useState();
const [localTotalItems, setLocalTotalItems] = useState(0);
- const [localSortState, setLocalSortState] = useState<
- ColumnState[] | undefined
- >();
+ const [localOrder, setLocalOrder] = useState();
+ const [localCurrentPage, setLocalCurrentPage] = useState(1);
+ const [localTreeType, setLocalTreeType] =
+ useState(DEFAULT_TREE_TYPE);
+ const [localLimit, setLocalLimit] = useState(DEFAULT_SEARCH_LIMIT);
// Return either context values or local state values based on isUnderActionViewContext
return isUnderActionViewContext
@@ -70,6 +88,9 @@ export function useSearchTreeState({
treeFirstVisibleRow: actionViewContext.treeFirstVisibleRow ?? 0,
setTreeFirstVisibleRow:
actionViewContext.setTreeFirstVisibleRow ?? (() => {}),
+ treeFirstVisibleColumn: actionViewContext.treeFirstVisibleColumn,
+ setTreeFirstVisibleColumn:
+ actionViewContext.setTreeFirstVisibleColumn ?? (() => {}),
searchParams: actionViewContext.searchParams || [],
setSearchParams: actionViewContext.setSearchParams ?? (() => {}),
searchValues: actionViewContext.searchValues || {},
@@ -84,8 +105,14 @@ export function useSearchTreeState({
totalItems: actionViewContext.totalItems ?? 0,
setTotalItems: actionViewContext.setTotalItems ?? (() => {}),
isActive: actionViewContext.isActive,
- sortState: actionViewContext.sortState,
- setSortState: actionViewContext.setSortState ?? (() => {}),
+ order: actionViewContext.order,
+ setOrder: actionViewContext.setOrder ?? (() => {}),
+ currentPage: actionViewContext.currentPage ?? 1,
+ setCurrentPage: actionViewContext.setCurrentPage ?? (() => {}),
+ treeType: actionViewContext.treeType ?? DEFAULT_TREE_TYPE,
+ setTreeType: actionViewContext.setTreeType ?? (() => {}),
+ limit: actionViewContext.limit ?? DEFAULT_SEARCH_LIMIT,
+ setLimit: actionViewContext.setLimit ?? (() => {}),
}
: {
treeIsLoading: localTreeIsLoading,
@@ -96,6 +123,8 @@ export function useSearchTreeState({
setSelectedRowItems: setLocalSelectedRowItems,
treeFirstVisibleRow: localTreeFirstVisibleRow,
setTreeFirstVisibleRow: setLocalTreeFirstVisibleRow,
+ treeFirstVisibleColumn: localTreeFirstVisibleColumn,
+ setTreeFirstVisibleColumn: setLocalTreeFirstVisibleColumn,
searchParams: localSearchParams,
setSearchParams: setLocalSearchParams,
searchValues: localSearchValues,
@@ -109,7 +138,13 @@ export function useSearchTreeState({
totalItems: localTotalItems,
setTotalItems: setLocalTotalItems,
isActive: undefined,
- sortState: localSortState,
- setSortState: setLocalSortState,
+ order: localOrder,
+ setOrder: setLocalOrder,
+ currentPage: localCurrentPage,
+ setCurrentPage: setLocalCurrentPage,
+ treeType: localTreeType,
+ setTreeType: setLocalTreeType,
+ limit: localLimit,
+ setLimit: setLocalLimit,
};
}
diff --git a/src/hooks/useTableConfiguration.ts b/src/hooks/useTableConfiguration.ts
new file mode 100644
index 000000000..6a65b4ff7
--- /dev/null
+++ b/src/hooks/useTableConfiguration.ts
@@ -0,0 +1,35 @@
+import { useDeepCompareMemo } from "use-deep-compare";
+import { Tree as TreeOoui } from "@gisce/ooui";
+import { getTableColumns } from "@/helpers/treeHelper";
+import { COLUMN_COMPONENTS } from "../widgets/views/Tree/treeComponents";
+import { useMemo } from "react";
+import { useLocale } from "@gisce/react-formiga-components";
+
+export const useTableConfiguration = (
+ treeOoui: TreeOoui | undefined,
+ parentContext: Record,
+) => {
+ const { t } = useLocale();
+
+ const columns = useDeepCompareMemo(() => {
+ if (!treeOoui) return undefined;
+ return getTableColumns(
+ treeOoui,
+ { ...COLUMN_COMPONENTS },
+ parentContext,
+ "paginated",
+ );
+ }, [treeOoui, parentContext]);
+
+ const strings = useMemo(
+ () => ({
+ resetTableViewLabel: t("resetTableView"),
+ }),
+ [t],
+ );
+
+ return {
+ columns,
+ strings,
+ };
+};
diff --git a/src/hooks/useUrlFromCurrentTab.ts b/src/hooks/useUrlFromCurrentTab.ts
index 099e0d8ca..ef135f164 100644
--- a/src/hooks/useUrlFromCurrentTab.ts
+++ b/src/hooks/useUrlFromCurrentTab.ts
@@ -13,7 +13,8 @@ export function useUrlFromCurrentTab({
}: {
currentTab?: Tab;
}): UseUrlFromCurrentTabResult {
- const { currentView, searchParams, currentId } = useActionViewContext();
+ const { currentView, searchParams, currentId, limit, currentPage, order } =
+ useActionViewContext();
const { currentTab: currentTabContext } = useTabs();
const currentTab = currentTabProps || currentTabContext;
@@ -33,6 +34,9 @@ export function useUrlFromCurrentTab({
...(initialView && { initialView }),
...(searchParams && { searchParams }),
...(currentId && { res_id: currentId }),
+ ...(limit && { limit }),
+ ...(currentPage && currentPage > 1 && { currentPage }),
+ ...(order && { order }),
};
const shareUrl = createShareOpenUrl(finalActionData);
diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts
index 86572832a..7f4f1d41f 100644
--- a/src/locales/en_US.ts
+++ b/src/locales/en_US.ts
@@ -91,9 +91,9 @@ export default {
errorWhileSavingForm: "Error while saving form",
author: "Author",
recordsSelected:
- "Hi ha {numberOfSelectedRows} registres seleccionats en aquesta pĂ gina.",
- selectAllRecords: "Seleccionar tots els {totalRecords} registres.",
- allRecordsSelected: "Hi ha {totalRecords} registres seleccionats.",
+ "There are {numberOfSelectedRows} records selected on this page.",
+ selectAllRecords: "Select all {totalRecords} records.",
+ allRecordsSelected: "There are {totalRecords} records selected.",
openInSameWindow: "Open in the current tab",
openInNewTab: "Open in a new tab",
confirmDuplicate: "Are you sure you want to duplicate the selected item/s?",
diff --git a/src/types/index.ts b/src/types/index.ts
index 324d888b4..707844a0d 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -413,6 +413,8 @@ type ActionInfo = {
limit?: number;
actionRawData?: ActionRawData;
searchParams?: any[];
+ currentPage?: number;
+ order?: any[];
};
type Tab = {
diff --git a/src/ui/TitleHeader.tsx b/src/ui/TitleHeader.tsx
index f64133c5e..f2984b920 100644
--- a/src/ui/TitleHeader.tsx
+++ b/src/ui/TitleHeader.tsx
@@ -29,7 +29,7 @@ const TitleHeader: React.FC = ({
results,
totalItems,
selectedRowItems,
- isInfiniteTree,
+ treeType,
} = useContext(ActionViewContext) as ActionViewContextType;
const { t } = useLocale();
const { token } = useToken();
@@ -53,11 +53,12 @@ const TitleHeader: React.FC = ({
}
const currentItemNumber = (currentItemIndex ?? 0) + 1;
- const itemCount = isInfiniteTree ? totalItems : results?.length;
+ const itemCount = treeType === "infinite" ? totalItems : results?.length;
return (
<>
- {t("register")} {currentItemNumber} {isInfiniteTree ? t("of") : "/"}{" "}
- {itemCount} {!isInfiniteTree && `${t("of")} ${totalItems}`} -{" "}
+ {t("register")} {currentItemNumber}{" "}
+ {treeType === "infinite" ? t("of") : "/"} {itemCount}{" "}
+ {treeType !== "infinite" && `${t("of")} ${totalItems}`} -{" "}
{t("editingDocument")} (id: {currentId})
>
);
@@ -92,7 +93,7 @@ const TitleHeader: React.FC = ({
selectedRowItems,
totalItems,
currentItemIndex,
- isInfiniteTree,
+ treeType,
results?.length,
t,
]);
diff --git a/src/views/ActionView.tsx b/src/views/ActionView.tsx
index bdccc7bf3..5d9a8fa6c 100644
--- a/src/views/ActionView.tsx
+++ b/src/views/ActionView.tsx
@@ -55,6 +55,8 @@ type Props = {
treeExpandable?: boolean;
limit?: number;
initialSearchParams?: any[];
+ currentPage?: number;
+ order?: any[];
};
function ActionView(props: Props, ref: any) {
@@ -75,6 +77,8 @@ function ActionView(props: Props, ref: any) {
treeExpandable = false,
limit,
initialSearchParams = [],
+ currentPage,
+ order,
} = props;
const [currentView, setCurrentViewInternal] = useState();
@@ -467,6 +471,8 @@ function ActionView(props: Props, ref: any) {
limit={limit}
isActive={tabKey === activeKey}
initialSearchParams={initialSearchParams}
+ initialCurrentPage={currentPage}
+ initialOrder={order}
>
("welcome");
@@ -77,7 +79,7 @@ function RootView(props: RootViewProps, ref: any) {
}
async function handleOpenActionUrl(action: ActionInfo) {
- const { actionRawData, res_id, initialView } = action;
+ const { actionRawData, res_id, limit } = action;
const fields = await ConnectionProvider.getHandler().getFields({
model: action.model,
@@ -148,6 +150,7 @@ function RootView(props: RootViewProps, ref: any) {
openAction({
...action,
+ limit: limit && limit > MAX_SEARCH_LIMIT ? MAX_SEARCH_LIMIT : limit,
context: { ...rootContext, ...parsedContext },
domain: parsedDomain,
actionRawData: {
@@ -562,6 +565,8 @@ function RootView(props: RootViewProps, ref: any) {
treeExpandable = false,
limit,
searchParams,
+ currentPage,
+ order,
} = parms;
const key = nanoid();
@@ -612,6 +617,8 @@ function RootView(props: RootViewProps, ref: any) {
treeExpandable={treeExpandable}
limit={limit}
initialSearchParams={searchParams}
+ currentPage={currentPage}
+ order={order}
/>
),
key,
diff --git a/src/views/actionViews/TreeActionView.tsx b/src/views/actionViews/TreeActionView.tsx
index a9bde73ee..d63756f4f 100644
--- a/src/views/actionViews/TreeActionView.tsx
+++ b/src/views/actionViews/TreeActionView.tsx
@@ -16,6 +16,7 @@ import {
import { SearchTreeInfinite } from "@/widgets/views/SearchTreeInfinite";
import SearchTree from "@/widgets/views/SearchTree";
import { extractTreeXmlAttribute } from "@/helpers/treeHelper";
+import { SearchTreePaginated } from "@/widgets/views/Tree/Paginated/SearchTreePaginated";
export type TreeActionViewProps = {
formView: FormView;
@@ -34,6 +35,9 @@ export type TreeActionViewProps = {
limit?: number;
};
+export type TreeType = "infinite" | "paginated" | "legacy";
+export const DEFAULT_TREE_TYPE: TreeType = "legacy";
+
export const TreeActionView = (props: TreeActionViewProps) => {
const {
visible,
@@ -52,25 +56,24 @@ export const TreeActionView = (props: TreeActionViewProps) => {
} = props;
const previousVisibleRef = useRef(visible);
- const isInfiniteTree = useMemo(() => {
+ const treeType: TreeType = useMemo(() => {
if (!treeView?.arch || treeView.isExpandable) {
- return false;
+ return "legacy";
}
const tagValue = extractTreeXmlAttribute(treeView.arch, "infinite");
- return tagValue === "1";
+ if (!tagValue) {
+ return "legacy";
+ }
+ return tagValue === "1" ? "infinite" : "paginated";
}, [treeView]);
+ const { currentView, setPreviousView, setTreeType, setSelectedRowItems } =
+ useContext(ActionViewContext) as ActionViewContextType;
+
useEffect(() => {
- setIsInfiniteTree?.(isInfiniteTree);
+ setTreeType?.(treeType);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isInfiniteTree]);
-
- const {
- currentView,
- setPreviousView,
- setIsInfiniteTree,
- setSelectedRowItems,
- } = useContext(ActionViewContext) as ActionViewContextType;
+ }, [treeType]);
const onRowClicked = useCallback(
(event: any) => {
@@ -98,12 +101,12 @@ export const TreeActionView = (props: TreeActionViewProps) => {
);
useEffect(() => {
- if (previousVisibleRef.current && !visible && isInfiniteTree) {
+ if (previousVisibleRef.current && !visible && treeType === "infinite") {
setSelectedRowItems?.([]);
}
previousVisibleRef.current = visible;
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [visible, isInfiniteTree]);
+ }, [visible, treeType]);
if (!visible) {
return null;
@@ -111,14 +114,14 @@ export const TreeActionView = (props: TreeActionViewProps) => {
return (
-
+
- {isInfiniteTree && (
+ {treeType === "infinite" && (
{
onRowClicked={onRowClicked}
/>
)}
- {!isInfiniteTree && (
+ {treeType === "paginated" && (
+
+ )}
+ {treeType === "legacy" && (
{
})}%`;
return (
-
+ {textValue}
+
);
};
+const StyledProgressContainer = styled.div`
+ display: flex;
+ align-items: center;
+ width: 100%;
+ min-width: 0;
+`;
+
const StyledProgress = styled(Progress)`
+ flex: 1;
+ min-width: 0;
.ant-progress-outer {
margin-right: 0px;
padding-right: 0px;
@@ -36,3 +45,8 @@ const StyledProgress = styled(Progress)`
display: none;
}
`;
+
+const StyledText = styled.div`
+ padding-left: 10px;
+ white-space: nowrap;
+`;
diff --git a/src/widgets/base/one2many/One2manyTree.tsx b/src/widgets/base/one2many/One2manyTree.tsx
index 6eda2a9f8..fa1436ed1 100644
--- a/src/widgets/base/one2many/One2manyTree.tsx
+++ b/src/widgets/base/one2many/One2manyTree.tsx
@@ -110,6 +110,7 @@ export const One2manyTree = ({
...COLUMN_COMPONENTS,
},
context,
+ "infinite",
);
}, [context, ooui]);
@@ -153,12 +154,12 @@ export const One2manyTree = ({
}, []);
const { loading, getColumnState, updateColumnState } =
- useTreeColumnStorageFetch(
- getKey({
+ useTreeColumnStorageFetch({
+ key: getKey({
...dataForHash,
model: relation,
}),
- );
+ });
if (loading) {
return ;
diff --git a/src/widgets/base/one2many/useTreeColumnStorageFetch.ts b/src/widgets/base/one2many/useTreeColumnStorageFetch.ts
index 6bbd173ef..f0435091e 100644
--- a/src/widgets/base/one2many/useTreeColumnStorageFetch.ts
+++ b/src/widgets/base/one2many/useTreeColumnStorageFetch.ts
@@ -2,42 +2,68 @@ import { ColumnState } from "@gisce/react-formiga-table";
import { useCallback, useEffect, useRef, useState } from "react";
import { useTreeColumnStorage } from "./useTreeColumnStorage";
-export const useTreeColumnStorageFetch = (key?: string) => {
+type TreeColumnStorageFetchProps = {
+ key?: string;
+ treeViewFetching?: boolean;
+};
+
+export const useTreeColumnStorageFetch = ({
+ key,
+ treeViewFetching = false,
+}: TreeColumnStorageFetchProps) => {
const [loading, setLoading] = useState(true);
const columnState = useRef(undefined);
const fetchInProgress = useRef(false);
- const { getColumnState: getColumnStateInternal, updateColumnState } =
- useTreeColumnStorage(key);
+ const {
+ getColumnState: getColumnStateInternal,
+ updateColumnState: updateColumnStateInternal,
+ } = useTreeColumnStorage(key);
+
+ const fetchColumnState = useCallback(async () => {
+ if (fetchInProgress.current || treeViewFetching) {
+ return;
+ }
+
+ fetchInProgress.current = true;
+ setLoading(true);
+ try {
+ columnState.current = await getColumnStateInternal();
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ fetchInProgress.current = false;
+ }
+ return columnState.current;
+ }, [getColumnStateInternal, treeViewFetching]);
useEffect(() => {
if (!key) {
setLoading(false);
return;
}
- const fetchColumnState = async () => {
- if (fetchInProgress.current) {
- return;
- }
-
- fetchInProgress.current = true;
- setLoading(true);
- try {
- columnState.current = await getColumnStateInternal();
- } catch (err) {
- console.error(err);
- } finally {
- setLoading(false);
- fetchInProgress.current = false;
- }
- };
fetchColumnState();
- }, [getColumnStateInternal, key]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [key, treeViewFetching]);
const getColumnState = useCallback(() => {
return columnState.current;
}, []);
- return { getColumnState, loading, updateColumnState };
+ const updateColumnState = useCallback(
+ (state: ColumnState[]) => {
+ const columnStatesWithoutSort = state.map((columnState) => {
+ const { sort, ...columnStateWithoutSort } = columnState;
+ return columnStateWithoutSort;
+ });
+ columnState.current = columnStatesWithoutSort;
+
+ updateColumnStateInternal(state);
+ },
+ [updateColumnStateInternal],
+ );
+
+ return { getColumnState, loading, updateColumnState, fetchColumnState };
};
diff --git a/src/widgets/views/SearchTreeInfinite.tsx b/src/widgets/views/SearchTreeInfinite.tsx
index e91ed2d69..05ec0720b 100644
--- a/src/widgets/views/SearchTreeInfinite.tsx
+++ b/src/widgets/views/SearchTreeInfinite.tsx
@@ -136,8 +136,8 @@ function SearchTreeInfiniteComp(props: SearchTreeInfiniteProps, ref: any) {
setSearchQuery,
setTotalItems: setTotalItemsActionView,
isActive,
- sortState: actionViewSortState,
- setSortState: setActionViewSortState,
+ order: actionViewSortState,
+ setOrder: setActionViewSortState,
} = useSearchTreeState({ useLocalState: !rootTree });
const nameSearch = nameSearchProps || searchTreeNameSearch;
@@ -192,6 +192,7 @@ function SearchTreeInfiniteComp(props: SearchTreeInfiniteProps, ref: any) {
...COLUMN_COMPONENTS,
},
parentContext,
+ "infinite",
);
}, [treeOoui, parentContext]);
@@ -209,7 +210,7 @@ function SearchTreeInfiniteComp(props: SearchTreeInfiniteProps, ref: any) {
loading: getColumnStateInProgress,
getColumnState,
updateColumnState,
- } = useTreeColumnStorageFetch(columnStateKey);
+ } = useTreeColumnStorageFetch({ key: columnStateKey });
const mergedParams = useMemo(
() => mergeParams(searchParams || [], domain),
diff --git a/src/widgets/views/Tree/Paginated/SearchTreePaginated.tsx b/src/widgets/views/Tree/Paginated/SearchTreePaginated.tsx
new file mode 100644
index 000000000..d721017f1
--- /dev/null
+++ b/src/widgets/views/Tree/Paginated/SearchTreePaginated.tsx
@@ -0,0 +1,250 @@
+import {
+ Fragment,
+ RefObject,
+ forwardRef,
+ useCallback,
+ useImperativeHandle,
+ useMemo,
+ useRef,
+ useEffect,
+} from "react";
+
+import { Tree as TreeOoui } from "@gisce/ooui";
+import { PaginatedTableRef } from "@gisce/react-formiga-table";
+
+import { Badge, Spin } from "antd";
+import { PaginationHeader } from "@gisce/react-formiga-components";
+import { AggregatesFooter } from "../../../base/one2many/AggregatesFooter";
+
+import { useFetchTreeViews } from "@/hooks/useFetchTreeViews";
+import { useAvailableHeight } from "@/hooks/useAvailableHeight";
+import { useTreeAggregates } from "../../../base/one2many/useTreeAggregates";
+import { useAutorefreshableTreeFields } from "@/hooks/useAutorefreshableTreeFields";
+import {
+ DEFAULT_PAGE_SIZE,
+ usePaginatedSearch,
+} from "@/widgets/views/Tree/Paginated/hooks/usePaginatedSearch";
+
+import { getTree } from "@/helpers/treeHelper";
+import {
+ SearchTreePaginatedProps,
+ OnRowClickedData,
+} from "./SearchTreePaginated.types";
+import { useTableConfiguration } from "../../../../hooks/useTableConfiguration";
+import { PaginatedSearchControls } from "./components/PaginatedSearchControls";
+import { PaginatedTableComponent } from "./components/PaginatedTableComponent";
+
+export const HEIGHT_OFFSET = 10;
+
+function SearchTreePaginatedComp(props: SearchTreePaginatedProps, ref: any) {
+ const {
+ model,
+ formView: formViewProps,
+ treeView: treeViewProps,
+ onRowClicked,
+ domain = [],
+ visible = true,
+ rootTree = false,
+ parentContext = {},
+ nameSearch: nameSearchProps,
+ filterType = "side",
+ } = props;
+
+ // Refs
+ const tableRef: RefObject = useRef(null);
+ const containerRef = useRef(null);
+ const onRowClickedRef = useRef(onRowClicked);
+
+ // Update ref when onRowClicked changes
+ useEffect(() => {
+ onRowClickedRef.current = onRowClicked;
+ }, [onRowClicked]);
+
+ // Callback that uses the ref
+ const handleRowDoubleClick = useCallback((data: OnRowClickedData) => {
+ onRowClickedRef.current?.(data);
+ }, []);
+
+ const availableHeight = useAvailableHeight({
+ elementRef: containerRef,
+ offset: HEIGHT_OFFSET,
+ });
+
+ // Views data fetching
+ const { treeView, formView, loading } = useFetchTreeViews({
+ model,
+ formViewProps,
+ treeViewProps,
+ context: parentContext,
+ });
+
+ const treeOoui: TreeOoui | undefined = useMemo(() => {
+ if (!treeView) return;
+ return getTree(treeView);
+ }, [treeView]);
+
+ const { columns, strings } = useTableConfiguration(treeOoui, parentContext);
+
+ // Ensure columns is never undefined
+ const safeColumns = useMemo(() => columns || [], [columns]);
+
+ // Pagination and search state
+ const {
+ isActive,
+ searchVisible,
+ searchValues,
+ selectedRowKeys,
+ refresh,
+ onRowStatus,
+ onGetFirstVisibleRowIndex,
+ setTreeFirstVisibleRow,
+ onRowHasBeenSelected,
+ onSearchFilterClear,
+ onSearchFilterSubmit,
+ onSideSearchFilterClose,
+ onSideSearchFilterSubmit,
+ totalRowsLoading,
+ totalRows,
+ onRowStyle,
+ results,
+ onRequestPageChange,
+ treeIsLoading,
+ selectAllRecords,
+ onHeaderCheckboxClick,
+ headerCheckboxState,
+ getColumnStateInProgress,
+ getColumnState,
+ updateColumnState,
+ currentPage,
+ limit,
+ order: actionViewSortState,
+ setOrder: setActionViewSortState,
+ setTreeFirstVisibleColumn,
+ onGetFirstVisibleColumn,
+ } = usePaginatedSearch({
+ treeViewFetching: loading,
+ treeOoui,
+ treeView,
+ model,
+ rootTree,
+ nameSearchProps,
+ tableRef,
+ domain,
+ filterType,
+ context: parentContext,
+ });
+
+ // Aggregates handling
+ const [loadingAggregates, aggregates, hasAggregates] = useTreeAggregates({
+ ooui: treeOoui,
+ model,
+ showEmptyValues: true,
+ domain:
+ selectedRowKeys?.length > 0
+ ? // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
+ [["id", "in", selectedRowKeys.sort()]]
+ : undefined,
+ });
+
+ // Auto-refresh setup
+ useAutorefreshableTreeFields({
+ model,
+ tableRef,
+ autorefreshableFields: treeOoui?.autorefreshableFields,
+ fieldDefs: treeView?.field_parent
+ ? { ...treeView?.fields, [treeView?.field_parent]: {} }
+ : treeView?.fields,
+ context: parentContext,
+ isActive,
+ });
+
+ // External control
+ useImperativeHandle(ref, () => ({
+ refreshResults: refresh,
+ getFields: () => treeView?.fields,
+ getDomain: () => domain,
+ }));
+
+ // UI Components and Styles
+ const footerComp = useMemo(() => {
+ if (!hasAggregates) return null;
+ return (
+
+ );
+ }, [aggregates, loadingAggregates, hasAggregates]);
+
+ const statusComp = useCallback(
+ (status: any) => ,
+ [],
+ );
+
+ const containerStyle = useMemo(
+ () => ({
+ overflow: "hidden",
+ height: `${availableHeight}px`,
+ ...(visible ? {} : { display: "none" }),
+ }),
+ [availableHeight, visible],
+ );
+
+ // Render
+ return (
+
+
+
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+export const SearchTreePaginated = forwardRef(SearchTreePaginatedComp);
diff --git a/src/widgets/views/Tree/Paginated/SearchTreePaginated.types.ts b/src/widgets/views/Tree/Paginated/SearchTreePaginated.types.ts
new file mode 100644
index 000000000..a9760bdbd
--- /dev/null
+++ b/src/widgets/views/Tree/Paginated/SearchTreePaginated.types.ts
@@ -0,0 +1,66 @@
+import { FormView, TreeView } from "@/types/index";
+import { Tree as TreeOoui } from "@gisce/ooui";
+import { PaginatedTableRef } from "@gisce/react-formiga-table";
+import { CheckboxState } from "@gisce/react-formiga-table/dist/components/PaginatedTable/PaginatedHeaderCheckbox";
+import { RefObject } from "react";
+
+export type OnRowClickedData = {
+ id: number;
+ model: string;
+ formView: FormView;
+ treeView: TreeView;
+};
+
+export type SearchTreePaginatedProps = {
+ model: string;
+ formView: FormView;
+ treeView: TreeView;
+ onRowClicked: (data: OnRowClickedData) => void;
+ nameSearch?: string;
+ domain?: any[];
+ visible?: boolean;
+ rootTree?: boolean;
+ parentContext?: Record;
+ filterType?: "side" | "top";
+};
+
+export type PaginatedSearchControlsProps = {
+ filterType: "side" | "top";
+ formView?: FormView;
+ treeView?: TreeView;
+ searchVisible: boolean;
+ searchValues: any;
+ onSearchFilterClear: () => void;
+ onSearchFilterSubmit: (values: any) => void;
+ onSideSearchFilterClose: () => void;
+ onSideSearchFilterSubmit: (values: any) => void;
+};
+
+export type PaginatedTableContentProps = {
+ columns: any[];
+ treeOoui: TreeOoui;
+ strings: Record;
+ isLoading: boolean;
+ availableHeight: number;
+ results: any[];
+ handleRowDoubleClick: (data: OnRowClickedData) => void;
+ onRowHasBeenSelected:
+ | ((changedRow: { id: number; selected: boolean }) => void)
+ | undefined;
+ updateColumnState: (state: any) => void;
+ getColumnState: () => any;
+ setTreeFirstVisibleRow: (index: number) => void;
+ onGetFirstVisibleRowIndex: () => number;
+ onGetFirstVisibleColumn?: (() => string | undefined) | undefined;
+ setTreeFirstVisibleColumn: ((columnId: string) => void) | undefined;
+ footerComp: React.ReactNode;
+ statusComp: (status: any) => React.ReactNode;
+ onRowStatus: (record: any) => any;
+ onRowStyle: (record: any) => any;
+ headerCheckboxState: CheckboxState;
+ onHeaderCheckboxClick: () => void;
+ refresh: () => void;
+ actionViewSortState: any;
+ setActionViewSortState: (state: any) => void;
+ tableRef: RefObject;
+};
diff --git a/src/widgets/views/Tree/Paginated/components/PaginatedSearchControls.tsx b/src/widgets/views/Tree/Paginated/components/PaginatedSearchControls.tsx
new file mode 100644
index 000000000..ce9dcec53
--- /dev/null
+++ b/src/widgets/views/Tree/Paginated/components/PaginatedSearchControls.tsx
@@ -0,0 +1,78 @@
+import { FC, useMemo } from "react";
+import SearchFilter from "../../../searchFilter/SearchFilter";
+import { SideSearchFilter } from "../../../searchFilter/SideSearchFilter";
+import { mergeSearchFields } from "@/helpers/formHelper";
+import { PaginatedSearchControlsProps } from "../SearchTreePaginated.types";
+
+export const PaginatedSearchControls: FC = ({
+ filterType,
+ formView,
+ treeView,
+ searchVisible,
+ searchValues,
+ onSearchFilterClear,
+ onSearchFilterSubmit,
+ onSideSearchFilterClose,
+ onSideSearchFilterSubmit,
+}) => {
+ const searchFilterProps = useMemo(
+ () => ({
+ fields: { ...formView?.fields, ...treeView?.fields },
+ searchFields: mergeSearchFields([
+ formView?.search_fields,
+ treeView?.search_fields,
+ ]),
+ showLimitOptions: false,
+ limit: 0,
+ offset: 0,
+ isSearching: false,
+ searchValues,
+ searchVisible: true,
+ }),
+ [
+ formView?.fields,
+ formView?.search_fields,
+ treeView?.fields,
+ treeView?.search_fields,
+ searchValues,
+ ],
+ );
+
+ const sideSearchFilterProps = useMemo(
+ () => ({
+ isOpen: searchVisible,
+ fields: { ...formView?.fields, ...treeView?.fields },
+ searchFields: mergeSearchFields([
+ formView?.search_fields,
+ treeView?.search_fields,
+ ]),
+ searchValues,
+ }),
+ [
+ formView?.fields,
+ formView?.search_fields,
+ treeView?.fields,
+ treeView?.search_fields,
+ searchValues,
+ searchVisible,
+ ],
+ );
+
+ if (filterType === "top") {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/src/widgets/views/Tree/Paginated/components/PaginatedTableComponent.tsx b/src/widgets/views/Tree/Paginated/components/PaginatedTableComponent.tsx
new file mode 100644
index 000000000..9e1d11fad
--- /dev/null
+++ b/src/widgets/views/Tree/Paginated/components/PaginatedTableComponent.tsx
@@ -0,0 +1,65 @@
+import { memo } from "react";
+import { PaginatedTable } from "@gisce/react-formiga-table";
+import { PaginatedTableContentProps } from "../SearchTreePaginated.types";
+
+export const PaginatedTableComponent = memo(
+ ({
+ columns,
+ treeOoui,
+ strings,
+ isLoading,
+ availableHeight,
+ results,
+ handleRowDoubleClick,
+ onRowHasBeenSelected,
+ updateColumnState,
+ getColumnState,
+ setTreeFirstVisibleRow,
+ onGetFirstVisibleRowIndex,
+ onGetFirstVisibleColumn,
+ setTreeFirstVisibleColumn,
+ footerComp,
+ statusComp,
+ onRowStatus,
+ onRowStyle,
+ headerCheckboxState,
+ onHeaderCheckboxClick,
+ refresh,
+ actionViewSortState,
+ setActionViewSortState,
+ tableRef,
+ }: PaginatedTableContentProps) => {
+ if (!columns || !treeOoui) return null;
+
+ return (
+
+ );
+ },
+);
+
+PaginatedTableComponent.displayName = "PaginatedTableComponent";
diff --git a/src/widgets/views/Tree/Paginated/hooks/usePaginatedSearch.ts b/src/widgets/views/Tree/Paginated/hooks/usePaginatedSearch.ts
new file mode 100644
index 000000000..1e9c4aff5
--- /dev/null
+++ b/src/widgets/views/Tree/Paginated/hooks/usePaginatedSearch.ts
@@ -0,0 +1,536 @@
+import { mergeParams } from "@/helpers/searchHelper";
+import { useSearchTreeState } from "@/hooks/useSearchTreeState";
+import { PaginatedTableRef, CheckboxState } from "@gisce/react-formiga-table";
+import {
+ CSSProperties,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { useNetworkRequest } from "../../../../../hooks/useNetworkRequest";
+import { ConnectionProvider, TreeView } from "../../../../..";
+import { useShowErrorDialog } from "@/ui/GenericErrorDialog";
+import { useDeepCompareEffect } from "use-deep-compare";
+import deepEqual from "deep-equal";
+import {
+ getColorMap,
+ getStatusMap,
+ getTableItems,
+ getSortedFieldsFromState,
+ getOrderFromSortFields,
+} from "@/helpers/treeHelper";
+import { Tree as TreeOoui } from "@gisce/ooui";
+import { getKey } from "@/helpers/tree-columnStorageHelper";
+import { useTreeColumnStorageFetch } from "@/widgets/base/one2many/useTreeColumnStorageFetch";
+
+export const DEFAULT_PAGE_SIZE = 80;
+
+export type PaginatedSearchProps = {
+ treeViewFetching: boolean;
+ treeOoui?: TreeOoui;
+ treeView?: TreeView;
+ model: string;
+ rootTree?: boolean;
+ nameSearchProps?: string;
+ tableRef: React.RefObject;
+ domain?: any;
+ context?: any;
+ filterType?: "side" | "top";
+};
+
+export const usePaginatedSearch = (props: PaginatedSearchProps) => {
+ const {
+ treeViewFetching,
+ treeOoui,
+ treeView,
+ model,
+ rootTree = false,
+ nameSearchProps,
+ tableRef,
+ domain = [],
+ context,
+ filterType = "side",
+ } = props;
+
+ // State from useSearchTreeState
+ const {
+ treeIsLoading,
+ setTreeIsLoading,
+ searchVisible,
+ setSearchVisible,
+ setSelectedRowItems,
+ setTreeFirstVisibleRow,
+ treeFirstVisibleRow,
+ treeFirstVisibleColumn,
+ setTreeFirstVisibleColumn,
+ selectedRowItems,
+ setSearchParams,
+ searchValues,
+ searchParams,
+ setSearchValues,
+ searchTreeNameSearch,
+ setSearchTreeNameSearch,
+ setResults: setActionViewResults,
+ setSearchQuery,
+ setTotalItems: setTotalItemsActionView,
+ isActive,
+ currentPage,
+ setCurrentPage,
+ order: actionViewOrder,
+ setOrder: setActionViewOrder,
+ limit,
+ setLimit,
+ } = useSearchTreeState({ useLocalState: !rootTree });
+
+ // Local state
+ const [totalRowsLoading, setTotalRowsLoading] = useState(true);
+ const [totalRows, setTotalRows] = useState();
+ const [results, setResults] = useState([]);
+
+ // Refs
+ const nameSearch = nameSearchProps || searchTreeNameSearch;
+ const prevNameSearch = useRef(nameSearch);
+ const prevSearchParamsRef = useRef(searchParams);
+ const prevSearchVisibleRef = useRef(searchVisible);
+ const currentSearchParamsString = useRef();
+ const colorsForResults = useRef<{ [key: number]: string }>({});
+ const statusForResults = useRef<{ [key: number]: string }>();
+ const lastAssignedResults = useRef([]);
+
+ const columnStateKey = useMemo(() => {
+ return getKey({ treeViewId: treeView?.view_id, model });
+ }, [treeView?.view_id, model]);
+
+ const {
+ fetchColumnState,
+ loading: getColumnStateInProgress,
+ getColumnState,
+ updateColumnState,
+ } = useTreeColumnStorageFetch({
+ key: columnStateKey,
+ treeViewFetching,
+ });
+
+ // Hooks
+ const showErrorDialog = useShowErrorDialog();
+ const [fetchTotalRows, cancelFetchTotalRows] = useNetworkRequest(
+ ConnectionProvider.getHandler().searchCount,
+ );
+ const [searchForTree, cancelSearchForTree] = useNetworkRequest(
+ ConnectionProvider.getHandler().searchForTree,
+ );
+
+ const [fetchAllIds, cancelFetchAllIds] = useNetworkRequest(
+ ConnectionProvider.getHandler().searchAllIds,
+ );
+
+ // Memoized values
+ const mergedParams = useMemo(
+ () => mergeParams(searchParams || [], domain),
+ [domain, searchParams],
+ );
+
+ const selectedRowKeys = useMemo(() => {
+ return selectedRowItems?.map((item) => item.id) || [];
+ }, [selectedRowItems]);
+
+ // Helper functions
+ const mustUpdateTotal = useCallback(() => {
+ const params = nameSearch ? domain : mergedParams;
+ const paramsString = `${JSON.stringify(params)}-${nameSearch}`;
+
+ if (paramsString !== currentSearchParamsString.current) {
+ currentSearchParamsString.current = paramsString;
+ return true;
+ }
+ return false;
+ }, [domain, mergedParams, nameSearch]);
+
+ // Core functionality
+ const updateTotalRows = useCallback(async () => {
+ setTotalRows(undefined);
+ setTotalItemsActionView(0);
+ setTotalRowsLoading(true);
+ try {
+ const totalItems = await fetchTotalRows({
+ params: nameSearch ? domain : mergedParams,
+ model,
+ context,
+ name_search: nameSearch,
+ });
+ setTotalRows(totalItems);
+ setTotalItemsActionView(totalItems);
+ } catch (err) {
+ showErrorDialog(err);
+ } finally {
+ setTotalRowsLoading(false);
+ }
+ }, [
+ setTotalItemsActionView,
+ fetchTotalRows,
+ nameSearch,
+ domain,
+ mergedParams,
+ model,
+ context,
+ showErrorDialog,
+ ]);
+
+ // Event handlers
+ const onGetFirstVisibleRowIndex = useCallback(() => {
+ return treeFirstVisibleRow;
+ }, [treeFirstVisibleRow]);
+
+ const onGetFirstVisibleColumn = useCallback(() => {
+ return treeFirstVisibleColumn;
+ }, [treeFirstVisibleColumn]);
+
+ const onRowStyle = useCallback((item: Record): CSSProperties => {
+ if (colorsForResults.current[item.node?.data?.id]) {
+ return { color: colorsForResults.current[item.node?.data?.id] };
+ }
+ return {};
+ }, []);
+
+ const onRowStatus = useCallback(
+ (record: any) => statusForResults.current?.[record.id],
+ [],
+ );
+
+ // Search filter handlers
+ const onSearchFilterClear = useCallback(() => {
+ setSelectedRowItems([]);
+ tableRef.current?.unselectAll();
+ setSearchTreeNameSearch?.(undefined);
+ setSearchParams?.([]);
+ setSearchValues?.(undefined);
+ }, [
+ setSelectedRowItems,
+ tableRef,
+ setSearchTreeNameSearch,
+ setSearchParams,
+ setSearchValues,
+ ]);
+
+ const onSearchFilterSubmit = useCallback(
+ ({ params, searchValues }: any) => {
+ setSelectedRowItems([]);
+ tableRef.current?.unselectAll();
+ setSearchTreeNameSearch?.(undefined);
+ setSearchParams?.(params);
+ setSearchValues?.(searchValues);
+ },
+ [
+ setSelectedRowItems,
+ tableRef,
+ setSearchTreeNameSearch,
+ setSearchParams,
+ setSearchValues,
+ ],
+ );
+
+ const onSideSearchFilterClose = useCallback(
+ () => setSearchVisible?.(false),
+ [setSearchVisible],
+ );
+
+ const onSideSearchFilterSubmit = useCallback(
+ ({ params, values }: any) => {
+ setSelectedRowItems([]);
+ tableRef.current?.unselectAll();
+ setSearchTreeNameSearch?.(undefined);
+ setSearchParams?.(params);
+ setSearchValues?.(values);
+ setSearchVisible?.(false);
+ },
+ [
+ setSelectedRowItems,
+ tableRef,
+ setSearchTreeNameSearch,
+ setSearchParams,
+ setSearchValues,
+ setSearchVisible,
+ ],
+ );
+
+ // Effects
+ useEffect(() => {
+ if (treeViewFetching) {
+ return;
+ }
+ return () => {
+ cancelFetchTotalRows();
+ cancelSearchForTree();
+ cancelFetchAllIds();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [treeViewFetching]);
+
+ useDeepCompareEffect(() => {
+ if (!treeOoui || !treeView || treeViewFetching) {
+ return;
+ }
+ fetchResults();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ treeView,
+ treeOoui,
+ limit,
+ currentPage,
+ mergedParams,
+ nameSearch,
+ domain,
+ actionViewOrder,
+ ]);
+
+ useEffect(() => {
+ if (
+ (nameSearch !== undefined && prevNameSearch.current === undefined) ||
+ (typeof nameSearch === "string" &&
+ typeof prevNameSearch.current === "string" &&
+ nameSearch !== prevNameSearch.current)
+ ) {
+ setSearchParams?.([]);
+ setSearchValues?.({});
+ tableRef.current?.unselectAll();
+ refresh();
+ }
+ prevNameSearch.current = nameSearch;
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [nameSearch]);
+
+ useDeepCompareEffect(() => {
+ const searchParamsChanged = !deepEqual(
+ searchParams,
+ prevSearchParamsRef.current,
+ );
+ const searchVisibleChangedToFalse =
+ prevSearchVisibleRef.current && !searchVisible;
+
+ if (
+ searchParamsChanged &&
+ (searchVisibleChangedToFalse || filterType === "top")
+ ) {
+ refresh();
+ }
+
+ prevSearchParamsRef.current = searchParams;
+ prevSearchVisibleRef.current = searchVisible;
+ }, [searchParams, searchVisible]);
+
+ const fetchResults = useCallback(async () => {
+ if (!treeOoui || treeViewFetching) {
+ return [];
+ }
+ setTreeIsLoading(true);
+
+ const attrs: any = {};
+ if (treeOoui.colors) {
+ attrs.colors = treeOoui.colors;
+ }
+ if (treeOoui.status) {
+ attrs.status = treeOoui.status;
+ }
+
+ let order;
+ if (actionViewOrder?.length) {
+ const sortFields = getSortedFieldsFromState({
+ state: actionViewOrder,
+ });
+ order = getOrderFromSortFields(sortFields);
+ }
+
+ const params = nameSearch ? domain : mergedParams;
+
+ const { results, attrsEvaluated } = await searchForTree({
+ params,
+ limit,
+ offset: ((currentPage || 1) - 1) * limit,
+ model,
+ fields: treeView!.field_parent
+ ? { ...treeView!.fields, [treeView!.field_parent]: {} }
+ : treeView!.fields,
+ context,
+ attrs,
+ order,
+ name_search: nameSearch,
+ });
+
+ const newResults = results.map((item: any) => ({ id: item.id }));
+
+ setSearchQuery?.({
+ model,
+ params,
+ name_search: nameSearch,
+ context,
+ });
+
+ setActionViewResults?.(newResults);
+
+ if (mustUpdateTotal()) {
+ updateTotalRows();
+ }
+
+ if (results.length === 0) {
+ lastAssignedResults.current = [];
+ setTotalRows(0);
+ setTotalItemsActionView(0);
+ setResults([]);
+ setTreeIsLoading(false);
+ return;
+ }
+
+ const preparedResults = getTableItems(treeOoui, results);
+
+ const colors = getColorMap(attrsEvaluated);
+
+ colorsForResults.current = {
+ ...colorsForResults.current,
+ ...colors,
+ };
+
+ if (!statusForResults.current && treeOoui.status) {
+ statusForResults.current = {};
+ }
+
+ if (treeOoui.status) {
+ const status = getStatusMap(attrsEvaluated);
+ statusForResults.current = {
+ ...statusForResults.current,
+ ...status,
+ };
+ }
+
+ setTreeIsLoading(false);
+ lastAssignedResults.current = [...preparedResults];
+ setResults([...preparedResults]);
+ }, [
+ treeOoui,
+ treeViewFetching,
+ setTreeIsLoading,
+ actionViewOrder,
+ nameSearch,
+ domain,
+ mergedParams,
+ searchForTree,
+ limit,
+ currentPage,
+ model,
+ treeView,
+ context,
+ setSearchQuery,
+ setActionViewResults,
+ mustUpdateTotal,
+ updateTotalRows,
+ setTotalItemsActionView,
+ ]);
+
+ const refresh = useCallback(async () => {
+ setTreeFirstVisibleRow(0);
+ fetchColumnState();
+ setSelectedRowItems([]);
+ currentSearchParamsString.current = undefined;
+ fetchResults();
+ }, [
+ fetchColumnState,
+ fetchResults,
+ setSelectedRowItems,
+ setTreeFirstVisibleRow,
+ ]);
+
+ const onRequestPageChange = useCallback(
+ (page: number, pageSize?: number) => {
+ setTreeFirstVisibleRow(0);
+ setSelectedRowItems([]);
+ setCurrentPage(page);
+ pageSize && setLimit(pageSize);
+ },
+ [setCurrentPage, setLimit, setSelectedRowItems, setTreeFirstVisibleRow],
+ );
+
+ const getAllIds = useCallback(async () => {
+ return await fetchAllIds({
+ params: mergeParams(searchParams, domain),
+ model,
+ context,
+ totalItems: totalRows,
+ });
+ }, [fetchAllIds, searchParams, domain, model, context, totalRows]);
+
+ const selectAllRecords = useCallback(async () => {
+ const allIds = await getAllIds();
+ setSelectedRowItems?.(allIds.map((id: number) => ({ id })));
+ // onChangeSelectedRowKeys?.(allIds);
+ }, [getAllIds, setSelectedRowItems]);
+
+ const headerCheckboxState: CheckboxState = useMemo(() => {
+ if (selectedRowKeys.length === 0) return "unchecked";
+ if (selectedRowKeys.length === limit && limit > 0) return "checked";
+ if (selectedRowKeys.length === totalRows) return "checked";
+ return "indeterminate";
+ }, [selectedRowKeys, limit, totalRows]);
+
+ const onHeaderCheckboxClick = useCallback(() => {
+ if (headerCheckboxState === "unchecked") {
+ // Moving to checked state
+ tableRef.current?.selectAll();
+ setSelectedRowItems(results.map((item) => ({ id: item.id })));
+ } else {
+ // Moving to unchecked state
+ setSelectedRowItems([]);
+ tableRef.current?.unselectAll();
+ }
+ }, [tableRef, setSelectedRowItems, results, headerCheckboxState]);
+
+ const onRowHasBeenSelected = useCallback(
+ ({ id, selected }: { id: number; selected: boolean }) => {
+ setSelectedRowItems((prevItems) => {
+ if (selected) {
+ const item = results.find((result) => result.id === id);
+ if (item && !prevItems.some((existing) => existing.id === id)) {
+ return [...prevItems, item];
+ }
+ return prevItems;
+ }
+ return prevItems.filter((existing) => existing.id !== id);
+ });
+ },
+ [results, setSelectedRowItems],
+ );
+
+ return {
+ isActive,
+ searchVisible,
+ searchValues,
+ selectedRowKeys,
+ refresh,
+ onRowStatus,
+ onGetFirstVisibleRowIndex,
+ setTreeFirstVisibleRow,
+ onRowHasBeenSelected,
+ onSearchFilterClear,
+ onSearchFilterSubmit,
+ onSideSearchFilterClose,
+ onSideSearchFilterSubmit,
+ totalRowsLoading,
+ totalRows,
+ onRowStyle,
+ results,
+ onRequestPageChange,
+ treeIsLoading,
+ selectAllRecords,
+ onHeaderCheckboxClick,
+ headerCheckboxState,
+ getColumnStateInProgress,
+ getColumnState,
+ updateColumnState,
+ currentPage,
+ limit,
+ order: actionViewOrder,
+ setOrder: setActionViewOrder,
+ setTreeFirstVisibleColumn,
+ onGetFirstVisibleColumn,
+ };
+};
diff --git a/src/widgets/views/Tree/Tree.tsx b/src/widgets/views/Tree/Tree.tsx
index 51492051e..67fa64e26 100644
--- a/src/widgets/views/Tree/Tree.tsx
+++ b/src/widgets/views/Tree/Tree.tsx
@@ -113,6 +113,7 @@ export const UnmemoizedTree = forwardRef(
...COLUMN_COMPONENTS,
},
context,
+ "legacy",
);
}, [context, treeOoui]);