diff --git a/client/packages/lowcoder-design/src/components/Search.tsx b/client/packages/lowcoder-design/src/components/Search.tsx index 11e5f2adc..dff0ebeeb 100644 --- a/client/packages/lowcoder-design/src/components/Search.tsx +++ b/client/packages/lowcoder-design/src/components/Search.tsx @@ -62,24 +62,35 @@ interface ISearch { placeholder: string; value: string; onChange: (value: React.ChangeEvent) => void; + onEnterPress?: (value: string) => void; // Added for capturing Enter key press disabled?: boolean; } export const Search = (props: ISearch & InputProps) => { - const { value, onChange, style, disabled, placeholder, ...others } = props; + const { value, onChange, style, disabled, placeholder, onEnterPress, ...others } = props; + const handleChange = (e: React.ChangeEvent) => { onChange && onChange(e); }; + + // Handling Enter key press + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && onEnterPress) { + onEnterPress(value); + } + }; + return ( - - } - {...others} - /> - + + } + {...others} + /> + ); -}; +}; \ No newline at end of file diff --git a/client/packages/lowcoder/src/api/applicationApi.ts b/client/packages/lowcoder/src/api/applicationApi.ts index a0edb7424..2411b50d8 100644 --- a/client/packages/lowcoder/src/api/applicationApi.ts +++ b/client/packages/lowcoder/src/api/applicationApi.ts @@ -12,7 +12,7 @@ import { SetAppEditingStatePayload, UpdateAppPermissionPayload, } from "redux/reduxActions/applicationActions"; -import { ApiResponse, GenericApiResponse } from "./apiResponses"; +import {ApiResponse, GenericApiResponse} from "./apiResponses"; import { JSONObject, JSONValue } from "util/jsonTypes"; import { ApplicationDetail, @@ -24,6 +24,7 @@ import { } from "constants/applicationConstants"; import { CommonSettingResponseData } from "./commonSettingApi"; import { ResourceType } from "@lowcoder-ee/constants/queryConstants"; +import {fetchAppRequestType, GenericApiPaginationResponse} from "@lowcoder-ee/util/pagination/type"; export interface HomeOrgMeta { id: string; @@ -108,6 +109,10 @@ class ApplicationApi extends Api { return Api.get(ApplicationApi.newURLPrefix + "/list", { ...request, withContainerSize: false }); } + static fetchAllApplicationsPagination(request: fetchAppRequestType): AxiosPromise> { + return Api.get(ApplicationApi.newURLPrefix + "/list", { ...request, withContainerSize: false, applicationStatus: "RECYCLED" }); + } + static fetchAllModules(request: HomeDataPayload): AxiosPromise { return Api.get(ApplicationApi.newURLPrefix + "/list", { applicationType: AppTypeEnum.Module, diff --git a/client/packages/lowcoder/src/api/datasourceApi.ts b/client/packages/lowcoder/src/api/datasourceApi.ts index ea08bb934..1be29e646 100644 --- a/client/packages/lowcoder/src/api/datasourceApi.ts +++ b/client/packages/lowcoder/src/api/datasourceApi.ts @@ -8,6 +8,11 @@ import { JSONArray } from "util/jsonTypes"; import { AuthType, HttpOAuthGrantType } from "pages/datasource/form/httpDatasourceForm"; import { Datasource } from "@lowcoder-ee/constants/datasourceConstants"; import { DataSourcePluginMeta } from "lowcoder-sdk/dataSource"; +import { + fetchDataSourcePaginationRequestType, + fetchDBRequestType, + GenericApiPaginationResponse +} from "@lowcoder-ee/util/pagination/type"; export interface PreparedStatementConfig { enableTurnOffPreparedStatement: boolean; @@ -164,6 +169,11 @@ export class DatasourceApi extends Api { return Api.get(DatasourceApi.url + `/jsDatasourcePlugins?appId=${appId}`); } + static fetchJsDatasourcePaginationByApp( request: fetchDataSourcePaginationRequestType ): AxiosPromise> { + const {appId, ...res} = request + return Api.get(DatasourceApi.url + `/jsDatasourcePlugins?appId=${appId}` ,{...res}); + } + static fetchDatasourceByApp(appId: string): AxiosPromise> { return Api.get(DatasourceApi.url + `/listByApp?appId=${appId}`); } @@ -172,6 +182,11 @@ export class DatasourceApi extends Api { return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`); } + static fetchDatasourcePaginationByOrg(request: fetchDBRequestType): AxiosPromise> { + const {orgId, ...res} = request; + return Api.get(DatasourceApi.url + `/listByOrg?orgId=${orgId}`, {...res}); + } + static createDatasource( datasourceConfig: Partial ): AxiosPromise> { diff --git a/client/packages/lowcoder/src/api/folderApi.ts b/client/packages/lowcoder/src/api/folderApi.ts index 0f2fd47e5..113bab046 100644 --- a/client/packages/lowcoder/src/api/folderApi.ts +++ b/client/packages/lowcoder/src/api/folderApi.ts @@ -9,6 +9,10 @@ import { UpdateFolderPayload, } from "../redux/reduxActions/folderActions"; import { ApplicationMeta, FolderMeta } from "../constants/applicationConstants"; +import { + fetchFolderRequestType, + GenericApiPaginationResponse +} from "@lowcoder-ee/util/pagination/type"; export class FolderApi extends Api { static url = "/folders"; @@ -40,4 +44,11 @@ export class FolderApi extends Api { ): AxiosPromise> { return Api.get(FolderApi.url + `/elements`, { id: request.folderId }); } + + static fetchFolderElementsPagination( + request: fetchFolderRequestType + ): AxiosPromise> { + const {id, ...res} = request + return request.id ? Api.get(FolderApi.url + `/elements`,{id: id, ...res}) : Api.get(FolderApi.url + `/elements`, { ...request }); + } } diff --git a/client/packages/lowcoder/src/api/orgApi.ts b/client/packages/lowcoder/src/api/orgApi.ts index 6e7c532e4..588a20df5 100644 --- a/client/packages/lowcoder/src/api/orgApi.ts +++ b/client/packages/lowcoder/src/api/orgApi.ts @@ -10,6 +10,15 @@ import { UpdateUserOrgRolePayload, } from "redux/reduxActions/orgActions"; import { ApiResponse, GenericApiResponse } from "./apiResponses"; +import { + ApiPaginationResponse, + fetchGroupUserRequestType, + fetchOrgsByEmailRequestType, + fetchOrgUserRequestType, + GenericApiPaginationResponse, + GroupUsersPaginationResponse, + orgGroupRequestType, OrgUsersPaginationResponse +} from "@lowcoder-ee/util/pagination/type"; export interface GroupUsersResponse extends ApiResponse { data: { @@ -66,6 +75,10 @@ export class OrgApi extends Api { return Api.get(OrgApi.fetchGroupURL); } + static fetchGroupPagination(request: orgGroupRequestType): AxiosPromise> { + return Api.get(OrgApi.fetchGroupURL, {...request}); + } + static deleteGroup(groupId: string): AxiosPromise { return Api.delete(OrgApi.deleteGroupURL(groupId)); } @@ -88,10 +101,20 @@ export class OrgApi extends Api { return Api.get(OrgApi.fetchOrgUsersURL(orgId)); } + static fetchOrgUsersPagination(request:fetchOrgUserRequestType): AxiosPromise { + const {orgId, ...res} = request; + return Api.get(OrgApi.fetchOrgUsersURL(orgId), {...res}); + } + static fetchGroupUsers(groupId: string): AxiosPromise { return Api.get(OrgApi.fetchGroupUsersURL(groupId)); } + static fetchGroupUsersPagination(request: fetchGroupUserRequestType): AxiosPromise { + const {groupId, ...res} = request; + return Api.get(OrgApi.fetchGroupUsersURL(groupId), {...res}); + } + static deleteGroupUser(request: RemoveGroupUserPayload): AxiosPromise { return Api.delete(OrgApi.deleteGroupUserURL(request.groupId), { userId: request.userId, @@ -145,6 +168,11 @@ export class OrgApi extends Api { static fetchOrgsByEmail(email: string): AxiosPromise { return Api.get(OrgApi.fetchOrgsByEmailURL(email)); } + + static fetchOrgsPaginationByEmail(request: fetchOrgsByEmailRequestType): AxiosPromise { + const { email, ...rest } = request; + return Api.get(OrgApi.fetchOrgsByEmailURL(email), {...rest}); + } } export default OrgApi; diff --git a/client/packages/lowcoder/src/api/queryLibraryApi.ts b/client/packages/lowcoder/src/api/queryLibraryApi.ts index 063cf6ecc..16e6a9dc0 100644 --- a/client/packages/lowcoder/src/api/queryLibraryApi.ts +++ b/client/packages/lowcoder/src/api/queryLibraryApi.ts @@ -2,6 +2,7 @@ import Api from "./api"; import { AxiosPromise } from "axios"; import { GenericApiResponse } from "./apiResponses"; import { DatasourceType } from "@lowcoder-ee/constants/queryConstants"; +import {fetchQueryLibraryPaginationRequestType, GenericApiPaginationResponse} from "@lowcoder-ee/util/pagination/type"; export interface LibraryQuery { id: string; @@ -49,6 +50,10 @@ export class QueryLibraryApi extends Api { return Api.get(QueryLibraryApi.url + `/listByOrg`); } + static fetchQueryLibraryPaginationByOrg(request: fetchQueryLibraryPaginationRequestType): AxiosPromise>> { + return Api.get(QueryLibraryApi.url + `/listByOrg`, {...request}); + } + static fetchQueryLibraryDropdown(): AxiosPromise< GenericApiResponse> > { diff --git a/client/packages/lowcoder/src/components/TypographyText.tsx b/client/packages/lowcoder/src/components/TypographyText.tsx index 7bf156859..81db5a69b 100644 --- a/client/packages/lowcoder/src/components/TypographyText.tsx +++ b/client/packages/lowcoder/src/components/TypographyText.tsx @@ -40,9 +40,9 @@ const StyledTypographyText = styled(AntdTypographyText)` `; export const TypographyText = (props: { - value: string; - editing: boolean; - onChange: (value: string) => void; + value?: string; + editing?: boolean; + onChange?: (value: string) => void; }) => ( void; onHistoryShow: () => void }) { + propertyView(params: { onPublish: () => void; onHistoryShow: () => void; setModify: any; modify: boolean }) { return ( - + ); } @@ -99,11 +99,13 @@ function getMetaData( } const PropertyView = (props: { - comp: QueryLibraryCompType; - onPublish: () => void; - onHistoryShow: () => void; + comp: QueryLibraryCompType, + onPublish: () => void, + onHistoryShow: () => void, + setModify?: any + modify?: boolean }) => { - const { comp, onPublish, onHistoryShow } = props; + const { comp, onPublish, onHistoryShow, setModify, modify } = props; const reduxDispatch = useDispatch(); @@ -157,12 +159,16 @@ const PropertyView = (props: { CustomModal.confirm({ title: trans("queryLibrary.deleteQueryLabel"), content: trans("queryLibrary.deleteQueryContent"), - onConfirm: () => + onConfirm: () =>{ reduxDispatch( deleteQueryLibrary({ queryLibraryId: comp.children.query.children.id.getView(), }) - ), + ) + setTimeout(() => { + setModify(!modify); + }, 500); + }, confirmBtnType: "delete", okText: trans("delete"), }) diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/BackButton.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/BackButton.tsx new file mode 100644 index 000000000..65952d07c --- /dev/null +++ b/client/packages/lowcoder/src/pages/ApplicationV2/BackButton.tsx @@ -0,0 +1,4 @@ +export const BackButton = () =>{ + return +
123
+} \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/CreateDropdown.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/CreateDropdown.tsx index c2d93086d..787d3a243 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/CreateDropdown.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/CreateDropdown.tsx @@ -185,14 +185,14 @@ function NavLayoutPickModal(props: { ); } -export const CreateDropdown = (props: { defaultVisible?: boolean; mode: HomeLayoutMode }) => { - const { defaultVisible, mode } = props; +export const CreateDropdown = (props: { defaultVisible?: boolean; mode: HomeLayoutMode; setModify: any; modify: boolean }) => { + const { defaultVisible, mode, setModify, modify} = props; const [createDropdownVisible, setCreateDropdownVisible] = useState(false); const [layoutPickerVisible, setLayoutPickerVisible] = useState(false); const user = useSelector(getUser); - const [handleCreate, isCreating] = useCreateHomeRes(); + const [handleCreate, isCreating] = useCreateHomeRes(setModify, modify); const getCreateMenuItem = (type: HomeResTypeEnum, mode?: HomeLayoutMode): ItemType => { if ( diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/FolderView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/FolderView.tsx index 1862533d8..1d606bf84 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/FolderView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/FolderView.tsx @@ -1,13 +1,14 @@ import { useDispatch, useSelector } from "react-redux"; import { useParams } from "react-router-dom"; import { HomeBreadcrumbType, HomeLayout } from "./HomeLayout"; -import { useEffect } from "react"; -import { fetchFolderElements } from "../../redux/reduxActions/folderActions"; -import { FolderMeta } from "../../constants/applicationConstants"; +import {useEffect, useState} from "react"; +import {ApplicationMeta, FolderMeta} from "../../constants/applicationConstants"; import { buildFolderUrl } from "../../constants/routesURL"; import { folderElementsSelector, foldersSelector } from "../../redux/selectors/folderSelector"; import { Helmet } from "react-helmet"; import { trans } from "i18n"; +import {ApplicationPaginationType} from "@lowcoder-ee/util/pagination/type"; +import {fetchFolderElements} from "@lowcoder-ee/util/pagination/axios"; function getBreadcrumbs( folder: FolderMeta, @@ -30,12 +31,25 @@ function getBreadcrumbs( return breadcrumb; } +interface ElementsState { + elements: ApplicationMeta[]; + total: number; +} + export function FolderView() { const { folderId } = useParams<{ folderId: string }>(); + const [elements, setElements] = useState({ elements: [], total: 0 }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [searchValues, setSearchValues] = useState(""); + const [typeFilter, setTypeFilter] = useState(0); + const [modify, setModify] = useState(true); + const [searchValue, setSearchValue] = useState(""); + const dispatch = useDispatch(); - const elements = useSelector(folderElementsSelector); + const element = useSelector(folderElementsSelector); const allFolders = useSelector(foldersSelector); const folder = allFolders.filter((f) => f.folderId === folderId)[0] || {}; @@ -46,16 +60,60 @@ export function FolderView() { }, ]); - useEffect(() => { - setTimeout(() => { - dispatch(fetchFolderElements({ folderId: folderId })); - }, 100); - }, [folderId]); + useEffect( () => { + try{ + fetchFolderElements({ + id: folderId, + pageNum:currentPage, + pageSize:pageSize, + applicationType: ApplicationPaginationType[typeFilter], + name: searchValues, + }).then( + (data: any) => { + if (data.success) { + setElements({elements: data.data || [], total: data.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", data.error) + } + ); + } catch (error) { + console.error('Failed to fetch data:', error); + } + }, [currentPage, pageSize, searchValues, typeFilter, modify]); + + useEffect( () => { + if (searchValues !== "") + setCurrentPage(1); + }, [searchValues] + ); + + useEffect(()=> { + const timer = setTimeout(() => { + if (searchValue.length > 2 || searchValue === "") + setSearchValues(searchValue) + }, 500); + return () => clearTimeout(timer); + }, [searchValue]) return ( <> {{trans("home.yourFolders")}} - + ); } diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeCardView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeCardView.tsx index ac515b574..0ad356fbf 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeCardView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeCardView.tsx @@ -1,7 +1,7 @@ import styled from "styled-components"; import { HomeRes } from "./HomeLayout"; -import { HomeResCard } from "./HomeResCard"; -import { MarketplaceResCard } from "./MarketplaceResCard"; +import {Back, HomeResCard} from "./HomeResCard"; +import { MarketplaceResCard} from "./MarketplaceResCard"; import React, { useState } from "react"; import { MoveToFolderModal } from "./MoveToFolderModal"; @@ -19,17 +19,19 @@ const ApplicationCardsWrapper = styled.div` } `; -export function HomeCardView(props: { resources: HomeRes[] }) { +export function HomeCardView(props: { resources: HomeRes[], setModify?: any, modify?: boolean, mode?: string }) { + const {setModify, modify,mode} = props; const [needMoveRes, setNeedMoveRes] = useState(undefined); return ( + {props.resources.map((res) => ( res.isMarketplace ? : - + ))} - setNeedMoveRes(undefined)} /> + setNeedMoveRes(undefined)} setModify={setModify} modify={modify!} /> ); } diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx index e69792bbd..228fd0487 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeLayout.tsx @@ -35,6 +35,7 @@ import { isFetchingFolderElements } from "../../redux/selectors/folderSelector"; import { checkIsMobile } from "util/commonUtils"; import { default as Divider } from "antd/es/divider"; import { ApplicationCategoriesEnum } from "constants/applicationConstants"; +import { Pagination } from 'antd'; const Wrapper = styled.div` display: flex; @@ -199,6 +200,12 @@ const EmptyView = styled.div` } } `; +const PaginationLayout = styled.div` + display: flex; + justify-content: center; + margin-top: 20px; + margin-bottom: 20px; +` const LayoutSwitcher = styled.div` position: absolute; @@ -301,11 +308,43 @@ export interface HomeLayoutProps { localMarketplaceApps?: Array; globalMarketplaceApps?: Array; mode: HomeLayoutMode; + setCurrentPage?: any; + setPageSize?: any; + currentPage?: number; + pageSize?: number; + total?: number; + searchValue?: string; + setSearchValue?: any; + setTypeFilterPagination?: any; + setModify?: any; + modify?: boolean; } export function HomeLayout(props: HomeLayoutProps) { + const { breadcrumb = [], + elements = [], + localMarketplaceApps = [], + globalMarketplaceApps = [], + mode , + setCurrentPage, + setPageSize, + pageSize, + currentPage, + searchValue, + setSearchValue, + total, + setTypeFilterPagination, + setModify, + modify + + } = props; + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; - const { breadcrumb = [], elements = [], localMarketplaceApps = [], globalMarketplaceApps = [], mode } = props; + const handlePageSizeChange = (current: number, size: number) => { + setPageSize(size); + }; const categoryOptions = [ { label: {trans("home.allCategories")}, value: 'All' }, @@ -324,7 +363,7 @@ export function HomeLayout(props: HomeLayoutProps) { const isSelfHost = window.location.host !== 'app.lowcoder.cloud'; const [typeFilter, setTypeFilter] = useState("All"); const [categoryFilter, setCategoryFilter] = useState("All"); - const [searchValue, setSearchValue] = useState(""); + const [visibility, setVisibility] = useState(mode === "view" || mode === "trash" || mode === "folder"); const [layout, setLayout] = useState( checkIsMobile(window.innerWidth) ? "card" : getHomeLayout() ); @@ -342,7 +381,15 @@ export function HomeLayout(props: HomeLayoutProps) { return null; } - var displayElements = elements; + var displayElements = elements.sort((a, b) => { + if (a.folder && !b.folder) { + return -1; + } else if (!a.folder && b.folder) { + return 1; + } else { + return 0; + } + }); if (mode === "marketplace" && isSelfHost) { const markedLocalApps = localMarketplaceApps.map(app => ({ ...app, isLocalMarketplace: true })); @@ -354,27 +401,34 @@ export function HomeLayout(props: HomeLayoutProps) { const markedLocalApps = localMarketplaceApps.map(app => ({ ...app, isLocalMarketplace: true })); displayElements = [...markedLocalApps]; } - const resList: HomeRes[] = displayElements - .filter((e) => - searchValue - ? e.name?.toLocaleLowerCase().includes(searchValue?.toLocaleLowerCase()) || - e.createBy?.toLocaleLowerCase().includes(searchValue?.toLocaleLowerCase()) - : true - ) .filter((e) => { - if (HomeResTypeEnum[typeFilter].valueOf() === HomeResTypeEnum.All) { + if (!visibility) { + if (searchValue) { + const lowerCaseSearchValue = searchValue.toLocaleLowerCase(); + return e.name?.toLocaleLowerCase().includes(lowerCaseSearchValue) || + e.createBy?.toLocaleLowerCase().includes(lowerCaseSearchValue); + } return true; } - if (e.folder) { - return HomeResTypeEnum[typeFilter] === HomeResTypeEnum.Folder; - } else { - if (typeFilter === "Navigation") { - return NavigationTypes.map((t) => t.valueOf()).includes(e.applicationType); + return true; + }) + .filter((e) => { + if(!visibility) { + if (HomeResTypeEnum[typeFilter].valueOf() === HomeResTypeEnum.All) { + return true; + } + if (e.folder) { + return HomeResTypeEnum[typeFilter] === HomeResTypeEnum.Folder; + } else { + if (typeFilter === "Navigation") { + return NavigationTypes.map((t) => t.valueOf()).includes(e.applicationType); + } + return HomeResTypeEnum[typeFilter].valueOf() === e.applicationType; } - return HomeResTypeEnum[typeFilter].valueOf() === e.applicationType; } - }) + return true; + }) .filter((e) => { // If "All" is selected, do not filter out any elements based on category if (categoryFilter === 'All' || !categoryFilter) { @@ -415,6 +469,7 @@ export function HomeLayout(props: HomeLayoutProps) { } ); + const getFilterMenuItem = (type: HomeResTypeEnum) => { const Icon = HomeResInfo[type].icon; return { @@ -462,7 +517,7 @@ export function HomeLayout(props: HomeLayoutProps) { {showNewUserGuide(user) && } - +

{mode === "marketplace" && trans("home.appMarketplace")} @@ -480,17 +535,32 @@ export function HomeLayout(props: HomeLayoutProps) { setTypeFilter(value as HomeResKey)} + onChange={(value: any) => { + setTypeFilter(value as HomeResKey); + if(visibility) + setTypeFilterPagination(HomeResTypeEnum[value]) + } + } options={[ getFilterMenuItem(HomeResTypeEnum.All), getFilterMenuItem(HomeResTypeEnum.Application), getFilterMenuItem(HomeResTypeEnum.Module), - ...(mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Navigation)] : []), + ...(mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Navigation), getFilterMenuItem(HomeResTypeEnum.MobileTabLayout)] : []), ...(mode !== "trash" && mode !== "marketplace" ? [getFilterMenuItem(HomeResTypeEnum.Folder)] : []), ]} getPopupContainer={(node: any) => node} suffixIcon={} /> )} + {mode === "view" && + setCategoryFilter(value as ApplicationCategoriesEnum)} + options={categoryOptions} + // getPopupContainer={(node) => node} + suffixIcon={} + />} {mode === "marketplace" && ( setSearchValue(e.target.value)} style={{ width: "192px", height: "32px", margin: "0" }} /> {mode !== "trash" && mode !== "marketplace" && user.orgDev && ( - + )} @@ -526,7 +596,7 @@ export function HomeLayout(props: HomeLayoutProps) { {resList.length > 0 ? ( <> {mode === "trash" ? ( - + ) : ( <> setLayout(layout === "list" ? "card" : "list")}> @@ -575,9 +645,9 @@ export function HomeLayout(props: HomeLayoutProps) { {mode !== "marketplace" && ( <> {layout === "list" ? ( - + ) : ( - + )} )} @@ -597,16 +667,27 @@ export function HomeLayout(props: HomeLayoutProps) { ? trans("home.projectEmptyCanAdd") : trans("home.projectEmpty")} - {mode !== "trash" && mode !== "marketplace" && user.orgDev && } + {mode !== "trash" && mode !== "marketplace" && user.orgDev && } )} )} - + {visibility && resList.length ?
+ + + +
: null} - + ); diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeResCard.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeResCard.tsx index 846d59cbf..0ce784047 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeResCard.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeResCard.tsx @@ -8,6 +8,7 @@ import { HomeRes } from "./HomeLayout"; import { HomeResTypeEnum } from "../../types/homeRes"; import { updateFolder } from "../../redux/reduxActions/folderActions"; import { + backFolderViewClick, handleAppEditClick, handleAppViewClick, handleFolderViewClick, @@ -23,6 +24,7 @@ import { TypographyText } from "../../components/TypographyText"; import { useParams } from "react-router-dom"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import { colorPickerEvent } from "@lowcoder-ee/comps/comps/mediaComp/colorPickerComp"; +import {FolderIcon} from "icons"; const EditButton = styled(TacoButton)` width: 52px; @@ -141,8 +143,8 @@ const OperationWrapper = styled.div` const MONTH_MILLIS = 30 * 24 * 60 * 60 * 1000; -export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => void }) { - const { res, onMove } = props; +export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => void; setModify:any; modify: boolean }) { + const { res, onMove, setModify, modify } = props; const [appNameEditing, setAppNameEditing] = useState(false); const dispatch = useDispatch(); @@ -214,10 +216,16 @@ export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => voi } if (res.type === HomeResTypeEnum.Folder) { dispatch(updateFolder({ id: res.id, name: value })); + setTimeout(() => { + setModify(!modify); + }, 200); } else { dispatch( updateAppMetaAction({ applicationId: res.id, name: value, folderId: folderId }) ); + setTimeout(() => { + setModify(!modify); + }, 200); } setAppNameEditing(false); }} @@ -245,9 +253,37 @@ export function HomeResCard(props: { res: HomeRes; onMove: (res: HomeRes) => voi res={res} onRename={() => setAppNameEditing(true)} onMove={(res) => onMove(res)} + setModify={setModify} + modify={modify} /> ); } + +export function Back(props: { mode: string }) { + const { mode } = props; + return mode === "folder" ? + + + + { + backFolderViewClick(); + }} + > + +

...

+ +
+
+
+ : <>; +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeResOptions.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeResOptions.tsx index b712fe7e4..0049ff1b6 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeResOptions.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeResOptions.tsx @@ -38,8 +38,10 @@ export const HomeResOptions = (props: { onDuplicate?: (res: HomeRes | undefined) => void; onRename: (res: HomeRes) => void; onMove: (res: HomeRes) => void; + setModify: any; + modify: boolean; }) => { - const { res, onDuplicate, onRename, onMove } = props; + const { res, onDuplicate, onRename, onMove, setModify, modify } = props; const dispatch = useDispatch(); const [showCopyModal, setShowCopyModal] = useState(false); @@ -78,19 +80,24 @@ export const HomeResOptions = (props: { type: HomeResInfo[res.type].name, name: {res.name}, }), - onConfirm: () => + onConfirm: () =>{ new Promise((resolve, reject) => { dispatch( - recycleApplication( - { applicationId: res.id, folderId: folderId }, - () => { - messageInstance.success(trans("success")); - resolve(true); - }, - () => reject() - ) + recycleApplication( + { applicationId: res.id, folderId: folderId }, + () => { + messageInstance.success(trans("success")); + resolve(true); + }, + () => reject() + ) ); - }), + setTimeout(() => { + setModify(!modify); + }, 200); + }) + + }, confirmBtnType: "delete", okText: trans("home.moveToTrash"), }); @@ -115,19 +122,23 @@ export const HomeResOptions = (props: { type: HomeResInfo[res.type].name.toLowerCase(), name: {res.name}, }), - onConfirm: () => + onConfirm: () =>{ new Promise((resolve, reject) => { - dispatch( + dispatch( deleteFolder( - { folderId: res.id, parentFolderId: folderId }, - () => { - messageInstance.success(trans("home.deleteSuccessMsg")); - resolve(true); - }, - () => reject() + { folderId: res.id, parentFolderId: folderId }, + () => { + messageInstance.success(trans("home.deleteSuccessMsg")); + resolve(true); + }, + () => reject() ) - ); - }), + ); + }) + setTimeout(() => { + setModify(!modify); + }, 200); + }, confirmBtnType: "delete", okText: trans("delete"), }); diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeTableView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeTableView.tsx index 1eeb261e6..bd0cf6b82 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeTableView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeTableView.tsx @@ -4,6 +4,7 @@ import { TacoButton } from "lowcoder-design/src/components/button" import styled from "styled-components"; import { useDispatch } from "react-redux"; import { + backFolderViewClick, handleAppEditClick, handleAppViewClick, handleFolderViewClick, @@ -51,7 +52,8 @@ const TypographyText = styled(AntdTypographyText)` width: 100%; `; -export const HomeTableView = (props: { resources: HomeRes[] }) => { +export const HomeTableView = (props: { resources: HomeRes[], setModify?: any, modify?: boolean, mode?: string }) => { + const {setModify, modify, resources, mode} = props const dispatch = useDispatch(); const { folderId } = useParams<{ folderId: string }>(); @@ -60,26 +62,43 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { const [needDuplicateRes, setNeedDuplicateRes] = useState(undefined); const [needMoveRes, setNeedMoveRes] = useState(undefined); + const back: HomeRes = { + key: "", + id: "", + name: ". . .", + type: 4, + creator: "", + lastModifyTime: 0, + isManageable: false, + isDeletable: false + } + if (mode === "folder"){ + resources.unshift(back) + } + return ( <> ({ onClick: (e) => { - // console.log(e.target); - const item = record as HomeRes; - if (needRenameRes?.id === item.id || needDuplicateRes?.id === item.id) { - return; - } - if (item.type === HomeResTypeEnum.Folder) { - handleFolderViewClick(item.id); - } else if(item.isMarketplace) { - handleMarketplaceAppViewClick(item.id); - } else { - item.isEditable ? handleAppEditClick(e, item.id) : handleAppViewClick(item.id); + if (mode === "folder" && record.type === 4){ + backFolderViewClick() + } else{ + const item = record as HomeRes; + if (needRenameRes?.id === item.id || needDuplicateRes?.id === item.id) { + return; + } + if (item.type === HomeResTypeEnum.Folder) { + handleFolderViewClick(item.id); + } else if(item.isMarketplace) { + handleMarketplaceAppViewClick(item.id); + } else { + item.isEditable ? handleAppEditClick(e, item.id) : handleAppViewClick(item.id); + } } }, })} @@ -122,6 +141,9 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { } if (item.type === HomeResTypeEnum.Folder) { dispatch(updateFolder({ id: item.id, name: value })); + setTimeout(() => { + setModify(!modify); + }, 200); } else { dispatch( updateAppMetaAction({ @@ -130,6 +152,9 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { folderId: folderId, }) ); + setTimeout(() => { + setModify(!modify); + }, 200); } setNeedRenameRes(undefined); }, @@ -154,7 +179,7 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { }, render: (_, record) => ( - {HomeResInfo[(record as any).type as HomeResTypeEnum].name} + { mode === "folder" && record.type === 4 ? "" : HomeResInfo[(record as any).type as HomeResTypeEnum].name } ), }, @@ -216,7 +241,7 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { ? handleMarketplaceAppViewClick(item.id) : handleAppViewClick(item.id); }} - style={{ marginRight: "52px" }} + style={{ marginRight: "52px", display: mode === "folder" && record.type === 4 ? "none" : "block" }} > {trans("view")} @@ -225,15 +250,17 @@ export const HomeTableView = (props: { resources: HomeRes[] }) => { onDuplicate={(res) => setNeedDuplicateRes(res)} onRename={(res) => setNeedRenameRes(res)} onMove={(res) => setNeedMoveRes(res)} + setModify={setModify} + modify={modify!} /> ); }, }, ]} - dataSource={props.resources} + dataSource={resources} /> - setNeedMoveRes(undefined)} /> + setNeedMoveRes(undefined)} setModify={setModify} modify={modify!} /> ); }; diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx index b4309e321..8ae72d322 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/HomeView.tsx @@ -1,12 +1,62 @@ import { useSelector } from "react-redux"; import { HomeLayout } from "./HomeLayout"; import { getUser } from "../../redux/selectors/usersSelectors"; -import { folderElementsSelector } from "../../redux/selectors/folderSelector"; import { Helmet } from "react-helmet"; import { trans } from "i18n"; +import {useState, useEffect } from "react"; +import {fetchFolderElements} from "@lowcoder-ee/util/pagination/axios"; +import {ApplicationMeta, FolderMeta} from "@lowcoder-ee/constants/applicationConstants"; +import {ApplicationPaginationType} from "@lowcoder-ee/util/pagination/type"; + +interface ElementsState { + elements: (ApplicationMeta | FolderMeta)[]; + total: number; +} export function HomeView() { - const elements = useSelector(folderElementsSelector)[""]; + const [elements, setElements] = useState({ elements: [], total: 1 }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [searchValue, setSearchValue] = useState(""); + const [searchValues, setSearchValues] = useState(""); + const [typeFilter, setTypeFilter] = useState(0); + const [modify, setModify] = useState(true); + useEffect( () => { + try{ + fetchFolderElements({ + pageNum:currentPage, + pageSize:pageSize, + applicationType: ApplicationPaginationType[typeFilter], + name: searchValues, + }).then( + (data: any) => { + if (data.success) { + setElements({elements: data.data || [], total: data.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", data.error) + } + ); + } catch (error) { + console.error('Failed to fetch data:', error); + } + }, [currentPage, pageSize, searchValues, typeFilter, modify] + ); + + useEffect( () => { + if (searchValues !== "") + setCurrentPage(1); + }, [searchValues] + ); + + useEffect(()=> { + const timer = setTimeout(() => { + if (searchValue.length > 2 || searchValue === "") + setSearchValues(searchValue) + }, 500); + return () => clearTimeout(timer); + }, [searchValue]) + const user = useSelector(getUser); if (!user.currentOrgId) { @@ -16,9 +66,19 @@ export function HomeView() { return ( <> {{trans("productName")} {trans("home.home")}} - ); diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/MarketplaceView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/MarketplaceView.tsx index 01b76fb78..185c2b18b 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/MarketplaceView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/MarketplaceView.tsx @@ -13,6 +13,7 @@ import { Helmet } from "react-helmet"; export function MarketplaceView() { const [ marketplaceApps, setMarketplaceApps ] = useState>([]); const [ localMarketplaceApps, setLocalMarketplaceApps ] = useState>([]); + const [searchValue, setSearchValue] = useState(""); const fetchMarketplaceApps = async () => { try { @@ -60,7 +61,10 @@ export function MarketplaceView() { localMarketplaceApps={localMarketplaceApps} globalMarketplaceApps={marketplaceApps} breadcrumb={[{ text: trans("home.marketplace"), path: MARKETPLACE_URL }]} - mode={"marketplace"} /> + mode={"marketplace"} + searchValue={searchValue} + setSearchValue={setSearchValue} + /> ); }; \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/MoveToFolderModal.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/MoveToFolderModal.tsx index 561020905..34bd6b9a1 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/MoveToFolderModal.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/MoveToFolderModal.tsx @@ -40,7 +40,8 @@ const MoveModalFooter = styled.div` gap: 8px; `; -export const MoveToFolderModal = (props: { source?: HomeRes; onClose: () => void }) => { +export const MoveToFolderModal = (props: { source?: HomeRes; onClose: () => void, setModify: any, modify: boolean }) => { + const {setModify, modify} = props; const [form] = Form.useForm(); const [loading, setLoading] = useState(false); @@ -83,6 +84,9 @@ export const MoveToFolderModal = (props: { source?: HomeRes; onClose: () => void () => setLoading(false) ) ); + setTimeout(() => { + setModify(!modify); + }, 200); }); }} > diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/RootFolderListView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/RootFolderListView.tsx deleted file mode 100644 index a2263017c..000000000 --- a/client/packages/lowcoder/src/pages/ApplicationV2/RootFolderListView.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useSelector } from "react-redux"; -import { HomeLayout } from "./HomeLayout"; -import { getUser } from "../../redux/selectors/usersSelectors"; -import { FOLDERS_URL } from "../../constants/routesURL"; -import { trans } from "../../i18n"; -import { foldersSelector } from "../../redux/selectors/folderSelector"; - -export function RootFolderListView() { - const user = useSelector(getUser); - const allFolders = useSelector(foldersSelector); - - if (!user.currentOrgId) { - return null; - } - - return ( - - ); -} diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/TrashTableView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/TrashTableView.tsx index 0b600a472..424d67507 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/TrashTableView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/TrashTableView.tsx @@ -32,7 +32,8 @@ const EditBtn = styled(TacoButton)` height: 24px; `; -export const TrashTableView = (props: { resources: HomeRes[] }) => { +export const TrashTableView = (props: { resources: HomeRes[] , setModify: any, modify: boolean }) => { + const {resources, setModify, modify} = props; const dispatch = useDispatch(); return ( @@ -119,13 +120,17 @@ export const TrashTableView = (props: { resources: HomeRes[] }) => { style={{ padding: "0 8px", width: "fit-content", minWidth: "52px" }} buttonType={"blue"} className={"home-datasource-edit-button"} - onClick={() => - dispatch( - restoreApplication({ applicationId: item.id }, () => { - messageInstance.success(trans("home.recoverSuccessMsg")); - }) - ) + onClick={() =>{ + dispatch( + restoreApplication({ applicationId: item.id }, () => { + messageInstance.success(trans("home.recoverSuccessMsg")); + }) + ) + setTimeout(() => { + setModify(!modify); + }, 200); } + } > {trans("recover")} @@ -140,7 +145,7 @@ export const TrashTableView = (props: { resources: HomeRes[] }) => { type: HomeResInfo[item.type].name.toLowerCase(), name: {item.name}, }), - onConfirm: () => + onConfirm: () =>{ new Promise((resolve, reject) => { dispatch( deleteApplication( @@ -152,10 +157,15 @@ export const TrashTableView = (props: { resources: HomeRes[] }) => { () => reject() ) ); - }), + }) + setTimeout(() => { + setModify(!modify); + }, 200); + }, confirmBtnType: "delete", okText: trans("delete"), }) + } style={{ marginLeft: "12px", width: "76px" }} > @@ -166,7 +176,7 @@ export const TrashTableView = (props: { resources: HomeRes[] }) => { }, }, ]} - dataSource={props.resources} + dataSource={resources} /> ); }; diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx index d1b0586c2..410a2632f 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/TrashView.tsx @@ -1,27 +1,79 @@ import { HomeLayout } from "./HomeLayout"; -import { useDispatch, useSelector } from "react-redux"; -import { recycleListSelector } from "../../redux/selectors/applicationSelector"; import { TRASH_URL } from "../../constants/routesURL"; -import { useEffect } from "react"; -import { fetchApplicationRecycleList } from "../../redux/reduxActions/applicationActions"; +import {useEffect, useState} from "react"; import { trans } from "../../i18n"; import { Helmet } from "react-helmet"; +import {fetchApplicationElements} from "@lowcoder-ee/util/pagination/axios"; + +interface ElementsState { + elements: any; + total: number; +} export function TrashView() { - const dispatch = useDispatch(); - const recycleList = useSelector(recycleListSelector); + const [elements, setElements] = useState({ elements: [], total: 1 }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [searchValues, setSearchValues] = useState(""); + const [searchValue, setSearchValue] = useState(""); + const [typeFilter, setTypeFilter] = useState(0); + const [modify, setModify] = useState(false); - useEffect(() => { - dispatch(fetchApplicationRecycleList()); - }, [dispatch]); + useEffect( () => { + try{ + fetchApplicationElements({ + pageNum:currentPage, + pageSize:pageSize, + applicationType: typeFilter === 7 ? 3 : typeFilter, // // Application of Navigation is 3 in API. + name: searchValues, + }).then( + data => { + if (data.success) { + setElements({elements: data.data || [], total: data.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", data.error) + } + ); + } catch (error) { + console.error('Failed to fetch data:', error); + } + }, [currentPage, pageSize, searchValues, typeFilter, modify] + ); + useEffect( () => { + if (searchValues !== "") + setCurrentPage(1); + }, [searchValues] + ); + + //debouncing + useEffect(()=> { + const timer = setTimeout(() => { + if (searchValue.length > 2 || searchValue === "") + setSearchValues(searchValue) + }, 500); + return () => clearTimeout(timer); + }, [searchValue]) return ( <> {{trans("home.trash")}} + elements={elements.elements} + breadcrumb={[{ text: trans("home.trash"), path: TRASH_URL }]} + mode={"trash"} + currentPage ={currentPage} + setCurrentPage={setCurrentPage} + pageSize={pageSize} + setPageSize={setPageSize} + total={elements.total} + setSearchValue={setSearchValue} + searchValue={searchValue} + setTypeFilterPagination={setTypeFilter} + setModify={setModify} + modify={modify} + /> ); } + diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx index fc2f7536a..5a3a2f3fa 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/index.tsx @@ -4,7 +4,6 @@ import { DATASOURCE_URL, FOLDER_URL, FOLDER_URL_PREFIX, - FOLDERS_URL, MARKETPLACE_URL, QUERY_LIBRARY_URL, SETTING_URL, @@ -53,7 +52,6 @@ import { FolderView } from "./FolderView"; import { TrashView } from "./TrashView"; import { MarketplaceView } from "./MarketplaceView"; // import { SideBarItemType } from "../../components/layout/SideBarSection"; -import { RootFolderListView } from "./RootFolderListView"; // import InviteDialog from "../common/inviteDialog"; import { fetchFolderElements, updateFolder } from "../../redux/reduxActions/folderActions"; // import { ModuleView } from "./ModuleView"; @@ -262,12 +260,12 @@ export default function ApplicationHome() { { items: [ - { - text: {trans("home.allFolders")}, - routePath: FOLDERS_URL, - routeComp: RootFolderListView, - icon: ({ selected, ...otherProps }) => selected ? : , - }, + // { + // text: {trans("home.allFolders")}, + // routePath: FOLDERS_URL, + // routeComp: RootFolderListView, + // icon: ({ selected, ...otherProps }) => selected ? : , + // }, { text: {trans("home.allApplications")}, routePath: ALL_APPLICATIONS_URL, diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/useCreateFolder.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/useCreateFolder.tsx index 4c1243949..04c50f22c 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/useCreateFolder.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/useCreateFolder.tsx @@ -17,7 +17,7 @@ const CreateFolderLabel = styled.div` margin-bottom: 8px; `; -export function useCreateFolder() { +export function useCreateFolder(setModify: any, modify: boolean) { const dispatch = useDispatch(); const user = useSelector(getUser); const allFolders = useSelector(foldersSelector); @@ -73,7 +73,7 @@ export function useCreateFolder() { ), - onConfirm: () => + onConfirm: () =>{ form.validateFields().then( () => new Promise((resolve, reject) => { @@ -82,7 +82,11 @@ export function useCreateFolder() { () => reject(false) ); }) - ), + ) + setTimeout(() => { + setModify(!modify); + }, 200); + }, okText: trans("create"), }); }, [user, allFolders, form, dispatch]); diff --git a/client/packages/lowcoder/src/pages/ApplicationV2/useCreateHomeRes.tsx b/client/packages/lowcoder/src/pages/ApplicationV2/useCreateHomeRes.tsx index 6198279b8..7c314ab11 100644 --- a/client/packages/lowcoder/src/pages/ApplicationV2/useCreateHomeRes.tsx +++ b/client/packages/lowcoder/src/pages/ApplicationV2/useCreateHomeRes.tsx @@ -31,7 +31,7 @@ export const newAppPrefix = (userName: string, appType: AppTypeEnum = AppTypeEnu return trans("home.newApp", { userName: userName, name: toLower(HomeResInfo[appType].name) }); }; -export function useCreateHomeRes() { +export function useCreateHomeRes(setModify:any, modify: boolean) { const dispatch = useDispatch(); const user = useSelector(getUser); const allApplications = useSelector(normalAppListSelector); @@ -39,7 +39,7 @@ export function useCreateHomeRes() { const { folderId } = useParams<{ folderId: string }>(); - const handleFolderCreate = useCreateFolder(); + const handleFolderCreate = useCreateFolder(setModify, modify); const handleCreate = useCallback( (type: HomeResTypeEnum) => { diff --git a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx index 87fb7ec08..f85ab88ba 100644 --- a/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx +++ b/client/packages/lowcoder/src/pages/datasource/datasourceList.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { EditPopover, PointIcon, Search, TacoButton } from "lowcoder-design"; -import React, { useState } from "react"; +import React, {useEffect, useState} from "react"; import { useDispatch, useSelector } from "react-redux"; import { getDataSource, getDataSourceLoading, getDataSourceTypesMap } from "../../redux/selectors/datasourceSelectors"; import { deleteDatasource } from "../../redux/reduxActions/datasourceActions"; @@ -17,6 +17,10 @@ import { DatasourcePermissionDialog } from "../../components/PermissionDialog/Da import DataSourceIcon from "components/DataSourceIcon"; import { Helmet } from "react-helmet"; import LoadingOutlined from "@ant-design/icons/LoadingOutlined"; +import PaginationComp from "@lowcoder-ee/util/pagination/Pagination"; +import {DatasourceInfo} from "@lowcoder-ee/api/datasourceApi"; +import {fetchDatasourcePagination} from "@lowcoder-ee/util/pagination/axios"; +import {getUser} from "@lowcoder-ee/redux/selectors/usersSelectors"; const DatasourceWrapper = styled.div` display: flex; @@ -103,11 +107,54 @@ const StyledTable = styled(Table)` export const DatasourceList = () => { const dispatch = useDispatch(); const [searchValue, setSearchValue] = useState(""); + const [searchValues, setSearchValues] = useState(""); const [isCreateFormShow, showCreateForm] = useState(false); const [shareDatasourceId, setShareDatasourceId] = useState(undefined); - const datasource = useSelector(getDataSource); + const [modify, setModify] = useState(false); + const currentUser = useSelector(getUser); + const orgId = currentUser.currentOrgId; const datasourceLoading = useSelector(getDataSourceLoading); const plugins = useSelector(getDataSourceTypesMap); + interface ElementsState { + elements: DatasourceInfo[]; + total: number; + } + + const [elements, setElements] = useState({ elements: [], total: 0 }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + useEffect(()=> { + const timer = setTimeout(() => { + if (searchValue.length > 2 || searchValue === "") + setSearchValues(searchValue) + }, 500); + return () => clearTimeout(timer); + }, [searchValue]) + + useEffect( () => { + fetchDatasourcePagination( + { + orgId: orgId, + pageNum: currentPage, + pageSize: pageSize, + name: searchValues + } + ).then((result: any) => { + if (result.success){ + setElements({elements: result.data || [], total: result.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", result.error) + }) + }, [currentPage, pageSize, searchValues, modify] + ) + + useEffect( () => { + if (searchValues !== "") + setCurrentPage(1); + }, [searchValues] + ); return ( <> @@ -254,6 +301,10 @@ export const DatasourceList = () => { text: trans("delete"), onClick: () => { dispatch(deleteDatasource({ datasourceId: record.id })); + setTimeout(() => { + setModify(!modify); + }, 500); + }, type: "delete", }, @@ -267,19 +318,7 @@ export const DatasourceList = () => { ), }, ]} - dataSource={datasource - .filter((info) => { - if (info.datasource.creationSource === 2) { - return false; - } - if (!isEmpty(searchValue)) { - return ( - info.datasource.name.toLowerCase().includes(searchValue.trim().toLowerCase()) || - info.datasource.type.toLowerCase().includes(searchValue.trim().toLowerCase()) - ); - } - return true; - }) + dataSource={elements.elements .map((info, i) => ({ key: i, id: info.datasource.id, @@ -296,6 +335,13 @@ export const DatasourceList = () => { creator: info.creatorName, edit: info.edit, }))} /> + { !!elements.elements.length ? : <>} {shareDatasourceId && ( { !visible && setShareDatasourceId(undefined); } } /> )} - + + + ); }; diff --git a/client/packages/lowcoder/src/pages/editor/AppEditor.tsx b/client/packages/lowcoder/src/pages/editor/AppEditor.tsx index 0af4823f1..512e2d8d1 100644 --- a/client/packages/lowcoder/src/pages/editor/AppEditor.tsx +++ b/client/packages/lowcoder/src/pages/editor/AppEditor.tsx @@ -37,6 +37,8 @@ import { currentApplication } from "@lowcoder-ee/redux/selectors/applicationSele import { notificationInstance } from "components/GlobalInstances"; import { AppState } from "@lowcoder-ee/redux/reducers"; import { resetIconDictionary } from "@lowcoder-ee/constants/iconConstants"; +import {fetchJsDSPaginationByApp} from "@lowcoder-ee/util/pagination/axios"; +import PaginationComp from "@lowcoder-ee/util/pagination/Pagination"; const AppSnapshot = lazy(() => { return import("pages/editor/appSnapshot") @@ -57,6 +59,9 @@ const AppEditor = React.memo(() => { const fetchOrgGroupsFinished = useSelector(getFetchOrgGroupsFinished); const isCommonSettingsFetching = useSelector(getIsCommonSettingFetching); const application = useSelector(currentApplication); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [elements, setElements] = useState({ elements: [], total: 1 }) const isLowcoderCompLoading = useSelector((state: AppState) => state.npmPlugin.loading.lowcoderComps); const isUserViewMode = useMemo( @@ -140,8 +145,13 @@ const AppEditor = React.memo(() => { }, [dispatch, applicationId, paramViewMode]); const fetchJSDataSourceByApp = useCallback(() => { - DatasourceApi.fetchJsDatasourceByApp(applicationId).then((res) => { - res.data.data.forEach((i) => { + fetchJsDSPaginationByApp({ + appId: applicationId, + pageNum: currentPage, + pageSize: pageSize + }).then((res) => { + setElements({elements: [], total: res.total || 1}) + res.data!.forEach((i: any) => { registryDataSourcePlugin(i.type, i.id, i.pluginDefinition); }); setIsDataSourcePluginRegistered(true); @@ -153,6 +163,8 @@ const AppEditor = React.memo(() => { setIsDataSourcePluginRegistered, setShowAppSnapshot, dispatch, + currentPage, + pageSize ]); useEffect(() => { @@ -219,6 +231,13 @@ const AppEditor = React.memo(() => { return ( + {/**/} {showAppSnapshot ? ( }> ` display: flex; @@ -72,7 +73,7 @@ const CreateBtn = styled(TacoButton)<{ $readOnly?: boolean }>` `; const Body = styled.div` - height: calc(100% - 80px); + height: calc(100% - 120px); display: flex; flex-direction: column; `; @@ -158,11 +159,31 @@ export const LeftNav = (props: { addQuery: () => void; onSelect: (queryId: string) => void; readOnly?: boolean; + setCurrentPage: (page: number) => void; + setPageSize: (size: number) => void; + currentPage: number; + pageSize: number; + total: number; + setSearchValues: any; + searchValues: string; + setModify?: any; + modify?: boolean; }) => { + const {currentPage, setCurrentPage, pageSize, setPageSize, total , setSearchValues, searchValues, modify, setModify} = props const dispatch = useDispatch(); const [searchValue, setSearchValue] = useState(""); const datasourceTypes = useSelector(getDataSourceTypesMap); + useEffect(()=> { + const timer = setTimeout(() => { + if (searchValue.length > 2 || searchValue === "") + setSearchValues(searchValue) + }, 500); + return () => clearTimeout(timer); + }, [searchValue]) + + + return ( @@ -189,12 +210,6 @@ export const LeftNav = (props: { let datasourceTypeName = datasourceTypes[q.libraryQueryDSL?.query?.compType as DatasourceType]?.name ?? ""; - if (searchValue) { - return ( - q.name.toLowerCase().includes(searchValue) || - datasourceTypeName.toLowerCase().includes(searchValue) - ); - } return true; }) .map((q) => ( @@ -234,8 +249,12 @@ export const LeftNav = (props: { CustomModal.confirm({ title: trans("queryLibrary.deleteQueryTitle"), content: trans("queryLibrary.deleteQueryContent"), - onConfirm: () => - dispatch(deleteQueryLibrary({ queryLibraryId: q.id })), + onConfirm: () => { + dispatch(deleteQueryLibrary({ queryLibraryId: q.id })) + setTimeout(() => { + setModify(!modify); + }, 200); + }, confirmBtnType: "delete", okText: trans("delete"), }), @@ -272,6 +291,17 @@ export const LeftNav = (props: { + ); diff --git a/client/packages/lowcoder/src/pages/queryLibrary/QueryLibraryEditor.tsx b/client/packages/lowcoder/src/pages/queryLibrary/QueryLibraryEditor.tsx index 9882c360a..d331b568a 100644 --- a/client/packages/lowcoder/src/pages/queryLibrary/QueryLibraryEditor.tsx +++ b/client/packages/lowcoder/src/pages/queryLibrary/QueryLibraryEditor.tsx @@ -22,7 +22,7 @@ import { useCompInstance } from "../../comps/utils/useCompInstance"; import { QueryLibraryComp } from "../../comps/comps/queryLibrary/queryLibraryComp"; import { useSearchParam, useThrottle } from "react-use"; import { Comp } from "lowcoder-core"; -import { LibraryQuery } from "../../api/queryLibraryApi"; +import {LibraryQuery} from "../../api/queryLibraryApi"; import { NameGenerator } from "../../comps/utils"; import { QueryLibraryHistoryView } from "./QueryLibraryHistoryView"; import { default as Form } from "antd/es/form"; @@ -46,6 +46,7 @@ import { importQueryLibrary } from "./importQueryLibrary"; import { registryDataSourcePlugin } from "constants/queryConstants"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import { Helmet } from "react-helmet"; +import {fetchQLPaginationByOrg} from "@lowcoder-ee/util/pagination/axios"; const Wrapper = styled.div` display: flex; @@ -59,9 +60,21 @@ const RightContent = styled.div` position: relative; `; +interface ElementsState { + elements: LibraryQuery[]; + total: number; +} + +function transformData(input: LibraryQuery[]) { + const output: any = {}; + input.forEach(item => { + output[item.id] = item; + }); + return output; +} + export const QueryLibraryEditor = () => { const dispatch = useDispatch(); - const queryLibrary = useSelector(getQueryLibrary); const queryLibraryRecords = useSelector(getQueryLibraryRecords); const originDatasourceInfo = useSelector(getDataSource); const currentUser = useSelector(getUser); @@ -74,6 +87,12 @@ export const QueryLibraryEditor = () => { const [publishModalVisible, setPublishModalVisible] = useState(false); const [showHistory, setShowHistory] = useState(false); const [isDataSourceReady, setIsDataSourceReady] = useState(false); + const [elements, setElements] = useState({ elements: [], total: 0 }); + const [queryLibrary, setQueryLibrary] = useState({}); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [searchValues, setSearchValues] = useState(""); + const [modify, setModify] = useState(false); const selectedRecords = queryLibraryRecords[selectedQuery] ?? {}; const libraryQuery = queryLibrary[selectedQuery]; @@ -98,10 +117,33 @@ export const QueryLibraryEditor = () => { const [comp, container] = useCompInstance(params); useSaveQueryLibrary(libraryQuery, comp); + useEffect(() => { + try { + fetchQLPaginationByOrg( + { + name: searchValues, + pageNum: currentPage, + pageSize: pageSize, + } + ).then(result => { + if (result.success){ + setElements({elements: result.data || [], total: result.total || 1}) + setQueryLibrary(transformData(result.data || [])); + } + }); + } catch (error) { + console.error(error) + } + }, [currentPage, pageSize, searchValues, modify]) + + useEffect( () => { + if (searchValues !== "") + setCurrentPage(1); + }, [searchValues] + ); + useEffect(() => { if (orgId) { - dispatch(fetchQueryLibrary()); - dispatch(fetchDataSourceTypes({ organizationId: orgId })); dispatch( fetchDatasource({ organizationId: orgId, @@ -125,7 +167,8 @@ export const QueryLibraryEditor = () => { useEffect(() => { if (!forwardQueryId && !queryLibrary[selectedQuery]) { - setSelectedQuery(Object.values(queryLibrary)?.[0]?.id); + // @ts-ignore + setSelectedQuery(Object.values(queryLibrary)?.[0]?.id); } }, [dispatch, Object.keys(queryLibrary).length]); @@ -145,13 +188,13 @@ export const QueryLibraryEditor = () => { }) .map((info) => info.datasource); - const recentlyUsed = Object.values(queryLibrary) - .map((i) => i.libraryQueryDSL?.query.datasourceId) + const recentlyUsed = Object.values(queryLibrary) + .map((i: any) => i.libraryQueryDSL?.query.datasourceId) .map((id) => datasource.find((d) => d.id === id)) .filter((i) => !!i) as Datasource[]; const nameGenerator = new NameGenerator(); - nameGenerator.init(Object.values(queryLibrary).map((t) => t.name)); + nameGenerator.init(Object.values(queryLibrary).map((t: any) => t.name)); const newName = nameGenerator.genItemName(trans("queryLibrary.unnamed")); const handleAdd = (type: BottomResTypeEnum, extraInfo?: any) => { @@ -170,6 +213,11 @@ export const QueryLibraryEditor = () => { }, (resp) => { setSelectedQuery(resp.data.data.id); + setTimeout(() => { + setModify(!modify); + }, 200); + setCurrentPage(Math.ceil(elements.total / pageSize)); + }, () => {} ) @@ -189,7 +237,16 @@ export const QueryLibraryEditor = () => { setSelectedQuery(id); showCreatePanel(false); } } - readOnly={showHistory} /> + setCurrentPage={setCurrentPage} + setPageSize={setPageSize} + currentPage={currentPage} + pageSize={pageSize} + total={elements.total} + setSearchValues={setSearchValues} + searchValues={searchValues} + setModify={setModify} + modify={modify} + /> {!selectedQuery || !comp?.children.query.children.id.getView() ? ( EmptyQueryWithoutTab @@ -202,6 +259,8 @@ export const QueryLibraryEditor = () => { comp.propertyView({ onPublish: () => setPublishModalVisible(true), onHistoryShow: () => setShowHistory(true), + setModify: setModify, + modify: modify }) )} @@ -219,6 +278,10 @@ export const QueryLibraryEditor = () => { onSuccess: (resp) => { setSelectedQuery(resp.data.data.id); showCreatePanel(false); + setTimeout(() => { + setModify(!modify); + }, 200); + setCurrentPage(Math.ceil(elements.total / pageSize)); }, })} /> )} diff --git a/client/packages/lowcoder/src/pages/setting/permission/addGroupUserDialog.tsx b/client/packages/lowcoder/src/pages/setting/permission/addGroupUserDialog.tsx index b49d22199..726308be9 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/addGroupUserDialog.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/addGroupUserDialog.tsx @@ -31,8 +31,10 @@ function AddGroupUserDialog(props: { orgUsersFetching: boolean; groupUsers: GroupUser[]; style?: CSSProperties; + setModify?: any; + modify?: boolean }) { - const { orgId, orgUsers, orgUsersFetching, groupUsers, groupId } = props; + const { orgId, orgUsers, orgUsersFetching, groupUsers, groupId, setModify, modify } = props; const groupUserIdMap = new Map(groupUsers.map((gUser) => [gUser.userId, gUser])); const [dialogVisible, setDialogVisible] = useState(false); const addableUsers = orgUsers.filter((user) => !groupUserIdMap.has(user.userId)); @@ -83,6 +85,9 @@ function AddGroupUserDialog(props: { } } dispatch(fetchGroupUsersAction({ groupId })); + setTimeout(() => { + setModify(!modify); + }, 200); setDialogVisible(false); }} > diff --git a/client/packages/lowcoder/src/pages/setting/permission/groupUsersPermission.tsx b/client/packages/lowcoder/src/pages/setting/permission/groupUsersPermission.tsx index c0f7c79d8..4ed3e0a3c 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/groupUsersPermission.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/groupUsersPermission.tsx @@ -3,16 +3,13 @@ import { User } from "constants/userConstants"; import { AddIcon, ArrowIcon, CustomSelect, PackUpIcon, SuperUserIcon } from "lowcoder-design"; import { trans } from "i18n"; import ProfileImage from "pages/common/profileImage"; -import React, { useEffect, useMemo } from "react"; -import { connect, useDispatch } from "react-redux"; -import { AppState } from "redux/reducers"; +import React, { useMemo } from "react"; +import { useDispatch } from "react-redux"; import { deleteGroupUserAction, - fetchGroupUsersAction, quitGroupAction, updateUserGroupRoleAction, } from "redux/reduxActions/orgActions"; -import { getUser } from "redux/selectors/usersSelectors"; import styled from "styled-components"; import { formatTimestamp } from "util/dateTimeUtils"; import { currentOrgAdmin, isGroupAdmin } from "util/permissionUtils"; @@ -44,14 +41,15 @@ type GroupPermissionProp = { group: OrgGroup; orgId: string; groupUsers: GroupUser[]; - groupUsersFetching: boolean; currentUserGroupRole: string; currentUser: User; + setModify?: any; + modify?: boolean; }; function GroupUsersPermission(props: GroupPermissionProp) { const { Column } = TableStyled; - const { group, orgId, groupUsersFetching, groupUsers, currentUserGroupRole, currentUser } = props; + const { group, orgId, groupUsers, currentUserGroupRole, currentUser , setModify, modify} = props; const adminCount = groupUsers.filter((user) => isGroupAdmin(user.role)).length; const sortedGroupUsers = useMemo(() => { return [...groupUsers].sort((a, b) => { @@ -65,9 +63,6 @@ function GroupUsersPermission(props: GroupPermissionProp) { }); }, [groupUsers]); const dispatch = useDispatch(); - useEffect(() => { - dispatch(fetchGroupUsersAction({ groupId: group.groupId })); - }, []); return ( <> @@ -85,6 +80,8 @@ function GroupUsersPermission(props: GroupPermissionProp) { groupUsers={groupUsers} orgId={orgId} groupId={group.groupId} + setModify={setModify} + modify={modify} trigger={ }> {trans("memberSettings.addMember")} @@ -100,7 +97,7 @@ function GroupUsersPermission(props: GroupPermissionProp) { dataSource={sortedGroupUsers} rowKey="userId" pagination={false} - loading={groupUsersFetching} + loading={groupUsers.length === 0} > { + setModify(!modify); + }, 200); }} > {TacoRoles.map((role) => ( @@ -177,6 +177,9 @@ function GroupUsersPermission(props: GroupPermissionProp) { dispatch( quitGroupAction({ groupId: group.groupId, userId: currentUser.id }) ); + setTimeout(() => { + setModify(!modify); + }, 200); }} > {trans("memberSettings.exitGroup")} @@ -192,6 +195,9 @@ function GroupUsersPermission(props: GroupPermissionProp) { groupId: group.groupId, }) ); + setTimeout(() => { + setModify(!modify); + }, 200); }} > {trans("memberSettings.moveOutGroup")} @@ -208,13 +214,4 @@ function GroupUsersPermission(props: GroupPermissionProp) { ); } -const mapStateToProps = (state: AppState) => { - return { - groupUsers: state.ui.org.groupUsers, - groupUsersFetching: state.ui.org.groupUsersFetching, - currentUser: getUser(state), - currentUserGroupRole: state.ui.org.currentUserGroupRole, - }; -}; - -export default connect(mapStateToProps)(GroupUsersPermission); +export default GroupUsersPermission; diff --git a/client/packages/lowcoder/src/pages/setting/permission/index.tsx b/client/packages/lowcoder/src/pages/setting/permission/index.tsx index 8c59eaa2e..6302d535a 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/index.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/index.tsx @@ -1,13 +1,16 @@ -import { Route, Switch } from "react-router"; +import React, {useState} from "react"; +import { Route, Switch } from "react-router-dom"; import PermissionList from "./permissionList"; import PermissionDetail from "./permissionDetail"; import { PERMISSION_SETTING, PERMISSION_SETTING_DETAIL, SETTING_URL } from "constants/routesURL"; -export default () => { +export default function PermissionRoutes() { + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); return ( - - + } /> + } /> ); -}; +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/permission/orgUsersPermission.tsx b/client/packages/lowcoder/src/pages/setting/permission/orgUsersPermission.tsx index 992e7e0f8..e00d06e66 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/orgUsersPermission.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/orgUsersPermission.tsx @@ -14,16 +14,13 @@ import { import { trans, transToNode } from "i18n"; import InviteDialog from "pages/common/inviteDialog"; import ProfileImage from "pages/common/profileImage"; -import React, { useEffect, useMemo } from "react"; -import { connect, useDispatch, useSelector } from "react-redux"; -import { AppState } from "redux/reducers"; +import React, { useMemo } from "react"; +import { useDispatch, useSelector } from "react-redux"; import { deleteOrgUserAction, - fetchOrgUsersAction, quitOrgAction, updateUserOrgRoleAction, } from "redux/reduxActions/orgActions"; -import { getUser } from "redux/selectors/usersSelectors"; import styled from "styled-components"; import { formatTimestamp } from "util/dateTimeUtils"; import { currentOrgAdmin } from "util/permissionUtils"; @@ -58,13 +55,14 @@ const StyledMembersIcon = styled(MembersIcon)` type UsersPermissionProp = { orgId: string; orgUsers: OrgUser[]; - orgUsersFetching: boolean; currentUser: User; + setModify?: any; + modify?: boolean; }; function OrgUsersPermission(props: UsersPermissionProp) { const { Column } = TableStyled; - const { orgId, orgUsers, orgUsersFetching, currentUser } = props; + const { orgId, orgUsers, currentUser , setModify, modify} = props; const adminCount = orgUsers.filter( (user) => user.role === ADMIN_ROLE || user.role === SUPER_ADMIN_ROLE, ).length; @@ -82,9 +80,9 @@ function OrgUsersPermission(props: UsersPermissionProp) { }); }, [orgUsers]); - useEffect(() => { - dispatch(fetchOrgUsersAction(orgId)); - }, [dispatch, orgId]); + // useEffect(() => { + // dispatch(fetchOrgUsersAction(orgId)); + // }, [dispatch, orgId]); const onResetPass = (userId: string) => { return UserApi.resetPassword(userId) @@ -151,7 +149,7 @@ function OrgUsersPermission(props: UsersPermissionProp) { dataSource={sortedOrgUsers} rowKey="userId" pagination={false} - loading={orgUsersFetching} + loading={orgUsers.length === 0} > { + setModify(!modify); + }, 200); }, confirmBtnType: "delete", okText: trans("memberSettings.moveOutOrg"), @@ -299,12 +300,4 @@ function OrgUsersPermission(props: UsersPermissionProp) { ); } -const mapStateToProps = (state: AppState) => { - return { - orgUsersFetching: state.ui.org.orgUsersFetching, - orgUsers: state.ui.org.orgUsers, - currentUser: getUser(state), - }; -}; - -export default connect(mapStateToProps)(OrgUsersPermission); +export default OrgUsersPermission; diff --git a/client/packages/lowcoder/src/pages/setting/permission/permissionDetail.tsx b/client/packages/lowcoder/src/pages/setting/permission/permissionDetail.tsx index 2e190121e..d144c4e47 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/permissionDetail.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/permissionDetail.tsx @@ -1,12 +1,13 @@ -import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { fetchGroupsAction } from "redux/reduxActions/orgActions"; +import React, {useEffect, useState} from "react"; +import { useSelector } from "react-redux"; import { getUser } from "redux/selectors/usersSelectors"; import styled from "styled-components"; import GroupPermission from "./groupUsersPermission"; import UsersPermission from "./orgUsersPermission"; -import { getOrgGroups } from "redux/selectors/orgSelectors"; -import { useParams } from "react-router"; +import { useParams } from "react-router-dom"; +import {fetchGroupUsrPagination, fetchOrgGroups, fetchOrgUsrPagination} from "@lowcoder-ee/util/pagination/axios"; +import PaginationComp from "@lowcoder-ee/util/pagination/Pagination"; +import {OrgGroup} from "@lowcoder-ee/constants/orgConstants"; const PermissionContent = styled.div` display: flex; @@ -18,34 +19,109 @@ const PermissionContent = styled.div` width: 100%; `; -const All_Users = "users"; +export default function PermissionSetting(props: {currentPageProp: number, pageSizeProp: number}) { -export default function PermissionSetting() { + const {currentPageProp, pageSizeProp} = props; const user = useSelector(getUser); + const [elements, setElements] = useState({ elements: [], total: 1, role: "" }); + const [group, setGrouop] = useState(); + const [orgMemberElements, setOrgMemberElements] = useState({ elements: [], total: 1 }) + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [modify, setModify] = useState(false); + const orgId = user.currentOrgId; - const orgGroups = useSelector(getOrgGroups); - const groupIdMap = new Map(orgGroups.map((group) => [group.groupId, group])); - const dispatch = useDispatch(); + const currentUser = useSelector(getUser); const selectKey = useParams<{ groupId: string }>().groupId; - useEffect(() => { - if (!orgId) { - return; + + useEffect( () => { + fetchOrgGroups( + { + pageNum: currentPageProp, + pageSize: pageSizeProp, + } + ).then(result => { + if (result.success && !!result.data){ + setGrouop(result.data.find(group => group.groupId === selectKey)) + } + else + console.error("ERROR: fetchFolderElements", result.error) + }) + }, [currentPageProp, pageSizeProp] + ) + + useEffect( () => { + if (selectKey !== "users" && selectKey) + fetchGroupUsrPagination( + { + groupId:selectKey, + pageNum: currentPage, + pageSize: pageSize, + } + ).then(result => { + if (result.success){ + setElements({elements: result.data || [], total: result.total || 1, role: result.visitorRole || ""}) + } + else + console.error("ERROR: fetchFolderElements", result.error) + } + ) + else + { + fetchOrgUsrPagination( + { + orgId: orgId, + pageNum: currentPage, + pageSize: pageSize, + } + ).then(result => { + if (result.success){ + setOrgMemberElements({elements: result.data || [], total: result.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", result.error) + } + ) } - dispatch(fetchGroupsAction(orgId)); - }, [orgId]); + }, + [currentPage, pageSize, modify, selectKey] + ) + if (!orgId) { return null; } return ( - - {selectKey === All_Users ? ( - - ) : ( - groupIdMap.has(selectKey) && ( - - ) - )} - + + {selectKey === "users" ? ( + <> + + + + ) : ( + group && ( + <> + + + + + ) + )} + ); -} +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/pages/setting/permission/permissionList.tsx b/client/packages/lowcoder/src/pages/setting/permission/permissionList.tsx index c72579a36..c2a5f3778 100644 --- a/client/packages/lowcoder/src/pages/setting/permission/permissionList.tsx +++ b/client/packages/lowcoder/src/pages/setting/permission/permissionList.tsx @@ -21,7 +21,6 @@ import { } from "lowcoder-design"; import styled from "styled-components"; import { trans } from "i18n"; -import { getOrgGroups } from "redux/selectors/orgSelectors"; import { Table } from "components/Table"; import history from "util/history"; import { Level1SettingPageContentWithList, Level1SettingPageTitleWithBtn } from "../styled"; @@ -32,6 +31,8 @@ import { OrgGroup } from "constants/orgConstants"; import { messageInstance } from "lowcoder-design/src/components/GlobalInstances"; import InviteDialog from "pages/common/inviteDialog"; import { Flex } from "antd"; +import {fetchOrgGroups} from "@lowcoder-ee/util/pagination/axios"; +import PaginationComp from "@lowcoder-ee/util/pagination/Pagination"; const NEW_GROUP_PREFIX = trans("memberSettings.newGroupPrefix"); @@ -51,23 +52,58 @@ type DataItemInfo = { group?: OrgGroup; }; -export default function PermissionSetting() { +type PermissionSettingProps = { + currentPage: number; + setCurrentPage: (value: number) => void; + pageSize: number; + setPageSize: (value: number) => void; +}; + +interface ElementsState { + elements: OrgGroup[]; + total: number; +} + +export default function PermissionSetting(props: PermissionSettingProps) { + + const {currentPage, setCurrentPage, pageSize, setPageSize} = props; + let dataSource: DataItemInfo[] = []; const user = useSelector(getUser); const orgId = user.currentOrgId; - const orgGroups = useSelector(getOrgGroups); - const visibleOrgGroups = orgGroups.filter((g) => !g.allUsersGroup); - const allUsersGroup = orgGroups.find((g) => g.allUsersGroup); const dispatch = useDispatch(); const [needRenameId, setNeedRenameId] = useState(undefined); const { nameSuffixFunc, menuItemsFunc, menuExtraView } = usePermissionMenuItems(orgId); const [groupCreating, setGroupCreating] = useState(false); + const [elements, setElements] = useState({ elements: [], total: 0 }); + const [modify, setModify] = useState(false); + const visibleOrgGroups = elements.elements.filter((g) => !g.allUsersGroup); + const allUsersGroup = elements.elements.find((g) => g.allUsersGroup); - useEffect(() => { - if (!orgId) { - return; - } - dispatch(fetchGroupsAction(orgId)); - }, [orgId]); + useEffect( () => { + fetchOrgGroups( + { + pageNum: currentPage, + pageSize: pageSize, + } + ).then(result => { + if (result.success){ + setElements({elements: result.data || [], total: result.total || 1}) + } + else + console.error("ERROR: fetchFolderElements", result.error) + }) + }, [currentPage, pageSize, modify] + ) + + + dataSource = currentPage === 1 ? [{ + key: "users", + label: trans("memberSettings.allMembers"), + createTime: allUsersGroup?.createTime, + lock: true, + del: false, + rename: false, + }] : []; if (!orgId) { return null; } @@ -84,6 +120,9 @@ export default function PermissionSetting() { setTimeout(() => { dispatch(fetchGroupsAction(orgId)); }, 200); + setTimeout(() => { + setModify(!modify); + }, 200); } }) .catch((e) => { @@ -98,6 +137,9 @@ export default function PermissionSetting() { .then((resp) => { if (validateResponse(resp)) { dispatch(fetchGroupsAction(orgId)); + setTimeout(() => { + setModify(!modify); + }, 200); } }) .catch((e) => { @@ -105,17 +147,6 @@ export default function PermissionSetting() { }); }; - const dataSource: DataItemInfo[] = [ - { - key: "users", - label: trans("memberSettings.allMembers"), - createTime: allUsersGroup?.createTime, - lock: true, - del: false, - rename: false, - }, - ]; - visibleOrgGroups.forEach((group) => { dataSource.push({ key: group.groupId, @@ -180,6 +211,9 @@ export default function PermissionSetting() { return; } dispatch(updateGroupAction(record.key, { groupName: value }, orgId)); + setTimeout(() => { + setModify(!modify); + }, 200); setNeedRenameId(undefined); }, }} @@ -255,6 +289,13 @@ export default function PermissionSetting() { /> {menuExtraView} + ); } diff --git a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx index 958995e74..2504ca3f4 100644 --- a/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx +++ b/client/packages/lowcoder/src/pages/userAuth/formLoginSteps.tsx @@ -2,8 +2,6 @@ import { FormInput, messageInstance, PasswordInput } from "lowcoder-design"; import { AuthBottomView, ConfirmButton, - FormWrapperMobile, - LoginCardTitle, StyledRouteLink, } from "pages/userAuth/authComponents"; import React, { useContext, useEffect, useState } from "react"; @@ -15,7 +13,7 @@ import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants"; import { trans } from "i18n"; import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils"; import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth"; -import { AUTH_FORGOT_PASSWORD_URL, AUTH_REGISTER_URL, ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL"; +import { AUTH_FORGOT_PASSWORD_URL, AUTH_REGISTER_URL } from "constants/routesURL"; import { Link, useLocation, useParams } from "react-router-dom"; import { Divider } from "antd"; import Flex from "antd/es/flex"; @@ -27,8 +25,9 @@ import LeftOutlined from "@ant-design/icons/LeftOutlined"; import { fetchConfigAction } from "@lowcoder-ee/redux/reduxActions/configActions"; import { useDispatch, useSelector } from "react-redux"; import history from "util/history"; -import ApplicationApi from "@lowcoder-ee/api/applicationApi"; import { getServerSettings } from "@lowcoder-ee/redux/selectors/applicationSelector"; +import {fetchOrgPaginationByEmail} from "@lowcoder-ee/util/pagination/axios"; +import PaginationComp from "@lowcoder-ee/util/pagination/Pagination"; const StyledCard = styled.div<{$selected: boolean}>` display: flex; @@ -91,6 +90,11 @@ type FormLoginProps = { organizationId?: string; } +interface ElementsState { + elements: any; + total: number; +} + export default function FormLoginSteps(props: FormLoginProps) { const dispatch = useDispatch(); const location = useLocation(); @@ -111,6 +115,22 @@ export default function FormLoginSteps(props: FormLoginProps) { const [skipWorkspaceStep, setSkipWorkspaceStep] = useState(false); const [signupEnabled, setSignupEnabled] = useState(true); const serverSettings = useSelector(getServerSettings); + const [elements, setElements] = useState({ elements: [], total: 0 }); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + useEffect(() => { + if (account) + fetchOrgPaginationByEmail({ + email: account, + pageNum: currentPage, + pageSize: pageSize + }).then( result => { + setElements({elements: result.data || [], total: result.total || 1}) + setOrgList(result.data) + } + ) + }, [pageSize, currentPage]) useEffect(() => { const { LOWCODER_EMAIL_SIGNUP_ENABLED } = serverSettings; @@ -147,20 +167,25 @@ export default function FormLoginSteps(props: FormLoginProps) { } setOrgLoading(true); - OrgApi.fetchOrgsByEmail(account) + fetchOrgPaginationByEmail({ + email: account, + pageNum: currentPage, + pageSize: pageSize + }) .then((resp) => { - if (validateResponse(resp)) { - setOrgList(resp.data.data); - if (!resp.data.data.length) { + if (resp.success) { + setElements({elements: resp.data || [], total: resp.total || 1}) + setOrgList(resp.data); + if (!resp.data.length) { history.push( AUTH_REGISTER_URL, {...location.state || {}, email: account}, ) return; } - if (resp.data.data.length === 1) { - setOrganizationId(resp.data.data[0].orgId); - dispatch(fetchConfigAction(resp.data.data[0].orgId)); + if (resp.data.length === 1) { + setOrganizationId(resp.data[0].orgId); + dispatch(fetchConfigAction(resp.data[0].orgId)); setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS); return; } @@ -233,6 +258,14 @@ export default function FormLoginSteps(props: FormLoginProps) { {org.orgName} ))} + {orgList.length > 10 ? + : <>} ) diff --git a/client/packages/lowcoder/src/util/homeResUtils.tsx b/client/packages/lowcoder/src/util/homeResUtils.tsx index 1088ea01f..89c672634 100644 --- a/client/packages/lowcoder/src/util/homeResUtils.tsx +++ b/client/packages/lowcoder/src/util/homeResUtils.tsx @@ -7,7 +7,12 @@ import { NavDocIcon, } from "lowcoder-design"; import { HomeResTypeEnum } from "../types/homeRes"; -import { APPLICATION_VIEW_URL, APPLICATION_MARKETPLACE_VIEW_URL, buildFolderUrl } from "../constants/routesURL"; +import { + APPLICATION_VIEW_URL, + APPLICATION_MARKETPLACE_VIEW_URL, + buildFolderUrl, + ALL_APPLICATIONS_URL +} from "../constants/routesURL"; import history from "./history"; import { trans } from "../i18n"; import { FunctionComponent } from "react"; @@ -62,3 +67,5 @@ export const handleAppViewClick = (id: string) => window.open(APPLICATION_VIEW_U export const handleMarketplaceAppViewClick = (id: string, isLocalMarketplace?: boolean) => isLocalMarketplace == true ? window.open(APPLICATION_VIEW_URL(id, "view_marketplace"), '_blank') : window.open(APPLICATION_MARKETPLACE_VIEW_URL(id, "view_marketplace"), '_blank'); export const handleFolderViewClick = (id: string) => history.push(buildFolderUrl(id)); + +export const backFolderViewClick = () => history.push(ALL_APPLICATIONS_URL); \ No newline at end of file diff --git a/client/packages/lowcoder/src/util/pagination/Pagination.tsx b/client/packages/lowcoder/src/util/pagination/Pagination.tsx new file mode 100644 index 000000000..19001dea8 --- /dev/null +++ b/client/packages/lowcoder/src/util/pagination/Pagination.tsx @@ -0,0 +1,86 @@ +import styled from "styled-components"; +import { Pagination } from "antd"; + +interface PaginationLayoutProps { + height?: number; + marginTop?: number; + marginBottom?: number; +} + +const PaginationLayout = styled(Pagination)` + display: flex; + justify-content: center; + align-items: center; + margin-top: ${(props) => props.marginTop !== undefined ? props.marginTop : 40}px !important; + margin-bottom: ${(props) => props.marginBottom !== undefined ? props.marginBottom : 20}px !important; + height: ${(props) => props.height}px; +`; + +interface PaginationCompProps { + setCurrentPage: (page: number) => void; + setPageSize: (size: number) => void; + currentPage: number; + pageSize: number; + total: number; + height?: number; + marginTop?: number; + marginBottom?: number; + simple?: boolean; +} + +const PaginationComp = (props: PaginationCompProps) => { + const { + setCurrentPage, + setPageSize, + currentPage, + pageSize, + total, + height, + marginTop, + marginBottom, + simple, + } = props; + + const handlePageChange = (page: number, pageSize: number | undefined) => { + if (setCurrentPage) { + setCurrentPage(page); + } + }; + + const handlePageSizeChange = (current: number, size: number) => { + if (setPageSize) { + setPageSize(size); + } + }; + + return ( + <> + {simple ? + : + + } + + ); +}; + +export default PaginationComp; \ No newline at end of file diff --git a/client/packages/lowcoder/src/util/pagination/axios.ts b/client/packages/lowcoder/src/util/pagination/axios.ts new file mode 100644 index 000000000..42c0de270 --- /dev/null +++ b/client/packages/lowcoder/src/util/pagination/axios.ts @@ -0,0 +1,170 @@ +import { FolderApi } from "@lowcoder-ee/api/folderApi"; +import ApplicationApi from "@lowcoder-ee/api/applicationApi"; +import { + fetchAppRequestType, fetchDataSourcePaginationRequestType, + fetchDBRequestType, + fetchFolderRequestType, + fetchGroupUserRequestType, fetchOrgsByEmailRequestType, + fetchOrgUserRequestType, fetchQueryLibraryPaginationRequestType, + orgGroupRequestType +} from "@lowcoder-ee/util/pagination/type"; +import OrgApi from "@lowcoder-ee/api/orgApi"; +import { DatasourceApi } from "@lowcoder-ee/api/datasourceApi"; +import {QueryLibraryApi} from "@lowcoder-ee/api/queryLibraryApi"; + +export const fetchFolderElements = async (request: fetchFolderRequestType) => { + try { + const response = await FolderApi.fetchFolderElementsPagination(request); + return { + success: true, + data: response.data.data, + total:response.data.total + }; + } catch (error) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchApplicationElements = async (request: fetchAppRequestType)=> { + try { + const response = await ApplicationApi.fetchAllApplicationsPagination(request); + return { + success: true, + data: response.data.data, + total: response.data.total + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchOrgGroups = async (request: orgGroupRequestType) => { + try{ + const response = await OrgApi.fetchGroupPagination(request); + return { + success: true, + data:response.data.data, + total:response.data.total + } + } + catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchDatasourcePagination = async (request: fetchDBRequestType)=> { + try { + const response = await DatasourceApi.fetchDatasourcePaginationByOrg(request); + return { + success: true, + data: response.data.data, + total: response.data.total + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchGroupUsrPagination = async (request: fetchGroupUserRequestType)=> { + try { + const response = await OrgApi.fetchGroupUsersPagination(request); + return { + success: true, + data: response.data.data.members, + total: response.data.data.total, + visitorRole: response.data.data.visitorRole + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchOrgUsrPagination = async (request: fetchOrgUserRequestType)=> { + try { + const response = await OrgApi.fetchOrgUsersPagination(request); + return { + success: true, + data: response.data.data.members, + total: response.data.data.total, + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchQLPaginationByOrg = async (request: fetchQueryLibraryPaginationRequestType)=> { + try { + const response = await QueryLibraryApi.fetchQueryLibraryPaginationByOrg(request); + return { + success: true, + data: response.data.data, + total: response.data.total + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + +export const fetchJsDSPaginationByApp = async (request: fetchDataSourcePaginationRequestType)=> { + try { + const response = await DatasourceApi.fetchJsDatasourcePaginationByApp(request); + return { + success: true, + data: response.data.data, + total: response.data.total + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} + + + +export const fetchOrgPaginationByEmail = async (request: fetchOrgsByEmailRequestType)=> { + try { + const response = await OrgApi.fetchOrgsPaginationByEmail(request); + return { + success: true, + data: response.data.data, + total: response.data.total + } + } catch (error: any) { + console.error('Failed to fetch data:', error); + return { + success: false, + error: error + }; + } +} \ No newline at end of file diff --git a/client/packages/lowcoder/src/util/pagination/type.ts b/client/packages/lowcoder/src/util/pagination/type.ts new file mode 100644 index 000000000..d7c1ae2e8 --- /dev/null +++ b/client/packages/lowcoder/src/util/pagination/type.ts @@ -0,0 +1,107 @@ +import {GroupUser, OrgUser} from "@lowcoder-ee/constants/orgConstants"; + +type ApplicationType = { + [key: number]: string; // This allows numeric indexing +}; + +export const ApplicationPaginationType: ApplicationType = { + 0: "", + 1: "APPLICATION", + 2: "MODULE", + 3: "NAVLAYOUT", + 4: "FOLDER", + 6: "MOBILETABLAYOUT", + 7: "COMPOUND_APPLICATION", +}; + +export interface GenericApiPaginationResponse { + total: number; + success: boolean; + code: number; + message: string; + data: T; +} +export interface GroupUsersPaginationResponse { + success: boolean; + data: { + members: GroupUser[]; + visitorRole: string; + total: number; + }; +} + +export interface OrgUsersPaginationResponse { + success: boolean; + data: { + total: number; + members: OrgUser[]; + visitorRole: string; + }; +} + +export type ApiPaginationResponse = { + total: number; + success: boolean; + code: number; + message: string; + data: any; +}; + + +export interface fetchAppRequestType { + pageNum?: number; + pageSize?: number; + name?: string; + applicationType?: number; +} + +export interface fetchFolderRequestType { + id?: string; + pageNum?: number; + pageSize?: number; + name?: string; + applicationType?: string; +} + +export interface fetchDBRequestType { + orgId: string; + pageNum?: number; + pageSize?: number; + name?: string; + type?: string; +} + +export interface orgGroupRequestType{ + pageNum?: number; + pageSize?: number; +} +export interface fetchOrgUserRequestType { + orgId: string; + pageNum?: number; + pageSize?: number; +} + +export interface fetchGroupUserRequestType { + groupId: string; + pageNum?: number; + pageSize?: number; +} + +export interface fetchQueryLibraryPaginationRequestType { + name?: string; + pageNum?: number; + pageSize?: number; +} + +export interface fetchDataSourcePaginationRequestType { + appId: string; + name?: string; + pageNum?: number; + pageSize?: number; +} + +export interface fetchOrgsByEmailRequestType { + email: string; + pageNum?: number; + pageSize?: number; +} \ No newline at end of file diff --git a/server/node-service/yarn.lock b/server/node-service/yarn.lock index 608c8d086..409a8861c 100644 --- a/server/node-service/yarn.lock +++ b/server/node-service/yarn.lock @@ -8424,8 +8424,8 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 10.2.0 - resolution: "node-gyp@npm:10.2.0" + version: 10.3.1 + resolution: "node-gyp@npm:10.3.1" dependencies: env-paths: ^2.2.0 exponential-backoff: ^3.1.1 @@ -8439,7 +8439,7 @@ __metadata: which: ^4.0.0 bin: node-gyp: bin/node-gyp.js - checksum: 0233759d8c19765f7fdc259a35eb046ad86c3d09e22f7384613ae2b89647dd27fcf833fdf5293d9335041e91f9b1c539494225959cdb312a5c8080b7534b926f + checksum: 91b0690ab504fe051ad66863226dc5ecac72b8471f85e8428e4d5ca3217d3a2adfffae48cd555e8d009a4164689fff558b88d2bc9bfd246452a3336ab308cf99 languageName: node linkType: hard