Skip to content

[Feat]: Improve Profile Dropdown, and Workspaces Page using "myorg" endpoint #1787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jun 20, 2025
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
035fc26
redesign profile dropdown
iamfaran Jun 16, 2025
a42f83c
testing workspaces endpoint
iamfaran Jun 16, 2025
8b40672
fix profile dropdown
iamfaran Jun 16, 2025
98695fb
setup redux, sagas for the new myorg endpoint
iamfaran Jun 16, 2025
35b7c68
fix profile dropdown create workspace issue
iamfaran Jun 16, 2025
a5d372a
test
iamfaran Jun 17, 2025
1b63471
fix params
iamfaran Jun 17, 2025
049d372
make currentOrg selector
iamfaran Jun 17, 2025
a902532
add page size param
iamfaran Jun 17, 2025
7709d58
replace orgs data with myorg for dropdown
iamfaran Jun 17, 2025
e202fcb
remove dispatch from the profile dropdown
iamfaran Jun 17, 2025
818fdf8
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into profileDr…
iamfaran Jun 18, 2025
f851353
fetch 10 workspaces initially
iamfaran Jun 18, 2025
9ea107b
add pagination and filtering for the dropdown
iamfaran Jun 18, 2025
28a2101
refactor profile dropdown
iamfaran Jun 18, 2025
0d59610
fix debouncing
iamfaran Jun 18, 2025
c7edbd1
fix loading when search
iamfaran Jun 18, 2025
238698d
fix shrinking issues when page 1 to page 2
iamfaran Jun 18, 2025
0611910
add antD loader in UI
iamfaran Jun 18, 2025
ffa7757
fix delete sync
iamfaran Jun 19, 2025
96acd6a
fix sync after edit
iamfaran Jun 19, 2025
db11f0e
add pagination/filtering for the Workspaces page
iamfaran Jun 19, 2025
2f291f7
add selector for the current org
iamfaran Jun 19, 2025
b3abc61
add active indicator in the workspaces page
iamfaran Jun 19, 2025
23fcbf9
add the ability to switch workspaces from workspaces page
iamfaran Jun 19, 2025
cd92f7c
disable row click on switch
iamfaran Jun 19, 2025
73a64b4
fix switch org button
iamfaran Jun 19, 2025
763bd98
Merge branch 'dev' of github.com:lowcoder-org/lowcoder into feat/myor…
iamfaran Jun 19, 2025
24e04c0
Merge branch 'dev' into feat/myorg-endpoint
raheeliftikhar5 Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add pagination and filtering for the dropdown
  • Loading branch information
iamfaran committed Jun 18, 2025
commit 9ea107bcd249acd81d9d67a57a4f0af594fb620c
261 changes: 221 additions & 40 deletions client/packages/lowcoder/src/pages/common/profileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import { showSwitchOrg } from "@lowcoder-ee/pages/common/customerService";
import { checkIsMobile } from "util/commonUtils";
import { selectSystemConfig } from "redux/selectors/configSelectors";
import type { ItemType } from "antd/es/menu/interface";

import { Pagination } from "antd";
import { debounce } from "lodash";
import UserApi from "api/userApi";
const { Item } = Menu;

const ProfileDropdownContainer = styled.div`
Expand Down Expand Up @@ -231,6 +233,46 @@ const StyledDropdown = styled(Dropdown)`
align-items: end;
`;


const PaginationContainer = styled.div`
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;

.ant-pagination {
margin: 0;

.ant-pagination-item {
min-width: 24px;
height: 24px;
line-height: 22px;
font-size: 12px;
margin-right: 4px;
}

.ant-pagination-prev,
.ant-pagination-next {
min-width: 24px;
height: 24px;
line-height: 22px;
margin-right: 4px;
}

.ant-pagination-item-link {
font-size: 11px;
}
}
`;
const LoadingSpinner = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
color: #8b8fa3;
font-size: 13px;
`;

type DropDownProps = {
onClick?: (text: string) => void;
user: User;
Expand All @@ -246,9 +288,107 @@ export default function ProfileDropdown(props: DropDownProps) {
const settingModalVisible = useSelector(isProfileSettingModalVisible);
const sysConfig = useSelector(selectSystemConfig);
const dispatch = useDispatch();
const [searchTerm, setSearchTerm] = useState("");
const [dropdownVisible, setDropdownVisible] = useState(false);

// Local state for pagination and search
const [searchTerm, setSearchTerm] = useState("");
const [dropdownVisible, setDropdownVisible] = useState(false);
const [currentPageWorkspaces, setCurrentPageWorkspaces] = useState<Org[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalCount, setTotalCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [isSearching, setIsSearching] = useState(false);

const pageSize = 10;

// Determine which workspaces to show
const displayWorkspaces = useMemo(() => {
if (searchTerm.trim()) {
return currentPageWorkspaces; // Search results
}
if (currentPage === 1) {
return workspaces.items; // First page from Redux
}
return currentPageWorkspaces; // Other pages from API
}, [searchTerm, currentPage, workspaces.items, currentPageWorkspaces]);

// Update total count based on context
useEffect(() => {
if (searchTerm.trim()) {
// Keep search result count
return;
}
if (currentPage === 1) {
setTotalCount(workspaces.totalCount);
}
}, [searchTerm, currentPage, workspaces.totalCount]);

// Fetch workspaces for specific page
const fetchWorkspacesPage = async (page: number, search?: string) => {
setIsLoading(true);
try {
const response = await UserApi.getMyOrgs(page, pageSize, search);
if (response.data.success) {
const apiData = response.data.data;
const transformedItems = apiData.data.map(item => ({
id: item.orgId,
name: item.orgName,
}));

setCurrentPageWorkspaces(transformedItems as Org[]);
setTotalCount(apiData.total);
}
} catch (error) {
console.error('Error fetching workspaces:', error);
setCurrentPageWorkspaces([]);
} finally {
setIsLoading(false);
}
};

// Handle page change
const handlePageChange = (page: number) => {
setCurrentPage(page);
if (page === 1 && !searchTerm.trim()) {
// Use Redux data for first page when not searching
setCurrentPageWorkspaces([]);
} else {
// Fetch from API for other pages or when searching
fetchWorkspacesPage(page, searchTerm.trim() || undefined);
}
};


// Debounced search function
const debouncedSearch = useMemo(
() => debounce(async (term: string) => {
if (!term.trim()) {
setCurrentPage(1);
setCurrentPageWorkspaces([]);
setTotalCount(workspaces.totalCount);
setIsSearching(false);
return;
}

setIsSearching(true);
setCurrentPage(1);
await fetchWorkspacesPage(1, term);
setIsSearching(false);
}, 300),
[workspaces.totalCount]
);



// Reset state when dropdown closes
useEffect(() => {
if (!dropdownVisible) {
setCurrentPageWorkspaces([]);
setCurrentPage(1);
setSearchTerm("");
setTotalCount(workspaces.totalCount);
setIsSearching(false);
}
}, [dropdownVisible, workspaces.totalCount]);



Expand Down Expand Up @@ -292,12 +432,15 @@ export default function ProfileDropdown(props: DropDownProps) {
setDropdownVisible(false);
};

const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
};
// Handle search input change
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchTerm(value);
debouncedSearch(value);
};

const dropdownContent = (
<ProfileDropdownContainer onClick={e => e.stopPropagation()}>
<ProfileDropdownContainer onClick={(e) => e.stopPropagation()}>
{/* Profile Section */}
<ProfileSection onClick={handleProfileClick}>
<ProfileImage source={avatarUrl} userName={username} side={40} />
Expand All @@ -310,48 +453,86 @@ export default function ProfileDropdown(props: DropDownProps) {
<ProfileRole>{OrgRoleInfo[currentOrgRoleId].name}</ProfileRole>
)}
</ProfileInfo>
{!checkIsMobile(window.innerWidth) && <EditIcon style={{ color: '#8b8fa3' }} />}
{!checkIsMobile(window.innerWidth) && (
<EditIcon style={{ color: "#8b8fa3" }} />
)}
</ProfileSection>

{/* Workspaces Section */}
{workspaces.items && workspaces.items.length > 0 && showSwitchOrg(props.user, sysConfig) && (
<WorkspaceSection>
<SectionHeader>{trans("profile.switchOrg")}</SectionHeader>

{workspaces.items.length > 3 && (
<SearchContainer>
<StyledSearchInput
placeholder="Search workspaces..."
value={searchTerm}
onChange={handleSearchChange}
prefix={<SearchIcon style={{ color: '#8b8fa3' }} />}
size="small"
/>
</SearchContainer>
)}
{workspaces.items &&
workspaces.items.length > 0 &&
showSwitchOrg(props.user, sysConfig) && (
<WorkspaceSection>
<SectionHeader>{trans("profile.switchOrg")}</SectionHeader>

{workspaces.items.length > 3 && (
<SearchContainer>
<StyledSearchInput
placeholder="Search workspaces..."
value={searchTerm}
onChange={handleSearchChange}
prefix={<SearchIcon style={{ color: "#8b8fa3" }} />}
size="small"
/>
</SearchContainer>
)}

<WorkspaceList>
{filteredOrgs.length > 0 ? (
filteredOrgs.map((org: Org) => (
<WorkspaceItem
key={org.id}
isActive={currentOrgId === org.id}
onClick={() => handleOrgSwitch(org.id)}
>
<WorkspaceName title={org.name}>{org.name}</WorkspaceName>
{currentOrgId === org.id && <ActiveIcon />}
</WorkspaceItem>
))
) : (
<EmptyState>No workspaces found</EmptyState>
{/* Workspaces List */}
<WorkspaceList>
{isSearching || isLoading ? (
<LoadingSpinner>
<PackUpIcon
style={{
animation: "spin 1s linear infinite",
marginRight: "8px",
}}
/>
{isSearching ? "Searching..." : "Loading..."}
</LoadingSpinner>
) : displayWorkspaces.length > 0 ? (
displayWorkspaces.map((org: Org) => (
<WorkspaceItem
key={org.id}
isActive={currentOrgId === org.id}
onClick={() => handleOrgSwitch(org.id)}
>
<WorkspaceName title={org.name}>{org.name}</WorkspaceName>
{currentOrgId === org.id && <ActiveIcon />}
</WorkspaceItem>
))
) : (
<EmptyState>
{searchTerm.trim()
? "No workspaces found"
: "No workspaces available"}
</EmptyState>
)}
</WorkspaceList>

{/* Pagination */}
{totalCount > pageSize && !isSearching && !isLoading && (
<PaginationContainer>
<Pagination
current={currentPage}
total={totalCount}
pageSize={pageSize}
size="small"
showSizeChanger={false}
showQuickJumper={false}
showTotal={(total, range) =>
`${range[0]}-${range[1]} of ${total}`
}
onChange={handlePageChange}
simple={totalCount > 100}
/>
</PaginationContainer>
)}
</WorkspaceList>
<CreateWorkspaceItem onClick={handleCreateOrg}>
<AddIcon />
{trans("profile.createOrg")}
</CreateWorkspaceItem>
</WorkspaceSection>
)}
</WorkspaceSection>
)}

{/* Actions Section */}
<ActionsSection>
Expand Down