Skip to content

Commit

Permalink
feat: Retired workflow on the list policy screens
Browse files Browse the repository at this point in the history
  • Loading branch information
zwidekalanga committed May 21, 2024
1 parent 107a310 commit 45417b7
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 66 deletions.
3 changes: 3 additions & 0 deletions src/components/learner-credit-management/BudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const BudgetCard = ({ original }) => {
id,
isAssignable,
isRetired,
retiredAt,
name,
source,
start,
Expand All @@ -51,6 +52,7 @@ const BudgetCard = ({ original }) => {
enterpriseSlug={enterpriseSlug}
isAssignable={isAssignable}
isRetired={isRetired}
retiredAt={retiredAt}
/>
);
}
Expand Down Expand Up @@ -107,6 +109,7 @@ BudgetCard.propTypes = {
}),
isAssignable: PropTypes.bool,
isRetired: PropTypes.bool,
retiredAt: PropTypes.string,
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
status: PropTypes.string,
Expand Down
150 changes: 88 additions & 62 deletions src/components/learner-credit-management/SubBudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const BaseSubBudgetCard = ({
isLoading,
isAssignable,
isRetired,
retiredAt,
}) => {
const { isFetching: isFetchingBudgets } = useEnterpriseBudgets({
enablePortalLearnerCreditManagementScreen,
Expand All @@ -79,6 +80,7 @@ const BaseSubBudgetCard = ({
startDateStr: start,
endDateStr: end,
isBudgetRetired: isRetired,
retiredDateStr: retiredAt,
});
const formattedDate = budgetLabel?.date ? intl.formatDate(
dayjs(budgetLabel?.date).toDate(),
Expand All @@ -89,18 +91,36 @@ const BaseSubBudgetCard = ({
},
) : undefined;

const hasBudgetAggregatesSection = () => {
const { status } = budgetLabel;

return (
status === BUDGET_STATUSES.active
|| status === BUDGET_STATUSES.expiring
|| status === BUDGET_STATUSES.retired
);
};

const renderActions = (budgetId) => (
<Button
data-testid="view-budget"
as={Link}
to={`/${enterpriseSlug}/admin/${ROUTE_NAMES.learnerCredit}/${budgetId}`}
variant={[BUDGET_STATUSES.expired, BUDGET_STATUSES.retired].includes(budgetLabel.status) ? 'outline-primary' : 'primary'}
>
<FormattedMessage
id="lcm.budgets.budget.card.view.budget"
defaultMessage="View budget"
description="Button text to view a budget"
/>
{(isRetired) ? (
<FormattedMessage
id="lcm.budgets.budget.card.view.budget.history"
defaultMessage="View budget history"
description="Button text to view budget history"
/>
) : (
<FormattedMessage
id="lcm.budgets.budget.card.view.budget"
defaultMessage="View budget"
description="Button text to view a budget"
/>
)}
</Button>
);

Expand All @@ -116,77 +136,83 @@ const BaseSubBudgetCard = ({
</Stack>
);

const showActions = budgetLabel.status !== BUDGET_STATUSES.scheduled;

return (
<Card.Header
title={<BackgroundFetchingWrapper>{budgetType}</BackgroundFetchingWrapper>}
subtitle={<BackgroundFetchingWrapper>{subtitle}</BackgroundFetchingWrapper>}
actions={
budgetLabel.status !== BUDGET_STATUSES.scheduled
? renderActions(budgetId)
: undefined
}
className={classNames('align-items-center', {
'mb-4.5': budgetLabel.status !== BUDGET_STATUSES.active && budgetLabel.status !== BUDGET_STATUSES.expiring,
})}
actions={showActions ? renderActions(budgetId) : undefined}
className={classNames('align-items-center', { 'mb-4.5': !hasBudgetAggregatesSection })}
/>
);
};

const renderCardSection = () => (
<Card.Section
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">
const renderCardSection = () => {
if (!hasBudgetAggregatesSection) {
return null;
}

return (
<Card.Section
title={!isRetired && (
<h4>
<FormattedMessage
id="lcm.budgets.budget.card.available"
defaultMessage="Available"
description="Label for the available balance on the budget card"
id="lcm.budgets.budget.card.balance"
defaultMessage="Balance"
description="Header for the balance section of 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">
</h4>
)}
muted
>
<Col className="d-flex justify-content-start w-md-75">
{!isRetired && (
<>
<Col xs="6" md="auto" className="mb-3 mb-md-0 ml-n4.5">
<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">
<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={classNames('mb-3 mb-md-0', { 'ml-n4.5': isRetired })}>
<div className={classNames('font-weight-bold', { h4: isRetired, small: !isRetired })}>
<FormattedMessage
id="lcm.budgets.budget.card.assigned"
defaultMessage="Assigned"
description="Label for the assigned balance on the budget card"
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(pending)}
<span className={classNames({ small: !isRetired })}>
{isFetchingBudgets ? <Skeleton /> : formatPrice(spent)}
</span>
</Col>
)}
<Col xs="6" md="auto" className="mb-3 mb-md-0">
<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>
</Col>
</Col>
</Card.Section>
);
</Card.Section>
);
};

return (
<Card
Expand All @@ -196,8 +222,7 @@ const BaseSubBudgetCard = ({
<Card.Body>
<Stack gap={4.5}>
{renderCardHeader(displayName || 'Overview', id)}
{(budgetLabel.status === BUDGET_STATUSES.active || budgetLabel.status === BUDGET_STATUSES.expiring)
&& renderCardSection()}
{renderCardSection()}
</Stack>
</Card.Body>
</Card>
Expand All @@ -219,6 +244,7 @@ BaseSubBudgetCard.propTypes = {
displayName: PropTypes.string,
isAssignable: PropTypes.bool,
isRetired: PropTypes.bool,
retiredAt: PropTypes.string,
};

BaseSubBudgetCard.defaultProps = {
Expand Down
14 changes: 10 additions & 4 deletions src/components/learner-credit-management/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const getBudgetStatus = ({
endDateStr,
isBudgetRetired,
currentDate = new Date(),
retiredDateStr = null,
}) => {
const startDate = new Date(startDateStr);
const endDate = new Date(endDateStr);
Expand All @@ -171,10 +172,9 @@ export const getBudgetStatus = ({
if (isBudgetRetired) {
return {
status: BUDGET_STATUSES.retired,
badgeVariant: 'info',
// no term or date for retired budgets
term: null,
date: null,
badgeVariant: 'light',
term: 'Retired',
date: retiredDateStr,
};
}

Expand Down Expand Up @@ -532,6 +532,12 @@ export const getTranslatedBudgetTerm = (intl, term) => {
defaultMessage: 'Expired',
description: 'Term for when a budget has expired',
});
case 'Retired':
return intl.formatMessage({
id: 'lcm.budgets.budget.card.term.retired',
defaultMessage: 'Retired',
description: 'Term for when a budget has retired',
});
default:
return '';
}
Expand Down
61 changes: 61 additions & 0 deletions src/components/learner-credit-management/tests/BudgetCard.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dayjs from 'dayjs';
import {
screen,
render,
within,
} from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

Expand Down Expand Up @@ -547,4 +548,64 @@ describe('<BudgetCard />', () => {
expect(screen.getByText('Spent')).toBeInTheDocument();
expect(screen.getByText(formatPrice(mockBudgetAggregates.spent))).toBeInTheDocument();
});

it('displays correctly for a retired Policy (enterprise-access) (%s)', () => {
const mockBudgetAggregates = {
total: 5000,
spent: 200,
pending: undefined,
available: 4800,
};

// Mock budget data
const mockBudget = {
id: mockBudgetUuid,
name: mockBudgetDisplayName,
start: '2022-01-01',
end: '3023-01-01',
source: BUDGET_TYPES.policy,
aggregates: {
available: mockBudgetAggregates.available,
pending: mockBudgetAggregates.pending,
spent: mockBudgetAggregates.spent,
},
isAssignable: false,
enterpriseSlug,
enterpriseUUID,
isRetired: true,
retiredAt: '2022-01-01',
};

useSubsidySummaryAnalyticsApi.mockReturnValue({
isLoading: false,
subsidySummary: undefined,
});

render(<BudgetCardWrapper original={mockBudget} />);

// Assertions for budget card display
expect(screen.getByText(mockBudgetDisplayName)).toBeInTheDocument();
expect(screen.queryByText('Executive Education')).not.toBeInTheDocument();

const formattedString = `Retired ${dayjs(mockBudget.retiredAt).format('MMMM D, YYYY')}`;
const elementsWithTestId = screen.getAllByTestId('budget-date');
const firstElementWithTestId = elementsWithTestId[0];
expect(firstElementWithTestId).toHaveTextContent(formattedString);

// Verify 'View budget' CTA
const viewBudgetCTA = screen.getByText('View budget history', { selector: 'a' });
expect(viewBudgetCTA).toBeInTheDocument(); // Ensure 'View budget' CTA is present
expect(viewBudgetCTA).toHaveAttribute(
'href',
`/${enterpriseSlug}/admin/learner-credit/${mockBudgetUuid}`,
);

const balanceDetailSection = screen.getByTestId('balance-detail-section');

// Assertions for aggregates display
expect(within(balanceDetailSection).queryByText('Balance')).not.toBeInTheDocument();
expect(within(balanceDetailSection).queryByText('Available')).not.toBeInTheDocument();
expect(within(balanceDetailSection).getByText('Spent')).toBeInTheDocument();
expect(within(balanceDetailSection).getByText(formatPrice(mockBudgetAggregates.spent))).toBeInTheDocument();
});
});

0 comments on commit 45417b7

Please sign in to comment.