Skip to content

Commit ad413a3

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

19 files changed

Lines changed: 369 additions & 263 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: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@ const MultipleChoiceFilter = ({
2020
}: MultipleChoiceFilterProps) => {
2121
const [searchValue, setSearchValue] = useState<string | undefined>(undefined);
2222
const { formatMessage } = useIntl();
23+
2324
const checkedBoxes = filterValue || [];
24-
const handleClickCheckbox = (value) => {
25+
const handleClickCheckbox = (value, displayName) => {
2526
const newValue = {
2627
groupName: filterButtonText?.toLocaleLowerCase() || '',
2728
value,
28-
displayName: value,
29+
displayName,
2930
};
3031
if (checkedBoxes.includes(value)) {
3132
const newCheckedBoxes = checkedBoxes.filter((val) => val !== value);
3233
return setFilter(newCheckedBoxes, newValue);
3334
}
34-
checkedBoxes.push(value);
35-
return setFilter(checkedBoxes, newValue);
35+
const newCheckedBoxes = [...checkedBoxes, value];
36+
return setFilter(newCheckedBoxes, newValue);
3637
};
3738

3839
const getGroupedChoices = () => {
@@ -81,17 +82,18 @@ const MultipleChoiceFilter = ({
8182
aria-label={filterButtonText}
8283
value={checkedBoxes}
8384
>
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>
85+
<span className="small text-info-700 mt-2">
86+
{formatMessage(messages['authz.table.controlbar.filters.items.showing'], { current: filterChoices.length, total: filterChoices.length })}
87+
</span>
8688
{!isGrouped ? filterChoices.map(({
8789
displayName, value,
8890
}) => (
8991
<Form.Checkbox
90-
className="m-2"
92+
className="m-2 w-100"
9193
key={displayName}
9294
checked={checkedBoxes.includes(value)}
9395
value={value}
94-
onChange={() => handleClickCheckbox(value)}
96+
onChange={() => handleClickCheckbox(value, displayName)}
9597
aria-label={displayName}
9698
disabled={checkedBoxes.includes(value) ? false : disabled}
9799
>
@@ -106,10 +108,10 @@ const MultipleChoiceFilter = ({
106108
</div>
107109
{options.map(({ displayName, value }) => (
108110
<Form.Checkbox
109-
className="m-2"
111+
className="m-2 w-100"
110112
key={displayName}
111113
value={value}
112-
onChange={() => handleClickCheckbox(value)}
114+
onChange={() => handleClickCheckbox(value, displayName)}
113115
disabled={checkedBoxes.includes(value) ? false : disabled}
114116
aria-label={displayName}
115117
>

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);

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

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,21 @@
1-
import React, { useMemo } from 'react';
2-
import {
3-
Person, Language, School, LibraryBooks,
4-
} from '@openedx/paragon/icons';
1+
import { useMemo } from 'react';
2+
import { useIntl } from '@edx/frontend-platform/i18n';
3+
import { Person } from '@openedx/paragon/icons';
54
import MultipleChoiceFilter from './MultipleChoiceFilter';
65
import { MultipleChoiceFilterProps } from './types';
6+
import { getRolesFiltersOptions } from '../constants';
77

88
type RolesFilterProps = Omit<MultipleChoiceFilterProps, 'filterChoices' | 'isSearchable' | 'onSearchChange'>;
99

1010
const RolesFilter = ({
1111
filterButtonText, filterValue, setFilter, disabled,
1212
}: 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-
], []);
13+
const intl = useIntl();
14+
const rolesOptions = useMemo(() => getRolesFiltersOptions(intl), [intl]);
4815
return (
4916
<MultipleChoiceFilter
5017
filterButtonText={filterButtonText}
51-
filterChoices={filterChoices}
18+
filterChoices={rolesOptions}
5219
filterValue={filterValue}
5320
setFilter={setFilter}
5421
isGrouped

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)