-
-
{tableMetadata.title}
-
-
- {actionSlug && this.renderUrlResetButton()}
- {filtersActive && this.renderFiltersResetButton()}
-
+
+
+
+
+
{tableMetadata.title}
-
-
- {tableMetadata.subtitle &&
{tableMetadata.subtitle}
}
- {tableMetadata.description &&
{tableMetadata.description}
}
-
+
+ {actionSlug && renderUrlResetButton()}
+ {filtersActive && renderFiltersResetButton()}
+
+
+
+
+ {tableMetadata.subtitle &&
{tableMetadata.subtitle}
}
+ {tableMetadata.description &&
{tableMetadata.description}
}
+
-
-
- {lastUpdatedDate
- && (
- ,
- }}
- />
- )}
-
-
{
- this.setState({ activeTab: tab });
- }}
+
+
+ {lastUpdatedDate
+ && (
+ ,
+ }}
+ />
+ )}
+
+
{
+ setActiveTab(tab);
+ }}
+ >
+
+
+
+ {!error && !loading && !hasEmptyData() && (
+ <>
+
+
+ {renderDownloadButton()}
+
+
+
+ {displaySearchBar() && (
+ searchEnrollmentsList()}
+ tableData={getTableData() ? getTableData().results : []}
+ budgets={budgets}
+ groups={groups}
+ enterpriseId={enterpriseId}
+ />
+ )}
+
+ >
+ )}
+ {csvErrorMessage && renderCsvErrorMessage(csvErrorMessage)}
+
+ {enterpriseId && tableMetadata.component}
+
+
+
+
+ {ModuleActivityReportVisible && (
-
-
- {!error && !loading && !this.hasEmptyData() && (
- <>
-
-
- {this.renderDownloadButton()}
-
-
-
- {this.displaySearchBar() && (
- this.props.searchEnrollmentsList()}
- tableData={this.getTableData() ? this.getTableData().results : []}
- budgets={budgets}
- groups={groups}
- enterpriseId={enterpriseId}
- />
- )}
-
- >
- )}
- {csvErrorMessage && this.renderCsvErrorMessage(csvErrorMessage)}
-
- {enterpriseId && tableMetadata.component}
-
-
+
+
- {this.state.ModuleActivityReportVisible && (
-
-
-
-
-
- )}
-
-
+ )}
+
- >
- )}
-
- );
- }
-}
+
+ >
+ )}
+
+ );
+};
Admin.defaultProps = {
error: null,
@@ -680,8 +640,6 @@ Admin.propTypes = {
groups: PropTypes.arrayOf(PropTypes.shape({})),
insightsLoading: PropTypes.bool,
insights: PropTypes.objectOf(PropTypes.shape),
- // injected
- intl: intlShape.isRequired,
};
-export default withParams(withLocation(injectIntl(Admin)));
+export default withParams(withLocation(Admin));
diff --git a/src/components/AdminV2/AdminCards.jsx b/src/components/AdminV2/AdminCards.jsx
index cb592b8f83..15c950fcb5 100644
--- a/src/components/AdminV2/AdminCards.jsx
+++ b/src/components/AdminV2/AdminCards.jsx
@@ -1,105 +1,107 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { useIntl } from '@edx/frontend-platform/i18n';
import {
Award, Check, Groups, RemoveRedEye,
} from '@openedx/paragon/icons';
import NumberCard from './cards/NumberCard';
-class AdminCards extends React.Component {
- constructor(props) {
- super(props);
- const { intl } = this.props;
+const AdminCards = ({
+ activeLearners,
+ numberOfUsers,
+ courseCompletions,
+ enrolledLearners,
+}) => {
+ const intl = useIntl();
- this.cards = {
- numberOfUsers: {
- ref: React.createRef(),
- description: intl.formatMessage({
- id: 'adminPortal.cards.registeredLearners',
- defaultMessage: 'total number of learners registered',
+ const cards = {
+ numberOfUsers: {
+ ref: React.createRef(),
+ description: intl.formatMessage({
+ id: 'adminPortal.cards.registeredLearners',
+ defaultMessage: 'total number of learners registered',
+ }),
+ icon: Groups,
+ actions: [{
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.registeredUnenrolledLearners',
+ defaultMessage: 'Which learners are registered but not yet enrolled in any courses?',
}),
- icon: Groups,
- actions: [{
- label: intl.formatMessage({
- id: 'adminPortal.cards.registeredUnenrolledLearners',
- defaultMessage: 'Which learners are registered but not yet enrolled in any courses?',
- }),
- slug: 'registered-unenrolled-learners',
- }],
- },
- enrolledLearners: {
- ref: React.createRef(),
- description: intl.formatMessage({
- id: 'adminPortal.cards.enrolledOneCourse',
- defaultMessage: 'learners enrolled in at least one course',
+ slug: 'registered-unenrolled-learners',
+ }],
+ },
+ enrolledLearners: {
+ ref: React.createRef(),
+ description: intl.formatMessage({
+ id: 'adminPortal.cards.enrolledOneCourse',
+ defaultMessage: 'learners enrolled in at least one course',
+ }),
+ icon: Check,
+ actions: [{
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.enrolledLearners',
+ defaultMessage: 'How many courses are learners enrolled in?',
}),
- icon: Check,
- actions: [{
- label: intl.formatMessage({
- id: 'adminPortal.cards.enrolledLearners',
- defaultMessage: 'How many courses are learners enrolled in?',
- }),
- slug: 'enrolled-learners',
- }, {
- label: intl.formatMessage({
- id: 'adminPortal.cards.enrolledLearnersInactiveCourses',
- defaultMessage: 'Who is no longer enrolled in a current course?',
- }),
- slug: 'enrolled-learners-inactive-courses',
- }],
- },
- activeLearners: {
- ref: React.createRef(),
- description: intl.formatMessage({
- id: 'adminPortal.cards.activeLearnersPastWeek',
- defaultMessage: 'active learners in the past week',
+ slug: 'enrolled-learners',
+ }, {
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.enrolledLearnersInactiveCourses',
+ defaultMessage: 'Who is no longer enrolled in a current course?',
}),
- icon: RemoveRedEye,
- actions: [{
- label: intl.formatMessage({
- id: 'adminPortal.cards.learnersActiveWeek',
- defaultMessage: 'Who are my top active learners?',
- }),
- slug: 'learners-active-week',
- }, {
- label: intl.formatMessage({
- id: 'adminPortal.cards.learnersInactiveWeek',
- defaultMessage: 'Who has not been active for over a week?',
- }),
- slug: 'learners-inactive-week',
- }, {
- label: intl.formatMessage({
- id: 'adminPortal.cards.learnersInactiveMonth',
- defaultMessage: 'Who has not been active for over a month?',
- }),
- slug: 'learners-inactive-month',
- }],
- },
- courseCompletions: {
- ref: React.createRef(),
- description: 'course completions',
- icon: Award,
- actions: [{
- label: intl.formatMessage({
- id: 'adminPortal.cards.completedLearners',
- defaultMessage: 'How many courses have been completed by learners?',
- }),
- slug: 'completed-learners',
- }, {
- label: intl.formatMessage({
- id: 'adminPortal.cards.completedLearnersWeek',
- defaultMessage: 'Who completed a course in the past week?',
- }),
- slug: 'completed-learners-week',
- }],
- },
- };
- }
+ slug: 'enrolled-learners-inactive-courses',
+ }],
+ },
+ activeLearners: {
+ ref: React.createRef(),
+ description: intl.formatMessage({
+ id: 'adminPortal.cards.activeLearnersPastWeek',
+ defaultMessage: 'active learners in the past week',
+ }),
+ icon: RemoveRedEye,
+ actions: [{
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.learnersActiveWeek',
+ defaultMessage: 'Who are my top active learners?',
+ }),
+ slug: 'learners-active-week',
+ }, {
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.learnersInactiveWeek',
+ defaultMessage: 'Who has not been active for over a week?',
+ }),
+ slug: 'learners-inactive-week',
+ }, {
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.learnersInactiveMonth',
+ defaultMessage: 'Who has not been active for over a month?',
+ }),
+ slug: 'learners-inactive-month',
+ }],
+ },
+ courseCompletions: {
+ ref: React.createRef(),
+ description: 'course completions',
+ icon: Award,
+ actions: [{
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.completedLearners',
+ defaultMessage: 'How many courses have been completed by learners?',
+ }),
+ slug: 'completed-learners',
+ }, {
+ label: intl.formatMessage({
+ id: 'adminPortal.cards.completedLearnersWeek',
+ defaultMessage: 'Who completed a course in the past week?',
+ }),
+ slug: 'completed-learners-week',
+ }],
+ },
+ };
- renderCard({ title, cardKey }) {
- const card = this.cards[cardKey];
+ const renderCard = ({ title, cardKey }) => {
+ const card = cards[cardKey];
return (
);
- }
-
- render() {
- const {
- activeLearners,
- numberOfUsers,
- courseCompletions,
- enrolledLearners,
- } = this.props;
+ };
- const data = {
- activeLearners: activeLearners.past_week,
- numberOfUsers,
- courseCompletions,
- enrolledLearners,
- };
+ const data = {
+ activeLearners: activeLearners.past_week,
+ numberOfUsers,
+ courseCompletions,
+ enrolledLearners,
+ };
- return Object.keys(this.cards).map(cardKey => (
- this.renderCard({
- title: data[cardKey],
- cardKey,
- })
- ));
- }
-}
+ return Object.keys(cards).map(cardKey => (
+ renderCard({
+ title: data[cardKey],
+ cardKey,
+ })
+ ));
+};
AdminCards.propTypes = {
activeLearners: PropTypes.shape({
@@ -149,8 +142,6 @@ AdminCards.propTypes = {
numberOfUsers: PropTypes.number.isRequired,
courseCompletions: PropTypes.number.isRequired,
enrolledLearners: PropTypes.number.isRequired,
- // injected
- intl: intlShape.isRequired,
};
-export default injectIntl(AdminCards);
+export default AdminCards;
diff --git a/src/components/AdminV2/AdminSearchForm.jsx b/src/components/AdminV2/AdminSearchForm.jsx
index d727b9b41c..4f62917e2a 100644
--- a/src/components/AdminV2/AdminSearchForm.jsx
+++ b/src/components/AdminV2/AdminSearchForm.jsx
@@ -1,51 +1,45 @@
/* eslint-disable camelcase */
-import React from 'react';
-import PropTypes from 'prop-types';
import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import { useEffect, useRef } from 'react';
import { Form } from '@openedx/paragon';
import { Info } from '@openedx/paragon/icons';
-import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
+import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
-import SearchBar from '../SearchBar';
+import EVENT_NAMES from '../../eventTracking';
+import { withLocation, withNavigate } from '../../hoc';
import { formatTimestamp, updateUrl } from '../../utils';
import IconWithTooltip from '../IconWithTooltip';
-import { withLocation, withNavigate } from '../../hoc';
-import EVENT_NAMES from '../../eventTracking';
import { TRACK_LEARNER_PROGRESS_TARGETS } from '../ProductTours/AdminOnboardingTours/constants';
+import SearchBar from '../SearchBar';
-class AdminSearchForm extends React.Component {
- componentDidUpdate(prevProps) {
- const {
- searchParams: {
- searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery,
- },
- } = this.props;
- const {
- searchParams: {
- searchQuery: prevSearchQuery,
- searchCourseQuery: prevSearchCourseQuery,
- searchDateQuery: prevSearchDateQuery,
- searchBudgetQuery: prevSearchBudgetQuery,
- searchGroupQuery: prevSearchGroupQuery,
- },
- } = prevProps;
+const AdminSearchForm = ({
+ searchEnrollmentsList,
+ searchParams: {
+ searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery,
+ },
+ tableData = [],
+ budgets,
+ groups,
+ navigate,
+ location,
+ enterpriseId,
+}) => {
+ const intl = useIntl();
+ const isFirstRender = useRef(true);
- if (searchQuery !== prevSearchQuery || searchCourseQuery !== prevSearchCourseQuery
- || searchDateQuery !== prevSearchDateQuery || searchBudgetQuery !== prevSearchBudgetQuery
- || searchGroupQuery !== prevSearchGroupQuery) {
- this.handleSearch();
+ useEffect(() => {
+ if (isFirstRender.current) {
+ isFirstRender.current = false;
+ return;
}
- }
-
- handleSearch() {
- this.props.searchEnrollmentsList();
- }
+ searchEnrollmentsList();
+ }, [searchEnrollmentsList, searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, searchGroupQuery]);
- onCourseSelect(event) {
- const { navigate, location } = this.props;
+ const onCourseSelect = (event) => {
const updateParams = {
search_course: event.target.value,
page: 1,
@@ -54,249 +48,236 @@ class AdminSearchForm extends React.Component {
updateParams.search_start_date = '';
}
updateUrl(navigate, location.pathname, updateParams);
- }
+ };
- onBudgetSelect(event) {
- const { navigate, location } = this.props;
+ const onBudgetSelect = (event) => {
const updateParams = {
budget_uuid: event.target.value,
page: 1,
};
updateUrl(navigate, location.pathname, updateParams);
- }
+ };
- onGroupSelect(event) {
- const { navigate, location } = this.props;
+ const onGroupSelect = (event) => {
const updateParams = {
group_uuid: event.target.value,
page: 1,
};
updateUrl(navigate, location.pathname, updateParams);
sendEnterpriseTrackEvent(
- this.props.enterpriseId,
+ enterpriseId,
EVENT_NAMES.LEARNER_PROGRESS_REPORT.FILTER_BY_GROUP_DROPDOWN,
{ group: event.target.value },
);
- }
-
- render() {
- const {
- intl,
- tableData,
- budgets,
- groups,
- searchParams: {
- searchCourseQuery, searchDateQuery, searchQuery, searchBudgetQuery, searchGroupQuery,
- },
- } = this.props;
+ };
- const courseTitles = Array.from(new Set(tableData.map(en => en.course_title).sort()));
- const courseDates = Array.from(new Set(tableData.map(en => en.course_start_date).sort().reverse()));
- const columnWidth = (budgets?.length || groups?.length) ? 'col-md-3' : 'col-md-6';
+ const courseTitles = Array.from(new Set(tableData.map(en => en.course_title).sort()));
+ const courseDates = Array.from(new Set(tableData.map(en => en.course_start_date).sort().reverse()));
+ const columnWidth = (budgets?.length || groups?.length) ? 'col-md-3' : 'col-md-6';
- return (
-
-
-
- {groups?.length ? (
-
-
-
-
-
- this.onGroupSelect(e)}
- >
-
- {groups.map(group => (
-
- ))}
-
-
-
- ) : null}
-
+ return (
+
+
+
+ {groups?.length ? (
+
this.onCourseSelect(e)}
+ value={searchGroupQuery}
+ onChange={e => onGroupSelect(e)}
>
- {courseTitles.map(title => (
+ {groups.map(group => (
))}
-
-
-
-
-
-
-
+
+
+
+
+ onCourseSelect(e)}
+ >
+
+ {courseTitles.map(title => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+ updateUrl(navigate, location.pathname, {
+ search_start_date: event.target.value,
+ page: 1,
+ })}
+ disabled={!searchCourseQuery}
+ >
+
+ {searchCourseQuery && courseDates.map(date => (
+
+ ))}
+
+
+
+ {budgets?.length ? (
+
+
+
+
updateUrl(this.props.navigate, this.props.location.pathname, {
- search_start_date: event.target.value,
- page: 1,
- })}
- disabled={!searchCourseQuery}
+ value={searchBudgetQuery}
+ onChange={e => onBudgetSelect(e)}
>
- {searchCourseQuery && courseDates.map(date => (
+ {budgets.map(budget => (
))}
- {budgets?.length ? (
-
-
-
-
-
- this.onBudgetSelect(e)}
- >
-
- {budgets.map(budget => (
-
- ))}
-
-
-
- ) : null }
-
-
-
-
-
updateUrl(this.props.navigate, this.props.location.pathname, {
- search: query,
- page: 1,
- })}
- onClear={() => updateUrl(this.props.navigate, this.props.location.pathname, { search: undefined })}
- value={searchQuery}
- aria-labelledby="search-email-label"
- className="py-0"
- inputProps={{ 'data-hj-suppress': true }}
+ ) : null }
+
+
+
-
+
+ updateUrl(navigate, location.pathname, {
+ search: query,
+ page: 1,
+ })}
+ onClear={() => updateUrl(navigate, location.pathname, { search: undefined })}
+ value={searchQuery}
+ aria-labelledby="search-email-label"
+ className="py-0"
+ inputProps={{ 'data-hj-suppress': true }}
+ />
- );
- }
-}
+
+ );
+};
AdminSearchForm.defaultProps = {
tableData: [],
@@ -319,8 +300,6 @@ AdminSearchForm.propTypes = {
pathname: PropTypes.string,
}),
enterpriseId: PropTypes.string,
- // injected
- intl: intlShape.isRequired,
};
-export default withLocation(withNavigate(injectIntl(AdminSearchForm)));
+export default withLocation(withNavigate(AdminSearchForm));