Skip to content

Commit

Permalink
Adding in remove capability for group members + csv download (#1367)
Browse files Browse the repository at this point in the history
* fix: formatting without data

* fix: adding in tests

* fix: teeny fix

* feat: adding in remove member functionality

* fix: adding csv download

* fix: adding in download capability

* fix: remove formatting changes

* fix: PR requests
  • Loading branch information
kiram15 authored Jan 10, 2025
1 parent a13b2a7 commit 0716be5
Show file tree
Hide file tree
Showing 14 changed files with 412 additions and 51 deletions.
4 changes: 2 additions & 2 deletions src/components/PeopleManagement/CreateGroupModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import LmsApiService from '../../data/services/LmsApiService';
import SystemErrorAlertModal from '../learner-credit-management/cards/assignment-allocation-status-modals/SystemErrorAlertModal';
import CreateGroupModalContent from './CreateGroupModalContent';
import { learnerCreditManagementQueryKeys } from '../learner-credit-management/data';
import { peopleManagementQueryKeys } from './constants';

const CreateGroupModal = ({
isModalOpen,
Expand Down Expand Up @@ -53,7 +53,7 @@ const CreateGroupModal = ({
});
await LmsApiService.inviteEnterpriseLearnersToGroup(groupCreationResponse.data.uuid, requestBody);
queryClient.invalidateQueries({
queryKey: learnerCreditManagementQueryKeys.group(enterpriseUUID),
queryKey: peopleManagementQueryKeys.group(enterpriseUUID),
});
setCreateButtonState('complete');
handleCloseCreateGroupModal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { useEnterpriseLearnersTableData } from './data/hooks/useEnterpriseLearnersTableData';
import { GROUP_MEMBERS_TABLE_DEFAULT_PAGE, GROUP_MEMBERS_TABLE_PAGE_SIZE } from './constants';
import MemberDetailsCell from './MemberDetailsCell';
import AddMembersBulkAction from './AddMembersBulkAction';
import AddMembersBulkAction from './GroupDetailPage/AddMembersBulkAction';
import RemoveMembersBulkAction from './RemoveMembersBulkAction';
import MemberJoinedDateCell from './MemberJoinedDateCell';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { Add } from '@openedx/paragon/icons';
import PropTypes from 'prop-types';

const AddMemberTableAction = ({ openModal }) => (
<Button iconBefore={Add} onClick={openModal} variant="outline-primary">Add members</Button>
<Button
className="align-top"
iconBefore={Add}
onClick={openModal}
variant="outline-primary"
>Add members
</Button>
);

AddMemberTableAction.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import { StatefulButton } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useGetAllEnterpriseLearnerEmails } from './data/hooks/useEnterpriseLearnersTableData';
import { getSelectedEmailsByRow } from './utils';
import { useGetAllEnterpriseLearnerEmails } from '../data/hooks/useEnterpriseLearnersTableData';
import { getSelectedEmailsByRow } from '../utils';

const AddMembersBulkAction = ({
isEntireTableSelected,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, useIntl } from '@edx/frontend-platform/i18n';

import {
Icon, IconButtonWithTooltip, Toast, useToggle,
} from '@openedx/paragon';
import { Download } from '@openedx/paragon/icons';
import { logError } from '@edx/frontend-platform/logging';
import GeneralErrorModal from '../GeneralErrorModal';
import { downloadCsv, getTimeStampedFilename } from '../../../utils';

const csvHeaders = ['Name', 'Email', 'Recent action', 'Enrollments'];

const DownloadCsvIconButton = ({ fetchAllData, dataCount, testId }) => {
const [isToastOpen, openToast, closeToast] = useToggle(false);
const [isErrorModalOpen, openErrorModal, closeErrorModal] = useToggle(false);
const intl = useIntl();
const messages = defineMessages({
downloadToastText: {
id: 'adminPortal.peopleManagement.groupDetail.downloadCsv.toast',
defaultMessage: 'Downloaded group members',
description: 'Toast message for the download button on the group detail page.',
},
downloadHoverText: {
id: 'adminPortal.peopleManagement.groupDetail.downloadCsv.hoverTooltip',
defaultMessage: `Download (${dataCount})`,
description: 'Tooltip message for the download button on the group detail page.',
},
});

const dataEntryToRow = (entry) => {
const { memberDetails: { userEmail, userName }, recentAction, enrollments } = entry;
return [userName, userEmail, recentAction, enrollments];
};

const handleClick = async () => {
fetchAllData().then((response) => {
const fileName = getTimeStampedFilename('group-report.csv');
downloadCsv(fileName, response.results, csvHeaders, dataEntryToRow);
openToast();
}).catch((err) => {
logError(err);
openErrorModal();
});
};

return (
<>
{ isToastOpen
&& (
<Toast onClose={closeToast} show={isToastOpen}>
{intl.formatMessage(messages.downloadToastText)}
</Toast>
)}
<GeneralErrorModal
isOpen={isErrorModalOpen}
close={closeErrorModal}
/>
<IconButtonWithTooltip
data-testid={testId}
tooltipContent={intl.formatMessage(messages.downloadHoverText)}
src={Download}
iconAs={Icon}
alt="Download group members"
variant="primary"
onClick={handleClick}
/>
</>
);
};

DownloadCsvIconButton.defaultProps = {
testId: 'download-csv-icon-button',
};

DownloadCsvIconButton.propTypes = {
fetchAllData: PropTypes.func.isRequired,
dataCount: PropTypes.number.isRequired,
testId: PropTypes.string,
};

export default DownloadCsvIconButton;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ROUTE_NAMES } from '../../EnterpriseApp/data/constants';
import DeleteGroupModal from './DeleteGroupModal';
import EditGroupNameModal from './EditGroupNameModal';
import formatDates from '../utils';
import GroupMembersTable from '../GroupMembersTable';
import GroupMembersTable from './GroupMembersTable';
import AddMembersModal from '../AddMembersModal/AddMembersModal';

const GroupDetailPage = () => {
Expand All @@ -27,6 +27,9 @@ const GroupDetailPage = () => {
isLoading: isTableLoading,
enterpriseGroupLearnersTableData,
fetchEnterpriseGroupLearnersTableData,
fetchAllEnterpriseGroupLearnersData,
refresh,
setRefresh,
} = useEnterpriseGroupLearnersTableData({ groupUuid, isAddMembersModalOpen });
const handleNameUpdate = (name) => {
setGroupName(name);
Expand Down Expand Up @@ -108,7 +111,6 @@ const GroupDetailPage = () => {
<IconButtonWithTooltip
alt="icon to trash group"
key="trashGroupTooltip"
tooltipPlacement="top"
tooltipContent={tooltipContent}
src={Delete}
iconAs={Icon}
Expand Down Expand Up @@ -147,7 +149,11 @@ const GroupDetailPage = () => {
isLoading={isTableLoading}
tableData={enterpriseGroupLearnersTableData}
fetchTableData={fetchEnterpriseGroupLearnersTableData}
fetchAllData={fetchAllEnterpriseGroupLearnersData}
dataCount={enterpriseGroupLearnersTableData.itemCount}
groupUuid={groupUuid}
refresh={refresh}
setRefresh={setRefresh}
openAddMembersModal={openAddMembersModal}
/>
<AddMembersModal
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,77 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
DataTable, Dropdown, Icon, IconButton,
DataTable, Dropdown, Icon, IconButton, useToggle,
} from '@openedx/paragon';
import { MoreVert, RemoveCircle } from '@openedx/paragon/icons';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import TableTextFilter from '../learner-credit-management/TableTextFilter';
import CustomDataTableEmptyState from '../learner-credit-management/CustomDataTableEmptyState';
import MemberDetailsTableCell from '../learner-credit-management/members-tab/MemberDetailsTableCell';
import EnrollmentsTableColumnHeader from './EnrollmentsTableColumnHeader';
import { GROUP_MEMBERS_TABLE_DEFAULT_PAGE, GROUP_MEMBERS_TABLE_PAGE_SIZE } from './constants';
import RecentActionTableCell from './RecentActionTableCell';

import TableTextFilter from '../../learner-credit-management/TableTextFilter';
import CustomDataTableEmptyState from '../../learner-credit-management/CustomDataTableEmptyState';
import MemberDetailsTableCell from '../../learner-credit-management/members-tab/MemberDetailsTableCell';
import EnrollmentsTableColumnHeader from '../EnrollmentsTableColumnHeader';
import {
GROUP_MEMBERS_TABLE_DEFAULT_PAGE,
GROUP_MEMBERS_TABLE_PAGE_SIZE,
} from '../constants';
import RecentActionTableCell from '../RecentActionTableCell';
import DownloadCsvIconButton from './DownloadCsvIconButton';
import RemoveMemberModal from './RemoveMemberModal';
import GeneralErrorModal from '../GeneralErrorModal';
import AddMemberTableAction from './AddMemberTableAction';

const FilterStatus = (rest) => <DataTable.FilterStatus showFilteredFields={false} {...rest} />;

const KabobMenu = () => (
<Dropdown drop="top">
<Dropdown.Toggle
id="kabob-menu-dropdown"
data-testid="kabob-menu-dropdown"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
/>
<Dropdown.Menu>
<Dropdown.Item>
<Icon src={RemoveCircle} className="mr-2 text-danger-500" />
<FormattedMessage
id="people.management.budgetDetail.membersTab.kabobMenu.removeMember"
defaultMessage="Remove member"
description="Remove member option in the kabob menu"
const KabobMenu = ({
row, groupUuid, refresh, setRefresh,
}) => {
const [isRemoveModalOpen, openRemoveModal, closeRemoveModal] = useToggle(false);
const [isErrorModalOpen, openErrorModal, closeErrorModal] = useToggle(false);
return (
<>
<RemoveMemberModal
groupUuid={groupUuid}
row={row}
isOpen={isRemoveModalOpen}
close={closeRemoveModal}
openError={openErrorModal}
refresh={refresh}
setRefresh={setRefresh}
/>
<GeneralErrorModal
isOpen={isErrorModalOpen}
close={closeErrorModal}
/>
<Dropdown drop="top">
<Dropdown.Toggle
id="kabob-menu-dropdown"
data-testid="kabob-menu-dropdown"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
<Dropdown.Menu>
<Dropdown.Item onClick={openRemoveModal}>
<Icon src={RemoveCircle} className="mr-2 text-danger-500" />
<FormattedMessage
id="people.management.budgetDetail.membersTab.kabobMenu.removeMember"
defaultMessage="Remove member"
description="Remove member option in the kabob menu"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</>
);
};

KabobMenu.propTypes = {
row: PropTypes.shape({}).isRequired,
groupUuid: PropTypes.string.isRequired,
refresh: PropTypes.bool.isRequired,
setRefresh: PropTypes.func.isRequired,
};

const selectColumn = {
id: 'selection',
Expand All @@ -49,7 +84,11 @@ const GroupMembersTable = ({
isLoading,
tableData,
fetchTableData,
fetchAllData,
dataCount,
groupUuid,
refresh,
setRefresh,
openAddMembersModal,
}) => {
const intl = useIntl();
Expand All @@ -69,9 +108,6 @@ const GroupMembersTable = ({
defaultColumnValues={{ Filter: TableTextFilter }}
FilterStatusComponent={FilterStatus}
numBreakoutFilters={2}
tableActions={[
<AddMemberTableAction openModal={openAddMembersModal} />,
]}
columns={[
{
Header: intl.formatMessage({
Expand Down Expand Up @@ -106,9 +142,7 @@ const GroupMembersTable = ({
initialState={{
pageSize: GROUP_MEMBERS_TABLE_PAGE_SIZE,
pageIndex: GROUP_MEMBERS_TABLE_DEFAULT_PAGE,
sortBy: [
{ id: 'memberDetails', desc: true },
],
sortBy: [{ id: 'memberDetails', desc: true }],
filters: [],
}}
additionalColumns={[
Expand All @@ -117,10 +151,23 @@ const GroupMembersTable = ({
Header: '',
// eslint-disable-next-line react/no-unstable-nested-components
Cell: (props) => (
<KabobMenu {...props} groupUuid={groupUuid} />
<KabobMenu
{...props}
groupUuid={groupUuid}
refresh={refresh}
setRefresh={setRefresh}
/>
),
},
]}
tableActions={[
<AddMemberTableAction openModal={openAddMembersModal} />,
<DownloadCsvIconButton
fetchAllData={fetchAllData}
dataCount={dataCount}
testId="group-members-download"
/>,
]}
fetchData={fetchTableData}
data={tableData.results}
itemCount={tableData.itemCount}
Expand All @@ -134,13 +181,16 @@ const GroupMembersTable = ({
GroupMembersTable.propTypes = {
isLoading: PropTypes.bool.isRequired,
tableData: PropTypes.shape({
results: PropTypes.arrayOf(PropTypes.shape({
})),
results: PropTypes.arrayOf(PropTypes.shape({})),
itemCount: PropTypes.number.isRequired,
pageCount: PropTypes.number.isRequired,
}).isRequired,
fetchTableData: PropTypes.func.isRequired,
fetchAllData: PropTypes.func.isRequired,
dataCount: PropTypes.number.isRequired,
groupUuid: PropTypes.string.isRequired,
refresh: PropTypes.bool.isRequired,
setRefresh: PropTypes.func.isRequired,
openAddMembersModal: PropTypes.func.isRequired,
};

Expand Down
Loading

0 comments on commit 0716be5

Please sign in to comment.