Skip to content

Commit 8686a4d

Browse files
feat: integrating backend apis into team members table
1 parent 072542c commit 8686a4d

19 files changed

Lines changed: 330 additions & 270 deletions

File tree

src/authz-module/authz-home/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useIntl } from '@edx/frontend-platform/i18n';
22
import { Tab, Tabs } from '@openedx/paragon';
33
import { useLocation, useSearchParams } from 'react-router-dom';
4-
import TeamMembersTable from 'authz-module/team-members/TeamMembersTable';
4+
import TeamMembersTable from '@src/authz-module/team-members/TeamMembersTable';
55
import AddRoleButton from '@src/authz-module/components/AddRoleButton';
66
import RolesPermissions from '../roles-permissions/RolesPermissions';
77
import AuthZLayout from '../components/AuthZLayout';
@@ -34,7 +34,7 @@ const AuthzHome = () => {
3434
defaultActiveKey={hash ? 'permissionsRoles' : 'team'}
3535
className="bg-light-100 px-5"
3636
>
37-
<Tab eventKey="team" title={intl.formatMessage(messages['library.authz.tabs.team'])} className="p-5">
37+
<Tab eventKey="team" title={intl.formatMessage(messages['authz.tabs.team'])} className="p-5">
3838
<TeamMembersTable presetScope={presetScope} />
3939
</Tab>
4040
<Tab id="libraries-permissions-roles-tab" eventKey="permissionsRoles" title={intl.formatMessage(messages['authz.tabs.permissionsRoles'])}>

src/authz-module/components/TableCells.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,25 @@ import { useIntl } from '@edx/frontend-platform/i18n';
22
import { Icon, IconButton } from '@openedx/paragon';
33
import { AppContext } from '@edx/frontend-platform/react';
44
import {
5-
RemoveRedEye, Language, School, LibraryBooks,
5+
RemoveRedEye,
66
} from '@openedx/paragon/icons';
7-
import { TableCellValue, TeamMember, AppContextType } from '@src/types';
7+
import { TableCellValue, AppContextType, UserRole } from '@src/types';
88
import { useNavigate } from 'react-router-dom';
9-
import { useContext } from 'react';
9+
import { useContext, useMemo } from 'react';
10+
import { DJANGO_MANAGED_ROLES, MAP_ROLE_KEY_TO_LABEL } from '@src/authz-module/constants';
1011
import messages from './messages';
12+
import { RESOURCE_ICONS } from './constants';
1113

12-
type CellProps = TableCellValue<TeamMember>;
13-
type ExtendedCellProps = CellProps & {
14+
type CellProps = TableCellValue<UserRole>;
15+
type CellPropsWithValue = CellProps & {
1416
value: string;
17+
};
18+
type ExtendedCellProps = CellPropsWithValue & {
1519
cell: {
1620
getCellProps: (props?: Record<string, string>) => Record<string, string>;
1721
};
1822
};
1923

20-
const SCOPE_ICONS = {
21-
COURSE: School,
22-
LIBRARY: LibraryBooks,
23-
GLOBAL: Language,
24-
};
25-
2624
const NameCell = ({ row }: CellProps) => {
2725
const intl = useIntl();
2826
const { authenticatedUser } = useContext(AppContext) as AppContextType;
@@ -31,12 +29,12 @@ const NameCell = ({ row }: CellProps) => {
3129
if (row.original.username === username) {
3230
return (
3331
<span>
34-
{row.original.fullName}
32+
{row.original.fullName || row.original.username}
3533
<span className="text-gray-500">{intl.formatMessage(messages['authz.table.username.current'])}</span>
3634
</span>
3735
);
3836
}
39-
return row.original.fullName;
37+
return row.original.fullName || row.original.username;
4038
};
4139

4240
const ActionCell = ({ row }: CellProps) => {
@@ -53,23 +51,46 @@ const ActionCell = ({ row }: CellProps) => {
5351
);
5452
};
5553

54+
const OrgCell = ({ value, row }: CellPropsWithValue) => {
55+
const { formatMessage } = useIntl();
56+
return (
57+
<td>
58+
{DJANGO_MANAGED_ROLES.includes(row.original.role) ? formatMessage(messages['authz.user.table.org.all.organizations.label']) : value}
59+
</td>
60+
);
61+
};
62+
5663
const ScopeCell = ({ row }: CellProps) => {
57-
const { scope } = row.original;
58-
const iconSrc = SCOPE_ICONS[scope.type];
64+
const { formatMessage } = useIntl();
65+
66+
const { scopeText, iconSrc } = useMemo(() => {
67+
if (DJANGO_MANAGED_ROLES.includes(row.original.role)) {
68+
return {
69+
scopeText: formatMessage(messages['authz.user.table.scope.global.label']),
70+
iconSrc: RESOURCE_ICONS.GLOBAL,
71+
};
72+
}
73+
const scopeIcon = row.original.role.startsWith('lib') ? RESOURCE_ICONS.LIBRARY : RESOURCE_ICONS.COURSE;
74+
return {
75+
scopeText: row.original.scope,
76+
iconSrc: scopeIcon,
77+
};
78+
}, [row.original.role, row.original.scope, formatMessage]);
79+
5980
return (
6081
<span className="d-flex align-items-center">
6182
{iconSrc && <Icon color="primary" src={iconSrc} className="mr-2" size="xs" />}
62-
{scope.resource}
83+
{scopeText}
6384
</span>
6485
);
6586
};
6687

6788
const RoleCell = ({ value, cell }: ExtendedCellProps) => (
68-
<td {...cell.getCellProps({ 'data-role': value })}>
69-
{value}
89+
<td {...cell.getCellProps({ 'data-role': MAP_ROLE_KEY_TO_LABEL[value] || '' })}>
90+
{MAP_ROLE_KEY_TO_LABEL[value] || ''}
7091
</td>
7192
);
7293

7394
export {
74-
NameCell, ActionCell, ScopeCell, RoleCell,
95+
NameCell, ActionCell, ScopeCell, RoleCell, OrgCell,
7596
};

src/authz-module/components/TableControlBar/MultipleChoiceFilter.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ const MultipleChoiceFilter = ({
2121
const [searchValue, setSearchValue] = useState<string | undefined>(undefined);
2222
const { formatMessage } = useIntl();
2323
const checkedBoxes = filterValue || [];
24-
const handleClickCheckbox = (value) => {
24+
const handleClickCheckbox = (value, displayName) => {
2525
const newValue = {
2626
groupName: filterButtonText?.toLocaleLowerCase() || '',
2727
value,
28-
displayName: value,
28+
displayName,
2929
};
3030
if (checkedBoxes.includes(value)) {
3131
const newCheckedBoxes = checkedBoxes.filter((val) => val !== value);
@@ -81,17 +81,18 @@ const MultipleChoiceFilter = ({
8181
aria-label={filterButtonText}
8282
value={checkedBoxes}
8383
>
84-
{/** TODO: Change for actual values */}
85-
<span className="small text-info-700 mt-2">{formatMessage(messages['authz.table.controlbar.filters.items.showing'], { current: filterChoices.length, total: filterChoices.length })}</span>
84+
<span className="small text-info-700 mt-2">
85+
{formatMessage(messages['authz.table.controlbar.filters.items.showing'], { current: filterChoices.length, total: filterChoices.length })}
86+
</span>
8687
{!isGrouped ? filterChoices.map(({
8788
displayName, value,
8889
}) => (
8990
<Form.Checkbox
90-
className="m-2"
91+
className="m-2 w-100"
9192
key={displayName}
9293
checked={checkedBoxes.includes(value)}
9394
value={value}
94-
onChange={() => handleClickCheckbox(value)}
95+
onChange={() => handleClickCheckbox(value, displayName)}
9596
aria-label={displayName}
9697
disabled={checkedBoxes.includes(value) ? false : disabled}
9798
>
@@ -106,10 +107,10 @@ const MultipleChoiceFilter = ({
106107
</div>
107108
{options.map(({ displayName, value }) => (
108109
<Form.Checkbox
109-
className="m-2"
110+
className="m-2 w-100"
110111
key={displayName}
111112
value={value}
112-
onChange={() => handleClickCheckbox(value)}
113+
onChange={() => handleClickCheckbox(value, displayName)}
113114
disabled={checkedBoxes.includes(value) ? false : disabled}
114115
aria-label={displayName}
115116
>

src/authz-module/components/TableControlBar/OrgFilter.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ const OrgFilter = ({
1010
filterButtonText, filterValue, setFilter, disabled,
1111
}: OrgFilterProps) => {
1212
const [searchValue, setSearchValue] = React.useState<string | undefined>(undefined);
13-
const { data: orgsData = { orgs: [] } } = useOrgs(searchValue);
14-
15-
const filterChoices = useMemo(() => orgsData.orgs.map((org) => ({
13+
const {
14+
data: orgsData = {
15+
count: 0, next: null, previous: null, results: [],
16+
},
17+
} = useOrgs(searchValue);
18+
const filterChoices = useMemo(() => orgsData?.results?.map((org) => ({
1619
displayName: org.name,
17-
value: org.id,
18-
})), [orgsData]);
20+
value: org.shortName,
21+
})) || [], [orgsData]);
1922

2023
const handleSearchChange = (value: string) => {
2124
setSearchValue(value);
Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,22 @@
1-
import React, { useMemo } from 'react';
2-
import {
3-
Person, Language, School, LibraryBooks,
4-
} from '@openedx/paragon/icons';
1+
import { Person } from '@openedx/paragon/icons';
52
import MultipleChoiceFilter from './MultipleChoiceFilter';
63
import { MultipleChoiceFilterProps } from './types';
4+
import { ROLES_FILTERS_OPTIONS } from '../constants';
75

86
type RolesFilterProps = Omit<MultipleChoiceFilterProps, 'filterChoices' | 'isSearchable' | 'onSearchChange'>;
97

108
const RolesFilter = ({
119
filterButtonText, filterValue, setFilter, disabled,
12-
}: RolesFilterProps) => {
13-
// TODO: use a constant
14-
const filterChoices = useMemo(() => [
15-
{
16-
groupName: 'Global', groupIcon: Language, displayName: 'Super Admin', value: 'Super Admin',
17-
},
18-
{
19-
groupName: 'Global', groupIcon: Language, displayName: 'Global Staff', value: 'Global Staff',
20-
},
21-
22-
{
23-
groupName: 'Course', groupIcon: School, displayName: 'Course Admin', value: 'Course Admin',
24-
},
25-
{
26-
groupName: 'Course', groupIcon: School, displayName: 'Course Staff', value: 'Course Staff',
27-
},
28-
{
29-
groupName: 'Course', groupIcon: School, displayName: 'Course Editor', value: 'Course Editor',
30-
},
31-
{
32-
groupName: 'Course', groupIcon: School, displayName: 'Course Auditor', value: 'Course Auditor',
33-
},
34-
35-
{
36-
groupName: 'Library', groupIcon: LibraryBooks, displayName: 'Library Admin', value: 'Library Admin',
37-
},
38-
{
39-
groupName: 'Library', groupIcon: LibraryBooks, displayName: 'Library Author', value: 'Library Author',
40-
},
41-
{
42-
groupName: 'Library', groupIcon: LibraryBooks, displayName: 'Library Collaborator', value: 'Library Collaborator',
43-
},
44-
{
45-
groupName: 'Library', groupIcon: LibraryBooks, displayName: 'Library User', value: 'Library User',
46-
},
47-
], []);
48-
return (
49-
<MultipleChoiceFilter
50-
filterButtonText={filterButtonText}
51-
filterChoices={filterChoices}
52-
filterValue={filterValue}
53-
setFilter={setFilter}
54-
isGrouped
55-
iconSrc={Person}
56-
disabled={disabled}
57-
/>
58-
);
59-
};
10+
}: RolesFilterProps) => (
11+
<MultipleChoiceFilter
12+
filterButtonText={filterButtonText}
13+
filterChoices={ROLES_FILTERS_OPTIONS}
14+
filterValue={filterValue}
15+
setFilter={setFilter}
16+
isGrouped
17+
iconSrc={Person}
18+
disabled={disabled}
19+
/>
20+
);
6021

6122
export default RolesFilter;

src/authz-module/components/TableControlBar/ScopesFilter.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
import React, { useMemo } from 'react';
2+
import { useIntl } from '@edx/frontend-platform/i18n';
23
import { LocationOn } from '@openedx/paragon/icons';
34
import { useScopes } from '@src/authz-module/data/hooks';
45
import { MultipleChoiceFilterProps } from './types';
56
import MultipleChoiceFilter from './MultipleChoiceFilter';
7+
import { RESOURCE_ICONS } from '../constants';
8+
import messages from '../messages';
69

710
type ScopesFilterProps = Omit<MultipleChoiceFilterProps, 'filterChoices' | 'isSearchable' | 'onSearchChange'>;
811

912
const ScopesFilter = ({
1013
filterButtonText, filterValue, setFilter, disabled,
1114
}: ScopesFilterProps) => {
15+
const { formatMessage } = useIntl();
1216
const [searchValue, setSearchValue] = React.useState<string | undefined>(undefined);
13-
const { data: scopesData = { scopes: [] } } = useScopes(searchValue);
17+
const { data: scopesData = { results: [] } } = useScopes(searchValue);
1418

15-
const filterChoices = useMemo(() => scopesData.scopes.map((scope) => ({
16-
displayName: scope.name,
17-
value: scope.key,
18-
groupName: scope.organization.name,
19-
})), [scopesData]);
19+
const filterChoices = useMemo(() => scopesData.results.map((scope) => {
20+
const scopeIcon = scope.externalKey.startsWith('lib') ? RESOURCE_ICONS.LIBRARY : RESOURCE_ICONS.COURSE;
21+
let groupName = formatMessage(messages['authz.team.members.table.group.courses']);
22+
if (scope.externalKey.startsWith('lib')) {
23+
groupName = formatMessage(messages['authz.team.members.table.group.libraries']);
24+
}
25+
return {
26+
displayName: scope.displayName,
27+
value: scope.externalKey,
28+
groupName,
29+
groupIcon: scopeIcon,
30+
};
31+
}), [scopesData, formatMessage]);
2032

2133
const handleSearchChange = (value: string) => {
2234
setSearchValue(value);

0 commit comments

Comments
 (0)