Skip to content

Commit

Permalink
feat: ENT-7309 Added budget category based page for learner credit
Browse files Browse the repository at this point in the history
  • Loading branch information
IrfanUddinAhmad committed Jul 18, 2023
1 parent 71de4bc commit 00447df
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 2 deletions.
138 changes: 138 additions & 0 deletions src/components/learner-credit-management/BudgetCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { useHistory } from 'react-router-dom';
import {
Card,
Button,
Stack,
Row,
Col,
} from '@edx/paragon';

import { useOfferSummary } from './data/hooks';
import LearnerCreditAggregateCards from './LearnerCreditAggregateCards';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';

const BudgetCard = ({
offer,
enterpriseUUID,
enterpriseSlug,
}) => {
const {
start,
end,
} = offer;

const {
isLoading: isLoadingOfferSummary,
offerSummary,
} = useOfferSummary(enterpriseUUID, offer);

const formattedStartDate = moment(start).format('MMMM D, YYYY');
const formattedExpirationDate = moment(end).format('MMMM D, YYYY');
const history = useHistory();
const navigateToBudgetRedemptions = () => {
history.push(`/${enterpriseSlug}/admin/${ROUTE_NAMES.learnerCredit}`);
};

const renderActions = (budgetType) => (
<Button
onClick={navigateToBudgetRedemptions}
>
View Budget
</Button>
);

const renderCardHeader = (budgetType) => {
const subtitle = (
<div className="d-flex flex-wrap align-items-center">
<span>
{formattedStartDate} - {formattedExpirationDate}
</span>
</div>
);

return (
<Card.Header
title={budgetType}
subtitle={subtitle}
actions={(
<div>
{renderActions(budgetType)}
</div>
)}
/>
);
};

const renderCardSection = (available, spent) => (
<Card.Section
title="Balance"
muted
>
<Row className="d-flex flex-row justify-content-start w-md-75">
<Col xs="6" md="auto" className="d-flex flex-column mb-3 mb-md-0">
<span className="small">Available</span>
<span>{available}</span>
</Col>
<Col xs="6" md="auto" className="d-flex flex-column mb-3 mb-md-0">
<span className="small">Spent</span>
<span>{spent}</span>
</Col>
</Row>
</Card.Section>
);

const renderCardAggregate = () => (
<div className="mb-4.5 d-flex flex-wrap mx-n3">
<LearnerCreditAggregateCards
isLoading={isLoadingOfferSummary}
totalFunds={offerSummary?.totalFunds}
redeemedFunds={offerSummary?.redeemedFunds}
remainingFunds={offerSummary?.remainingFunds}
percentUtilized={offerSummary?.percentUtilized}
/>
</div>
);

return (
<Stack>
{renderCardAggregate()}
<h2>Budgets</h2>
<Card
orientation="horizontal"
>
<Card.Body>
<Stack gap={4}>
{renderCardHeader('Open Courses Marketplace')}
{renderCardSection(offerSummary?.remainingFunds, offerSummary?.redeemedFundsOcm)}
</Stack>
</Card.Body>
</Card>
<Card
orientation="horizontal"
>
<Card.Body>
<Stack gap={4}>
{renderCardHeader('Executive Education')}
{renderCardSection(offerSummary?.remainingFunds, offerSummary?.redeemedFundsExecEd)}
</Stack>
</Card.Body>
</Card>
</Stack>
);
};

BudgetCard.propTypes = {
offer: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
end: PropTypes.string.isRequired,
}).isRequired,
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

export default BudgetCard;
84 changes: 84 additions & 0 deletions src/components/learner-credit-management/MultipleBudgetsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import {
Stack,
Row,
Col,
Card,
Hyperlink,
} from '@edx/paragon';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import Hero from '../Hero';

import LoadingMessage from '../LoadingMessage';
import MultipleBudgetsPicker from './MultipleBudgetsPicker';
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';

import { configuration } from '../../config';

const PAGE_TITLE = 'Learner Credit';

const MultipleBudgetsPage = ({
enterpriseUUID,
enterpriseSlug,
}) => {
const { offers, isLoading } = useContext(EnterpriseSubsidiesContext);

if (isLoading) {
return <LoadingMessage className="offers" />;
}

if (offers.length === 0) {
return (
<Stack>
<Helmet title={PAGE_TITLE} />
<Hero title={PAGE_TITLE} />
<Card>
<Card.Section className="text-center">
<Row>
<Col xs={12} lg={{ span: 8, offset: 2 }}>
<h3 className="mb-3">No budgets for your organization</h3>
<p>
We were unable to find any budgets for your organization. Please contact
Customer Support if you have questions.
</p>
<Hyperlink
className="btn btn-brand"
target="_blank"
destination={configuration.ENTERPRISE_SUPPORT_URL}
>
Contact support
</Hyperlink>
</Col>
</Row>
</Card.Section>
</Card>
</Stack>
);
}

return (
<>
<Helmet title={PAGE_TITLE} />
<Hero title={PAGE_TITLE} />
<MultipleBudgetsPicker
offers={offers}
enterpriseUUID={enterpriseUUID}
enterpriseSlug={enterpriseSlug}
/>
</>
);
};

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

MultipleBudgetsPage.propTypes = {
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

export default connect(mapStateToProps)(MultipleBudgetsPage);
43 changes: 43 additions & 0 deletions src/components/learner-credit-management/MultipleBudgetsPicker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Stack,
Row,
Col,
} from '@edx/paragon';

import BudgetCard from './BudgetCard';

const MultipleBudgetsPicker = ({
offers,
enterpriseUUID,
enterpriseSlug,
}) => (
<Stack>
<Row>
<Col xs="6">
<h2>Budget Overview</h2>
</Col>
</Row>
<Row>
<Col lg="10">
{offers.map(offer => (
<BudgetCard
key={offer.id}
offer={offer}
enterpriseUUID={enterpriseUUID}
enterpriseSlug={enterpriseSlug}
/>
))}
</Col>
</Row>
</Stack>
);

MultipleBudgetsPicker.propTypes = {
offers: PropTypes.arrayOf(PropTypes.shape()).isRequired,
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

export default MultipleBudgetsPicker;
6 changes: 6 additions & 0 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ export const transformOfferSummary = (offerSummary) => {

const totalFunds = offerSummary.maxDiscount && parseFloat(offerSummary.maxDiscount);
let redeemedFunds = offerSummary.amountOfOfferSpent && parseFloat(offerSummary.amountOfOfferSpent);
let redeemedFundsOcm = offerSummary.amountOfferSpentOcm && parseFloat(offerSummary.amountOfferSpentOcm);
let redeemedFundsExecEd = offerSummary.amountOfferSpentExecEd && parseFloat(offerSummary.amountOfferSpentExecEd);

// cap redeemed funds at the maximum funds available (`maxDiscount`), if applicable, so we
// don't display redeemed funds > funds available.
if (totalFunds) {
redeemedFunds = Math.min(redeemedFunds, totalFunds);
redeemedFundsOcm = Math.min(redeemedFundsOcm, totalFunds);
redeemedFundsExecEd = Math.min(redeemedFundsExecEd, totalFunds);
}

let remainingFunds = offerSummary.remainingBalance && parseFloat(offerSummary.remainingBalance);
Expand All @@ -37,6 +41,8 @@ export const transformOfferSummary = (offerSummary) => {
return {
totalFunds,
redeemedFunds,
redeemedFundsOcm,
redeemedFundsExecEd,
remainingFunds,
percentUtilized,
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/learner-credit-management/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import LearnerCreditManagement from './LearnerCreditManagement';
import MultipleBudgetsPage from './MultipleBudgetsPage';

export default LearnerCreditManagement;
export default MultipleBudgetsPage;

0 comments on commit 00447df

Please sign in to comment.