Skip to content

Commit

Permalink
feat: adding in extra info to invite learners modal (#1216)
Browse files Browse the repository at this point in the history
* feat: adding in extra modal info

* fix: PR requests

* fix: more PR requests
  • Loading branch information
kiram15 authored May 9, 2024
1 parent 9ea71af commit 3cf71d4
Show file tree
Hide file tree
Showing 14 changed files with 404 additions and 90 deletions.
53 changes: 53 additions & 0 deletions src/components/learner-credit-management/BudgetDetail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ProgressBar, Stack } from '@edx/paragon';

import { formatPrice } from './data';
import { BUDGET_STATUSES } from '../EnterpriseApp/data/constants';

const BudgetDetail = ({
available, utilized, limit, status,
}) => {
const currentProgressBarLimit = (available / limit) * 100;

if (status === BUDGET_STATUSES.expired) {
return (
<Stack className="border border-light-400 p-4">
<h4>Spent</h4>
<Stack direction="horizontal" gap={4} className="mt-1">
<span className="display-1 text-dark" data-testid="budget-detail-spent">{formatPrice(utilized)}</span>
<span className="mt-auto small text-monospace" data-testid="budget-detail-unspent">
Unspent {formatPrice(available)}
</span>
</Stack>
</Stack>
);
}

return (
<Stack className="border border-light-400 p-4">
<h4>Available</h4>
<Stack direction="horizontal" gap={4} className="mt-1">
<span className="display-1 text-dark" data-testid="budget-detail-available">{formatPrice(available)}</span>
<span className="mt-auto small text-monospace" data-testid="budget-detail-utilized">
Utilized {formatPrice(utilized)}
</span>
</Stack>
<Stack gap={2} className="mt-3">
<ProgressBar now={currentProgressBarLimit} variant="info" />
<span className="ml-auto small text-monospace" data-testid="budget-detail-limit">
{formatPrice(limit)} limit
</span>
</Stack>
</Stack>
);
};

BudgetDetail.propTypes = {
available: PropTypes.number.isRequired,
utilized: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
};

export default BudgetDetail;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { generatePath, useParams, Link } from 'react-router-dom';
import {
Button, Col, Hyperlink, ProgressBar, Row, Stack,
Button, Col, Hyperlink, Row, Stack,
} from '@edx/paragon';
import { Add } from '@edx/paragon/icons';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
Expand All @@ -12,82 +12,13 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { configuration } from '../../config';
import { BudgetDetailPageContext } from './BudgetDetailPageWrapper';
import {
formatPrice,
useBudgetId,
useSubsidyAccessPolicy,
useEnterpriseCustomer,
useEnterpriseGroup,
useBudgetId, useSubsidyAccessPolicy, useEnterpriseCustomer, useEnterpriseGroup,
} from './data';
import EVENT_NAMES from '../../eventTracking';
import { LEARNER_CREDIT_ROUTE } from './constants';
import { BUDGET_STATUSES } from '../EnterpriseApp/data/constants';
import isLmsBudget from './utils';

const BudgetDetail = ({
available, utilized, limit, status,
}) => {
const currentProgressBarLimit = (available / limit) * 100;

if (status === BUDGET_STATUSES.expired) {
return (
<Stack className="border border-light-400 p-4">
<h4>Spent</h4>
<Stack direction="horizontal" gap={4} className="mt-1">
<span className="display-1 text-dark" data-testid="budget-detail-spent">{formatPrice(utilized)}</span>
<span className="mt-auto small text-monospace" data-testid="budget-detail-unspent">
<FormattedMessage
id="lcm.budget.detail.page.overview.budget.detail.unspent"
defaultMessage="Unspent {unspent}"
description="unspent budget amount on the budget detail page overview"
values={{ unspent: formatPrice(available) }}
/>
</span>
</Stack>
</Stack>
);
}

return (
<Stack className="border border-light-400 p-4">
<h4>
<FormattedMessage
id="lcm.budget.detail.page.overview.budget.detail.available"
defaultMessage="Available"
description="available budget amount on the budget detail page overview"
/>
</h4>
<Stack direction="horizontal" gap={4} className="mt-1">
<span className="display-1 text-dark" data-testid="budget-detail-available">{formatPrice(available)}</span>
<span className="mt-auto small text-monospace" data-testid="budget-detail-utilized">
<FormattedMessage
id="lcm.budget.detail.page.overview.budget.detail.utilized"
defaultMessage="Utilized {utilized}"
values={{ utilized: formatPrice(utilized) }}
description="utilized budget amount on the budget detail page overview"
/>
</span>
</Stack>
<Stack gap={2} className="mt-3">
<ProgressBar now={currentProgressBarLimit} variant="info" />
<span className="ml-auto small text-monospace" data-testid="budget-detail-limit">
<FormattedMessage
id="lcm.budget.detail.page.overview.budget.detail.limit"
defaultMessage="{limit} limit"
description="limit of the budget on the budget detail page overview"
values={{ limit: formatPrice(limit) }}
/>
</span>
</Stack>
</Stack>
);
};

BudgetDetail.propTypes = {
available: PropTypes.number.isRequired,
utilized: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
};
import BudgetDetail from './BudgetDetail';

const BudgetActions = ({
budgetId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const BudgetOverviewContent = ({
}

return (
<Card className="budget-overview-card">
<Card>
<Card.Section>
<h2>{budgetDisplayName}</h2>
<BudgetStatusSubtitle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export { default as useEnterpriseGroupLearners } from './useEnterpriseGroupLearn
export { default as useEnterpriseGroupMembersTableData } from './useEnterpriseGroupMembersTableData';
export { default as useEnterpriseCustomer } from './useEnterpriseCustomer';
export { default as useEnterpriseGroup } from './useEnterpriseGroup';
export { default as useContentMetadata } from './useContentMetadata';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { camelCaseObject } from '@edx/frontend-platform/utils';

import { learnerCreditManagementQueryKeys } from '../constants';
import EnterpriseCatalogApiService from '../../../../data/services/EnterpriseCatalogApiService';

const getContentMetadata = async ({ catalogUuid }) => {
const response = await EnterpriseCatalogApiService.fetchEnterpriseCatalogMetadata({ catalogUuid });
const contentMetadata = camelCaseObject(response.data);
return contentMetadata;
};

const useContentMetadata = (catalogUuid, { queryOptions } = {}) => useQuery({
queryKey: learnerCreditManagementQueryKeys.group(catalogUuid),
queryFn: () => getContentMetadata({ catalogUuid }),
...queryOptions,
});

export default useContentMetadata;
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const InviteMembersModalWrapper = ({
>
<InviteModalContent
onEmailAddressesChange={handleEmailAddressesChanged}
subsidyAccessPolicy={subsidyAccessPolicy}
/>
</FullscreenModal>
<SystemErrorAlertModal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Card, Col, Row, Skeleton,
} from '@edx/paragon';
import { makePlural } from '../../../utils';

import {
useBudgetDetailHeaderData,
useBudgetId,
useEnterpriseGroupLearners,
useEnterpriseOffer, useSubsidyAccessPolicy,
useSubsidySummaryAnalyticsApi,
} from '../data';
import BudgetDetail from '../BudgetDetail';
import { BUDGET_TYPES } from '../../EnterpriseApp/data/constants';
import BudgetStatusSubtitle from '../BudgetStatusSubtitle';

const InviteModalBudgetCard = ({
enterpriseUUID,
enterpriseFeatures,
}) => {
const { subsidyAccessPolicyId, enterpriseOfferId } = useBudgetId();
const { data: subsidyAccessPolicy } = useSubsidyAccessPolicy(subsidyAccessPolicyId);
const { data } = useEnterpriseGroupLearners(subsidyAccessPolicy.groupAssociations[0]);

const memberSubtitle = data?.count ? `${makePlural(data?.count, 'current member')}` : '';
const budgetType = (enterpriseOfferId !== null) ? BUDGET_TYPES.ecommerce : BUDGET_TYPES.policy;

const { isLoading: isLoadingSubsidySummary, subsidySummary } = useSubsidySummaryAnalyticsApi(
enterpriseUUID,
enterpriseOfferId,
budgetType,
);

const { isLoading: isLoadingEnterpriseOffer, data: enterpriseOfferMetadata } = useEnterpriseOffer(enterpriseOfferId);

const policyOrOfferId = subsidyAccessPolicyId || enterpriseOfferId;
const {
budgetDisplayName,
budgetTotalSummary,
status,
badgeVariant,
term,
date,
isAssignable,
} = useBudgetDetailHeaderData({
subsidyAccessPolicy,
subsidySummary,
budgetId: policyOrOfferId,
enterpriseOfferMetadata,
isTopDownAssignmentEnabled: enterpriseFeatures.topDownAssignmentRealTimeLcm,
});

if (!subsidyAccessPolicy && (isLoadingSubsidySummary || isLoadingEnterpriseOffer)) {
return (
<div data-testid="budget-detail-skeleton">
<Skeleton height={180} />
<span className="sr-only">Loading budget header data</span>
</div>
);
}

const { available, utilized, limit } = budgetTotalSummary;
return (
<Card className="budget-overview-card m-3">
<Card.Section>
<Row>
<Col lg={5}>
<h4>{budgetDisplayName}</h4>
<p>{memberSubtitle}</p>
<BudgetStatusSubtitle
badgeVariant={badgeVariant}
status={status}
isAssignable={isAssignable}
term={term}
date={date}
policy={subsidyAccessPolicy}
enterpriseUUID={enterpriseUUID}
/>
</Col>
<Col lg={7}>
<BudgetDetail available={available} utilized={utilized} limit={limit} status={status} />
</Col>
</Row>
</Card.Section>
</Card>
);
};

const mapStateToProps = state => ({
enterpriseUUID: state.portalConfiguration.enterpriseId,
enterpriseFeatures: state.portalConfiguration.enterpriseFeatures,
});

InviteModalBudgetCard.propTypes = {
enterpriseUUID: PropTypes.string.isRequired,
enterpriseFeatures: PropTypes.shape({
topDownAssignmentRealTimeLcm: PropTypes.bool,
}).isRequired,
};

export default connect(mapStateToProps)(InviteModalBudgetCard);
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import InviteModalSummary from './InviteModalSummary';
import { EMAIL_ADDRESSES_INPUT_VALUE_DEBOUNCE_DELAY, INPUT_TYPE, isInviteEmailAddressesInputValueValid } from '../cards/data';
import FileUpload from './FileUpload';
import InviteModalInputFeedback from './InviteModalInputFeedback';
import InviteModalMembershipInfo from './InviteModalMembershipInfo';
import InviteModalBudgetCard from './InviteModalBudgetCard';
import InviteModalPermissions from './InviteModalPermissions';

const InviteModalContent = ({ onEmailAddressesChange }) => {
const InviteModalContent = ({ onEmailAddressesChange, subsidyAccessPolicy }) => {
const [learnerEmails, setLearnerEmails] = useState([]);
const [inputType, setInputType] = useState('email');
const [emailAddressesInputValue, setEmailAddressesInputValue] = useState('');
Expand Down Expand Up @@ -58,6 +61,7 @@ const InviteModalContent = ({ onEmailAddressesChange }) => {
return (
<Container size="lg" className="py-3">
<h3>Invite members to this budget</h3>
<InviteModalBudgetCard />
<Row className="mt-3">
<Col>
<h4 className="mb-4">Send invite to</h4>
Expand Down Expand Up @@ -91,12 +95,13 @@ const InviteModalContent = ({ onEmailAddressesChange }) => {
setEmailAddressesInputValue={setEmailAddressesInputValue}
/>
)}
<InviteModalMembershipInfo subsidyAccessPolicy={subsidyAccessPolicy} />
</Col>
<Col>
<h4>Details</h4>
<InviteModalSummary
memberInviteMetadata={memberInviteMetadata}
/>
<InviteModalSummary memberInviteMetadata={memberInviteMetadata} />
<hr className="my-4" />
<InviteModalPermissions subsidyAccessPolicy={subsidyAccessPolicy} />
</Col>
</Row>
</Container>
Expand All @@ -105,6 +110,7 @@ const InviteModalContent = ({ onEmailAddressesChange }) => {

InviteModalContent.propTypes = {
onEmailAddressesChange: PropTypes.func.isRequired,
subsidyAccessPolicy: PropTypes.shape(),
};

export default InviteModalContent;
Loading

0 comments on commit 3cf71d4

Please sign in to comment.