diff --git a/CHANGELOG.md b/CHANGELOG.md index 12e005ab8..f11f4b067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - Adjust marker icon on selecting a new facility on the vector tiles layer [#749](https://github.com/open-apparel-registry/open-apparel-registry/pull/749) +- Fetch next page of facilities while scrolling through sidebar list [#750](https://github.com/open-apparel-registry/open-apparel-registry/pull/750) ### Changed diff --git a/src/app/src/__tests__/utils.tests.js b/src/app/src/__tests__/utils.tests.js index d2c5aa961..2b3fc2fc8 100644 --- a/src/app/src/__tests__/utils.tests.js +++ b/src/app/src/__tests__/utils.tests.js @@ -61,8 +61,6 @@ const { makeUserProfileURL, makeProfileRouteLink, joinDataIntoCSVString, - caseInsensitiveIncludes, - sortFacilitiesAlphabeticallyByName, updateListWithLabels, makeSubmitFormOnEnterKeyPressFunction, makeFacilityListItemsRetrieveCSVItemsURL, @@ -75,6 +73,7 @@ const { claimFacilityContactInfoStepIsValid, claimFacilityFacilityInfoStepIsValid, anyListItemMatchesAreInactive, + pluralizeResultsCount, } = require('../util/util'); const { @@ -88,6 +87,7 @@ const { ENTER_KEY, facilityListItemStatusChoicesEnum, facilityListSummaryStatusMessages, + FACILITIES_REQUEST_PAGE_SIZE, } = require('../util/constants'); it('creates a route for checking facility list items', () => { @@ -140,7 +140,7 @@ it('creates an API URL for getting a single facility by OAR ID', () => { it('creates an API URL for getting facilities with a query string', () => { const qs = 'hello=world'; - const expectedMatch = '/api/facilities/?hello=world'; + const expectedMatch = `/api/facilities/?hello=world&pageSize=${FACILITIES_REQUEST_PAGE_SIZE}`; expect(makeGetFacilitiesURLWithQueryString(qs)).toEqual(expectedMatch); }); @@ -877,83 +877,6 @@ it('joins a 2-d array into a correctly escaped CSV string', () => { expect(joinDataIntoCSVString(escapedArray)).toBe(expectedEscapedArrayMatch); }); -it('checks whether one string includes another regardless of char case', () => { - const uppercaseTarget = 'HELLOWORLD'; - const lowercaseTest = 'world'; - const lowercaseTarget = 'helloworld'; - const uppercaseTest = 'WORLD'; - const uppercaseNonMatchTest = 'FOO'; - const lowercaseNonMatchTest = 'foo'; - - expect(caseInsensitiveIncludes(uppercaseTarget, lowercaseTest)).toBe(true); - expect(caseInsensitiveIncludes(lowercaseTarget, uppercaseTest)).toBe(true); - expect(caseInsensitiveIncludes(lowercaseTarget, lowercaseTest)).toBe(true); - expect(caseInsensitiveIncludes(uppercaseTarget, uppercaseTest)).toBe(true); - - expect(caseInsensitiveIncludes(uppercaseTarget, lowercaseNonMatchTest)).toBe(false); - expect(caseInsensitiveIncludes(lowercaseTarget, uppercaseNonMatchTest)).toBe(false); - expect(caseInsensitiveIncludes(lowercaseTarget, lowercaseNonMatchTest)).toBe(false); - expect(caseInsensitiveIncludes(uppercaseTarget, uppercaseNonMatchTest)).toBe(false); -}); - -it('sorts an array of facilities alphabetically by name without mutating the input', () => { - const inputData = [ - { - properties: { - name: 'hello World', - }, - }, - { - properties: { - name: 'FOO', - }, - }, - { - properties: { - name: 'Bar', - }, - }, - { - properties: { - name: 'baz', - }, - }, - ]; - - const expectedSortedData = [ - { - properties: { - name: 'Bar', - }, - }, - { - properties: { - name: 'baz', - }, - }, - { - properties: { - name: 'FOO', - }, - }, - { - properties: { - name: 'hello World', - }, - }, - ]; - - expect(isEqual( - sortFacilitiesAlphabeticallyByName(inputData), - expectedSortedData, - )).toBe(true); - - expect(isEqual( - inputData, - expectedSortedData, - )).toBe(false); -}); - it('updates a list of unlabeled values with the correct labels from a given source', () => { const source = [ { @@ -1348,3 +1271,11 @@ it('checks a facility list item to see whether any matches have been set to inac expect(anyListItemMatchesAreInactive(listItemWithInactiveMatches)).toBe(true); }); + +it('pluralizes a results count correclty, returning null if count is undefined or null', () => { + expect(pluralizeResultsCount(undefined)).toBeNull(); + expect(pluralizeResultsCount(null)).toBeNull(); + expect(pluralizeResultsCount(1)).toBe('1 result'); + expect(pluralizeResultsCount(0)).toBe('0 results'); + expect(pluralizeResultsCount(200)).toBe('200 results'); +}); diff --git a/src/app/src/actions/facilities.js b/src/app/src/actions/facilities.js index 1d7cd3c24..910975b3f 100644 --- a/src/app/src/actions/facilities.js +++ b/src/app/src/actions/facilities.js @@ -65,6 +65,37 @@ export function fetchFacilities(pushNewRoute = noop) { }; } +export const startFetchNextPageOfFacilities = createAction('START_FETCH_NEXT_PAGE_OF_FACILITIES'); +export const failFetchNextPageOfFacilities = createAction('FAIL_FETCH_NEXT_PAGE_OF_FACILITIES'); +export const completeFetchNextPageOfFacilities = createAction('COMPLETE_FETCH_NEXT_PAGE_OF_FACILITIES'); + +export function fetchNextPageOfFacilities() { + return (dispatch, getState) => { + const { + facilities: { + facilities: { + nextPageURL, + }, + }, + } = getState(); + + if (!nextPageURL) { + return noop(); + } + + dispatch(startFetchNextPageOfFacilities()); + + return apiRequest + .get(nextPageURL) + .then(({ data }) => dispatch(completeFetchNextPageOfFacilities(data))) + .catch(err => dispatch(logErrorAndDispatchFailure( + err, + 'An error prevented fetching the next page of facilities', + failFetchNextPageOfFacilities, + ))); + }; +} + export function fetchSingleFacility(oarID = null) { return (dispatch) => { dispatch(startFetchSingleFacility()); diff --git a/src/app/src/actions/ui.js b/src/app/src/actions/ui.js index 58c41ce21..18b90740d 100644 --- a/src/app/src/actions/ui.js +++ b/src/app/src/actions/ui.js @@ -4,11 +4,6 @@ export const makeSidebarGuideTabActive = createAction('MAKE_SIDEBAR_GUIDE_TAB_AC export const makeSidebarSearchTabActive = createAction('MAKE_SIDEBAR_SEARCH_TAB_ACTIVE'); export const makeSidebarFacilitiesTabActive = createAction('MAKE_SIDEBAR_FACILITIES_TAB_ACTIVE'); -export const updateSidebarFacilitiesTabTextFilter = - createAction('UPDATE_SIDEBAR_FACILITIES_TAB_TEXT_FILTER'); -export const resetSidebarFacilitiesTabTextFilter = - createAction('RESET_SIDEBAR_FACILITIES_TAB_TEXT_FILTER'); - export const recordSearchTabResetButtonClick = createAction('RECORD_SEARCH_TAB_RESET_BUTTON_CLICK'); diff --git a/src/app/src/components/FilterSidebarFacilitiesTab.jsx b/src/app/src/components/FilterSidebarFacilitiesTab.jsx index a7c5a9864..1c9607080 100644 --- a/src/app/src/components/FilterSidebarFacilitiesTab.jsx +++ b/src/app/src/components/FilterSidebarFacilitiesTab.jsx @@ -17,13 +17,12 @@ import get from 'lodash/get'; import { toast } from 'react-toastify'; import InfiniteAnyHeight from 'react-infinite-any-height'; -import ControlledTextInput from './ControlledTextInput'; - import { makeSidebarSearchTabActive, - updateSidebarFacilitiesTabTextFilter, } from '../actions/ui'; +import { fetchNextPageOfFacilities } from '../actions/facilities'; + import { logDownload } from '../actions/logDownload'; import { facilityCollectionPropType } from '../util/propTypes'; @@ -35,17 +34,13 @@ import { import { makeFacilityDetailLink, - getValueFromEvent, - caseInsensitiveIncludes, - sortFacilitiesAlphabeticallyByName, + pluralizeResultsCount, } from '../util/util'; import COLOURS from '../util/COLOURS'; import { filterSidebarStyles } from '../util/styles'; -const SEARCH_TERM_INPUT = 'SEARCH_TERM_INPUT'; - const facilitiesTabStyles = Object.freeze({ noResultsTextStyles: Object.freeze({ margin: '30px', @@ -95,9 +90,9 @@ function FilterSidebarFacilitiesTab({ logDownloadError, user, returnToSearchTab, - filterText, - updateFilterText, handleDownload, + fetchNextPage, + isInfiniteLoading, }) { const [loginRequiredDialogIsOpen, setLoginRequiredDialogIsOpen] = useState(false); const [requestedDownload, setRequestedDownload] = useState(false); @@ -188,27 +183,9 @@ function FilterSidebarFacilitiesTab({ ); } - const filteredFacilities = filterText - ? facilities - .filter(({ - properties: { - address, - name, - country_name: countryName, - }, - }) => caseInsensitiveIncludes(address, filterText) - || caseInsensitiveIncludes(name, filterText) - || caseInsensitiveIncludes(countryName, filterText)) - : facilities; - - const orderedFacilities = - sortFacilitiesAlphabeticallyByName(filteredFacilities); - const facilitiesCount = get(data, 'count', null); - const headerDisplayString = facilitiesCount && (facilitiesCount !== filteredFacilities.length) - ? `Displaying ${filteredFacilities.length} facilities of ${facilitiesCount} results` - : `Displaying ${filteredFacilities.length} facilities`; + const headerDisplayString = pluralizeResultsCount(facilitiesCount); const LoginLink = props => ; const RegisterLink = props => ; @@ -242,19 +219,6 @@ function FilterSidebarFacilitiesTab({ -