diff --git a/client/src/components/AddToGroupModal/index.jsx b/client/src/components/AddToGroupModal/index.jsx
index af0475f..5948292 100644
--- a/client/src/components/AddToGroupModal/index.jsx
+++ b/client/src/components/AddToGroupModal/index.jsx
@@ -1,4 +1,5 @@
import React, { useEffect } from "react";
+import { List } from "react-virtualized";
import PT from "prop-types";
import Button from "../Button";
@@ -159,6 +160,42 @@ export default function AddToGroupModal({ onCancel, updateUser, user }) {
setFilter("");
};
+ const filteredOtherGroups = otherGroups.filter((g) =>
+ g.name.toLowerCase().includes(filter.toLowerCase())
+ );
+ const filteredMyGroups = myGroups.filter((g) =>
+ g.name.toLowerCase().includes(filter.toLowerCase())
+ );
+
+ const myGroupsListEstimatedWidth = filteredMyGroups.length > 10 ? 540 : 560;
+ const otherGroupsListEstimatedWidth =
+ filteredOtherGroups.length > 10 ? 540 : 560;
+ const listWidth =
+ myGroupsListEstimatedWidth > otherGroupsListEstimatedWidth
+ ? myGroupsListEstimatedWidth
+ : otherGroupsListEstimatedWidth;
+
+ /**
+ * Row renderer for react-virtualized#List.
+ * Renders each item as a row.
+ * @param {String} key unique key for the item
+ * @param {Number} index index of the item (row)
+ * @param {Object} style
+ * @return {SectionRow} row element
+ */
+ function rowRenderer({ key, index, style, items }) {
+ return (
+
- {!loadingGroups &&
- myGroups
- .filter((g) =>
- g.name.toLowerCase().includes(filter.toLowerCase())
- )
- .map((g) => (
- switchSelected(g)}
- />
- ))}
+ {!loadingGroups && (
+ 10
+ ? 450
+ : filteredMyGroups.length * 45
+ }
+ rowCount={filteredMyGroups.length}
+ rowHeight={45}
+ rowRenderer={(params) =>
+ rowRenderer({ ...params, items: filteredMyGroups })
+ }
+ />
+ )}
{myGroups.filter((g) =>
g.name.toLowerCase().includes(filter.toLowerCase())
@@ -225,19 +265,22 @@ export default function AddToGroupModal({ onCancel, updateUser, user }) {
Other Groups{loadingGroups && " (Loading...)"}
- {!loadingGroups &&
- otherGroups
- .filter((g) =>
- g.name.toLowerCase().includes(filter.toLowerCase())
- )
- .map((g) => (
- switchSelected(g)}
- />
- ))}
+ {!loadingGroups && (
+ 10
+ ? 450
+ : filteredOtherGroups.length * 45
+ }
+ rowCount={filteredOtherGroups.length}
+ rowHeight={45}
+ rowRenderer={(params) =>
+ rowRenderer({ ...params, items: filteredOtherGroups })
+ }
+ />
+ )}
{otherGroups.filter((g) =>
g.name.toLowerCase().includes(filter.toLowerCase())
diff --git a/client/src/components/AddToGroupModal/style.module.scss b/client/src/components/AddToGroupModal/style.module.scss
index e9cd4f4..5acfbad 100644
--- a/client/src/components/AddToGroupModal/style.module.scss
+++ b/client/src/components/AddToGroupModal/style.module.scss
@@ -48,6 +48,10 @@
overflow-y: auto;
}
+.groupsList {
+ outline: 0;
+}
+
.search {
background: $lightGray2;
border: none;
diff --git a/client/src/components/GroupsSideMenu/filters.js b/client/src/components/GroupsSideMenu/filters.js
index 924185b..0c7da53 100644
--- a/client/src/components/GroupsSideMenu/filters.js
+++ b/client/src/components/GroupsSideMenu/filters.js
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
+import { List } from "react-virtualized";
import PT from "prop-types";
import SearchBox from "../searchBox";
@@ -133,23 +134,42 @@ function GroupsSection({
selectedItemId,
loadingGroups,
}) {
+ /**
+ * Row renderer for react-virtualized#List.
+ * Renders each item as a row.
+ * @param {String} key unique key for the item
+ * @param {Number} index index of the item (row)
+ * @param {Object} style
+ * @return {SectionRow} row element
+ */
+ function rowRenderer({ key, index, style }) {
+ return (
+
+
+ !isSelected && onItemClicked && onItemClicked(title, items[index])
+ }
+ selected={items[index].id === selectedItemId}
+ />
+
+ );
+ }
+
return (
<>
{title}
- {items.map((item, index) => {
- return (
-
- !isSelected && onItemClicked && onItemClicked(title, item)
- }
- selected={item.id === selectedItemId}
- />
- );
- })}
+ 7 ? 450 : items.length * 66}
+ rowCount={items.length}
+ rowHeight={66}
+ rowRenderer={rowRenderer}
+ />
{items.length === 0 && !loadingGroups && (
No results found
@@ -173,6 +193,7 @@ function SectionRow({ title, badge, selected = false, action }) {
onClick={() => {
action(selected);
}}
+ title={title}
>
div {
margin-top: 20px;
}
@@ -101,8 +105,11 @@
}
.sectionItemTitle {
+ width: 280px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
color: gray2;
- word-break: break-all;
padding-right: 12px;
}
diff --git a/client/src/config.js b/client/src/config.js
index 62b8ee1..02249be 100644
--- a/client/src/config.js
+++ b/client/src/config.js
@@ -6,6 +6,7 @@ export default {
API_URL: process.env.REACT_APP_API_URL,
API_PREFIX: process.env.REACT_APP_API_PREFIX,
GROUPS_API_URL: process.env.REACT_APP_GROUPS_API_URL,
+ GROUPS_PER_PAGE: process.env.REACT_APP_GROUPS_PER_PAGE || 1000,
BULK_UPLOAD_TEMPLATE_ID: process.env.REACT_APP_BULK_UPLOAD_TEMPLATE_ID,
EMSI_SKILLPROVIDER_ID: process.env.REACT_APP_EMSI_SKILLPROVIDER_ID,
diff --git a/client/src/lib/groups.js b/client/src/lib/groups.js
index d730ab5..9626e6a 100644
--- a/client/src/lib/groups.js
+++ b/client/src/lib/groups.js
@@ -39,7 +39,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
// Now, get my groups first
try {
response = await apiClient.get(
- `${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user`,
+ `${config.GROUPS_API_URL}?universalUID=${userId}&membershipType=user&perPage=${config.GROUPS_PER_PAGE}`,
{ cancelToken }
);
} catch (error) {
@@ -66,7 +66,7 @@ export async function getGroups(apiClient, handle, cancelToken) {
// Fetch all groups in the org
try {
response = await apiClient.get(
- `${config.GROUPS_API_URL}?organizationId=${organizationId}`,
+ `${config.GROUPS_API_URL}?organizationId=${organizationId}&perPage=${config.GROUPS_PER_PAGE}`,
{ cancelToken }
);
} catch (error) {
diff --git a/package-lock.json b/package-lock.json
index 15f9307..0ce80a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,6 +30,21 @@
"js-tokens": "^4.0.0"
}
},
+ "@babel/runtime": {
+ "version": "7.10.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
+ "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ }
+ }
+ },
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@@ -830,6 +845,11 @@
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
"integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
},
+ "csstype": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz",
+ "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw=="
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -986,6 +1006,15 @@
"esutils": "^2.0.2"
}
},
+ "dom-helpers": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz",
+ "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==",
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^2.6.7"
+ }
+ },
"dtrace-provider": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
@@ -2150,8 +2179,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.14.0",
@@ -2433,7 +2461,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@@ -2974,7 +3001,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
- "dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -3064,8 +3090,36 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
+ "react-virtualized": {
+ "version": "9.21.2",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.2.tgz",
+ "integrity": "sha512-oX7I7KYiUM7lVXQzmhtF4Xg/4UA5duSA+/ZcAvdWlTLFCoFYq1SbauJT5gZK9cZS/wdYR6TPGpX/dqzvTqQeBA==",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "clsx": "^1.0.1",
+ "dom-helpers": "^5.0.0",
+ "loose-envify": "^1.3.0",
+ "prop-types": "^15.6.0",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "dependencies": {
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "requires": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ }
+ }
},
"read-pkg": {
"version": "2.0.0",
@@ -3111,6 +3165,11 @@
"backoff": "~2.5.0"
}
},
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+ },
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
diff --git a/package.json b/package.json
index 22ba665..e30bee0 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"lodash": "^4.17.19",
"multer": "^1.4.2",
"node-cache": "^5.1.0",
+ "react-virtualized": "^9.21.2",
"swagger-ui-express": "^4.1.4",
"tc-bus-api-wrapper": "topcoder-platform/tc-bus-api-wrapper.git#feature/auth0-proxy-server",
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4",