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 eabf148
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 2 deletions.
153 changes: 153 additions & 0 deletions src/components/learner-credit-management/BudgetCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Link } from 'react-router-dom';
import {
Card,
Button,
Stack,
Row,
Col,
} from '@edx/paragon';

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

const BudgetCard = ({
offer,
enterpriseUUID,
createActions,
}) => {
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 renderActions = () => {
const actions = createActions?.(offer) || [];

if (actions.length === 0) {
return null;
}

return actions.map(action => (
<Button
key={action.to}
variant={action.variant}
as={Link}
to={action.to}
>
{action.buttonText}
</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()}
</div>
)}
/>
);
};

const renderCardSection = (available, spent) => {

return (
<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 = () => {
return (
<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.defaultProps = {
createActions: null,
};

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,
createActions: PropTypes.func,
};

export default BudgetCard;
86 changes: 86 additions & 0 deletions src/components/learner-credit-management/MultipleBudgetsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import {
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,
createActions,
}) => {
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}
createActions={createActions}
/>
</>
);
};

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

MultipleBudgetsPage.defaultProps = {
createActions: null,
};

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

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

import BudgetCard from './BudgetCard';

const MultipleBudgetsPicker = ({
offers,
enterpriseUUID,
createActions,
}) => (
<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}
createActions={createActions}
/>
))}
</Col>
</Row>
</Stack>
);

MultipleBudgetsPicker.defaultProps = {
createActions: null,
};

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

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 eabf148

Please sign in to comment.