Skip to content

Commit

Permalink
feat: lpr budgets filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammad-ammar committed Oct 22, 2024
1 parent e196e52 commit 2fbec7e
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 5 deletions.
67 changes: 63 additions & 4 deletions src/components/Admin/AdminSearchForm.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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();
}
}
Expand All @@ -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 (
<div className="row">
Expand Down Expand Up @@ -151,7 +173,7 @@ class AdminSearchForm extends React.Component {
</Form.Control>
</Form.Group>
</div>
<div className="col-12 col-md-6 my-2 my-md-0 px-0 px-md-2 px-lg-3">
<div className={classNames('col-12 my-2 my-md-0 px-0 px-md-2 px-lg-3', columnWidth)}>
<Form.Label id="search-email-label" className="mb-2">
<FormattedMessage
id="admin.portal.lpr.filter.by.email.input.label"
Expand All @@ -176,6 +198,41 @@ class AdminSearchForm extends React.Component {
inputProps={{ 'data-hj-suppress': true }}
/>
</div>
{budgets?.length && (
<div className="col-12 col-md-3 my-2 my-md-0 px-0 px-md-2 px-lg-3">
<Form.Group>
<Form.Label className="search-label mb-2">
<FormattedMessage
id="admin.portal.lpr.filter.by.budget.dropdown.label"
defaultMessage="Filter by budget"
description="Label for the budget filter dropdown in the admin portal LPR page."
/>
</Form.Label>
<Form.Control
className="w-100"
as="select"
value={searchBudgetQuery}
onChange={e => this.onBudgetSelect(e)}
>
<option value="">
{intl.formatMessage({
id: 'admin.portal.lpr.filter.by.budget.dropdown.option.all.budgets',
defaultMessage: 'All budgets',
description: 'Label for the all budgets option in the budget filter dropdown in the admin portal LPR page.',
})}
</option>
{budgets.map(budget => (
<option
value={budget.subsidy_access_policy_uuid}
key={budget.subsidy_access_policy_uuid}
>
{budget.subsidy_access_policy_display_name}
</option>
))}
</Form.Control>
</Form.Group>
</div>
)}
</div>
</div>
</div>
Expand All @@ -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,
Expand Down
11 changes: 10 additions & 1 deletion src/components/Admin/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Admin extends React.Component {
if (enterpriseId) {
this.props.fetchDashboardAnalytics(enterpriseId);
this.props.fetchDashboardInsights(enterpriseId);
this.props.fetchEnterpriseBudgets(enterpriseId);
}
}

Expand All @@ -57,13 +58,15 @@ class Admin extends React.Component {
if (enterpriseId && enterpriseId !== prevProps.enterpriseId) {
this.props.fetchDashboardAnalytics(enterpriseId);
this.props.fetchDashboardInsights(enterpriseId);
this.props.fetchEnterpriseBudgets(enterpriseId);
}
}

componentWillUnmount() {
// Clear the overview data
this.props.clearDashboardAnalytics();
this.props.clearDashboardInsights();
this.props.clearEnterpriseBudgets();
}

getMetadataForAction(actionSlug) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -403,6 +406,7 @@ class Admin extends React.Component {
insights,
insightsLoading,
intl,
budgets,
} = this.props;
const { activeTab } = this.state;

Expand All @@ -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 (
Expand Down Expand Up @@ -531,6 +536,7 @@ class Admin extends React.Component {
searchParams={searchParams}
searchEnrollmentsList={() => this.props.searchEnrollmentsList()}
tableData={this.getTableData() ? this.getTableData().results : []}
budgets={budgets}
/>
)}
</>
Expand Down Expand Up @@ -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({
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/containers/AdminPage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 => ({
Expand All @@ -41,6 +44,12 @@ const mapDispatchToProps = dispatch => ({
clearDashboardInsights: () => {
dispatch(clearDashboardInsights());
},
fetchEnterpriseBudgets: (enterpriseId) => {
dispatch(fetchEnterpriseBudgets(enterpriseId));
},
clearEnterpriseBudgets: () => {
dispatch(clearEnterpriseBudgets());
},
});

export default connect(mapStateToProps, mapDispatchToProps)(Admin);
41 changes: 41 additions & 0 deletions src/data/actions/enterpriseBudgets.js
Original file line number Diff line number Diff line change
@@ -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,
};
11 changes: 11 additions & 0 deletions src/data/constants/enterpriseBudgets.js
Original file line number Diff line number Diff line change
@@ -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,
};
47 changes: 47 additions & 0 deletions src/data/reducers/enterpriseBudgets.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions src/data/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -38,6 +39,7 @@ const rootReducer = combineReducers({
licenseRemind,
userSubscription,
dashboardInsights,
enterpriseBudgets,
});

export default rootReducer;
6 changes: 6 additions & 0 deletions src/data/services/EnterpriseDataApiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down

0 comments on commit 2fbec7e

Please sign in to comment.