Skip to content

Commit

Permalink
feat: Add people management page + zero state (#1322)
Browse files Browse the repository at this point in the history
* fix: cleanup surrounding status chip implementation

* feat: Add people management page + zero state

* fix: adding info popover message

* fix: PR requests

* fix: PR reviews again
  • Loading branch information
kiram15 authored Oct 3, 2024
1 parent e3b1452 commit e0288ab
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/components/EnterpriseApp/EnterpriseAppContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const EnterpriseAppContent = ({
enableReportingPage,
enableSubscriptionManagementPage,
enableAnalyticsPage,
enterpriseGroupsV2,
}) => {
const { FEATURE_CONTENT_HIGHLIGHTS } = getConfig();
const enterpriseAppContext = useContext(EnterpriseAppContext);
Expand All @@ -32,6 +33,7 @@ const EnterpriseAppContent = ({
enableSubscriptionManagementPage={enableSubscriptionManagementPage}
enableAnalyticsPage={enableAnalyticsPage}
enableContentHighlightsPage={isContentHighlightsEnabled}
enterpriseGroupsV2={enterpriseGroupsV2}
/>
);
};
Expand All @@ -44,6 +46,7 @@ EnterpriseAppContent.propTypes = {
enableReportingPage: PropTypes.bool.isRequired,
enableSubscriptionManagementPage: PropTypes.bool.isRequired,
enableAnalyticsPage: PropTypes.bool.isRequired,
enterpriseGroupsV2: PropTypes.bool.isRequired,
};

export default EnterpriseAppContent;
10 changes: 10 additions & 0 deletions src/components/EnterpriseApp/EnterpriseAppRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import BulkEnrollmentResultsDownloadPage from '../BulkEnrollmentResultsDownloadP
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';
import ContentHighlights from '../ContentHighlights';
import LearnerCreditManagementRoutes from '../learner-credit-management';
import PeopleManagementPage from '../PeopleManagement';

const EnterpriseAppRoutes = ({
email,
Expand All @@ -27,6 +28,7 @@ const EnterpriseAppRoutes = ({
enableSubscriptionManagementPage,
enableAnalyticsPage,
enableContentHighlightsPage,
enterpriseGroupsV2,
}) => {
const { canManageLearnerCredit } = useContext(EnterpriseSubsidiesContext);
const { enterpriseAppPage } = useParams();
Expand Down Expand Up @@ -115,6 +117,13 @@ const EnterpriseAppRoutes = ({
/>
)}

{enterpriseGroupsV2 && enterpriseAppPage === ROUTE_NAMES.peopleManagement && (
<Route
path="/*"
element={<PeopleManagementPage />}
/>
)}

{enableContentHighlightsPage && enterpriseAppPage === ROUTE_NAMES.contentHighlights && (
<Route
path="/*"
Expand All @@ -136,6 +145,7 @@ EnterpriseAppRoutes.propTypes = {
enableSubscriptionManagementPage: PropTypes.bool.isRequired,
enableAnalyticsPage: PropTypes.bool.isRequired,
enableContentHighlightsPage: PropTypes.bool.isRequired,
enterpriseGroupsV2: PropTypes.bool.isRequired,
};

export default EnterpriseAppRoutes;
5 changes: 3 additions & 2 deletions src/components/EnterpriseApp/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
export const ROUTE_NAMES = {
analytics: 'analytics',
analyticsv2: 'analyticsv2',
appearance: 'appearance',
bulkEnrollment: 'enrollment',
bulkEnrollmentResults: 'bulk-enrollment-results',
codeManagement: 'coupons',
contentHighlights: 'content-highlights',
learners: 'learners',
learnerCredit: 'learner-credit',
peopleManagement: 'people-management',
reporting: 'reporting',
appearance: 'appearance',
settings: 'settings',
subscriptionManagement: 'subscriptions',
contentHighlights: 'content-highlights',
};

export const BUDGET_STATUSES = {
Expand Down
2 changes: 2 additions & 0 deletions src/components/EnterpriseApp/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class EnterpriseApp extends React.Component {
enableReportingPage={features.REPORTING_CONFIGURATIONS && enableReportingConfigurationsScreen}
enableSubscriptionManagementPage={enableSubscriptionManagementScreen}
enableAnalyticsPage={features.ANALYTICS && enableAnalyticsScreen}
enterpriseGroupsV2={enterpriseFeatures.enterpriseGroupsV2}
>
<FeatureAnnouncementBanner enterpriseSlug={enterpriseSlug} />
</EnterpriseAppContent>
Expand Down Expand Up @@ -195,6 +196,7 @@ EnterpriseApp.propTypes = {
enterpriseName: PropTypes.string,
enterpriseFeatures: PropTypes.shape({
topDownAssignmentRealTimeLcm: PropTypes.bool,
enterpriseGroupsV2: PropTypes.bool,
}),
enterpriseBranding: PropTypes.shape({
primary_color: PropTypes.string,
Expand Down
69 changes: 69 additions & 0 deletions src/components/PeopleManagement/images/ZeroStateImage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions src/components/PeopleManagement/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useContext } from 'react';
import { Helmet } from 'react-helmet';
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import {
ActionRow, Button, Card, useToggle,
} from '@openedx/paragon';
import { Add } from '@openedx/paragon/icons';

import cardImage from './images/ZeroStateImage.svg';
import Hero from '../Hero';
import { SUBSIDY_TYPES } from '../../data/constants/subsidyTypes';
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';

const PeopleManagementPage = () => {
const intl = useIntl();
const PAGE_TITLE = intl.formatMessage({
id: 'admin.portal.people.management.page',
defaultMessage: 'People Management',
description: 'Title for the people management page.',
});

const { enterpriseSubsidyTypes } = useContext(EnterpriseSubsidiesContext);

const hasLearnerCredit = enterpriseSubsidyTypes.includes(
SUBSIDY_TYPES.budget,
);
const hasOtherSubsidyTypes = enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.license)
|| enterpriseSubsidyTypes.includes(SUBSIDY_TYPES.coupon);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isModalOpen, openModal, closeModal] = useToggle(false);

return (
<>
<Helmet title={PAGE_TITLE} />
<Hero title={PAGE_TITLE} />
<div className="mx-3 mt-4">
<ActionRow className="mb-4">
<span className="flex-column">
<span className="d-flex">
<h3 className="mt-2">
<FormattedMessage
id="admin.portal.people.management.page.title"
defaultMessage="Your organization's groups"
description="Title for people management zero state."
/>
</h3>
</span>
{hasLearnerCredit && (
<FormattedMessage
id="admin.portal.people.management.page.subtitle.lc"
defaultMessage="Monitor group learning progress, assign more courses, and invite members to new Learner Credit budgets."
description="Subtitle for people management with learner credit."
/>
)}
{!hasLearnerCredit && hasOtherSubsidyTypes && (
<FormattedMessage
id="admin.portal.people.management.page.subtitle.noLc"
defaultMessage="Monitor group learning progress."
description="Subtitle for people management without learner credit."
/>
)}
</span>
<ActionRow.Spacer />
<Button iconBefore={Add} onClick={openModal}>
<FormattedMessage
id="admin.portal.people.management.page.newgroup.button"
defaultMessage="Create group"
description="CTA button text to open new group modal."
/>
</Button>
</ActionRow>
<Card>
<Card.ImageCap
className="mh-100"
src={cardImage}
srcAlt="Two people carrying a cartoon arrow"
/>
<span className="text-center align-self-center">
<h2 className="h3 mb-3 mt-3">
<FormattedMessage
id="admin.portal.people.management.page.zerostate.card.header"
defaultMessage="You don't have any groups yet."
description="Header message shown to admin there's no groups created yet."
/>
</h2>
<p className="mx-2">
{hasLearnerCredit && (
<FormattedMessage
id="admin.portal.people.management.page.zerostate.card.subtitle.lc"
defaultMessage="Once a group is created, you can track members' progress, assign extra courses, and invite them to additional budgets."
description="Detail message shown to admin benefits of creating a group with learner credit."
/>
)}
{!hasLearnerCredit && hasOtherSubsidyTypes && (
<FormattedMessage
id="admin.portal.people.management.page.zerostate.card.subtitle.noLc"
defaultMessage="Once a group is created, you can track members' progress."
description="Detail message shown to admin benefits of creating a group without learner credit."
/>
)}
</p>
</span>
</Card>
</div>
</>
);
};

export default PeopleManagementPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import { EnterpriseSubsidiesContext } from '../../EnterpriseSubsidiesContext';
import PeopleManagementPage from '..';

const mockStore = configureMockStore([thunk]);
const getMockStore = (store) => mockStore(store);
const enterpriseSlug = 'test-enterprise';
const enterpriseUUID = '1234';
const initialStoreState = {
portalConfiguration: {
enterpriseId: enterpriseUUID,
enterpriseSlug,
enterpriseGroupsV2: true,
},
};

const defaultEnterpriseSubsidiesContextValue = {
enterpriseSubsidyTypes: ['budget'],
isLoading: false,
};

const subsEnterpriseSubsidiesContextValue = {
enterpriseSubsidyTypes: ['license'],
isLoading: false,
};

const PeopleManagementPageWrapper = ({
initialState = initialStoreState,
enterpriseSubsidiesContextValue = defaultEnterpriseSubsidiesContextValue,
}) => {
const store = getMockStore(initialState);
return (
<IntlProvider locale="en">
<Provider store={store}>
<EnterpriseSubsidiesContext.Provider value={enterpriseSubsidiesContextValue}>
<PeopleManagementPage />
</EnterpriseSubsidiesContext.Provider>
</Provider>
</IntlProvider>
);
};

describe('<PeopleManagementPage >', () => {
it('renders the PeopleManagementPage zero state', () => {
render(<PeopleManagementPageWrapper />);
expect(document.querySelector('h3').textContent).toEqual("Your organization's groups");
expect(screen.getByText("You don't have any groups yet.")).toBeInTheDocument();
expect(screen.getByText(
'Monitor group learning progress, assign more courses, and invite members to new Learner Credit budgets.',
)).toBeInTheDocument();
});
it('renders the PeopleManagementPage zero state without LC', () => {
const store = getMockStore(initialStoreState);
render(
<IntlProvider locale="en">
<Provider store={store}>
<EnterpriseSubsidiesContext.Provider value={subsEnterpriseSubsidiesContextValue}>
<PeopleManagementPage />
</EnterpriseSubsidiesContext.Provider>
</Provider>
</IntlProvider>,
);
expect(document.querySelector('h3').textContent).toEqual(
"Your organization's groups",
);
expect(screen.getByText("You don't have any groups yet.")).toBeInTheDocument();
expect(screen.getByText("Once a group is created, you can track members' progress.")).toBeInTheDocument();
});
});
11 changes: 10 additions & 1 deletion src/components/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import classNames from 'classnames';
import { Icon } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
BookOpen, CreditCard, Description, InsertChartOutlined, MoneyOutline, Settings, Support, Tag, TrendingUp,
BookOpen, CreditCard, Description, InsertChartOutlined, MoneyOutline,
Person, Settings, Support, Tag, TrendingUp,
} from '@openedx/paragon/icons';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform/config';
Expand Down Expand Up @@ -35,6 +36,7 @@ const Sidebar = ({
onWidthChange,
isMobile,
enterpriseGroupsV1,
enterpriseGroupsV2,
onMount,
}) => {
const sidebarRef = useRef();
Expand Down Expand Up @@ -149,6 +151,12 @@ const Sidebar = ({
icon: <Icon src={MoneyOutline} />,
hidden: !canManageLearnerCredit,
},
{
title: 'People Management',
to: `${baseUrl}/admin/${ROUTE_NAMES.peopleManagement}`,
icon: <Icon src={Person} />,
hidden: !enterpriseGroupsV2,
},
{
title: intl.formatMessage({
id: 'sidebar.menu.item.highlights.title',
Expand Down Expand Up @@ -258,6 +266,7 @@ Sidebar.propTypes = {
onMount: PropTypes.func.isRequired,
isMobile: PropTypes.bool,
enterpriseGroupsV1: PropTypes.bool,
enterpriseGroupsV2: PropTypes.bool,
};

export default Sidebar;
1 change: 1 addition & 0 deletions src/containers/EnterpriseApp/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const mapStateToProps = (state) => {
enableLmsConfigurationsScreen: state.portalConfiguration.enableLmsConfigurationsScreen,
enableReportingConfigurationsScreen: state.portalConfiguration.enableReportingConfigScreen,
enablePortalLearnerCreditManagementScreen: state.portalConfiguration.enablePortalLearnerCreditManagementScreen,
enterpriseGroupsV2: state.portalConfiguration.enterpriseGroupsV2,
enterpriseId: state.portalConfiguration.enterpriseId,
enterpriseName: state.portalConfiguration.enterpriseName,
enterpriseFeatures: state.portalConfiguration.enterpriseFeatures,
Expand Down
1 change: 1 addition & 0 deletions src/containers/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const mapStateToProps = state => ({
enableLmsConfigurationsScreen: state.portalConfiguration.enableLmsConfigurationsScreen,
enableAnalyticsScreen: state.portalConfiguration.enableAnalyticsScreen,
enterpriseGroupsV1: state.portalConfiguration.enterpriseFeatures?.enterpriseGroupsV1,
enterpriseGroupsV2: state.portalConfiguration.enterpriseFeatures?.enterpriseGroupsV2,
});

const mapDispatchToProps = dispatch => ({
Expand Down

0 comments on commit e0288ab

Please sign in to comment.