Skip to content

Commit

Permalink
feat: add filtering and search to budget landing page (#1180)
Browse files Browse the repository at this point in the history
* feat: add filtering and search to budget landing page
  • Loading branch information
katrinan029 authored Mar 8, 2024
1 parent 7c27163 commit 135cb6e
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 110 deletions.
60 changes: 35 additions & 25 deletions src/components/learner-credit-management/BudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<SubBudgetCard
id={budget.id}
start={budget.start}
end={budget.end}
available={budget.aggregates.available}
spent={budget.aggregates.spent}
pending={budget.aggregates.pending}
displayName={budget.name}
id={id}
start={start}
end={end}
available={aggregates.available}
spent={aggregates.spent}
pending={aggregates.pending}
displayName={name}
enterpriseSlug={enterpriseSlug}
isAssignable={budget.isAssignable}
isRetired={budget.isRetired}
isAssignable={isAssignable}
isRetired={isRetired}
/>
);
}

// 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 (
<SubBudgetCard
isLoading={isLoadingSubsidySummaryAnalyticsApi}
id={subsidySummaryAnalyticsApi?.offerId}
start={budget.start}
end={budget.end}
start={start}
end={end}
available={subsidySummaryAnalyticsApi?.remainingFunds}
spent={subsidySummaryAnalyticsApi?.redeemedFunds}
displayName={budget.name}
displayName={name}
enterpriseSlug={enterpriseSlug}
/>
);
Expand All @@ -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}
Expand All @@ -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,
Expand All @@ -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;
90 changes: 69 additions & 21 deletions src/components/learner-credit-management/MultipleBudgetsPicker.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 (
<Stack gap={4}>
<Row>
<>
<Row className="mb-4">
<Col lg="12"><h2>Budgets</h2></Col>
</Row>
<Row>
<Col lg="12">
<Stack gap={4}>
{orderedBudgets.map(budget => (
<BudgetCard
key={budget.id}
budget={budget}
enterpriseUUID={enterpriseUUID}
enterpriseSlug={enterpriseSlug}
enableLearnerPortal={enableLearnerPortal}
/>
))}
</Stack>
</Col>
</Row>
</Stack>
<DataTable
defaultColumnValues={{ Filter: TextFilter }}
isFilterable
itemCount={orderedBudgets.length || 0}
data={rows}
columns={[
{
Header: 'budget name',
accessor: 'name',
},
{
Header: 'Status',
accessor: 'status',
Filter: CheckboxFilter,
filterChoices: reducedChoices,
},
]}
>
<DataTable.TableControlBar />
<CardView
CardComponent={BudgetCard}
columnSizes={{ xs: 12 }}
/>
<DataTable.TableFooter />
</DataTable>
</>
);
};

Expand Down
7 changes: 3 additions & 4 deletions src/components/learner-credit-management/SubBudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import classNames from 'classnames';
import {
Card,
Button,
Row,
Col,
Badge,
Stack,
Expand Down Expand Up @@ -123,8 +122,8 @@ const BaseSubBudgetCard = ({
title={<h4>Balance</h4>}
muted
>
<Row className="d-flex flex-row justify-content-start w-md-75">
<Col xs="6" md="auto" className="mb-3 mb-md-0">
<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>
<span className="small">
{isFetchingBudgets ? <Skeleton /> : formatPrice(available)}
Expand All @@ -144,7 +143,7 @@ const BaseSubBudgetCard = ({
{isFetchingBudgets ? <Skeleton /> : formatPrice(spent)}
</span>
</Col>
</Row>
</Col>
</Card.Section>
);

Expand Down
Loading

0 comments on commit 135cb6e

Please sign in to comment.