Skip to content

Commit

Permalink
Merge branch 'master' into hu/ent-8917
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 authored May 20, 2024
2 parents 98495d7 + ea27f5f commit 4c2447b
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 21 deletions.
38 changes: 31 additions & 7 deletions src/components/learner-credit-management/MultipleBudgetsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@ import {
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';

import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
import Hero from '../Hero';
import MultipleBudgetsPicker from './MultipleBudgetsPicker';
import { EnterpriseSubsidiesContext } from '../EnterpriseSubsidiesContext';

import { configuration } from '../../config';
import { useEnterpriseBudgets } from '../EnterpriseSubsidiesContext/data/hooks';

const PAGE_TITLE = 'Learner Credit Management';

const MultipleBudgetsPage = ({
enterpriseUUID,
enterpriseSlug,
enableLearnerPortal,
enterpriseFeatures,
enablePortalLearnerCreditManagementScreen,
}) => {
const intl = useIntl();
const PAGE_TITLE = intl.formatMessage({
id: 'lcm.page.title',
defaultMessage: 'Learner Credit Management',
description: 'Title for the Learner Credit Management page',
});
const { isLoading } = useContext(EnterpriseSubsidiesContext);
const { data: budgetsOverview } = useEnterpriseBudgets({
enterpriseId: enterpriseUUID,
Expand All @@ -43,7 +48,13 @@ const MultipleBudgetsPage = ({
<>
<h1><Skeleton /></h1>
<Skeleton height={200} count={2} />
<span className="sr-only">Loading budgets...</span>
<span className="sr-only">
<FormattedMessage
id="lcm.budgets.loading"
defaultMessage="Loading budgets..."
description="Loading budgets"
/>
</span>
</>
);
}
Expand All @@ -57,17 +68,30 @@ const MultipleBudgetsPage = ({
<Card.Section className="text-center">
<Row>
<Col xs={12} lg={{ span: 8, offset: 2 }}>
<h3 className="mb-3">No budgets for your organization</h3>
<h3 className="mb-3">
<FormattedMessage
id="lcm.budgets.no.budgets"
defaultMessage="No budgets for your organization"
description="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.
<FormattedMessage
id="lcm.budgets.no.budgets.description"
defaultMessage="We were unable to find any budgets for your organization. Please contact Customer Support if you have questions."
description="Description for no budgets found and guidance to contact support."
/>
</p>
<Hyperlink
className="btn btn-brand"
target="_blank"
destination={configuration.ENTERPRISE_SUPPORT_URL}
>
Contact support
<FormattedMessage
id="lcm.budgets.contact.support"
defaultMessage="Contact support"
description="Contact support text for no budgets found."
/>
</Hyperlink>
</Col>
</Row>
Expand Down
23 changes: 18 additions & 5 deletions src/components/learner-credit-management/MultipleBudgetsPicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
} from '@edx/paragon';
import groupBy from 'lodash/groupBy';

import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import BudgetCard from './BudgetCard';
import { getBudgetStatus, orderBudgets } from './data/utils';
import { getBudgetStatus, getTranslatedBudgetStatus, orderBudgets } from './data/utils';

const MultipleBudgetsPicker = ({
budgets,
Expand All @@ -20,7 +21,7 @@ const MultipleBudgetsPicker = ({
enableLearnerPortal,
}) => {
const orderedBudgets = orderBudgets(budgets);

const intl = useIntl();
const rows = useMemo(
() => orderedBudgets.map(budget => {
const budgetLabel = getBudgetStatus({
Expand Down Expand Up @@ -49,15 +50,23 @@ const MultipleBudgetsPicker = ({
));
const budgetLabelsByStatus = groupBy(budgetLabels, 'status');
const reducedChoices = Object.keys(budgetLabelsByStatus).map(budgetLabel => ({
name: budgetLabel,
name: getTranslatedBudgetStatus(intl, budgetLabel),
number: budgetLabelsByStatus[budgetLabel].length,
value: budgetLabel,
}));

return (
<>
<Row className="mb-4">
<Col lg="12"><h2>Budgets</h2></Col>
<Col lg="12">
<h2>
<FormattedMessage
id="lcm.budgets.budgets"
defaultMessage="Budgets"
description="Header for the budget picker page."
/>
</h2>
</Col>
</Row>
<DataTable
defaultColumnValues={{ Filter: TextFilter }}
Expand All @@ -70,7 +79,11 @@ const MultipleBudgetsPicker = ({
accessor: 'name',
},
{
Header: 'Status',
Header: intl.formatMessage({
id: 'lcm.budgets.budgets.filters.status',
defaultMessage: 'Status',
description: 'Header for the status column in the budget picker page.',
}),
accessor: 'status',
Filter: CheckboxFilter,
filterChoices: reducedChoices,
Expand Down
59 changes: 50 additions & 9 deletions src/components/learner-credit-management/SubBudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import {
Skeleton,
} from '@edx/paragon';

import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { BUDGET_STATUSES, ROUTE_NAMES } from '../EnterpriseApp/data/constants';
import { formatPrice, getBudgetStatus } from './data/utils';
import {
formatPrice, getBudgetStatus, getTranslatedBudgetStatus, getTranslatedBudgetTerm,
} from './data/utils';
import { useEnterpriseBudgets } from '../EnterpriseSubsidiesContext/data/hooks';

const BaseBackgroundFetchingWrapper = ({
Expand Down Expand Up @@ -71,12 +74,20 @@ const BaseSubBudgetCard = ({
enterpriseId,
isTopDownAssignmentEnabled,
});
const intl = useIntl();
const budgetLabel = getBudgetStatus({
startDateStr: start,
endDateStr: end,
isBudgetRetired: isRetired,
});
const formattedDate = budgetLabel?.date ? dayjs(budgetLabel?.date).format('MMMM D, YYYY') : undefined;
const formattedDate = budgetLabel?.date ? intl.formatDate(
dayjs(budgetLabel?.date).toDate(),
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
) : undefined;

Check warning on line 90 in src/components/learner-credit-management/SubBudgetCard.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/learner-credit-management/SubBudgetCard.jsx#L90

Added line #L90 was not covered by tests

const renderActions = (budgetId) => (
<Button
Expand All @@ -85,17 +96,21 @@ const BaseSubBudgetCard = ({
to={`/${enterpriseSlug}/admin/${ROUTE_NAMES.learnerCredit}/${budgetId}`}
variant={[BUDGET_STATUSES.expired, BUDGET_STATUSES.retired].includes(budgetLabel.status) ? 'outline-primary' : 'primary'}
>
View budget
<FormattedMessage
id="lcm.budgets.budget.card.view.budget"
defaultMessage="View budget"
description="Button text to view a budget"
/>
</Button>
);

const renderCardHeader = (budgetType, budgetId) => {
const subtitle = (
<Stack direction="horizontal" gap={2.5}>
<Badge variant={budgetLabel.badgeVariant}>{budgetLabel.status}</Badge>
<Badge variant={budgetLabel.badgeVariant}>{getTranslatedBudgetStatus(intl, budgetLabel.status)}</Badge>
{(budgetLabel.term && formattedDate) && (
<span data-testid="budget-date">
{budgetLabel.term} {formattedDate}
{getTranslatedBudgetTerm(intl, budgetLabel.term)} {formattedDate}
</span>
)}
</Stack>
Expand All @@ -119,26 +134,52 @@ const BaseSubBudgetCard = ({

const renderCardSection = () => (
<Card.Section
title={<h4>Balance</h4>}
title={(
<h4>
<FormattedMessage
id="lcm.budgets.budget.card.balance"
defaultMessage="Balance"
description="Header for the balance section of the budget card"
/>
</h4>
)}
muted
>
<Col className="d-flex justify-content-start w-md-75">
<Col xs="6" md="auto" className="mb-3 mb-md-0 ml-n4.5">
<div className="small font-weight-bold">Available</div>
<div className="small font-weight-bold">
<FormattedMessage
id="lcm.budgets.budget.card.available"
defaultMessage="Available"
description="Label for the available balance on the budget card"
/>
</div>
<span className="small">
{isFetchingBudgets ? <Skeleton /> : formatPrice(available)}
</span>
</Col>
{isAssignable && (
<Col xs="6" md="auto" className="mb-3 mb-md-0">
<div className="small font-weight-bold">Assigned</div>
<div className="small font-weight-bold">
<FormattedMessage
id="lcm.budgets.budget.card.assigned"
defaultMessage="Assigned"
description="Label for the assigned balance on the budget card"
/>
</div>
<span className="small">
{isFetchingBudgets ? <Skeleton /> : formatPrice(pending)}
</span>
</Col>
)}
<Col xs="6" md="auto" className="mb-3 mb-md-0">
<div className="small font-weight-bold">Spent</div>
<div className="small font-weight-bold">
<FormattedMessage
id="lcm.budgets.budget.card.spent"
defaultMessage="Spent"
description="Label for the spent balance on the budget card"
/>
</div>
<span className="small">
{isFetchingBudgets ? <Skeleton /> : formatPrice(spent)}
</span>
Expand Down
46 changes: 46 additions & 0 deletions src/components/learner-credit-management/data/tests/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
transformSubsidySummary,
getBudgetStatus,
orderBudgets,
getTranslatedBudgetStatus,
getTranslatedBudgetTerm,
} from '../utils';
import { EXEC_ED_OFFER_TYPE } from '../constants';

Expand Down Expand Up @@ -206,3 +208,47 @@ describe('orderBudgets', () => {
expect(sortedBudgets.map((budget) => budget.name)).toEqual(['Budget A', 'Budget B']);
});
});

describe('getTranslatedBudgetStatus', () => {
it('should translate the budget status correctly', () => {
const intl = { formatMessage: jest.fn() };
const status = 'Retired';

getTranslatedBudgetStatus(intl, status);

expect(intl.formatMessage).toHaveBeenCalledWith({
id: 'lcm.budgets.budget.card.status.retired',
defaultMessage: 'Retired',
description: 'Status for a retired budget',
});
});
it('should handle the case for an unknown value', () => {
const intl = { formatMessage: jest.fn() };
const status = 'unknown';

expect(getTranslatedBudgetStatus(intl, status)).toEqual('');
});
});

describe('getTranslatedBudgetTerm', () => {
it('should translate the budget term correctly', () => {
const intl = { formatMessage: jest.fn() };
const term = 'Expiring';

getTranslatedBudgetTerm(intl, term);

expect(intl.formatMessage).toHaveBeenCalledWith({
id: 'lcm.budgets.budget.card.term.expiring',
defaultMessage: 'Expiring',
description: 'Term for when a budget is expiring',
});
});
it('should handle the case when unknown or null term', () => {
const intl = { formatMessage: jest.fn() };
const term1 = 'unknown';
const term2 = null;

expect(getTranslatedBudgetTerm(intl, term1)).toEqual('');
expect(getTranslatedBudgetTerm(intl, term2)).toEqual('');
});
});
81 changes: 81 additions & 0 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,84 @@ export const transformSelectedRows = (selectedFlatRows) => {
totalSelectedRows,
};
};

/**
* Translates the budget status using the provided `intl` object.
*
* @param {object} intl - The `intl` object used for translation.
* @param {string} status - The status of the budget.
* @returns {string} The translated budget status.
*/
export const getTranslatedBudgetStatus = (intl, status) => {
switch (status) {
case BUDGET_STATUSES.active:
return intl.formatMessage({
id: 'lcm.budgets.budget.card.status.active',
defaultMessage: 'Active',
description: 'Status for an active budget',
});
case BUDGET_STATUSES.expiring:
return intl.formatMessage({
id: 'lcm.budgets.budget.card.status.expiring',
defaultMessage: 'Expiring',
description: 'Status for an expiring budget',
});
case BUDGET_STATUSES.expired:
return intl.formatMessage({
id: 'lcm.budgets.budget.card.status.expired',
defaultMessage: 'Expired',
description: 'Status for an expired budget',
});
case BUDGET_STATUSES.retired:
return intl.formatMessage({
id: 'lcm.budgets.budget.card.status.retired',
defaultMessage: 'Retired',
description: 'Status for a retired budget',
});
case BUDGET_STATUSES.scheduled:
return intl.formatMessage({
id: 'lcm.budgets.budget.card.status.scheduled',
defaultMessage: 'Scheduled',
description: 'Status for a scheduled budget',
});
default:
return '';
}
};

/**
* Translates the budget term using the provided `intl` object.
* @param {object} intl - The `intl` object used for translation.
* @param {string} term - The term of the budget.
* @returns {string} The translated budget term.
*/
export const getTranslatedBudgetTerm = (intl, term) => {
switch (term) {
case 'Starts':
return intl.formatMessage({
id: 'lcm.budgets.budget.card.term.starts',
defaultMessage: 'Starts',
description: 'Term for when a budget starts',
});
case 'Expires':
return intl.formatMessage({
id: 'lcm.budgets.budget.card.term.expires',
defaultMessage: 'Expires',
description: 'Term for when a budget expires',
});
case 'Expiring':
return intl.formatMessage({
id: 'lcm.budgets.budget.card.term.expiring',
defaultMessage: 'Expiring',
description: 'Term for when a budget is expiring',
});
case 'Expired':
return intl.formatMessage({
id: 'lcm.budgets.budget.card.term.expired',
defaultMessage: 'Expired',
description: 'Term for when a budget has expired',
});
default:
return '';
}
};

0 comments on commit 4c2447b

Please sign in to comment.