From fd7a4773efb34565ae193d068f231061dc9298f6 Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Wed, 9 Aug 2023 21:15:29 +0000 Subject: [PATCH 1/8] chore: refactor LCM data table --- .../DescriptionCell.jsx | 25 +++++++ .../LearnerCreditAllocationTable.jsx | 69 ++++++++++--------- 2 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 src/components/learner-credit-management/DescriptionCell.jsx diff --git a/src/components/learner-credit-management/DescriptionCell.jsx b/src/components/learner-credit-management/DescriptionCell.jsx new file mode 100644 index 0000000000..5753c69ecf --- /dev/null +++ b/src/components/learner-credit-management/DescriptionCell.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import EmailAddressTableCell from './EmailAddressTableCell'; + +const DescriptionCell = ({ value, row, enterpriseUUID }) => ( + <> +
{value.courseTitle}
+ + +); + +DescriptionCell.propTypes = { + value: PropTypes.shape({ + courseTitle: PropTypes.string.isRequired, + userEmail: PropTypes.string.isRequired, + }).isRequired, + row: PropTypes.shape({ + original: PropTypes.shape({ + userEmail: PropTypes.string, + enterpriseEnrollmentId: PropTypes.number, + }), + }).isRequired, + enterpriseUUID: PropTypes.string.isRequired, +}; +export default DescriptionCell; diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index e463487937..5931326589 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -6,12 +6,24 @@ import { } from '@edx/paragon'; import TableTextFilter from './TableTextFilter'; -import EmailAddressTableCell from './EmailAddressTableCell'; -import { getCourseProductLineText } from '../../utils'; +import DescriptionCell from './DescriptionCell'; export const PAGE_SIZE = 20; export const DEFAULT_PAGE = 0; // `DataTable` uses zero-index array +const getDescriptionAccessor = row => ({ + courseTitle: row.courseTitle, + userEmail: row.userEmail, +}); + +function renderDescriptionCell(enterpriseUUID) { + return function DescriptionCellRenderer(props) { + return ; + }; +} + +const FilterStatus = (rest) => ; + const LearnerCreditAllocationTable = ({ isLoading, tableData, @@ -21,6 +33,27 @@ const LearnerCreditAllocationTable = ({ }) => { const isDesktopTable = useMediaQuery({ minWidth: breakpoints.extraLarge.minWidth }); const defaultFilter = budgetType ? [{ id: 'courseProductLine', value: budgetType }] : []; + const columns = [ + { + Header: 'Date', + accessor: 'enrollmentDate', + // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components + Cell: ({ row }) => dayjs(row.values.enrollmentDate).format('MMMM DD, YYYY'), + disableFilters: true, + }, + { + Header: 'Description', + accessor: getDescriptionAccessor, + Cell: renderDescriptionCell(enterpriseUUID), + disableFilters: true, + }, + { + Header: 'Amount', + accessor: 'courseListPrice', + Cell: ({ row }) => `$${row.values.courseListPrice}`, + disableFilters: true, + }, + ]; return ( , - }, - { - Header: 'Course Name', - accessor: 'courseTitle', - }, - { - Header: 'Course Price', - accessor: 'courseListPrice', - Cell: ({ row }) => `$${row.values.courseListPrice}`, - disableFilters: true, - }, - { - Header: 'Date Spent', - accessor: 'enrollmentDate', - Cell: ({ row }) => dayjs(row.values.enrollmentDate).format('MMMM DD, YYYY'), - disableFilters: true, - }, - { - Header: 'Product', - accessor: 'courseProductLine', - Cell: ({ row }) => getCourseProductLineText(row.values.courseProductLine), - disableFilters: true, - }, - ]} + FilterStatusComponent={FilterStatus} + columns={columns} initialTableOptions={{ getRowId: row => row?.uuid?.toString(), }} From 52ebf666f4324a836ccf58650c3e758aea1cea4b Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Thu, 10 Aug 2023 22:10:45 +0000 Subject: [PATCH 2/8] chore: without DescriptionCell component --- .../DescriptionCell.jsx | 2 +- .../LearnerCreditAllocationTable.jsx | 50 ++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/components/learner-credit-management/DescriptionCell.jsx b/src/components/learner-credit-management/DescriptionCell.jsx index 5753c69ecf..e4d698690b 100644 --- a/src/components/learner-credit-management/DescriptionCell.jsx +++ b/src/components/learner-credit-management/DescriptionCell.jsx @@ -5,7 +5,7 @@ import EmailAddressTableCell from './EmailAddressTableCell'; const DescriptionCell = ({ value, row, enterpriseUUID }) => ( <>
{value.courseTitle}
- + ); diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index 5931326589..f141ada6d9 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -6,7 +6,7 @@ import { } from '@edx/paragon'; import TableTextFilter from './TableTextFilter'; -import DescriptionCell from './DescriptionCell'; +import EmailAddressTableCell from './EmailAddressTableCell'; export const PAGE_SIZE = 20; export const DEFAULT_PAGE = 0; // `DataTable` uses zero-index array @@ -33,27 +33,6 @@ const LearnerCreditAllocationTable = ({ }) => { const isDesktopTable = useMediaQuery({ minWidth: breakpoints.extraLarge.minWidth }); const defaultFilter = budgetType ? [{ id: 'courseProductLine', value: budgetType }] : []; - const columns = [ - { - Header: 'Date', - accessor: 'enrollmentDate', - // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components - Cell: ({ row }) => dayjs(row.values.enrollmentDate).format('MMMM DD, YYYY'), - disableFilters: true, - }, - { - Header: 'Description', - accessor: getDescriptionAccessor, - Cell: renderDescriptionCell(enterpriseUUID), - disableFilters: true, - }, - { - Header: 'Amount', - accessor: 'courseListPrice', - Cell: ({ row }) => `$${row.values.courseListPrice}`, - disableFilters: true, - }, - ]; return ( dayjs(row.values.enrollmentDate).format('MMMM DD, YYYY'), + disableFilters: true, + }, + { + Header: 'Description', + accessor: getDescriptionAccessor, + // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components + Cell: ({ row }) => ( + <> +
{row.original.courseTitle}
+ + ), + disableFilters: true, + }, + { + Header: 'Amount', + accessor: 'courseListPrice', + Cell: ({ row }) => `$${row.values.courseListPrice}`, + disableFilters: true, + }, + ]} initialTableOptions={{ getRowId: row => row?.uuid?.toString(), }} From c23677da9df1a9faec113be18943f65a20ddd5f6 Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Fri, 18 Aug 2023 15:24:22 +0000 Subject: [PATCH 3/8] chore: fix tests --- .../DescriptionCell.jsx | 25 ------------------- .../LearnerCreditAllocationTable.jsx | 18 ++++++------- .../learner-credit-management/data/hooks.js | 13 ++++------ .../data/tests/hooks.test.js | 6 ++--- .../tests/BudgetCard.test.jsx | 1 - .../LearnerCreditAllocationTable.test.jsx | 1 - 6 files changed, 14 insertions(+), 50 deletions(-) delete mode 100644 src/components/learner-credit-management/DescriptionCell.jsx diff --git a/src/components/learner-credit-management/DescriptionCell.jsx b/src/components/learner-credit-management/DescriptionCell.jsx deleted file mode 100644 index e4d698690b..0000000000 --- a/src/components/learner-credit-management/DescriptionCell.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import EmailAddressTableCell from './EmailAddressTableCell'; - -const DescriptionCell = ({ value, row, enterpriseUUID }) => ( - <> -
{value.courseTitle}
- - -); - -DescriptionCell.propTypes = { - value: PropTypes.shape({ - courseTitle: PropTypes.string.isRequired, - userEmail: PropTypes.string.isRequired, - }).isRequired, - row: PropTypes.shape({ - original: PropTypes.shape({ - userEmail: PropTypes.string, - enterpriseEnrollmentId: PropTypes.number, - }), - }).isRequired, - enterpriseUUID: PropTypes.string.isRequired, -}; -export default DescriptionCell; diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index f141ada6d9..744d866a70 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -16,12 +16,6 @@ const getDescriptionAccessor = row => ({ userEmail: row.userEmail, }); -function renderDescriptionCell(enterpriseUUID) { - return function DescriptionCellRenderer(props) { - return ; - }; -} - const FilterStatus = (rest) => ; const LearnerCreditAllocationTable = ({ @@ -59,11 +53,13 @@ const LearnerCreditAllocationTable = ({ accessor: getDescriptionAccessor, // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components Cell: ({ row }) => ( - <> -
{row.original.courseTitle}
- - ), - disableFilters: true, + <> +
{row.original.courseTitle}
+ + + ), + disableFilters: false, + disableSortBy: true, }, { Header: 'Amount', diff --git a/src/components/learner-credit-management/data/hooks.js b/src/components/learner-credit-management/data/hooks.js index 7ff74aaa69..81bac6c539 100644 --- a/src/components/learner-credit-management/data/hooks.js +++ b/src/components/learner-credit-management/data/hooks.js @@ -63,18 +63,15 @@ const applySortByToOptions = (sortBy, options) => { }; const applyFiltersToOptions = (filters, options) => { - const userSearchQuery = filters?.find(filter => filter.id === 'userEmail')?.value; - const courseTitleSearchQuery = filters?.find(filter => filter.id === 'courseTitle')?.value; const courseProductLineSearchQuery = filters?.find(filter => filter.id === 'courseProductLine')?.value; - if (userSearchQuery) { - Object.assign(options, { search: userSearchQuery }); - } - if (courseTitleSearchQuery) { - Object.assign(options, { searchCourse: courseTitleSearchQuery }); - } + const searchQuery = filters?.find(filter => filter.id.toLowerCase() === 'description')?.value; + if (courseProductLineSearchQuery) { Object.assign(options, { courseProductLine: courseProductLineSearchQuery }); } + if (searchQuery) { + Object.assign(options, { searchAll: searchQuery }); + } }; export const useOfferRedemptions = (enterpriseUUID, offerId) => { diff --git a/src/components/learner-credit-management/data/tests/hooks.test.js b/src/components/learner-credit-management/data/tests/hooks.test.js index fa03c60f9e..e55acbacec 100644 --- a/src/components/learner-credit-management/data/tests/hooks.test.js +++ b/src/components/learner-credit-management/data/tests/hooks.test.js @@ -105,8 +105,7 @@ describe('useOfferRedemptions', () => { { id: 'enrollmentDate', desc: true }, ], filters: [ - { id: 'userEmail', value: mockOfferEnrollments[0].user_email }, - { id: 'courseTitle', value: mockOfferEnrollments[0].course_title }, + { id: 'Description', value: mockOfferEnrollments[0].user_email }, ], }); }); @@ -118,8 +117,7 @@ describe('useOfferRedemptions', () => { pageSize: 20, offerId: mockEnterpriseOffer.id, ordering: '-enrollment_date', // default sort order - search: mockOfferEnrollments[0].user_email, - searchCourse: mockOfferEnrollments[0].course_title, + searchAll: mockOfferEnrollments[0].user_email, ignoreNullCourseListPrice: true, }; expect(EnterpriseDataApiService.fetchCourseEnrollments).toHaveBeenCalledWith( diff --git a/src/components/learner-credit-management/tests/BudgetCard.test.jsx b/src/components/learner-credit-management/tests/BudgetCard.test.jsx index afa6c60620..ccd5ae051c 100644 --- a/src/components/learner-credit-management/tests/BudgetCard.test.jsx +++ b/src/components/learner-credit-management/tests/BudgetCard.test.jsx @@ -133,7 +133,6 @@ describe('', () => { const elementsWithTestId = screen.getAllByTestId('view-budget'); const firstElementWithTestId = elementsWithTestId[0]; await waitFor(() => userEvent.click(firstElementWithTestId)); - expect(screen.getByText('Filters')); expect(screen.getByText('No results found')); }); }); diff --git a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx index fb79748c70..21951a1f54 100644 --- a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx +++ b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx @@ -36,7 +36,6 @@ describe('', () => { render(); - expect(screen.getByText('Open', { exact: false })); expect(screen.getByText(props.tableData.results[0].userEmail.toString(), { exact: false, })); From 062c513ca3a3c11dd600370b12dd4edace7831e4 Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Mon, 28 Aug 2023 16:28:42 +0000 Subject: [PATCH 4/8] chore: add ESLint rules --- .../learner-credit-management/LearnerCreditAllocationTable.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index 744d866a70..bc837c95af 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -54,6 +54,7 @@ const LearnerCreditAllocationTable = ({ // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components Cell: ({ row }) => ( <> + {/* eslint-disable-next-line react/prop-types */}
{row.original.courseTitle}
@@ -64,11 +65,13 @@ const LearnerCreditAllocationTable = ({ { Header: 'Amount', accessor: 'courseListPrice', + // eslint-disable-next-line react/prop-types Cell: ({ row }) => `$${row.values.courseListPrice}`, disableFilters: true, }, ]} initialTableOptions={{ + // eslint-disable-next-line react/prop-types getRowId: row => row?.uuid?.toString(), }} initialState={{ From 2920f849854927c5e117a84172389f67ffc6acab Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Tue, 29 Aug 2023 18:25:30 +0000 Subject: [PATCH 5/8] chore: courseTitle links to course --- .../LearnerCreditAllocationTable.jsx | 29 ++++++++--- .../learner-credit-management/data/utils.js | 1 + .../LearnerCreditAllocationTable.test.jsx | 50 +++++++++++++++++-- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index bc837c95af..1c77ab870e 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -2,9 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import dayjs from 'dayjs'; import { - DataTable, useMediaQuery, breakpoints, + Avatar, DataTable, Hyperlink, useMediaQuery, breakpoints, } from '@edx/paragon'; - +import { connect } from 'react-redux'; +import { getConfig } from '@edx/frontend-platform/config'; import TableTextFilter from './TableTextFilter'; import EmailAddressTableCell from './EmailAddressTableCell'; @@ -14,6 +15,7 @@ export const DEFAULT_PAGE = 0; // `DataTable` uses zero-index array const getDescriptionAccessor = row => ({ courseTitle: row.courseTitle, userEmail: row.userEmail, + courseKey: row.courseKey, }); const FilterStatus = (rest) => ; @@ -23,6 +25,7 @@ const LearnerCreditAllocationTable = ({ tableData, fetchTableData, enterpriseUUID, + enterpriseSlug, budgetType, }) => { const isDesktopTable = useMediaQuery({ minWidth: breakpoints.extraLarge.minWidth }); @@ -45,7 +48,7 @@ const LearnerCreditAllocationTable = ({ Header: 'Date', accessor: 'enrollmentDate', // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components - Cell: ({ row }) => dayjs(row.values.enrollmentDate).format('MMMM DD, YYYY'), + Cell: ({ row }) => dayjs(row.values.enrollmentDate).format('MMM D, YYYY'), disableFilters: true, }, { @@ -54,9 +57,18 @@ const LearnerCreditAllocationTable = ({ // eslint-disable-next-line react/prop-types, react/no-unstable-nested-components Cell: ({ row }) => ( <> - {/* eslint-disable-next-line react/prop-types */} -
{row.original.courseTitle}
+ {' '} +
+ + {/* eslint-disable-next-line react/prop-types */} + {row.original.courseTitle} + +
), disableFilters: false, @@ -104,6 +116,7 @@ LearnerCreditAllocationTable.defaultProps = { LearnerCreditAllocationTable.propTypes = { enterpriseUUID: PropTypes.string.isRequired, + enterpriseSlug: PropTypes.string.isRequired, isLoading: PropTypes.bool.isRequired, tableData: PropTypes.shape({ results: PropTypes.arrayOf(PropTypes.shape({ @@ -120,4 +133,8 @@ LearnerCreditAllocationTable.propTypes = { budgetType: PropTypes.string, }; -export default LearnerCreditAllocationTable; +const mapStateToProps = state => ({ + enterpriseSlug: state.portalConfiguration.enterpriseSlug, +}); + +export default connect(mapStateToProps)(LearnerCreditAllocationTable); diff --git a/src/components/learner-credit-management/data/utils.js b/src/components/learner-credit-management/data/utils.js index 22cff6cd3a..3307e0ce47 100644 --- a/src/components/learner-credit-management/data/utils.js +++ b/src/components/learner-credit-management/data/utils.js @@ -66,6 +66,7 @@ export const transformUtilizationTableResults = results => results.map(result => enrollmentDate: result.enrollmentDate, courseProductLine: result.courseProductLine, uuid: uuidv4(), + courseKey: result.courseKey, })); /** diff --git a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx index 21951a1f54..6ae6c00bae 100644 --- a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx +++ b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx @@ -4,13 +4,28 @@ import { render, } from '@testing-library/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; import LearnerCreditAllocationTable from '../LearnerCreditAllocationTable'; +const mockStore = configureMockStore(); +const store = mockStore({ + portalConfiguration: { + enterpriseSlug: 'test-enterprise-slug', + }, +}); + +jest.mock('@edx/frontend-platform/config', () => ({ + getConfig: () => ({ ENTERPRISE_LEARNER_PORTAL_URL: 'https://enterprise.edx.org' }), +})); + const LearnerCreditAllocationTableWrapper = (props) => ( - - - + + + + + ); describe('', () => { @@ -65,4 +80,33 @@ describe('', () => { expect(screen.getByText('No results found', { exact: false })); }); + + it('constructs the correct URL for the course', () => { + const props = { + enterpriseUUID: 'test-enterprise-id', + isLoading: false, + budgetType: 'OCM', + tableData: { + results: [{ + userEmail: 'test@example.com', + courseTitle: 'course-title', + courseKey: 'course-v1:edX=CTL.SC101x.3T2019', + courseListPrice: 100, + enrollmentDate: '2-2-23', + courseProductLine: 'OCM', + }], + itemCount: 1, + pageCount: 1, + }, + fetchTableData: jest.fn(), + }; + props.fetchTableData.mockReturnValue(props.tableData); + + render(); + + const expectedLink = 'https://enterprise.edx.org/test-enterprise-slug/course/course-v1:edX=CTL.SC101x.3T2019'; + const courseLinkElement = screen.getByText('course-title'); + + expect(courseLinkElement.getAttribute('href')).toBe(expectedLink); + }); }); From f9119886a19dc78c7a9afdb8f019fe8252f2a66d Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Tue, 29 Aug 2023 22:06:17 +0000 Subject: [PATCH 6/8] chore: no budget filtering --- .../BudgetCard-V2.jsx | 164 ++++++++++++++++++ .../LearnerCreditAllocationTable.jsx | 17 +- .../MultipleBudgetsPicker.jsx | 2 +- 3 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 src/components/learner-credit-management/BudgetCard-V2.jsx diff --git a/src/components/learner-credit-management/BudgetCard-V2.jsx b/src/components/learner-credit-management/BudgetCard-V2.jsx new file mode 100644 index 0000000000..d9a1db40c9 --- /dev/null +++ b/src/components/learner-credit-management/BudgetCard-V2.jsx @@ -0,0 +1,164 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import dayjs from 'dayjs'; +import { + Card, + Button, + Stack, + Row, + Col, + Breadcrumb, +} from '@edx/paragon'; + +import { useOfferRedemptions, useOfferSummary } from './data/hooks'; +import LearnerCreditAggregateCards from './LearnerCreditAggregateCards'; +import LearnerCreditAllocationTable from './LearnerCreditAllocationTable'; +import { ROUTE_NAMES } from '../EnterpriseApp/data/constants'; + +const BudgetCard = ({ + offer, + enterpriseUUID, + enterpriseSlug, +}) => { + const { + start, + end, + } = offer; + + const { + isLoading: isLoadingOfferSummary, + offerSummary, + } = useOfferSummary(enterpriseUUID, offer); + + const { + isLoading: isLoadingOfferRedemptions, + offerRedemptions, + fetchOfferRedemptions, + } = useOfferRedemptions(enterpriseUUID, offer?.id); + const [detailPage, setDetailPage] = useState(false); + const [activeLabel, setActiveLabel] = useState(''); + const links = [ + { label: 'Budgets', url: `/${enterpriseSlug}/admin/${ROUTE_NAMES.learnerCredit}` }, + ]; + const formattedStartDate = dayjs(start).format('MMMM D, YYYY'); + const formattedExpirationDate = dayjs(end).format('MMMM D, YYYY'); + const navigateToBudgetRedemptions = (budgetType) => { + setDetailPage(true); + links.push({ label: budgetType, url: `/${enterpriseSlug}/admin/learner-credit` }); + setActiveLabel(budgetType); + }; + + const renderActions = (budgetType) => ( + + ); + + const renderCardHeader = (budgetType) => { + const subtitle = ( +
+ + {formattedStartDate} - {formattedExpirationDate} + +
+ ); + + return ( + + {renderActions(budgetType)} + + )} + /> + ); + }; + + const renderCardSection = (available, spent) => ( + + + + Available + {available} + + + Spent + {spent} + + + + ); + + const renderCardAggregate = () => ( +
+ +
+ ); + + return ( + + + + + + + {!detailPage + ? ( + <> + {renderCardAggregate()} +

Budgets

+ + + + {renderCardHeader('Overview')} + {renderCardSection(offerSummary?.remainingFunds, offerSummary?.redeemedFunds)} + + + + + ) + : ( + + )} +
+ ); +}; + +BudgetCard.propTypes = { + offer: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + start: PropTypes.string.isRequired, + end: PropTypes.string.isRequired, + }).isRequired, + enterpriseUUID: PropTypes.string.isRequired, + enterpriseSlug: PropTypes.string.isRequired, +}; + +export default BudgetCard; diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index 1c77ab870e..3cb29d0096 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -4,7 +4,6 @@ import dayjs from 'dayjs'; import { Avatar, DataTable, Hyperlink, useMediaQuery, breakpoints, } from '@edx/paragon'; -import { connect } from 'react-redux'; import { getConfig } from '@edx/frontend-platform/config'; import TableTextFilter from './TableTextFilter'; import EmailAddressTableCell from './EmailAddressTableCell'; @@ -18,18 +17,15 @@ const getDescriptionAccessor = row => ({ courseKey: row.courseKey, }); -const FilterStatus = (rest) => ; - const LearnerCreditAllocationTable = ({ isLoading, tableData, fetchTableData, enterpriseUUID, enterpriseSlug, - budgetType, }) => { const isDesktopTable = useMediaQuery({ minWidth: breakpoints.extraLarge.minWidth }); - const defaultFilter = budgetType ? [{ id: 'courseProductLine', value: budgetType }] : []; + const defaultFilter = []; return ( ); }; -LearnerCreditAllocationTable.defaultProps = { - budgetType: null, -}; LearnerCreditAllocationTable.propTypes = { enterpriseUUID: PropTypes.string.isRequired, @@ -130,11 +122,6 @@ LearnerCreditAllocationTable.propTypes = { pageCount: PropTypes.number.isRequired, }).isRequired, fetchTableData: PropTypes.func.isRequired, - budgetType: PropTypes.string, }; -const mapStateToProps = state => ({ - enterpriseSlug: state.portalConfiguration.enterpriseSlug, -}); - -export default connect(mapStateToProps)(LearnerCreditAllocationTable); +export default LearnerCreditAllocationTable; diff --git a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx index a7b4bc814e..8535bd1d21 100644 --- a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx +++ b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx @@ -6,7 +6,7 @@ import { Col, } from '@edx/paragon'; -import BudgetCard from './BudgetCard'; +import BudgetCard from './BudgetCard-V2'; const MultipleBudgetsPicker = ({ offers, From 6c5cd39a0038936ea49e01976f8f7e8241dabca8 Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Wed, 30 Aug 2023 20:52:37 +0000 Subject: [PATCH 7/8] chore: add product column --- .../LearnerCreditAllocationTable.jsx | 22 +++++++++---------- .../learner-credit-management/data/hooks.js | 2 +- .../data/tests/hooks.test.js | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index f36ca586ba..7422b41594 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -1,17 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import dayjs from 'dayjs'; -import { - Avatar, DataTable, Hyperlink, useMediaQuery, breakpoints, -} from '@edx/paragon'; +import { DataTable, Hyperlink } from '@edx/paragon'; import { getConfig } from '@edx/frontend-platform/config'; import TableTextFilter from './TableTextFilter'; import EmailAddressTableCell from './EmailAddressTableCell'; +import { getCourseProductLineText } from '../../utils'; export const PAGE_SIZE = 20; export const DEFAULT_PAGE = 0; // `DataTable` uses zero-index array -const getDescriptionAccessor = row => ({ +const getEnrollmentDetailsAccessor = row => ({ courseTitle: row.courseTitle, userEmail: row.userEmail, courseKey: row.courseKey, @@ -24,7 +23,6 @@ const LearnerCreditAllocationTable = ({ enterpriseUUID, enterpriseSlug, }) => { - const isDesktopTable = useMediaQuery({ minWidth: breakpoints.extraLarge.minWidth }); const defaultFilter = []; return ( @@ -35,7 +33,6 @@ const LearnerCreditAllocationTable = ({ manualPagination isFilterable manualFilters - showFiltersInSidebar={isDesktopTable} isLoading={isLoading} defaultColumnValues={{ Filter: TableTextFilter }} /* eslint-disable */ @@ -47,19 +44,16 @@ const LearnerCreditAllocationTable = ({ disableFilters: true, }, { - Header: 'Description', - accessor: getDescriptionAccessor, + Header: 'Enrollment details', + accessor: getEnrollmentDetailsAccessor, Cell: ({ row }) => ( <> - {' '}
- {/* eslint-disable-next-line react/prop-types */} {row.original.courseTitle}
@@ -74,6 +68,12 @@ const LearnerCreditAllocationTable = ({ Cell: ({ row }) => `$${row.values.courseListPrice}`, disableFilters: true, }, + { + Header: 'Product', + accessor: 'courseProductLine', + Cell: ({ row }) => getCourseProductLineText(row.values.courseProductLine), + disableFilters: true, + }, ]} initialTableOptions={{ getRowId: row => row?.uuid?.toString(), diff --git a/src/components/learner-credit-management/data/hooks.js b/src/components/learner-credit-management/data/hooks.js index 81bac6c539..585970c35e 100644 --- a/src/components/learner-credit-management/data/hooks.js +++ b/src/components/learner-credit-management/data/hooks.js @@ -64,7 +64,7 @@ const applySortByToOptions = (sortBy, options) => { const applyFiltersToOptions = (filters, options) => { const courseProductLineSearchQuery = filters?.find(filter => filter.id === 'courseProductLine')?.value; - const searchQuery = filters?.find(filter => filter.id.toLowerCase() === 'description')?.value; + const searchQuery = filters?.find(filter => filter.id.toLowerCase() === 'enrollment details')?.value; if (courseProductLineSearchQuery) { Object.assign(options, { courseProductLine: courseProductLineSearchQuery }); diff --git a/src/components/learner-credit-management/data/tests/hooks.test.js b/src/components/learner-credit-management/data/tests/hooks.test.js index e55acbacec..8ab61bce2f 100644 --- a/src/components/learner-credit-management/data/tests/hooks.test.js +++ b/src/components/learner-credit-management/data/tests/hooks.test.js @@ -105,7 +105,7 @@ describe('useOfferRedemptions', () => { { id: 'enrollmentDate', desc: true }, ], filters: [ - { id: 'Description', value: mockOfferEnrollments[0].user_email }, + { id: 'Enrollment Details', value: mockOfferEnrollments[0].user_email }, ], }); }); From aac1dfc727bc622a942ef873cabd4cb3148bbe9a Mon Sep 17 00:00:00 2001 From: Emily Aquin Date: Thu, 31 Aug 2023 20:38:08 +0000 Subject: [PATCH 8/8] chore: courseTitle link in LCM only if learner portal enabled --- .../BudgetCard-V2.jsx | 3 ++ .../LearnerCreditAllocationTable.jsx | 9 ++++++ .../MultipleBudgetsPage.jsx | 4 +++ .../MultipleBudgetsPicker.jsx | 3 ++ .../LearnerCreditAllocationTable.test.jsx | 30 +++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/src/components/learner-credit-management/BudgetCard-V2.jsx b/src/components/learner-credit-management/BudgetCard-V2.jsx index d9a1db40c9..b39b9297d9 100644 --- a/src/components/learner-credit-management/BudgetCard-V2.jsx +++ b/src/components/learner-credit-management/BudgetCard-V2.jsx @@ -19,6 +19,7 @@ const BudgetCard = ({ offer, enterpriseUUID, enterpriseSlug, + enableLearnerPortal, }) => { const { start, @@ -144,6 +145,7 @@ const BudgetCard = ({ fetchTableData={fetchOfferRedemptions} enterpriseUUID={enterpriseUUID} enterpriseSlug={enterpriseSlug} + enableLearnerPortal={enableLearnerPortal} /> )} @@ -159,6 +161,7 @@ BudgetCard.propTypes = { }).isRequired, enterpriseUUID: PropTypes.string.isRequired, enterpriseSlug: PropTypes.string.isRequired, + enableLearnerPortal: PropTypes.bool.isRequired, }; export default BudgetCard; diff --git a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx index 7422b41594..555df1ca95 100644 --- a/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx +++ b/src/components/learner-credit-management/LearnerCreditAllocationTable.jsx @@ -16,12 +16,15 @@ const getEnrollmentDetailsAccessor = row => ({ courseKey: row.courseKey, }); +const FilterStatus = (rest) => ; + const LearnerCreditAllocationTable = ({ isLoading, tableData, fetchTableData, enterpriseUUID, enterpriseSlug, + enableLearnerPortal, }) => { const defaultFilter = []; @@ -35,6 +38,7 @@ const LearnerCreditAllocationTable = ({ manualFilters isLoading={isLoading} defaultColumnValues={{ Filter: TableTextFilter }} + FilterStatusComponent={FilterStatus} /* eslint-disable */ columns={[ { @@ -50,12 +54,16 @@ const LearnerCreditAllocationTable = ({ <>
+ {enableLearnerPortal ? ( {row.original.courseTitle} + ) : ( + row.original.courseTitle + )}
), @@ -106,6 +114,7 @@ const LearnerCreditAllocationTable = ({ LearnerCreditAllocationTable.propTypes = { enterpriseUUID: PropTypes.string.isRequired, enterpriseSlug: PropTypes.string.isRequired, + enableLearnerPortal: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired, tableData: PropTypes.shape({ results: PropTypes.arrayOf(PropTypes.shape({ diff --git a/src/components/learner-credit-management/MultipleBudgetsPage.jsx b/src/components/learner-credit-management/MultipleBudgetsPage.jsx index cec507a231..3df18c465a 100644 --- a/src/components/learner-credit-management/MultipleBudgetsPage.jsx +++ b/src/components/learner-credit-management/MultipleBudgetsPage.jsx @@ -22,6 +22,7 @@ const PAGE_TITLE = 'Learner Credit'; const MultipleBudgetsPage = ({ enterpriseUUID, enterpriseSlug, + enableLearnerPortal, }) => { const { offers, isLoading } = useContext(EnterpriseSubsidiesContext); @@ -66,6 +67,7 @@ const MultipleBudgetsPage = ({ offers={offers} enterpriseUUID={enterpriseUUID} enterpriseSlug={enterpriseSlug} + enableLearnerPortal={enableLearnerPortal} /> ); @@ -74,11 +76,13 @@ const MultipleBudgetsPage = ({ const mapStateToProps = state => ({ enterpriseUUID: state.portalConfiguration.enterpriseId, enterpriseSlug: state.portalConfiguration.enterpriseSlug, + enableLearnerPortal: state.portalConfiguration.enableLearnerPortal, }); MultipleBudgetsPage.propTypes = { enterpriseUUID: PropTypes.string.isRequired, enterpriseSlug: PropTypes.string.isRequired, + enableLearnerPortal: PropTypes.bool.isRequired, }; export default connect(mapStateToProps)(MultipleBudgetsPage); diff --git a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx index 8535bd1d21..4c3da2d0ce 100644 --- a/src/components/learner-credit-management/MultipleBudgetsPicker.jsx +++ b/src/components/learner-credit-management/MultipleBudgetsPicker.jsx @@ -12,6 +12,7 @@ const MultipleBudgetsPicker = ({ offers, enterpriseUUID, enterpriseSlug, + enableLearnerPortal, }) => ( @@ -22,6 +23,7 @@ const MultipleBudgetsPicker = ({ offer={offer} enterpriseUUID={enterpriseUUID} enterpriseSlug={enterpriseSlug} + enableLearnerPortal={enableLearnerPortal} /> ))} @@ -33,6 +35,7 @@ MultipleBudgetsPicker.propTypes = { offers: PropTypes.arrayOf(PropTypes.shape()).isRequired, enterpriseUUID: PropTypes.string.isRequired, enterpriseSlug: PropTypes.string.isRequired, + enableLearnerPortal: PropTypes.bool.isRequired, }; export default MultipleBudgetsPicker; diff --git a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx index f67aa0e8bb..9099404e4f 100644 --- a/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx +++ b/src/components/learner-credit-management/tests/LearnerCreditAllocationTable.test.jsx @@ -24,6 +24,7 @@ describe('', () => { isLoading: false, budgetType: 'OCM', enterpriseSlug: 'test-enterprise-slug', + enableLearnerPortal: true, tableData: { results: [{ userEmail: 'test@example.com', @@ -77,6 +78,7 @@ describe('', () => { isLoading: false, budgetType: 'OCM', enterpriseSlug: 'test-enterprise-slug', + enableLearnerPortal: true, tableData: { results: [{ userEmail: 'test@example.com', @@ -100,4 +102,32 @@ describe('', () => { expect(courseLinkElement.getAttribute('href')).toBe(expectedLink); }); + + it('does not render the course link if the learner portal is disabled', () => { + const props = { + enterpriseUUID: 'test-enterprise-id', + isLoading: false, + budgetType: 'OCM', + enterpriseSlug: 'test-enterprise-slug', + enableLearnerPortal: false, + tableData: { + results: [{ + userEmail: 'test@example.com', + courseTitle: 'course-title', + courseKey: 'course-v1:edX=CTL.SC101x.3T2019', + courseListPrice: 100, + enrollmentDate: '2-2-23', + courseProductLine: 'OCM', + }], + itemCount: 1, + pageCount: 1, + }, + fetchTableData: jest.fn(), + }; + props.fetchTableData.mockReturnValue(props.tableData); + + render(); + const courseTitleElement = screen.queryByText('course-title'); + expect(courseTitleElement.closest('a')).toBeNull(); + }); });