Skip to content

Commit

Permalink
feat: show all enterprise budgets regardless of plan and route correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
jajjibhai008 committed Sep 15, 2023
1 parent 4da552c commit 0f5e292
Show file tree
Hide file tree
Showing 9 changed files with 411 additions and 158 deletions.
7 changes: 7 additions & 0 deletions src/components/EnterpriseApp/EnterpriseAppRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PlotlyAnalyticsPage } from '../PlotlyAnalytics';
import { ROUTE_NAMES } from './data/constants';
import BulkEnrollmentResultsDownloadPage from '../BulkEnrollmentResultsDownloadPage';
import LearnerCreditManagement from '../learner-credit-management';
import BudgetDetailPage from '../learner-credit-management/BudgetDetailPage';
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';
import ContentHighlights from '../ContentHighlights';

Expand Down Expand Up @@ -105,6 +106,12 @@ const EnterpriseAppRoutes = ({
/>
)}

<Route
exact
path={`${baseUrl}/admin/${ROUTE_NAMES.learnerCredit}/:id`}
component={BudgetDetailPage}
/>

{enableContentHighlightsPage && (
<Route
path={`${baseUrl}/admin/${ROUTE_NAMES.contentHighlights}`}
Expand Down
76 changes: 45 additions & 31 deletions src/components/EnterpriseSubsidiesContext/data/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SubsidyApiService from '../../../data/services/EnterpriseSubsidyApiServic
export const useEnterpriseOffers = ({ enablePortalLearnerCreditManagementScreen, enterpriseId }) => {
const [offers, setOffers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [canManageLearnerCredit, setCanManageLearnerCredit] = useState(false);
const [canManageLearnerCredit, setCanManageLearnerCredit] = useState(true);

dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);
Expand All @@ -30,38 +30,52 @@ export const useEnterpriseOffers = ({ enablePortalLearnerCreditManagementScreen,
}),
]);

// If there are no subsidies in enterprise, fall back to the e-commerce API.
let { results } = camelCaseObject(enterpriseSubsidyResponse.data);
let source = 'subsidyApi';

if (results.length === 0) {
results = camelCaseObject(ecommerceApiResponse.data.results);
source = 'ecommerceApi';
// We have to conside both type of offers active and inactive.

Check failure on line 33 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 8 spaces but found 6

Check failure on line 34 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Trailing spaces not allowed
const enterpriseResults = camelCaseObject(enterpriseSubsidyResponse.data).results;

Check failure on line 35 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 8 spaces but found 6
const ecommerceResults = camelCaseObject(ecommerceApiResponse.data.results);

Check failure on line 36 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 8 spaces but found 6

const offerData = [];

Check failure on line 38 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 8 spaces but found 6

Check failure on line 39 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Trailing spaces not allowed
for (let i = 0; i < enterpriseResults.length; i++) {

Check failure on line 40 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 8 spaces but found 6
const subsidy = enterpriseResults[i];

Check failure on line 41 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 10 spaces but found 8
const source = 'subsidyApi';

Check failure on line 42 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 10 spaces but found 8
const isActive = subsidy.isActive; // Always check isActive for enterprise subsidies

Check failure on line 43 in src/components/EnterpriseSubsidiesContext/data/hooks.js

View workflow job for this annotation

GitHub Actions / tests

Expected indentation of 10 spaces but found 8
const isCurrent = isActive; // You can adjust this based on your specific requirements
const activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent: isCurrent,
source: source,
};
offerData.push(activeSubsidyData);
if (isActive) {
setCanManageLearnerCredit(true);
}
let activeSubsidyFound = false;
if (results.length !== 0) {
let subsidy = results[0];
const offerData = [];
let activeSubsidyData = {};
for (let i = 0; i < results.length; i++) {
subsidy = results[i];
activeSubsidyFound = source === 'ecommerceApi'
? subsidy.isCurrent
: subsidy.isActive;
if (activeSubsidyFound === true) {
activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent: activeSubsidyFound,
};
offerData.push(activeSubsidyData);
setCanManageLearnerCredit(true);
}
}
setOffers(offerData);
}

for (let i = 0; i < ecommerceResults.length; i++) {
const subsidy = ecommerceResults[i];
const source = 'ecommerceApi';
const isCurrent = subsidy.isCurrent;
const activeSubsidyData = {
id: subsidy.uuid || subsidy.id,
name: subsidy.title || subsidy.displayName,
start: subsidy.activeDatetime || subsidy.startDatetime,
end: subsidy.expirationDatetime || subsidy.endDatetime,
isCurrent: isCurrent,
source: source,
};
offerData.push(activeSubsidyData);
if (isCurrent) {
setCanManageLearnerCredit(true);
}
}

setOffers(offerData);

} catch (error) {
logError(error);
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import userEvent from '@testing-library/user-event';
import configureMockStore from 'redux-mock-store';
import {
screen,
render,
waitFor,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { IntlProvider } from '@edx/frontend-platform/i18n';
import BudgetDetailPage from '../BudgetDetailPage';
import { useOfferSummary, useOfferRedemptions } from '../data/hooks';
import { EXEC_ED_OFFER_TYPE } from '../data/constants';
import { MemoryRouter } from 'react-router-dom';

jest.mock('../data/hooks');
useOfferSummary.mockReturnValue({
isLoading: false,
offerSummary: null,
});
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: {
itemCount: 0,
pageCount: 0,
results: [],
},
fetchOfferRedemptions: jest.fn(),
});

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

},
};
const store = getMockStore({ ...initialStore });

const mockEnterpriseOfferId = '123';
const mockEnterpriseOfferEnrollmentId = 456;

const mockOfferDisplayName = 'Test Enterprise Offer';
const mockOfferSummary = {
totalFunds: 5000,
redeemedFunds: 200,
remainingFunds: 4800,
percentUtilized: 0.04,
offerType: EXEC_ED_OFFER_TYPE,
};

const BudgetDetailPageWrapper = ({ ...rest }) => (
<MemoryRouter initialEntries={['/test-enterprise/admin/learner-credit/1234']}>

<Provider store={store}>
<IntlProvider locale="en">
<BudgetDetailPage {...rest} />
</IntlProvider>
</Provider>
</MemoryRouter>
);

describe('<BudgetDetailPage />', () => {
describe('with enterprise offer', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('displays table on clicking view budget', async () => {
const mockOffer = {
id: mockEnterpriseOfferId,
name: mockOfferDisplayName,
start: '2022-01-01',
end: '2023-01-01',
};
useOfferSummary.mockReturnValue({
isLoading: false,
offerSummary: mockOfferSummary,
});
useOfferRedemptions.mockReturnValue({
isLoading: false,
offerRedemptions: {
itemCount: 0,
pageCount: 0,
results: [],
},
fetchOfferRedemptions: jest.fn(),
});
render(<BudgetDetailPageWrapper
enterpriseUUID={enterpriseUUID} enterpriseSlug={enterpriseId}
offer={mockOffer}
/>);
expect(screen.getByText('Learner Credit Budget Detail'));
expect(screen.getByText('Overview'));
expect(screen.getByText('No results found'));
});
});
});
152 changes: 30 additions & 122 deletions src/components/learner-credit-management/BudgetCard-V2.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,18 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import {
Card,
Button,
Stack,
Row,
Col,
Breadcrumb,
} from '@edx/paragon';

import { useOfferRedemptions, useOfferSummary } from './data/hooks';
import LearnerCreditAggregateCards from './LearnerCreditAggregateCards';
import LearnerCreditAllocationTable from './LearnerCreditAllocationTable';
import { ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import { useOfferSummary } from './data/hooks';
import SubBudgetCard from './Budgetcard-V3';

const BudgetCard = ({
offer,
enterpriseUUID,
enterpriseSlug,
enableLearnerPortal,
offerType,
}) => {
const {
start,
Expand All @@ -31,123 +25,37 @@ const BudgetCard = ({
offerSummary,
} = useOfferSummary(enterpriseUUID, offer);

const {
isLoading: isLoadingOfferRedemptions,
offerRedemptions,
fetchOfferRedemptions,
} = useOfferRedemptions(enterpriseUUID, offer?.id);
const [detailPage, setDetailPage] = useState(false);
const [activeLabel, setActiveLabel] = useState('');
const links = [
{ label: 'Budgets', url: `/${enterpriseSlug}/admin/${ROUTE_NAMES.learnerCredit}` },
];
const formattedStartDate = dayjs(start).format('MMMM D, YYYY');
const formattedExpirationDate = dayjs(end).format('MMMM D, YYYY');
const navigateToBudgetRedemptions = (budgetType) => {
setDetailPage(true);
links.push({ label: budgetType, url: `/${enterpriseSlug}/admin/learner-credit` });
setActiveLabel(budgetType);
};

const renderActions = (budgetType) => (
<Button
data-testid="view-budget"
onClick={() => navigateToBudgetRedemptions(budgetType)}
>
View Budget
</Button>
);

const renderCardHeader = (budgetType) => {
const subtitle = (
<div className="d-flex flex-wrap align-items-center">
<span data-testid="offer-date">
{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>
<Row className="m-3">
<Col xs="12">
<Breadcrumb
ariaLabel="Breadcrumb"
links={links}
activeLabel={activeLabel}
/>
</Col>
</Row>
{!detailPage
? (
<>
{renderCardAggregate()}
<h2>Budgets</h2>
<Card
orientation="horizontal"
>
<Card.Body>
<Stack gap={4}>
{renderCardHeader('Overview')}
{renderCardSection(offerSummary?.remainingFunds, offerSummary?.redeemedFunds)}
</Stack>
</Card.Body>
</Card>
</>
)
: (
<LearnerCreditAllocationTable
isLoading={isLoadingOfferRedemptions}
tableData={offerRedemptions}
fetchTableData={fetchOfferRedemptions}
enterpriseUUID={enterpriseUUID}
<Stack gap={4}>
<>
<h2>Budgets</h2>
{!isLoadingOfferSummary &&
offerType == 'ecommerceApi' ?
<SubBudgetCard
id={offerSummary.offerId}
start={formattedStartDate}
end={formattedExpirationDate}
available={offerSummary?.remainingFunds}
spent={offerSummary?.redeemedFunds}
enterpriseSlug={enterpriseSlug}
enableLearnerPortal={enableLearnerPortal}
/>
)}
/> :
offerSummary?.budgetsSumary.map((budget, index) => (
<SubBudgetCard
key={index}
id={budget.subsidyAccessPolicyUuid}
start={formattedStartDate}
end={formattedExpirationDate}
available={budget?.remainingFunds}
spent={budget?.redeemedFunds}
enterpriseSlug={enterpriseSlug}
/>
))

}
</>
</Stack>
);
};
Expand Down
Loading

0 comments on commit 0f5e292

Please sign in to comment.