diff --git a/src/components/learner-credit-management/BudgetCard.jsx b/src/components/learner-credit-management/BudgetCard.jsx index 78d22f57c7..5eb4f2d678 100644 --- a/src/components/learner-credit-management/BudgetCard.jsx +++ b/src/components/learner-credit-management/BudgetCard.jsx @@ -17,46 +17,55 @@ import { BUDGET_TYPES } from '../EnterpriseApp/data/constants'; * * @returns Budget card component(s). */ -const BudgetCard = ({ - budget, - enterpriseUUID, - enterpriseSlug, -}) => { +const BudgetCard = ({ original }) => { + const { + aggregates, + end, + enterpriseSlug, + enterpriseUUID, + id, + isAssignable, + isRetired, + name, + source, + start, + } = original; + const { isLoading: isLoadingSubsidySummaryAnalyticsApi, subsidySummary: subsidySummaryAnalyticsApi, - } = useSubsidySummaryAnalyticsApi(enterpriseUUID, budget.id, budget.source); + } = useSubsidySummaryAnalyticsApi(enterpriseUUID, id, source); // Subsidy Access Policies will always have a single budget, so we can render a single card // without relying on `useSubsidySummaryAnalyticsApi`. - if (budget.source === BUDGET_TYPES.policy) { + if (source === BUDGET_TYPES.policy) { return ( ); } // Enterprise Offers (ecommerce) will always have a single budget, so we can render a single card. - if (budget.source === BUDGET_TYPES.ecommerce) { + if (source === BUDGET_TYPES.ecommerce) { return ( ); @@ -74,8 +83,8 @@ const BudgetCard = ({ isLoading={isLoadingSubsidySummaryAnalyticsApi} key={subBudget.subsidyAccessPolicyUuid} id={subBudget.subsidyAccessPolicyUuid} - start={budget.start} - end={budget.end} + start={start} + end={end} available={subBudget.remainingFunds} spent={subBudget.redeemedFunds} displayName={subBudget.subsidyAccessPolicyDisplayName} @@ -85,7 +94,7 @@ const BudgetCard = ({ }; BudgetCard.propTypes = { - budget: PropTypes.shape({ + original: PropTypes.shape({ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, name: PropTypes.string.isRequired, start: PropTypes.string.isRequired, @@ -98,9 +107,10 @@ BudgetCard.propTypes = { }), isAssignable: PropTypes.bool, isRetired: PropTypes.bool, + enterpriseUUID: PropTypes.string.isRequired, + enterpriseSlug: PropTypes.string.isRequired, + status: PropTypes.string, }).isRequired, - enterpriseUUID: PropTypes.string.isRequired, - enterpriseSlug: PropTypes.string.isRequired, }; export default BudgetCard; diff --git a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx index 2477b74281..0af1fe06a5 100644 --- a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx +++ b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx @@ -1,13 +1,17 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { - Stack, + DataTable, + CardView, + TextFilter, + CheckboxFilter, Row, Col, } from '@edx/paragon'; +import groupBy from 'lodash/groupBy'; import BudgetCard from './BudgetCard'; -import { orderBudgets } from './data/utils'; +import { getBudgetStatus, orderBudgets } from './data/utils'; const MultipleBudgetsPicker = ({ budgets, @@ -16,27 +20,71 @@ const MultipleBudgetsPicker = ({ enableLearnerPortal, }) => { const orderedBudgets = orderBudgets(budgets); + + const rows = useMemo( + () => orderedBudgets.map(budget => { + const budgetLabel = getBudgetStatus({ + startDateStr: budget.start, + endDateStr: budget.end, + isBudgetRetired: budget.isRetired, + }); + + return ({ + ...budget, + status: budgetLabel.status, + enterpriseUUID, + enterpriseSlug, + enableLearnerPortal, + }); + }), + [orderedBudgets, enterpriseUUID, enterpriseSlug, enableLearnerPortal], + ); + + const budgetLabels = orderedBudgets.map(budget => ( + getBudgetStatus({ + startDateStr: budget.start, + endDateStr: budget.end, + isBudgetRetired: budget.isRetired, + }) + )); + const budgetLabelsByStatus = groupBy(budgetLabels, 'status'); + const reducedChoices = Object.keys(budgetLabelsByStatus).map(budgetLabel => ({ + name: budgetLabel, + number: budgetLabelsByStatus[budgetLabel].length, + value: budgetLabel, + })); + return ( - - + <> +

Budgets

- - - - {orderedBudgets.map(budget => ( - - ))} - - - -
+ + + + + + ); }; diff --git a/src/components/learner-credit-management/SubBudgetCard.jsx b/src/components/learner-credit-management/SubBudgetCard.jsx index 16a2b1cc7a..f27e0f0c77 100644 --- a/src/components/learner-credit-management/SubBudgetCard.jsx +++ b/src/components/learner-credit-management/SubBudgetCard.jsx @@ -6,7 +6,6 @@ import classNames from 'classnames'; import { Card, Button, - Row, Col, Badge, Stack, @@ -123,8 +122,8 @@ const BaseSubBudgetCard = ({ title={

Balance

} muted > - - + +
Available
{isFetchingBudgets ? : formatPrice(available)} @@ -144,7 +143,7 @@ const BaseSubBudgetCard = ({ {isFetchingBudgets ? : formatPrice(spent)} -
+ ); diff --git a/src/components/learner-credit-management/tests/BudgetCard.test.jsx b/src/components/learner-credit-management/tests/BudgetCard.test.jsx index 80ec4dc7db..43687867db 100644 --- a/src/components/learner-credit-management/tests/BudgetCard.test.jsx +++ b/src/components/learner-credit-management/tests/BudgetCard.test.jsx @@ -90,18 +90,20 @@ describe('', () => { }); it('displays correctly for a scheduled Enterprise Offers (ecommerce)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '3022-01-01', end: '3023-01-01', source: BUDGET_TYPES.ecommerce, + aggregates: mockBudgetAggregates, }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, - }; + useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, subsidySummary: { @@ -116,9 +118,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -130,17 +130,18 @@ describe('', () => { }); it('displays correctly for a scheduled Subsidy (enterprise-subsidy)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '3022-01-01', end: '4023-01-01', source: BUDGET_TYPES.subsidy, - }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, + aggregates: mockBudgetAggregates, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -167,9 +168,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -202,6 +201,8 @@ describe('', () => { spent: mockBudgetAggregates.spent, }, isAssignable: isAssignableBudget, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -209,9 +210,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -223,17 +222,20 @@ describe('', () => { }); it('displays correctly for an expired Enterprise Offers (ecommerce)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '2022-01-01', end: '2023-01-01', source: BUDGET_TYPES.ecommerce, - }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, + aggregates: mockBudgetAggregates, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -249,9 +251,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -268,17 +268,20 @@ describe('', () => { }); it('displays correctly for an expired Subsidy (enterprise-subsidy)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '2022-01-01', end: '2023-01-01', source: BUDGET_TYPES.subsidy, - }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, + aggregates: mockBudgetAggregates, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -305,9 +308,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -345,6 +346,8 @@ describe('', () => { spent: mockBudgetAggregates.spent, }, isAssignable: isAssignableBudget, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -352,9 +355,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -371,17 +372,20 @@ describe('', () => { }); it('displays correctly for a current Enterprise Offers (ecommerce)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '2022-01-01', end: '3022-01-01', source: BUDGET_TYPES.ecommerce, - }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, + aggregates: mockBudgetAggregates, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -397,9 +401,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -423,17 +425,20 @@ describe('', () => { }); it('displays correctly for a current Subsidy (enterprise-subsidy)', () => { + const mockBudgetAggregates = { + total: 5000, + spent: 200, + available: 4800, + }; const mockBudget = { id: mockEnterpriseOfferId, name: mockBudgetDisplayName, start: '2022-01-01', end: '3023-01-01', source: BUDGET_TYPES.subsidy, - }; - const mockBudgetAggregates = { - total: 5000, - spent: 200, - available: 4800, + aggregates: mockBudgetAggregates, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -460,9 +465,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); @@ -507,6 +510,8 @@ describe('', () => { spent: mockBudgetAggregates.spent, }, isAssignable: isAssignableBudget, + enterpriseSlug, + enterpriseUUID, }; useSubsidySummaryAnalyticsApi.mockReturnValue({ isLoading: false, @@ -514,9 +519,7 @@ describe('', () => { }); render(); expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument(); diff --git a/src/components/learner-credit-management/tests/MultipleBudgetsPage.test.jsx b/src/components/learner-credit-management/tests/MultipleBudgetsPage.test.jsx index f81d9afeb5..041114312b 100644 --- a/src/components/learner-credit-management/tests/MultipleBudgetsPage.test.jsx +++ b/src/components/learner-credit-management/tests/MultipleBudgetsPage.test.jsx @@ -10,6 +10,8 @@ import { } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; + import { EnterpriseSubsidiesContext } from '../../EnterpriseSubsidiesContext'; import MultipleBudgetsPage from '../MultipleBudgetsPage'; import { queryClient } from '../../test/testUtils'; @@ -61,9 +63,11 @@ const MultipleBudgetsPageWrapper = ({ }) => ( - - - + + + + + );