Skip to content

Commit 87ab48a

Browse files
author
katrinan029
committed
feat: add users table
1 parent 9ddfb37 commit 87ab48a

File tree

7 files changed

+242
-2
lines changed

7 files changed

+242
-2
lines changed

src/Configuration/Customers/CustomerDataTable/CustomerDetails.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useCopyToClipboard } from '../data/utils';
1212
const { HOME } = ROUTES.CONFIGURATION.SUB_DIRECTORY.CUSTOMERS;
1313

1414
export const CustomerDetailLink = ({ row }) => {
15-
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard();
15+
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard(row.original.uuid);
1616
const { ADMIN_PORTAL_BASE_URL } = getConfig();
1717

1818
return (

src/Configuration/Customers/CustomerDetailView/CustomerCard.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import DJANGO_ADMIN_BASE_URL from '../data/constants';
1414

1515
const CustomerCard = ({ enterpriseCustomer }) => {
1616
const { ADMIN_PORTAL_BASE_URL } = getConfig();
17-
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard();
17+
const { showToast, copyToClipboard, setShowToast } = useCopyToClipboard(enterpriseCustomer.uuid);
1818

1919
return (
2020
<div>

src/Configuration/Customers/CustomerDetailView/CustomerViewContainer.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
1111
import CustomerCard from './CustomerCard';
1212
import { getEnterpriseCustomer } from '../data/utils';
1313
import CustomerIntegrations from './CustomerIntegrations';
14+
import EnterpriseCustomerUsersTable from './EnterpriseCustomerUsersTable';
1415

1516
const CustomerViewContainer = () => {
1617
const { id } = useParams();
@@ -65,6 +66,7 @@ const CustomerViewContainer = () => {
6566
activeSSO={enterpriseCustomer.activeSsoConfigurations}
6667
apiCredentialsEnabled={enterpriseCustomer.enableGenerationOfApiCredentials}
6768
/>
69+
<EnterpriseCustomerUsersTable />
6870
</Stack>
6971
</Container>
7072
</div>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import {
4+
Icon, IconButton, Stack, Chip
5+
} from '@openedx/paragon';
6+
import { Person, Check, Timelapse } from '@openedx/paragon/icons';
7+
8+
export const EnterpriseCustomerUserDetail = ({
9+
row,
10+
}) => {
11+
let memberDetails;
12+
let memberDetailIcon = (
13+
<IconButton
14+
isActive
15+
invertColors
16+
src={Person}
17+
iconAs={Icon}
18+
className="border rounded-circle mr-3"
19+
alt="members detail column icon"
20+
style={{ opacity: 1, flexShrink: 0 }}
21+
/>
22+
);
23+
24+
if (row.original.enterpriseCustomerUser?.username) {
25+
memberDetails = (
26+
<div className="mb-n3">
27+
<p className="font-weight-bold mb-0">
28+
{row.original.enterpriseCustomerUser?.username}
29+
</p>
30+
<p>{row.original.enterpriseCustomerUser?.email}</p>
31+
</div>
32+
);
33+
} else {
34+
console.log(row.original)
35+
memberDetails = (
36+
<p className="align-middle mb-0">
37+
{row.original.pendingEnterpriseCustomerUser?.userEmail}
38+
</p>
39+
);
40+
}
41+
return (
42+
<Stack gap={0} direction="horizontal">
43+
{memberDetailIcon}
44+
{memberDetails}
45+
</Stack>
46+
);
47+
};
48+
49+
export const AdministratorCell = ({ row }) => {
50+
return (
51+
<div>
52+
{row.original?.roleAssignments?.includes("enterprise_admin") ? <Check /> : null}
53+
</div>
54+
)
55+
}
56+
57+
export const LearnerCell = ({ row }) => {
58+
if (!row.original?.pendingEnterpriseCustomerUser) {
59+
return (
60+
<div>
61+
{row.original?.roleAssignments?.includes("enterprise_learner") ? <Check /> : null}
62+
</div>
63+
)
64+
}
65+
66+
return (
67+
<Chip
68+
iconBefore={Timelapse}
69+
>
70+
Pending
71+
</Chip>
72+
)
73+
};
74+
75+
76+
EnterpriseCustomerUserDetail.propTypes = {
77+
row: PropTypes.shape({
78+
original: PropTypes.shape({
79+
memberDetails: PropTypes.shape({
80+
userEmail: PropTypes.string.isRequired,
81+
userName: PropTypes.string,
82+
}),
83+
status: PropTypes.string,
84+
recentAction: PropTypes.string.isRequired,
85+
memberEnrollments: PropTypes.string,
86+
}).isRequired,
87+
}).isRequired,
88+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useParams } from 'react-router-dom';
2+
import { Container, DataTable, TextFilter } from '@openedx/paragon';
3+
import { EnterpriseCustomerUserDetail, LearnerCell, AdministratorCell } from './EnterpriseCustomerUserDetail';
4+
import useEnterpriseUsersTableData from '../data/hooks/useCustomerUsersTableData';
5+
6+
const EnterpriseCustomerUsersTable = () => {
7+
const { id } = useParams();
8+
const {
9+
isLoading,
10+
enterpriseUsersTableData,
11+
fetchEnterpriseUsersData,
12+
} = useEnterpriseUsersTableData(id);
13+
return (
14+
<Container>
15+
<h2>Associated users ({ enterpriseUsersTableData.itemCount })</h2>
16+
<hr />
17+
<DataTable
18+
isLoading={isLoading}
19+
isExpandable
20+
isSortable
21+
manualSortBy
22+
isPaginated
23+
manualPagination
24+
isFilterable
25+
manualFilters
26+
initialState={{
27+
pageSize: 8,
28+
pageIndex: 0,
29+
sortBy: [],
30+
filters: [],
31+
}}
32+
defaultColumnValues={{ Filter: TextFilter }}
33+
fetchData={fetchEnterpriseUsersData}
34+
data={enterpriseUsersTableData.results}
35+
itemCount={enterpriseUsersTableData.itemCount}
36+
pageCount={enterpriseUsersTableData.pageCount}
37+
columns={[
38+
{
39+
id: 'userDetails',
40+
Header: 'User details',
41+
accessor: 'userDetails',
42+
Cell: EnterpriseCustomerUserDetail,
43+
},
44+
{
45+
id: 'administrator',
46+
Header: 'Administrator',
47+
accessor: 'administrator',
48+
disableFilters: true,
49+
Cell: AdministratorCell,
50+
},
51+
{
52+
id: 'learner',
53+
Header: 'Learner',
54+
accessor: 'learner',
55+
disableFilters: true,
56+
Cell: LearnerCell,
57+
},
58+
]}
59+
/>
60+
</Container>
61+
);
62+
};
63+
64+
export default EnterpriseCustomerUsersTable;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
useCallback, useMemo, useState,
3+
} from 'react';
4+
import _ from 'lodash';
5+
import { camelCaseObject } from '@edx/frontend-platform/utils';
6+
import { logError } from '@edx/frontend-platform/logging';
7+
import debounce from 'lodash.debounce';
8+
9+
import LmsApiService from '../../../../data/services/EnterpriseApiService';
10+
11+
const useEnterpriseUsersTableData = (enterpriseUuid) => {
12+
const [isLoading, setIsLoading] = useState(true);
13+
const [enterpriseUsersTableData, setEnterpriseUsersTableData] = useState({
14+
itemCount: 0,
15+
pageCount: 0,
16+
results: [],
17+
});
18+
const fetchEnterpriseUsersData = useCallback((args) => {
19+
const fetch = async () => {
20+
try {
21+
setIsLoading(true);
22+
const options = {};
23+
24+
args.sortBy.filter((sort) => {
25+
const { id, desc } = sort;
26+
if (id === 'administrator') {
27+
options.ordering = desc ? id : `-${id}`;
28+
}
29+
if (id === 'userDetails') {
30+
options.ordering = desc ? id : `-${id}`;
31+
}
32+
if (id === 'learner') {
33+
options.ordering = desc ? id : `-${id}`;
34+
}
35+
});
36+
37+
args.filters.forEach((filter) => {
38+
const { id, value } = filter;
39+
if (id === 'username') {
40+
options.user_query = value;
41+
}
42+
});
43+
44+
45+
options.page = args.pageIndex + 1;
46+
const response = await LmsApiService.fetchEnterpriseCustomerUsers(enterpriseUuid, options);
47+
const { data } = camelCaseObject(response);
48+
console.log(args)
49+
setEnterpriseUsersTableData({
50+
itemCount: data.count,
51+
pageCount: data.numPages,
52+
results:data.results,
53+
});
54+
} catch (error) {
55+
logError(error);
56+
} finally {
57+
setIsLoading(false);
58+
}
59+
};
60+
if (enterpriseUuid) {
61+
fetch();
62+
}
63+
}, [enterpriseUuid]);
64+
65+
const debouncedFetchEnterpriseUsersData = useMemo(
66+
() => debounce(fetchEnterpriseUsersData, 300),
67+
[fetchEnterpriseUsersData],
68+
);
69+
70+
return {
71+
isLoading,
72+
enterpriseUsersTableData,
73+
fetchEnterpriseUsersData: debouncedFetchEnterpriseUsersData,
74+
};
75+
};
76+
77+
export default useEnterpriseUsersTableData;

src/data/services/EnterpriseApiService.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@ class LmsApiService {
2222

2323
static integratedChannelsUrl = `${LmsApiService.baseUrl}/integrated_channels/api/v1/configs/`;
2424

25+
static enterpriseCustomerSupportUrl = `${LmsApiService.enterpriseAPIBaseUrl}enterprise-customer-support/`;
26+
2527
static fetchEnterpriseCatalogQueries = () => LmsApiService.apiClient().get(LmsApiService.enterpriseCatalogQueriesUrl);
2628

2729
static fetchEnterpriseCustomersBasicList = (enterpriseNameOrUuid) => LmsApiService.apiClient().get(`${LmsApiService.enterpriseCustomersBasicListUrl}${enterpriseNameOrUuid !== undefined ? `?name_or_uuid=${enterpriseNameOrUuid}` : ''}`);
2830

31+
static fetchEnterpriseCustomerUsers = (enterpriseUuid, options) => {
32+
const queryParams = new URLSearchParams({
33+
...options,
34+
});
35+
return LmsApiService.apiClient().get(`${LmsApiService.enterpriseCustomerSupportUrl}${enterpriseUuid}?${queryParams}`);
36+
};
37+
2938
static postEnterpriseCustomerCatalog = (
3039
enterpriseCustomerUuid,
3140
catalogQueryId,

0 commit comments

Comments
 (0)