coforma/swift-tech-challenge

View on GitHub
src/app/components/layout/Browse.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
B
89%
"use client";
import { useContext, useEffect, useState } from "react";
// components
import { Button } from "@trussworks/react-uswds";
import {
  CollegeCard,
  FilterModal,
  InstitutionContext,
  Spinner,
  USWDSForm,
} from "../../components";
// types
import { College, FilterShape } from "../../types";
// utils
import { filterInstitutions } from "../../utils/filtering";
import { get20Institutions } from "../../utils/institutions";

const defaultFilters = {
  "filter-type": ["Public", "Private nonprofit", "Private for-profit"],
  "filter-undergrad-pop": ["<2", "2-5", "5-10", "10-20", ">20"],
  "filter-avg-cost-per-year": ["<10$", "10-20$", "20-40$", "40-60$", ">60$"],
  "filter-state": "- Select -",
  "filter-grad-rate": [">90", "60-90", "30-60", "<30"],
};

export const Browse = () => {
  const { institutionsArray, setFilteredInstitutions } =
    useContext(InstitutionContext);
  const [instArray, setInstArray] = useState<College[]>([]);
  const [filterChips, setFilterChips] = useState<FilterShape>(defaultFilters);
  const [hasFiltered, setHasFiltered] = useState<boolean>(false);
  const [loading, setLoading] = useState<boolean>(true);
  const [lastScannedKey, setLastScannedKey] = useState<any | undefined>();
  const [scrollPosition, setScrollPosition] = useState<boolean>(false);
  const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
  const [showPagination, setShowPagination] = useState<boolean>(true);

  useEffect(() => {
    const onScroll = (e: any) => {
      setScrollPosition(e.target.documentElement.scrollTop);
    };
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, [scrollPosition]);

  const launchModal = () => {
    setIsModalVisible(true);
  };

  const closeModal = () => {
    setIsModalVisible(false);
  };

  const checkFilterEquality = (selectedFilters: any, defaultFilters: any) => {
    const sortObject = (obj: any) =>
      Object.entries(obj)
        .sort((a, b) => (a > b ? 1 : -1))
        .map((el: any[]) =>
          typeof el[1] === "string"
            ? el[1]
            : el[1].sort((a: any, b: any) => (a > b ? 1 : -1)),
        );
    return (
      JSON.stringify(sortObject(selectedFilters)) ===
      JSON.stringify(sortObject(defaultFilters))
    );
  };

  const applyFilters = async (filters: any) => {
    const filtersAreEqual = checkFilterEquality(filters, defaultFilters);
    if (filtersAreEqual) {
      await load20Institutions(true);
    } else {
      const filteredColleges = filterInstitutions(institutionsArray!, filters);
      setFilteredInstitutions(filteredColleges);
      setInstArray(filteredColleges);
      setFilterChips(filters);
      setHasFiltered(true);
      setLastScannedKey(undefined);
      setShowPagination(false);
    }
    closeModal();
  };

  const load20Institutions = async (shouldReset?: boolean) => {
    try {
      let newArray;
      const { colleges: result, lastKey } =
        await get20Institutions(lastScannedKey);
      if (shouldReset) {
        newArray = Array(...result);
      } else {
        newArray = Array(...instArray, ...result);
      }
      setInstArray(newArray);
      setFilterChips(defaultFilters);
      setLastScannedKey(lastKey);
      setShowPagination(true);
      setLoading(false);
      setHasFiltered(false);
    } catch (e: any) {
      throw new Error("Institution data could not be loaded.");
    }
  };

  useEffect(() => {
    // loads initial 20 institutions to display
    load20Institutions();
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const InstList = () => {
    return (
      <ul className="usa-card-group">
        {instArray!.map((school: College) => (
          <CollegeCard key={school.id} college={school} />
        ))}
      </ul>
    );
  };

  if (loading) return <Spinner />;
  return (
    <>
      <div className="browse_header">
        <h2 className="browse_header-title">Browse colleges</h2>
        <p className="site_text-intro browse_header-subtitle">
          Find the college that’s right for you
        </p>
        <div className="browse_filter-chips">
          {checkFilterEquality(filterChips, defaultFilters) || !hasFiltered ? (
            <p className="browse_chip">No filters applied</p>
          ) : (
            <>
              {filterChips["filter-state"] && (
                <p className="browse_chip">{`State (${filterChips["filter-state"] === "- Select -" ? "All" : filterChips["filter-state"]})`}</p>
              )}
              {filterChips["filter-type"] && (
                <p
                  className={`browse_chip ${filterChips["filter-type"].length === 0 ? "browse_chip-error" : ""}`}
                >{`Types (${filterChips["filter-type"].length === 3 ? "All" : filterChips["filter-type"].length})`}</p>
              )}
              {filterChips["filter-undergrad-pop"] && (
                <p
                  className={`browse_chip ${filterChips["filter-undergrad-pop"].length === 0 ? "browse_chip-error" : ""}`}
                >{`Population Ranges (${filterChips["filter-undergrad-pop"].length === 5 ? "All" : filterChips["filter-undergrad-pop"].length})`}</p>
              )}
              {filterChips["filter-avg-cost-per-year"] && (
                <p
                  className={`browse_chip ${filterChips["filter-avg-cost-per-year"].length === 0 ? "browse_chip-error" : ""}`}
                >{`Cost Ranges (${filterChips["filter-avg-cost-per-year"].length === 5 ? "All" : filterChips["filter-avg-cost-per-year"].length})`}</p>
              )}
              {filterChips["filter-grad-rate"] && (
                <p
                  className={`browse_chip ${filterChips["filter-grad-rate"].length === 0 ? "browse_chip-error" : ""}`}
                >{`Graduation Rate Ranges (${filterChips["filter-grad-rate"].length === 4 ? "All" : filterChips["filter-grad-rate"].length})`}</p>
              )}
            </>
          )}
        </div>
        <div className="browse_filter-warning-container">
          {filterChips["filter-type"].length === 0 && (
            <p className="browse-filter-warning">
              Please select at least one institution type.
            </p>
          )}
          {filterChips["filter-undergrad-pop"].length === 0 && (
            <p className="browse-filter-warning">
              Please select at least one population range.
            </p>
          )}
          {filterChips["filter-avg-cost-per-year"].length === 0 && (
            <p className="browse-filter-warning">
              Please select at least one cost range.
            </p>
          )}
          {filterChips["filter-grad-rate"].length === 0 && (
            <p className="browse-filter-warning">
              Please select at least one graduation rate range.
            </p>
          )}
        </div>

        <Button type="button" outline={true} onClick={launchModal}>
          {hasFiltered ? "Edit filters" : "Add filters"}
        </Button>
      </div>
      <USWDSForm initialValues={defaultFilters} submit={applyFilters}>
        {isModalVisible && <FilterModal closeHandler={closeModal} />}
      </USWDSForm>
      {instArray.length > 0 ? (
        InstList()
      ) : (
        <p className="site_text-intro browse_header-subtitle">
          No matches found. Please update your filters.
        </p>
      )}
      {showPagination && (
        <Button
          type="button"
          className="browse_load-more-button"
          onClick={() => load20Institutions()}
        >
          Load 20 more
        </Button>
      )}
      {scrollPosition && (
        <Button
          type="button"
          className="usa-button usa-button--outline usa-button--unstyled browse_back-to-top-button"
          onClick={() => window.scrollTo(0, 0)}
        >
          Back to Top
        </Button>
      )}
    </>
  );
};