From 2fbec7e2357a622de740f82dad361cc529a48565 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Thu, 17 Oct 2024 11:55:39 +0500 Subject: [PATCH] feat: lpr budgets filtering --- src/components/Admin/AdminSearchForm.jsx | 67 +++++++++++++++++-- src/components/Admin/index.jsx | 11 ++- src/containers/AdminPage/index.jsx | 9 +++ src/data/actions/enterpriseBudgets.js | 41 ++++++++++++ src/data/constants/enterpriseBudgets.js | 11 +++ src/data/reducers/enterpriseBudgets.js | 47 +++++++++++++ src/data/reducers/index.js | 2 + src/data/services/EnterpriseDataApiService.js | 6 ++ src/utils.js | 3 + 9 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 src/data/actions/enterpriseBudgets.js create mode 100644 src/data/constants/enterpriseBudgets.js create mode 100644 src/data/reducers/enterpriseBudgets.js diff --git a/src/components/Admin/AdminSearchForm.jsx b/src/components/Admin/AdminSearchForm.jsx index a2624cef84..0d4a6e347c 100644 --- a/src/components/Admin/AdminSearchForm.jsx +++ b/src/components/Admin/AdminSearchForm.jsx @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import { Form } from '@openedx/paragon'; import { Info } from '@openedx/paragon/icons'; @@ -14,17 +15,22 @@ import { withLocation, withNavigate } from '../../hoc'; class AdminSearchForm extends React.Component { componentDidUpdate(prevProps) { - const { searchParams: { searchQuery, searchCourseQuery, searchDateQuery } } = this.props; + const { + searchParams: { + searchQuery, searchCourseQuery, searchDateQuery, searchBudgetQuery, + }, + } = this.props; const { searchParams: { searchQuery: prevSearchQuery, searchCourseQuery: prevSearchCourseQuery, searchDateQuery: prevSearchDateQuery, + searchBudgetQuery: prevSearchBudgetQuery, }, } = prevProps; if (searchQuery !== prevSearchQuery || searchCourseQuery !== prevSearchCourseQuery - || searchDateQuery !== prevSearchDateQuery) { + || searchDateQuery !== prevSearchDateQuery || searchBudgetQuery !== prevSearchBudgetQuery) { this.handleSearch(); } } @@ -45,14 +51,30 @@ class AdminSearchForm extends React.Component { updateUrl(navigate, location.pathname, updateParams); } + onBudgetSelect(event) { + const { navigate, location } = this.props; + const updateParams = { + budget_uuid: event.target.value, + page: 1, + }; + if (event.target.value === '') { + updateParams.budget_uuid = ''; + } + updateUrl(navigate, location.pathname, updateParams); + } + render() { const { intl, tableData, - searchParams: { searchCourseQuery, searchDateQuery, searchQuery }, + budgets, + searchParams: { + searchCourseQuery, searchDateQuery, searchQuery, searchBudgetQuery, + }, } = this.props; const courseTitles = Array.from(new Set(tableData.map(en => en.course_title).sort())); const courseDates = Array.from(new Set(tableData.map(en => en.course_start_date).sort().reverse())); + const columnWidth = budgets?.length ? 'col-md-3' : 'col-md-6'; return (
@@ -151,7 +173,7 @@ class AdminSearchForm extends React.Component {
-
+
+ {budgets?.length && ( +
+ + + + + this.onBudgetSelect(e)} + > + + {budgets.map(budget => ( + + ))} + + +
+ )}
@@ -193,8 +250,10 @@ AdminSearchForm.propTypes = { searchQuery: PropTypes.string, searchCourseQuery: PropTypes.string, searchDateQuery: PropTypes.string, + searchBudgetQuery: PropTypes.string, }).isRequired, tableData: PropTypes.arrayOf(PropTypes.shape({})), + budgets: PropTypes.arrayOf(PropTypes.shape({})), navigate: PropTypes.func, location: PropTypes.shape({ pathname: PropTypes.string, diff --git a/src/components/Admin/index.jsx b/src/components/Admin/index.jsx index d259d8f9a1..1fa1ac8b6d 100644 --- a/src/components/Admin/index.jsx +++ b/src/components/Admin/index.jsx @@ -49,6 +49,7 @@ class Admin extends React.Component { if (enterpriseId) { this.props.fetchDashboardAnalytics(enterpriseId); this.props.fetchDashboardInsights(enterpriseId); + this.props.fetchEnterpriseBudgets(enterpriseId); } } @@ -57,6 +58,7 @@ class Admin extends React.Component { if (enterpriseId && enterpriseId !== prevProps.enterpriseId) { this.props.fetchDashboardAnalytics(enterpriseId); this.props.fetchDashboardInsights(enterpriseId); + this.props.fetchEnterpriseBudgets(enterpriseId); } } @@ -64,6 +66,7 @@ class Admin extends React.Component { // Clear the overview data this.props.clearDashboardAnalytics(); this.props.clearDashboardInsights(); + this.props.clearEnterpriseBudgets(); } getMetadataForAction(actionSlug) { @@ -324,7 +327,7 @@ class Admin extends React.Component { const { location: { search, pathname } } = this.props; // remove the querys from the path const queryParams = new URLSearchParams(search); - ['search', 'search_course', 'search_start_date'].forEach((searchTerm) => { + ['search', 'search_course', 'search_start_date', 'budget_uuid'].forEach((searchTerm) => { queryParams.delete(searchTerm); }); const resetQuery = queryParams.toString(); @@ -403,6 +406,7 @@ class Admin extends React.Component { insights, insightsLoading, intl, + budgets, } = this.props; const { activeTab } = this.state; @@ -416,6 +420,7 @@ class Admin extends React.Component { searchQuery: queryParams.get('search') || '', searchCourseQuery: queryParams.get('search_course') || '', searchDateQuery: queryParams.get('search_start_date') || '', + searchBudgetQuery: queryParams.get('budget_uuid') || '', }; return ( @@ -531,6 +536,7 @@ class Admin extends React.Component { searchParams={searchParams} searchEnrollmentsList={() => this.props.searchEnrollmentsList()} tableData={this.getTableData() ? this.getTableData().results : []} + budgets={budgets} /> )} @@ -587,6 +593,8 @@ Admin.propTypes = { clearDashboardAnalytics: PropTypes.func.isRequired, fetchDashboardInsights: PropTypes.func.isRequired, clearDashboardInsights: PropTypes.func.isRequired, + fetchEnterpriseBudgets: PropTypes.func.isRequired, + clearEnterpriseBudgets: PropTypes.func.isRequired, enterpriseId: PropTypes.string, searchEnrollmentsList: PropTypes.func.isRequired, location: PropTypes.shape({ @@ -606,6 +614,7 @@ Admin.propTypes = { csv: PropTypes.shape({}), actionSlug: PropTypes.string, table: PropTypes.shape({}), + budgets: PropTypes.arrayOf(PropTypes.shape({})), insightsLoading: PropTypes.bool, insights: PropTypes.objectOf(PropTypes.shape), // injected diff --git a/src/containers/AdminPage/index.jsx b/src/containers/AdminPage/index.jsx index 62434c71a7..650c19f836 100644 --- a/src/containers/AdminPage/index.jsx +++ b/src/containers/AdminPage/index.jsx @@ -9,6 +9,7 @@ import Admin from '../../components/Admin'; import { paginateTable } from '../../data/actions/table'; import EnterpriseDataApiService from '../../data/services/EnterpriseDataApiService'; import { fetchDashboardInsights, clearDashboardInsights } from '../../data/actions/dashboardInsights'; +import { fetchEnterpriseBudgets, clearEnterpriseBudgets } from '../../data/actions/enterpriseBudgets'; const mapStateToProps = state => ({ loading: state.dashboardAnalytics.loading, @@ -23,6 +24,8 @@ const mapStateToProps = state => ({ table: state.table, insightsLoading: state.dashboardInsights.loading, insights: state.dashboardInsights.insights, + budgetsLoading: state.enterpriseBudgets.loading, + budgets: state.enterpriseBudgets.budgets, }); const mapDispatchToProps = dispatch => ({ @@ -41,6 +44,12 @@ const mapDispatchToProps = dispatch => ({ clearDashboardInsights: () => { dispatch(clearDashboardInsights()); }, + fetchEnterpriseBudgets: (enterpriseId) => { + dispatch(fetchEnterpriseBudgets(enterpriseId)); + }, + clearEnterpriseBudgets: () => { + dispatch(clearEnterpriseBudgets()); + }, }); export default connect(mapStateToProps, mapDispatchToProps)(Admin); diff --git a/src/data/actions/enterpriseBudgets.js b/src/data/actions/enterpriseBudgets.js new file mode 100644 index 0000000000..e578c48400 --- /dev/null +++ b/src/data/actions/enterpriseBudgets.js @@ -0,0 +1,41 @@ +import { logError } from '@edx/frontend-platform/logging'; +import { + FETCH_ENTERPRISE_BUDGETS_REQUEST, + FETCH_ENTERPRISE_BUDGETS_SUCCESS, + FETCH_ENTERPRISE_BUDGETS_FAILURE, + CLEAR_ENTERPRISE_BUDGETS, +} from '../constants/enterpriseBudgets'; +import EnterpriseDataApiService from '../services/EnterpriseDataApiService'; + +const fetchEnterpriseBudgetsRequest = () => ({ type: FETCH_ENTERPRISE_BUDGETS_REQUEST }); +const fetchEnterpriseBudgetsSuccess = data => ({ + type: FETCH_ENTERPRISE_BUDGETS_SUCCESS, + payload: { data }, +}); +const fetchEnterpriseBudgetsFailure = error => ({ + type: FETCH_ENTERPRISE_BUDGETS_FAILURE, + payload: { error }, +}); + +const fetchEnterpriseBudgets = enterpriseId => ( + (dispatch) => { + dispatch(fetchEnterpriseBudgetsRequest()); + return EnterpriseDataApiService.fetchEnterpriseBudgets(enterpriseId) + .then((response) => { + dispatch(fetchEnterpriseBudgetsSuccess(response.data)); + }) + .catch((error) => { + logError(error); + dispatch(fetchEnterpriseBudgetsFailure(error)); + }); + } +); + +const clearEnterpriseBudgets = () => dispatch => (dispatch({ + type: CLEAR_ENTERPRISE_BUDGETS, +})); + +export { + fetchEnterpriseBudgets, + clearEnterpriseBudgets, +}; diff --git a/src/data/constants/enterpriseBudgets.js b/src/data/constants/enterpriseBudgets.js new file mode 100644 index 0000000000..c1229aec38 --- /dev/null +++ b/src/data/constants/enterpriseBudgets.js @@ -0,0 +1,11 @@ +const FETCH_ENTERPRISE_BUDGETS_REQUEST = 'FETCH_ENTERPRISE_BUDGETS_REQUEST'; +const FETCH_ENTERPRISE_BUDGETS_SUCCESS = 'FETCH_ENTERPRISE_BUDGETS_SUCCESS'; +const FETCH_ENTERPRISE_BUDGETS_FAILURE = 'FETCH_ENTERPRISE_BUDGETS_FAILURE'; +const CLEAR_ENTERPRISE_BUDGETS = 'CLEAR_ENTERPRISE_BUDGETS'; + +export { + FETCH_ENTERPRISE_BUDGETS_REQUEST, + FETCH_ENTERPRISE_BUDGETS_SUCCESS, + FETCH_ENTERPRISE_BUDGETS_FAILURE, + CLEAR_ENTERPRISE_BUDGETS, +}; diff --git a/src/data/reducers/enterpriseBudgets.js b/src/data/reducers/enterpriseBudgets.js new file mode 100644 index 0000000000..e27dc36093 --- /dev/null +++ b/src/data/reducers/enterpriseBudgets.js @@ -0,0 +1,47 @@ +import { + FETCH_ENTERPRISE_BUDGETS_REQUEST, + FETCH_ENTERPRISE_BUDGETS_SUCCESS, + FETCH_ENTERPRISE_BUDGETS_FAILURE, + CLEAR_ENTERPRISE_BUDGETS, +} from '../constants/enterpriseBudgets'; + +const initialState = { + loading: false, + error: null, + budgets: null, +}; + +const enterpriseBudgets = (state = initialState, action) => { + switch (action.type) { + case FETCH_ENTERPRISE_BUDGETS_REQUEST: + return { + ...state, + loading: true, + error: null, + }; + case FETCH_ENTERPRISE_BUDGETS_SUCCESS: + return { + ...state, + loading: false, + budgets: action.payload.data, + }; + case FETCH_ENTERPRISE_BUDGETS_FAILURE: + return { + ...state, + loading: false, + error: action.payload.error, + budgets: null, + }; + case CLEAR_ENTERPRISE_BUDGETS: + return { + ...state, + loading: false, + error: null, + budgets: null, + }; + default: + return state; + } +}; + +export default enterpriseBudgets; diff --git a/src/data/reducers/index.js b/src/data/reducers/index.js index c3d5abfaf3..9e7d27bc78 100644 --- a/src/data/reducers/index.js +++ b/src/data/reducers/index.js @@ -14,6 +14,7 @@ import emailTemplate from './emailTemplate'; import licenseRemind from './licenseRemind'; import userSubscription from './userSubscription'; import dashboardInsights from './dashboardInsights'; +import enterpriseBudgets from './enterpriseBudgets'; const identityReducer = (state) => { const newState = { ...state }; @@ -38,6 +39,7 @@ const rootReducer = combineReducers({ licenseRemind, userSubscription, dashboardInsights, + enterpriseBudgets, }); export default rootReducer; diff --git a/src/data/services/EnterpriseDataApiService.js b/src/data/services/EnterpriseDataApiService.js index 5fe872cbeb..3163d45e6b 100644 --- a/src/data/services/EnterpriseDataApiService.js +++ b/src/data/services/EnterpriseDataApiService.js @@ -182,6 +182,12 @@ class EnterpriseDataApiService { return EnterpriseDataApiService.apiClient().get(url); } + static fetchEnterpriseBudgets(enterpriseId) { + const enterpriseUUID = EnterpriseDataApiService.getEnterpriseUUID(enterpriseId); + const url = `${EnterpriseDataApiService.enterpriseBaseUrl}${enterpriseUUID}/budgets/`; + return EnterpriseDataApiService.apiClient().get(url); + } + static fetchEnterpriseModuleActivityReport(enterpriseId, options, { csv } = {}) { const enterpriseUUID = EnterpriseDataApiService.getEnterpriseUUID(enterpriseId); const endpoint = csv ? 'module-performance.csv' : 'module-performance'; diff --git a/src/utils.js b/src/utils.js index e2f712da3e..fd034b75c4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -170,6 +170,9 @@ const getPageOptionsFromUrl = () => { if (query.has('search_course')) { pageOptions.search_course = query.get('search_course'); } + if (query.has('budget_uuid')) { + pageOptions.budget_uuid = query.get('budget_uuid'); + } if (query.has('search_start_date')) { pageOptions.search_start_date = query.get('search_start_date'); }