diff --git a/package-lock.json b/package-lock.json
index 4b5f900d..6692a6ac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"react": "16.14.0",
"react-dom": "16.14.0",
"react-intl": "^5.25.1",
- "react-paragon-topaz": "1.19.1",
+ "react-paragon-topaz": "1.20.0",
"react-redux": "^7.2.9",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
@@ -17174,9 +17174,9 @@
}
},
"node_modules/react-paragon-topaz": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/react-paragon-topaz/-/react-paragon-topaz-1.19.1.tgz",
- "integrity": "sha512-vYYqud4M1PkCAIdqoCBCQ0z3a7gd8YN6KBzeZw+Nx0lUrS8aqTKZ9WBX6aJvUj+mLwSGLBUInppsXlXJryrNyA==",
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/react-paragon-topaz/-/react-paragon-topaz-1.20.0.tgz",
+ "integrity": "sha512-LxdkTWzgWe4FJ/wYXvaEE6lbFOZYjDPQ6KVzkgTYZ9BGi6W4iWL8gt6p90L8gYWdoPvRn3ypMasYbPn3o9CFrg==",
"dependencies": {
"@babel/runtime": "7.25.6",
"@edx/frontend-platform": "4.5.1",
diff --git a/package.json b/package.json
index df359612..0875c546 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
"react": "16.14.0",
"react-dom": "16.14.0",
"react-intl": "^5.25.1",
- "react-paragon-topaz": "1.19.1",
+ "react-paragon-topaz": "1.20.0",
"react-redux": "^7.2.9",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
diff --git a/src/features/Courses/CoursesFilters/_test_/index.test.jsx b/src/features/Courses/CoursesFilters/_test_/index.test.jsx
index b1c75240..058342f4 100644
--- a/src/features/Courses/CoursesFilters/_test_/index.test.jsx
+++ b/src/features/Courses/CoursesFilters/_test_/index.test.jsx
@@ -1,17 +1,17 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import { fireEvent, act } from '@testing-library/react';
+import { fireEvent } from '@testing-library/react';
import CoursesFilters from 'features/Courses/CoursesFilters';
import '@testing-library/jest-dom/extend-expect';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { renderWithProviders } from 'test-utils';
+import { allResultsOption } from 'features/constants';
jest.mock('react-select', () => function reactSelect({ options, valueR, onChange }) {
function handleChange(event) {
- const option = options.find(
- (optionR) => optionR.value === event.currentTarget.value,
- );
- onChange(option);
+ onChange({ id: event.currentTarget.value });
+
+ return event;
}
return (
@@ -69,15 +69,15 @@ describe('CoursesFilters Component', () => {
});
});
- test('call service when apply filters', async () => {
- const resetPagination = jest.fn();
+ test('should select a course', async () => {
const { getByText, getByTestId } = renderWithProviders(
- ,
+ ,
{ preloadedState: mockStore },
);
const courseSelect = getByTestId('select');
- const buttonApplyFilters = getByText('Apply');
+
+ expect(getByText('Find a primary course')).toBeInTheDocument();
expect(courseSelect).toBeInTheDocument();
fireEvent.change(courseSelect, {
@@ -85,27 +85,21 @@ describe('CoursesFilters Component', () => {
});
expect(getByText('Demo Course 1')).toBeInTheDocument();
- await act(async () => {
- fireEvent.click(buttonApplyFilters);
- });
});
- test('clear filters', async () => {
- const resetPagination = jest.fn();
+ test('Should have option for all results ', async () => {
const { getByText, getByTestId } = renderWithProviders(
- ,
+ ,
);
const courseSelect = getByTestId('select');
- const buttonClearFilters = getByText('Reset');
expect(courseSelect).toBeInTheDocument();
expect(courseSelect).toBeInTheDocument();
fireEvent.change(courseSelect, {
- target: { value: 'Demo Course 1' },
- });
- await act(async () => {
- fireEvent.click(buttonClearFilters);
+ target: { value: allResultsOption.value },
});
+
+ expect(getByText('Show all search results')).toBeInTheDocument();
});
});
diff --git a/src/features/Courses/CoursesFilters/index.jsx b/src/features/Courses/CoursesFilters/index.jsx
index c9b2c0a4..da8bdacb 100644
--- a/src/features/Courses/CoursesFilters/index.jsx
+++ b/src/features/Courses/CoursesFilters/index.jsx
@@ -1,44 +1,33 @@
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import PropTypes from 'prop-types';
import { Col, Form } from '@edx/paragon';
-import { Select, Button } from 'react-paragon-topaz';
-import { logError } from '@edx/frontend-platform/logging';
+import { Select } from 'react-paragon-topaz';
import { updateFilters, updateCurrentPage } from 'features/Courses/data/slice';
import { fetchCoursesData, fetchCoursesOptionsData } from 'features/Courses/data/thunks';
-import { initialPage } from 'features/constants';
+import { initialPage, styleFirstOption, allResultsOption } from 'features/constants';
-const CoursesFilters = ({ resetPagination }) => {
+const CoursesFilters = () => {
const dispatch = useDispatch();
const selectedInstitution = useSelector((state) => state.main.selectedInstitution);
const courses = useSelector((state) => state.courses.selectOptions);
const [courseOptions, setCourseOptions] = useState([]);
const [courseSelected, setCourseSelected] = useState(null);
+ const [inputCourse, setInputCourse] = useState('');
- const isButtonDisabled = courseSelected === null;
-
- const handleCoursesFilter = async (e) => {
- e.preventDefault();
- const form = e.target;
- const formData = new FormData(form);
- const formJson = Object.fromEntries(formData.entries());
- dispatch(updateFilters(formJson));
- try {
- dispatch(updateCurrentPage(initialPage));
- dispatch(fetchCoursesData(selectedInstitution.id, initialPage, formJson));
- } catch (error) {
- logError(error);
+ const filterOptions = (option, input) => {
+ if (input) {
+ return option.label.toLowerCase().includes(input) || option.value === allResultsOption.value;
}
+ return true;
};
- const handleCleanFilters = () => {
- dispatch(fetchCoursesData(selectedInstitution.id));
- resetPagination();
- setCourseSelected(null);
- dispatch(updateFilters({}));
+ const handleInputChange = (value, { action }) => {
+ if (action === 'input-change') {
+ setInputCourse(value);
+ }
};
useEffect(() => {
@@ -57,37 +46,48 @@ const CoursesFilters = ({ resetPagination }) => {
}))
: [];
- setCourseOptions(options);
+ setCourseOptions([allResultsOption, ...options]);
}, [courses]);
+ useEffect(() => {
+ if (Object.keys(selectedInstitution).length > 0) {
+ const params = {};
+
+ if (courseSelected) {
+ params.course_name = courseSelected.value === allResultsOption.value
+ ? inputCourse : courseSelected.value;
+ }
+ dispatch(fetchCoursesData(selectedInstitution.id, initialPage, params));
+ setInputCourse('');
+ dispatch(updateFilters(params));
+ dispatch(updateCurrentPage(initialPage));
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [courseSelected, selectedInstitution]);
+
return (
-
Find a course
-
-
+
-
-
-
-
@@ -95,8 +95,4 @@ const CoursesFilters = ({ resetPagination }) => {
);
};
-CoursesFilters.propTypes = {
- resetPagination: PropTypes.func.isRequired,
-};
-
export default CoursesFilters;
diff --git a/src/features/constants.js b/src/features/constants.js
index 4c58624f..cbfa2fce 100644
--- a/src/features/constants.js
+++ b/src/features/constants.js
@@ -98,3 +98,30 @@ export const modalDeleteText = {
title: 'Delete this class',
body: 'This action will permanently delete this class and cannot be undone. Booked seat in this class will not be affected by this action.',
};
+
+/**
+ * Custom styles for first option underlline in selector.
+ *@object
+ */
+export const styleFirstOption = {
+ menuList: (base) => ({
+ ...base,
+ boxShadow: '0 5px 5px -3px #0003, 0 8px 10px 1px #00000024, 0 3px 14px 2px #0000001f',
+ border: 0,
+ padding: 0,
+ borderRadius: '0px 0px 4px 4px;',
+ '& :first-child': {
+ textDecoration: 'underline',
+ color: '#007394',
+ },
+ }),
+};
+
+/**
+ * All result option.
+ * @constant {Object}
+ */
+export const allResultsOption = {
+ label: 'Show all search results',
+ value: 'all_results',
+};