Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 3f4b0e0

Browse files
Merge pull request #619 from cagdas001/develop
feat(groups): add virtual scrolling at groups list
2 parents cda61fd + de27fbe commit 3f4b0e0

File tree

8 files changed

+184
-48
lines changed

8 files changed

+184
-48
lines changed

client/src/components/AddToGroupModal/index.jsx

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useEffect } from "react";
2+
import { List } from "react-virtualized";
23
import PT from "prop-types";
34

45
import Button from "../Button";
@@ -159,6 +160,42 @@ export default function AddToGroupModal({ onCancel, updateUser, user }) {
159160
setFilter("");
160161
};
161162

163+
const filteredOtherGroups = otherGroups.filter((g) =>
164+
g.name.toLowerCase().includes(filter.toLowerCase())
165+
);
166+
const filteredMyGroups = myGroups.filter((g) =>
167+
g.name.toLowerCase().includes(filter.toLowerCase())
168+
);
169+
170+
const myGroupsListEstimatedWidth = filteredMyGroups.length > 10 ? 540 : 560;
171+
const otherGroupsListEstimatedWidth =
172+
filteredOtherGroups.length > 10 ? 540 : 560;
173+
const listWidth =
174+
myGroupsListEstimatedWidth > otherGroupsListEstimatedWidth
175+
? myGroupsListEstimatedWidth
176+
: otherGroupsListEstimatedWidth;
177+
178+
/**
179+
* Row renderer for react-virtualized#List.
180+
* Renders each item as a row.
181+
* @param {String} key unique key for the item
182+
* @param {Number} index index of the item (row)
183+
* @param {Object} style
184+
* @return {SectionRow} row element
185+
*/
186+
function rowRenderer({ key, index, style, items }) {
187+
return (
188+
<div key={key} style={style}>
189+
<Group
190+
checked={items[index].isSelected === true}
191+
group={items[index]}
192+
key={items[index].id}
193+
onSwitch={() => switchSelected(items[index])}
194+
/>
195+
</div>
196+
);
197+
}
198+
162199
return (
163200
<Modal
164201
onCancel={onCancel}
@@ -201,19 +238,22 @@ export default function AddToGroupModal({ onCancel, updateUser, user }) {
201238
My groups{loadingGroups && " (Loading...)"}
202239
</h3>
203240
<div>
204-
{!loadingGroups &&
205-
myGroups
206-
.filter((g) =>
207-
g.name.toLowerCase().includes(filter.toLowerCase())
208-
)
209-
.map((g) => (
210-
<Group
211-
checked={g.isSelected === true}
212-
group={g}
213-
key={g.id}
214-
onSwitch={() => switchSelected(g)}
215-
/>
216-
))}
241+
{!loadingGroups && (
242+
<List
243+
className={style.groupsList}
244+
width={listWidth}
245+
height={
246+
filteredMyGroups.length > 10
247+
? 450
248+
: filteredMyGroups.length * 45
249+
}
250+
rowCount={filteredMyGroups.length}
251+
rowHeight={45}
252+
rowRenderer={(params) =>
253+
rowRenderer({ ...params, items: filteredMyGroups })
254+
}
255+
/>
256+
)}
217257
</div>
218258
{myGroups.filter((g) =>
219259
g.name.toLowerCase().includes(filter.toLowerCase())
@@ -225,19 +265,22 @@ export default function AddToGroupModal({ onCancel, updateUser, user }) {
225265
Other Groups{loadingGroups && " (Loading...)"}
226266
</h3>
227267
<div>
228-
{!loadingGroups &&
229-
otherGroups
230-
.filter((g) =>
231-
g.name.toLowerCase().includes(filter.toLowerCase())
232-
)
233-
.map((g) => (
234-
<Group
235-
checked={g.isSelected === true}
236-
group={g}
237-
key={g.id}
238-
onSwitch={() => switchSelected(g)}
239-
/>
240-
))}
268+
{!loadingGroups && (
269+
<List
270+
className={style.groupsList}
271+
width={listWidth}
272+
height={
273+
filteredOtherGroups.length > 10
274+
? 450
275+
: filteredOtherGroups.length * 45
276+
}
277+
rowCount={filteredOtherGroups.length}
278+
rowHeight={45}
279+
rowRenderer={(params) =>
280+
rowRenderer({ ...params, items: filteredOtherGroups })
281+
}
282+
/>
283+
)}
241284
</div>
242285
{otherGroups.filter((g) =>
243286
g.name.toLowerCase().includes(filter.toLowerCase())

client/src/components/AddToGroupModal/style.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
overflow-y: auto;
4949
}
5050

51+
.groupsList {
52+
outline: 0;
53+
}
54+
5155
.search {
5256
background: $lightGray2;
5357
border: none;

client/src/components/GroupsSideMenu/filters.js

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useState, useEffect } from "react";
2+
import { List } from "react-virtualized";
23
import PT from "prop-types";
34

45
import SearchBox from "../searchBox";
@@ -133,23 +134,42 @@ function GroupsSection({
133134
selectedItemId,
134135
loadingGroups,
135136
}) {
137+
/**
138+
* Row renderer for react-virtualized#List.
139+
* Renders each item as a row.
140+
* @param {String} key unique key for the item
141+
* @param {Number} index index of the item (row)
142+
* @param {Object} style
143+
* @return {SectionRow} row element
144+
*/
145+
function rowRenderer({ key, index, style }) {
146+
return (
147+
<div key={key} style={style}>
148+
<SectionRow
149+
key={`${title}${index}`}
150+
title={items[index].name}
151+
badge={items[index].count + ""}
152+
action={(isSelected) =>
153+
!isSelected && onItemClicked && onItemClicked(title, items[index])
154+
}
155+
selected={items[index].id === selectedItemId}
156+
/>
157+
</div>
158+
);
159+
}
160+
136161
return (
137162
<>
138163
<div className={styles.sectionTitle}>{title}</div>
139164
<div className={styles.sectionItemsContainer}>
140-
{items.map((item, index) => {
141-
return (
142-
<SectionRow
143-
key={`${title}${index}`}
144-
title={item.name}
145-
badge={item.count + ""}
146-
action={(isSelected) =>
147-
!isSelected && onItemClicked && onItemClicked(title, item)
148-
}
149-
selected={item.id === selectedItemId}
150-
/>
151-
);
152-
})}
165+
<List
166+
className={styles.groupsList}
167+
width={385}
168+
height={items.length > 7 ? 450 : items.length * 66}
169+
rowCount={items.length}
170+
rowHeight={66}
171+
rowRenderer={rowRenderer}
172+
/>
153173
</div>
154174
{items.length === 0 && !loadingGroups && (
155175
<div className={styles.message}>No results found</div>
@@ -173,6 +193,7 @@ function SectionRow({ title, badge, selected = false, action }) {
173193
onClick={() => {
174194
action(selected);
175195
}}
196+
title={title}
176197
>
177198
<div
178199
className={

client/src/components/GroupsSideMenu/filters.module.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@
7373
flex-direction: column;
7474
}
7575

76+
.groupsList {
77+
outline: 0;
78+
}
79+
7680
.sectionItemsContainer > div {
7781
margin-top: 20px;
7882
}
@@ -101,8 +105,11 @@
101105
}
102106

103107
.sectionItemTitle {
108+
width: 280px;
109+
overflow: hidden;
110+
white-space: nowrap;
111+
text-overflow: ellipsis;
104112
color: gray2;
105-
word-break: break-all;
106113
padding-right: 12px;
107114
}
108115

client/src/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
API_URL: process.env.REACT_APP_API_URL,
77
API_PREFIX: process.env.REACT_APP_API_PREFIX,
88
GROUPS_API_URL: process.env.REACT_APP_GROUPS_API_URL,
9+
GROUPS_PER_PAGE: process.env.REACT_APP_GROUPS_PER_PAGE || 1000,
910

1011
BULK_UPLOAD_TEMPLATE_ID: process.env.REACT_APP_BULK_UPLOAD_TEMPLATE_ID,
1112
EMSI_SKILLPROVIDER_ID: process.env.REACT_APP_EMSI_SKILLPROVIDER_ID,

client/src/lib/groups.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
3939
// Now, get my groups first
4040
try {
4141
response = await apiClient.get(
42-
`${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user`,
42+
`${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user&perPage=${config.GROUPS_PER_PAGE}`,
4343
{ cancelToken }
4444
);
4545
} catch (error) {
@@ -66,7 +66,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
6666
// Fetch all groups in the org
6767
try {
6868
response = await apiClient.get(
69-
`${config.GROUPS_API_URL}?organizationId=${organizationId}`,
69+
`${config.GROUPS_API_URL}?organizationId=${organizationId}&perPage=${config.GROUPS_PER_PAGE}`,
7070
{ cancelToken }
7171
);
7272
} catch (error) {

package-lock.json

Lines changed: 65 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"lodash": "^4.17.19",
3838
"multer": "^1.4.2",
3939
"node-cache": "^5.1.0",
40+
"react-virtualized": "^9.21.2",
4041
"swagger-ui-express": "^4.1.4",
4142
"tc-bus-api-wrapper": "topcoder-platform/tc-bus-api-wrapper.git#feature/auth0-proxy-server",
4243
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4",

0 commit comments

Comments
 (0)