department-of-veterans-affairs/vets-website

View on GitHub
src/applications/gi/components/profile/YellowRibbonSelector.jsx

Summary

Maintainability
C
1 day
Test Coverage
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import {
  VaSelect,
  VaButton,
  VaPagination,
  VaCard,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { yellowRibbonDegreeLevelTypeHash } from '../../constants';
import { deriveEligibleStudents, deriveMaxAmount } from '../../utils/helpers';

const ProgramCard = ({ program }) => {
  return (
    <VaCard
      background
      key={program.divisionProfessionalSchool}
      className="degree-level-card"
    >
      <div>
        <p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
          College or professional school
        </p>
        <p className="vads-u-margin-top--0">
          {program.divisionProfessionalSchool}
        </p>
      </div>
      <div>
        <p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
          Funding available
        </p>
        <p className="vads-u-margin-top--0">
          <span>{deriveEligibleStudents(program.numberOfStudents)}</span>
        </p>
      </div>
      <div>
        <p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
          Max school contribution
        </p>
        <p className="vads-u-margin-top--0">
          <span>{deriveMaxAmount(program.contributionAmount)}</span>
        </p>
      </div>
    </VaCard>
  );
};

ProgramCard.propTypes = {
  program: PropTypes.shape({
    divisionProfessionalSchool: PropTypes.string,
    numberOfStudents: PropTypes.number,
    contributionAmount: PropTypes.string,
    degreeLevel: PropTypes.string,
  }).isRequired,
};

const YellowRibbonSelector = ({ programs }) => {
  const mappedPrograms = programs.map(program => ({
    degreeLevel: program.degreeLevel,
    divisionProfessionalSchool: program.divisionProfessionalSchool,
    numberOfStudents: program.numberOfStudents,
    contributionAmount: program.contributionAmount,
  }));

  console.table(mappedPrograms); // eslint-disable-line no-console
  const resultsSummaryRef = useRef(null);
  const [selectedOption, setSelectedOption] = useState('');
  const [activeOption, setActiveOption] = useState('');
  const [filteredPrograms, setFilteredPrograms] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const itemsPerPage = 3;

  const degreeLevelOrder = [
    'All',
    'Certificate',
    'Associates',
    'Bachelors',
    'Undergraduate',
    'Masters',
    'Doctoral',
    'Graduate',
    'Other',
  ];

  const getDegreeLevels = degreeLevelKey => {
    return yellowRibbonDegreeLevelTypeHash[degreeLevelKey] || [];
  };

  const degreeLevelOptions = [
    ...new Set(
      programs.reduce((acc, program) => {
        const degreeLevels = getDegreeLevels(program.degreeLevel);
        return acc.concat(degreeLevels);
      }, []),
    ),
  ].sort((a, b) => degreeLevelOrder.indexOf(a) - degreeLevelOrder.indexOf(b));

  const filterPrograms = degreeLevel => {
    const filtered = programs.filter(program =>
      getDegreeLevels(program.degreeLevel).includes(degreeLevel),
    );
    setFilteredPrograms(filtered);
    setCurrentPage(1);
  };

  useEffect(() => {
    if (degreeLevelOptions.length === 1) {
      const autoSelectOption = degreeLevelOptions[0];
      setSelectedOption(autoSelectOption);
      setActiveOption(autoSelectOption);
      filterPrograms(autoSelectOption);
    }
  }, []);

  const handleSelectionChange = event => {
    setSelectedOption(event.target.value);
  };

  const handleDisplayResults = () => {
    setActiveOption(selectedOption);
    filterPrograms(selectedOption);
    setTimeout(() => {
      if (resultsSummaryRef.current) {
        resultsSummaryRef.current.focus();
      }
    }, 0);
  };

  const handlePageChange = page => {
    setCurrentPage(page);
    setTimeout(() => {
      if (resultsSummaryRef.current) {
        resultsSummaryRef.current.focus();
      }
    }, 0);
  };

  const totalPages = Math.ceil(filteredPrograms.length / itemsPerPage);
  const currentPrograms = filteredPrograms.slice(
    (currentPage - 1) * itemsPerPage,
    currentPage * itemsPerPage,
  );

  // Calculate start and end indices for the displayed programs
  const startIndex = (currentPage - 1) * itemsPerPage + 1;
  const endIndex = Math.min(
    currentPage * itemsPerPage,
    filteredPrograms.length,
  );

  const resultText = filteredPrograms.length === 1 ? 'result' : 'results';
  const resultDegreeLevel =
    degreeLevelOptions.length === 1
      ? `${activeOption} degree levels`
      : `"${activeOption}" degree levels`;

  const renderResultsSummary = () => {
    let activeOptionLabel = '';
    if (activeOption === 'All' || activeOption === 'Other') {
      activeOptionLabel = resultDegreeLevel;
    } else if (degreeLevelOptions.length === 1) {
      activeOptionLabel = `${activeOption} degree level`;
    } else {
      activeOptionLabel = `"${activeOption}" degree level`;
    }
    return (
      <p
        id="results-summary"
        ref={resultsSummaryRef}
        tabIndex="-1"
        className="vads-u-margin-top--3 vads-u-margin-bottom--3"
      >
        {`Showing ${startIndex}-${endIndex} of ${
          filteredPrograms.length
        } ${resultText} for ${activeOptionLabel}`}
      </p>
    );
  };

  return (
    <div className="yellow-ribbon-selector-container">
      {degreeLevelOptions.length > 1 && (
        <div>
          <p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
            Select a degree level to get started.
          </p>
          <div className="selector-wrapper vads-u-display--flex vads-u-align-items--flex-end ">
            <VaSelect
              className="degree-selector"
              id="degree"
              name="degree"
              label="Degree level"
              value={selectedOption}
              onVaSelect={handleSelectionChange}
              uswds
            >
              {degreeLevelOptions.map(degreeLevel => (
                <option key={degreeLevel} value={degreeLevel}>
                  {degreeLevel}
                </option>
              ))}
            </VaSelect>
            <VaButton
              onClick={handleDisplayResults}
              secondary
              text="Display Results"
              className="degree-selector-btn vads-u-margin-left--2p5"
            />
          </div>
        </div>
      )}

      <div className="vads-u-margin-bottom--0">
        {filteredPrograms.length > 0 && activeOption && renderResultsSummary()}
        {/* eslint-disable-next-line jsx-a11y/no-redundant-roles */}
        <ul
          role="list"
          className="degree-level-results vads-u-margin-bottom--1"
        >
          {currentPrograms.map((program, index) => (
            <li
              className="degree-item"
              key={`${program.divisionProfessionalSchool}-${index}`}
            >
              <ProgramCard program={program} />
            </li>
          ))}
        </ul>
        {currentPrograms.length > 0 && (
          <VaPagination
            page={currentPage}
            pages={totalPages}
            maxPageListLength={3}
            showLastPage
            onPageSelect={e => handlePageChange(e.detail.page)}
            className="vads-u-border-top--0 vads-u-padding-y--0 vads-u-margin-bottom--0"
          />
        )}
      </div>
    </div>
  );
};

YellowRibbonSelector.propTypes = {
  programs: PropTypes.arrayOf(
    PropTypes.shape({
      divisionProfessionalSchool: PropTypes.string,
      numberOfStudents: PropTypes.number,
      contributionAmount: PropTypes.string,
      degreeLevel: PropTypes.string,
    }),
  ).isRequired,
};

export default YellowRibbonSelector;