department-of-veterans-affairs/vets-website

View on GitHub
src/applications/representative-search/components/results/SearchResultsHeader.jsx

Summary

Maintainability
C
1 day
Test Coverage
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { VaSelect } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { useFeatureToggle } from '~/platform/utilities/feature-toggles/useFeatureToggle';
import { sortOptions } from '../../config';

/* eslint-disable camelcase */
/* eslint-disable @department-of-veterans-affairs/prefer-button-component */

export const SearchResultsHeader = props => {
  const { searchResults, pagination, query } = props;
  const {
    inProgress,
    context,
    representativeType,
    sortType,
    searchArea,
  } = query;
  const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();

  const reportFeatureEnabled = useToggleValue(
    TOGGLE_NAMES.findARepresentativeFlagResultsEnabled,
  );
  const { totalEntries, currentPage, totalPages } = pagination;
  const noResultsFound = !searchResults || !searchResults?.length;

  const [selectedSortType, setSelectedSortType] = useState(sortType);

  if (inProgress || !context) {
    return <div style={{ height: '38px' }} />;
  }

  const repFormat = {
    veteran_service_officer: 'Accredited VSO Representative',
    attorney: 'Accredited attorney',
    claim_agents: 'Accredited claims agent',
  };

  const handleNumberOfResults = () => {
    if (noResultsFound) {
      return 'No results found';
    }
    if (totalEntries === 1) {
      return 'Showing 1 result';
    }
    if (totalEntries < 11 && totalEntries > 1) {
      return `Showing ${totalEntries} results`;
    }
    if (totalEntries > 10) {
      const startResultNum = 10 * (currentPage - 1) + 1;
      let endResultNum;

      if (currentPage !== totalPages) {
        endResultNum = 10 * currentPage;
      } else endResultNum = totalEntries;

      return `Showing ${startResultNum} - ${endResultNum} of ${totalEntries} results`;
    }
    return 'Results';
  };

  const options = Object.keys(sortOptions).map(option => (
    <option key={option} value={option}>
      {sortOptions[option]}
    </option>
  ));

  // selection is updated in redux
  const onClickApplyButton = () => {
    props.updateSearchQuery({
      id: Date.now(),
      page: 1,
      sortType: selectedSortType,
    });
  };

  return (
    <div className="search-results-header vads-u-margin-bottom--5 vads-u-margin-padding-x--5">
      {/* Trigger methods for unit testing - temporary workaround for shadow root issues */}
      {props.onClickApplyButtonTester ? (
        <button
          id="test-button"
          label="test-button"
          type="button"
          text-label="button"
          onClick={onClickApplyButton}
        />
      ) : null}
      <h2 className="vads-u-margin-y--1">Your search results</h2>
      <div className="vads-u-margin-top--3">
        {searchResults?.length ? (
          <div>
            {' '}
            <va-alert
              close-btn-aria-label="Close notification"
              status="info"
              uswds
              visible
            >
              <h3 id="track-your-status-on-mobile" slot="headline">
                We’re updating our search tool
              </h3>
              <p>
                Our search tool may show outdated contact information for some
                accredited representatives.{' '}
                {reportFeatureEnabled
                  ? `You can report outdated information
                in your search results.`
                  : null}
              </p>
            </va-alert>
          </div>
        ) : null}

        <p
          id="search-results-subheader"
          className="vads-u-font-family--sans vads-u-font-weight--normal vads-u-margin-bottom--0 vads-u-margin-top--3"
          tabIndex="-1"
        >
          {handleNumberOfResults()} for
          {` `}
          <b>{repFormat[representativeType]}</b>
          {context.repOrgName && (
            <>
              {` `}
              named
              {` `}
              <b>{context.repOrgName}</b>
            </>
          )}
          {` `}
          {context.location && (
            <>
              within
              {` `}
              <b>
                {searchArea === 'Show all' ? (
                  'Show all'
                ) : (
                  <>{searchArea} miles</>
                )}
              </b>
              {` `}
              of
              {` `}
              <b>{context.location}</b>{' '}
            </>
          )}
          <>
            sorted by
            {` `}
            <b>{sortOptions[sortType]}</b>
          </>
        </p>

        {noResultsFound ? (
          <p className="vads-u-margin-bottom--8">
            For better results, try increasing your <b>search area</b>.
          </p>
        ) : (
          <div className="sort-dropdown">
            <div className="sort-select-and-apply">
              <div className="sort-select">
                <VaSelect
                  name="sort"
                  value={selectedSortType}
                  label="Sort by"
                  onVaSelect={e => setSelectedSortType(e.target.value)}
                  uswds
                >
                  {options}
                </VaSelect>
              </div>

              <div className="sort-apply-button">
                <va-button onClick={onClickApplyButton} text="Sort" secondary />
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

SearchResultsHeader.propTypes = {
  pagination: PropTypes.object,
  query: PropTypes.shape({
    context: PropTypes.shape({
      repOrgName: PropTypes.string,
      location: PropTypes.string,
    }),
    inProgress: PropTypes.bool,
    representativeType: PropTypes.string,
    searchArea: PropTypes.any,
    sortType: PropTypes.string,
  }),
  searchResults: PropTypes.array,
  updateSearchQuery: PropTypes.func,
  onClickApplyButtonTester: PropTypes.func,
};

// Only re-render if results or inProgress props have changed
const areEqual = (prevProps, nextProps) => {
  return (
    nextProps.searchResults === prevProps.searchResults &&
    nextProps.inProgress === prevProps.inProgress
  );
};

const mapStateToProps = state => ({
  ...state,
});

export default React.memo(
  connect(mapStateToProps)(SearchResultsHeader),
  areEqual,
);