Skip to content

Commit

Permalink
feat: loading for search input and layout of course tab
Browse files Browse the repository at this point in the history
  • Loading branch information
johnvente committed Apr 5, 2024
1 parent 160f0ba commit 8fca777
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 111 deletions.
12 changes: 11 additions & 1 deletion src/generic/Loading.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Spinner } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';

export const LoadingSpinner = () => (
export const LoadingSpinner = ({ size}) => (

Check failure on line 6 in src/generic/Loading.jsx

View workflow job for this annotation

GitHub Actions / tests

'size' is missing in props validation

Check failure on line 6 in src/generic/Loading.jsx

View workflow job for this annotation

GitHub Actions / tests

A space is required before '}'
<Spinner
animation="border"
role="status"
variant="primary"
size={size}
screenReaderText={(
<FormattedMessage
id="authoring.loading"
Expand All @@ -17,6 +19,14 @@ export const LoadingSpinner = () => (
/>
);

LoadingSpinner.defaultProps = {
size: undefined,
}

Check failure on line 24 in src/generic/Loading.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing semicolon

LoadingSpinner.prototype = {
size: PropTypes.string

Check failure on line 27 in src/generic/Loading.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing trailing comma
}

Check failure on line 28 in src/generic/Loading.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing semicolon

const Loading = () => (
<div className="d-flex justify-content-center align-items-center flex-column vh-100">
<LoadingSpinner />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,66 @@ exports[`CoursesFilters snapshot 1`] = `
class="d-flex"
>
<div
class="pgn__searchfield d-flex mr-4"
data-testid="input-filter-courses-search"
class="d-flex flex-row"
>
<form
class="pgn__searchfield-form"
role="search"
<div
class="pgn__searchfield d-flex mr-4"
data-testid="input-filter-courses-search"
>
<label
class="m-0"
for="pgn-searchfield-input-1"
<form
class="pgn__searchfield-form"
role="search"
>
<span
class="sr-only"
<label
class="m-0"
for="pgn-searchfield-input-1"
>
search
</span>
</label>
<input
class="form-control"
id="pgn-searchfield-input-1"
name="searchfield-input"
placeholder="Search"
role="searchbox"
type="text"
value=""
/>
<button
class="btn"
type="submit"
>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
<span
class="sr-only"
>
<path
d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5Zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14Z"
fill="currentColor"
/>
</svg>
</span>
<span
class="sr-only"
search
</span>
</label>
<input
class="form-control"
id="pgn-searchfield-input-1"
name="searchfield-input"
placeholder="Search"
role="searchbox"
type="text"
value=""
/>
<button
class="btn"
type="submit"
>
submit search
</span>
</button>
</form>
<span
class="pgn__icon"
>
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="24"
role="img"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5Zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14Z"
fill="currentColor"
/>
</svg>
</span>
<span
class="sr-only"
>
submit search
</span>
</button>
</form>
</div>
</div>
<div
class="pgn__dropdown pgn__dropdown-light dropdown"
Expand Down
93 changes: 55 additions & 38 deletions src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { useState, useEffect } from 'react';
import { useState, useCallback } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { SearchField } from '@openedx/paragon';
import debounce from 'lodash.debounce';

Check failure on line 5 in src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx

View workflow job for this annotation

GitHub Actions / tests

'lodash.debounce' should be listed in the project's dependencies. Run 'npm i -S lodash.debounce' to add it

import { getStudioHomeCoursesParams } from '../../../data/selectors';
import { updateStudioHomeCoursesCustomParams } from '../../../data/slice';
import { fetchStudioHomeData } from '../../../data/thunks';
import { useDebounce } from '../../../../hooks';
import { LoadingSpinner } from '../../../../generic/Loading';
import CoursesTypesFilterMenu from './courses-types-filter-menu';
import CoursesOrderFilterMenu from './courses-order-filter-menu';

const CoursesFilters = ({ dispatch, locationValue, onSubmitSearchField }) => {
import './index.scss';

/*regex to check if a string has only whitespace

Check failure on line 15 in src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Expected exception block, space or tab after '/*' in comment
example " "
*/
const regexOnlyWhiteSpaces = /^\s+$/;

const CoursesFilters = ({

Check failure on line 20 in src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Trailing spaces not allowed
dispatch,

Check failure on line 21 in src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Trailing spaces not allowed
locationValue,

Check failure on line 22 in src/studio-home/tabs-section/courses-tab/courses-filters/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Trailing spaces not allowed
onSubmitSearchField,
isLoading
}) => {
const studioHomeCoursesParams = useSelector(getStudioHomeCoursesParams);
const {
order,
Expand All @@ -19,11 +32,6 @@ const CoursesFilters = ({ dispatch, locationValue, onSubmitSearchField }) => {
cleanFilters,
} = studioHomeCoursesParams;
const [inputSearchValue, setInputSearchValue] = useState('');
const searchValueDebounced = useDebounce(inputSearchValue);

const handleSearchCourses = (value) => {
setInputSearchValue(value);
};

const getFilterTypeData = (baseFilters) => ({
archivedCourses: { ...baseFilters, archivedOnly: true, activeOnly: undefined },
Expand Down Expand Up @@ -80,47 +88,54 @@ const CoursesFilters = ({ dispatch, locationValue, onSubmitSearchField }) => {
dispatch(fetchStudioHomeData(locationValue, false, { page: 1, ...filterParams }, true));
};

useEffect(() => {
const debouncedCleanedSearchValue = searchValueDebounced.trim();
const loadCoursesSearched = () => {
const valueFormatted = debouncedCleanedSearchValue;
const handleSearchCourses = (searchValueDebounced) => {
const valueFormatted = searchValueDebounced.trim();
const searchValueRequest = valueFormatted.length > 0 ? valueFormatted : undefined;
const filterParams = {
search: valueFormatted,
search: searchValueRequest,
activeOnly,
archivedOnly,
order,
};
dispatch(updateStudioHomeCoursesCustomParams({
currentPage: 1,
isFiltered: true,
cleanFilters: false,
inputValue: searchValueDebounced,
...filterParams,
}));

if (valueFormatted !== search) {
const hasOnlySpaces = regexOnlyWhiteSpaces.test(searchValueDebounced);

if (searchValueRequest && valueFormatted !== search && !hasOnlySpaces && !cleanFilters) {
dispatch(updateStudioHomeCoursesCustomParams({
currentPage: 1,
isFiltered: true,
cleanFilters: false,
...filterParams,
}));

dispatch(fetchStudioHomeData(locationValue, false, { page: 1, ...filterParams }, true));
}
};

const hasSearchValueDebouncedValue = debouncedCleanedSearchValue.length;
setInputSearchValue(searchValueDebounced);
};

if (hasSearchValueDebouncedValue) {
loadCoursesSearched();
}
}, [searchValueDebounced]);
const handleSearchCoursesDebounced = useCallback(
debounce((value) => handleSearchCourses(value), 400),
[]
);

return (
<div className="d-flex">
<SearchField
onSubmit={onSubmitSearchField}
onChange={handleSearchCourses}
value={cleanFilters ? '' : inputSearchValue}
className="mr-4"
data-testid="input-filter-courses-search"
placeholder="Search"
onClear={handleClearSearchInput}
/>
<div className="d-flex flex-row">
<SearchField
onSubmit={onSubmitSearchField}
onChange={handleSearchCoursesDebounced}
value={cleanFilters ? '' : inputSearchValue}
className="mr-4"
data-testid="input-filter-courses-search"
placeholder="Search"
onClear={handleClearSearchInput}
/>
{isLoading && (
<span className="search-field-loading">
<LoadingSpinner size="sm" />
</span>)
}
</div>

<CoursesTypesFilterMenu onItemMenuSelected={handleMenuFilterItemSelected} />
<CoursesOrderFilterMenu onItemMenuSelected={handleMenuFilterItemSelected} />
Expand All @@ -131,12 +146,14 @@ const CoursesFilters = ({ dispatch, locationValue, onSubmitSearchField }) => {
CoursesFilters.defaultProps = {
locationValue: '',
onSubmitSearchField: () => {},
isLoading: false,
};

CoursesFilters.propTypes = {
dispatch: PropTypes.func.isRequired,
locationValue: PropTypes.string,
onSubmitSearchField: PropTypes.func,
isProcessing: PropTypes.bool,
};

export default CoursesFilters;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.search-field-loading {
margin-left: -25%;
margin-top: .5rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,6 @@ describe('CoursesFilters', () => {
expect(dispatchMock).toHaveBeenCalled();
});

it('should clear the search input and call dispatch when the clear button is clicked', () => {
renderComponent();
const searchInput = screen.getByRole('searchbox');
fireEvent.change(searchInput, { target: { value: 'test' } });
const searchForm = searchInput.closest('form');
const clearButton = searchForm.querySelector('button[type="reset"]');
fireEvent.click(clearButton);
expect(searchInput.value).toBe('');
expect(dispatchMock).toHaveBeenCalled();
});

it('should clear the search input when cleanFilters is true', () => {
useSelector.mockReturnValue({
cleanFilters: true,
Expand Down
16 changes: 5 additions & 11 deletions src/studio-home/tabs-section/courses-tab/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ProcessingCourses from '../../processing-courses';
import { LoadingSpinner } from '../../../generic/Loading';
import AlertMessage from '../../../generic/alert-message';
import messages from '../messages';
import './index.scss';

const CoursesTab = ({
coursesDataItems,
Expand Down Expand Up @@ -81,8 +82,6 @@ const CoursesTab = ({
cleanFilters: true,
archivedOnly: undefined,
activeOnly: undefined,
coursesTypesLabel: undefined,
coursesOrderLabel: undefined,
};

dispatch(fetchStudioHomeData(locationValue, false, { page: 1, order: 'display_name' }, true));
Expand Down Expand Up @@ -112,11 +111,11 @@ const CoursesTab = ({
)}
/>
) : (
<>
{isShowProcessing && <ProcessingCourses />}
<div className="courses-tab-container">
{/*isShowProcessing && <ProcessingCourses />*/}
{isEnabledPagination && (
<div className="d-flex flex-row justify-content-between my-4">
<CoursesFilters dispatch={dispatch} locationValue={locationValue} />
<CoursesFilters dispatch={dispatch} locationValue={locationValue} isLoading={isLoading} />
<p data-testid="pagination-info">
{intl.formatMessage(messages.coursesPaginationInfo, {
length: coursesDataItems.length,
Expand Down Expand Up @@ -174,11 +173,6 @@ const CoursesTab = ({
)
)}

{isLoading && isFiltered && (
<Row className="m-0 mt-4 justify-content-start">
<LoadingSpinner />
</Row>
)}
{isFiltered && !hasCourses && !isLoading && (
<Alert className="mt-4">
<Alert.Heading>
Expand All @@ -198,7 +192,7 @@ const CoursesTab = ({
className="mt-3"
/>
)}
</>
</div>
)
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/studio-home/tabs-section/courses-tab/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.courses-tab-container {
min-height: 80vh;
}

0 comments on commit 8fca777

Please sign in to comment.