Skip to content

Commit

Permalink
feat: add preselected filters (#1388)
Browse files Browse the repository at this point in the history
  • Loading branch information
katrinan029 authored Jan 28, 2025
1 parent b024ad8 commit 8b494f6
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
77 changes: 77 additions & 0 deletions src/components/learner-credit-management/BudgetCheckboxFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useMemo, useRef } from 'react';
import {
Form,
Stack,
Badge,
FormLabel,
} from '@openedx/paragon';
import PropTypes from 'prop-types';

let lastId = 0;

export const newId = (prefix = 'id') => {
lastId += 1;
return `${prefix}${lastId}`;
};

// BudgetCheckboxFilter builds on top of Paragon's CheckboxFilter component to support preselected filtering
// where the checkboxes are checked based on the filterValue.
// https://github.com/openedx/paragon/blob/release-23.x/src/DataTable/filters/CheckboxFilter.jsx

const BudgetCheckboxFilter = ({
column: {
filterValue, setFilter, Header, filterChoices, getHeaderProps,
},
}) => {
const ariaLabel = useRef(newId(`checkbox-filter-label-${getHeaderProps().key}-`));

const checkedBoxes = filterValue || [];

const changeCheckbox = (value) => {
if (checkedBoxes.includes(value)) {
const newCheckedBoxes = checkedBoxes.filter((val) => val !== value);
return setFilter(newCheckedBoxes);
}
checkedBoxes.push(value);
return setFilter(checkedBoxes);
};
const headerBasedId = useMemo(() => `checkbox-filter-check-${getHeaderProps().key}-`, [getHeaderProps]);

return (
<Form.Group role="group" aria-labelledby={ariaLabel.current}>
<FormLabel id={ariaLabel.current} className="pgn__checkbox-filter-label">{Header}</FormLabel>
{/* To add support to preselected filters in the paragon CheckboxFilter, include the value prop */}
<Form.CheckboxSet name={Header} value={checkedBoxes}>
{filterChoices.map(({ name, number, value }) => (
<Form.Checkbox
key={`${headerBasedId}${name}`}
value={name}
checked={checkedBoxes.includes(value)}
onChange={() => changeCheckbox(value)}
aria-label={name}
>
<Stack direction="horizontal" gap={2}>
{name} {number !== undefined && <Badge variant="light">{number}</Badge>}
</Stack>
</Form.Checkbox>
))}
</Form.CheckboxSet>
</Form.Group>
);
};

BudgetCheckboxFilter.propTypes = {
column: PropTypes.shape({
setFilter: PropTypes.func.isRequired,
Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
filterChoices: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
number: PropTypes.number,
})).isRequired,
getHeaderProps: PropTypes.func.isRequired,
filterValue: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
};

export default BudgetCheckboxFilter;
20 changes: 18 additions & 2 deletions src/components/learner-credit-management/MultipleBudgetsPicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
DataTable,
CardView,
TextFilter,
CheckboxFilter,
Row,
Col,
} from '@openedx/paragon';
Expand All @@ -13,6 +12,7 @@ import groupBy from 'lodash/groupBy';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import BudgetCard from './BudgetCard';
import { getBudgetStatus, getTranslatedBudgetStatus, orderBudgets } from './data/utils';
import BudgetCheckboxFilter from './BudgetCheckboxFilter';

const MultipleBudgetsPicker = ({
budgets,
Expand Down Expand Up @@ -51,6 +51,16 @@ const MultipleBudgetsPicker = ({
})
));
const budgetLabelsByStatus = groupBy(budgetLabels, 'status');

const preSelectedBudgetFilters = [];
if (budgetLabelsByStatus.Active) {
preSelectedBudgetFilters.push('Active');
}

if (budgetLabelsByStatus.Scheduled) {
preSelectedBudgetFilters.push('Scheduled');
}

const reducedChoices = Object.keys(budgetLabelsByStatus).map(budgetLabel => ({
name: getTranslatedBudgetStatus(intl, budgetLabel),
number: budgetLabelsByStatus[budgetLabel].length,
Expand All @@ -75,6 +85,12 @@ const MultipleBudgetsPicker = ({
isFilterable
itemCount={orderedBudgets.length || 0}
data={rows}
initialState={{
filters: [{
id: 'status',
value: preSelectedBudgetFilters,
}],
}}
columns={[
{
Header: 'budget name',
Expand All @@ -88,7 +104,7 @@ const MultipleBudgetsPicker = ({
}),
accessor: 'status',
filter: 'includesValue',
Filter: CheckboxFilter,
Filter: BudgetCheckboxFilter,
filterChoices: reducedChoices,
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ jest.mock('../../EnterpriseSubsidiesContext/data/hooks', () => ({
isCurrent: true,
isRetired: false,
},
{
source: 'subsidy',
id: '392f1fe1-ee91-4f44-b174-13ecf59866ef',
name: 'Subsidy 5 scheduled',
start: '2099-06-07T15:38:29Z',
end: '3099-06-07T15:38:30Z',
isCurrent: false,
isRetired: false,
},
{
source: 'subsidy',
id: '392f1fe1-ee91-4f44-b174-13ecf59866ef',
name: 'Subsidy 6 active',
start: '2020-06-07T15:38:29Z',
end: '3099-06-07T15:38:30Z',
isCurrent: true,
isRetired: false,
},
],
},
}),
Expand Down Expand Up @@ -118,6 +136,17 @@ describe('<MultipleBudgetsPage />', () => {
});
waitFor(() => expect(screen.getByText('Showing 3 of 3.')).toBeInTheDocument());
});
it('shows only active and scheduled budgets on initial render', () => {
render(<MultipleBudgetsPageWrapper enterpriseUUID={enterpriseUUID} enterpriseSlug={enterpriseId} />);
expect(screen.getByText('Budgets'));
const clearFilterButton = screen.getByText('Clear filters');
// only scheduled and active budgets are rendered first
expect(screen.getByText('Showing 1 - 2 of 5.')).toBeInTheDocument();

userEvent.click(clearFilterButton);
// budget page renders all 5 budgets once user clears filter
waitFor(() => expect(screen.getByText('Showing 5 of 5.')).toBeInTheDocument());
});
it('Shows loading spinner', () => {
const enterpriseSubsidiesContextValue = {
...defaultEnterpriseSubsidiesContextValue,
Expand Down

0 comments on commit 8b494f6

Please sign in to comment.