MetaPhase-Consulting/State-TalentMAP

View on GitHub
src/Components/CycleManagement/CycleManagement.jsx

Summary

Maintainability
A
2 hrs
Test Coverage
F
3%
import { useEffect, useState } from 'react';
import { withRouter } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import Picky from 'react-picky';
import PropTypes from 'prop-types';
import FA from 'react-fontawesome';
import swal from '@sweetalert/with-react';
import Spinner from 'Components/Spinner';
import ProfileSectionTitle from 'Components/ProfileSectionTitle';
import Alert from 'Components/Alert';
import TMDatePicker from 'Components/TMDatePicker';
import {
  cycleManagementCreateCycle,
  cycleManagementFetchData,
  cycleManagementMergeCycle,
  cycleManagementMergeCycleSuccess,
  saveCycleManagementSelections,
} from 'actions/cycleManagement';
import { renderSelectionList, userHasPermissions } from 'utilities';
import CycleSearchCard from './CycleSearchCard';
import NewAssignmentCycle from './NewAssignmentCycle';
import MergeAssignmentCycles from './MergeAssignmentCycles';
import { history } from '../../store';

const CycleManagement = () => {
  const dispatch = useDispatch();

  // Redux State
  const userProfile = useSelector(state => state.userProfile);
  const userSelections = useSelector(state => state.cycleManagementSelections);
  const cycleManagementDataLoading = useSelector(state => state.cycleManagementFetchDataLoading);
  const cycleManagementData = useSelector(state => state.cycleManagement);
  const cycleManagementError = useSelector(state => state.cycleManagementFetchDataErrored);
  const cycleManagementMergeSuccess = useSelector(state => state.cycleManagementMerge);
  const isSuperUser = userHasPermissions(['superuser'], userProfile?.permission_groups);

  // Filters
  const [selectedCycles, setSelectedCycles] = useState(userSelections?.selectedCycles || []);
  const [selectedStatus, setSelectedStatus] = useState(userSelections?.selectedStatus || []);
  const [selectedDate, setSelectedDate] = useState(userSelections?.selectedDate || null);
  const [mergeTargetCycle, setMergeTargetCycle] = useState('');
  const [cycleManagementData$, setCycleManagementData$] = useState(cycleManagementData);
  const [clearFilters, setClearFilters] = useState(false);
  const noFiltersSelected = [selectedCycles, selectedStatus].flat().length === 0
    && !selectedDate;

  const cycleClassLink = '/profile/administrator/cycleclassifications';

  const getCurrentInputs = () => ({
    selectedCycles,
    selectedStatus,
    selectedDate,
  });

  const filterCyclesByDate = (cycles, date) => {
    const filteredCycles = cycles.filter(cycle => {
      const startDate = new Date(cycle.begin_date).getTime();
      const endDate = new Date(cycle.end_date).getTime();
      return ((date >= startDate) && (date <= endDate));
    });
    return filteredCycles;
  };

  const cycleDataFiltered = () => {
    if (noFiltersSelected) return cycleManagementData;

    let cycles = cycleManagementData;
    if (selectedCycles.length > 0) {
      cycles = selectedCycles;
    }
    if (selectedStatus.length > 0) {
      cycles = cycles.filter(cycle =>
        selectedStatus.some(status => status.text === cycle.status),
      );
    }

    if (selectedDate) return filterCyclesByDate(cycles, selectedDate);
    return cycles;
  };


  // initial render
  useEffect(() => {
    dispatch(cycleManagementFetchData());
  }, []);

  useEffect(() => {
    dispatch(saveCycleManagementSelections(getCurrentInputs()));
    setCycleManagementData$(cycleDataFiltered);
    if (noFiltersSelected) {
      setClearFilters(false);
    } else {
      setClearFilters(true);
    }
  }, [
    selectedCycles,
    selectedStatus,
    selectedDate,
    cycleManagementData,
  ]);

  useEffect(() => {
    if (swal?.getState()?.isOpen) {
      swal.close();
    }
  }, [cycleManagementDataLoading]);

  useEffect(() => {
    if (cycleManagementMergeSuccess) {
      swal.close();
      dispatch(cycleManagementMergeCycleSuccess(false));
      history.push(`/profile/administrator/assignmentcycle/${mergeTargetCycle}`);
    }
  }, [cycleManagementMergeSuccess]);

  const uniqueStatuses = () => {
    const statuses = cycleManagementData.map(cycles => cycles.status);
    const uniq = [...new Set(statuses)];
    const uniqObj = uniq.map(x => ({ text: x }));
    return uniqObj;
  };

  const resetFilters = () => {
    setSelectedCycles([]);
    setSelectedStatus([]);
    setSelectedDate(null);
    setClearFilters(false);
  };

  const onSave = (data) => {
    dispatch(cycleManagementCreateCycle(data));
  };

  const onMergeSave = (data) => {
    setMergeTargetCycle(data.targetCycle);
    dispatch(cycleManagementMergeCycle(data));
  };

  const createNewAssignmentCycle = () => {
    swal({
      title: 'New Assignment Cycle',
      className: 'modal-700-long',
      button: false,
      content: (
        <NewAssignmentCycle
          onSave={onSave}
        />
      ),
    });
  };

  const mergeCycles = () => {
    swal({
      title: 'Merge Assignment Cycles',
      className: 'modal-700',
      button: false,
      content: (
        <MergeAssignmentCycles
          onSave={onMergeSave}
          cycles={cycleManagementData}
        />
      ),
    });
  };

  // Overlay for error, info, and loading state
  const noResults = cycleManagementData$?.length === 0;
  const getOverlay = () => {
    let overlay;
    if (cycleManagementDataLoading) {
      overlay = <Spinner type="bureau-results" class="homepage-position-results" size="big" />;
    } else if (cycleManagementError) {
      overlay = <Alert type="error" title="Error loading results" messages={[{ body: 'Please try again.' }]} />;
    } else if (noResults) {
      overlay = <Alert type="info" title="No results found" messages={[{ body: 'Please broaden your search criteria and try again.' }]} />;
    } else {
      return false;
    }
    return overlay;
  };

  const pickyProps = {
    numberDisplayed: 2,
    multiple: true,
    includeFilter: true,
    dropdownHeight: 255,
    renderList: renderSelectionList,
    includeSelectAll: true,
  };

  return (
    cycleManagementDataLoading ? <Spinner type="bureau-filters" size="small" /> :
      (
        <div className="cycle-management-page position-search">
          <div className="usa-grid-full position-search--header">
            <ProfileSectionTitle title="Cycle Search" icon="cogs" className="xl-icon" />
            <div className="filterby-container" >
              <div className="filterby-label">Filter by:</div>
              <span className="filterby-clear">
                {clearFilters &&
                  <button className="unstyled-button" onClick={resetFilters}>
                    <FA name="times" />
                    Clear Filters
                  </button>
                }
              </span>
            </div>

            <div className="usa-width-one-whole position-search--filters--cm">
              <div className="filter-div">
                <div className="label">Cycle:</div>
                <Picky
                  {...pickyProps}
                  placeholder="Select Bid Cycle(s)"
                  options={cycleManagementData}
                  valueKey="id"
                  labelKey="name"
                  onChange={setSelectedCycles}
                  value={selectedCycles}
                />
              </div>
              <div className="filter-div">
                <div className="label">Status:</div>
                <Picky
                  {...pickyProps}
                  placeholder="Select Status"
                  options={uniqueStatuses()}
                  valueKey="text"
                  labelKey="text"
                  onChange={setSelectedStatus}
                  value={selectedStatus}
                />
              </div>
              <div className="filter-div">
                <div className="label">Cycle Date:</div>
                <TMDatePicker
                  onChange={setSelectedDate}
                  selected={selectedDate}
                  type="filter"
                  showMonthDropdown
                  showYearDropdown
                  isClearable
                />
              </div>
            </div>

          </div>
          {
            getOverlay() ||
            <>
              <div className="usa-grid-full results-dropdown controls-container">

                {isSuperUser &&
                  <div className="cm-heading">
                    <Link to={cycleClassLink}>
                      Cycle Date Classifications
                    </Link>
                    <div className="cm-new-merge">
                      <Link
                        onClick={(e) => {
                          e.preventDefault();
                          createNewAssignmentCycle();
                        }}
                        to="#"
                      >
                        <FA className="fa-solid fa-plus" />
                        Add New Assignment Cycle
                      </Link>
                      <Link
                        onClick={(e) => {
                          e.preventDefault();
                          mergeCycles();
                        }}
                        to="#"
                      >
                        <FA className="fa-solid fa-plus" />
                        Merge Assignment Cycles
                      </Link>
                    </div>
                  </div>
                }

              </div>
              <div className="cm-lower-section">
                {cycleManagementData$?.map(data => (
                  <CycleSearchCard
                    {...{ ...data }}
                    isSuperUser={isSuperUser}
                  />
                ))}
              </div>
            </>
          }
        </div>
      )
  );
};

CycleManagement.propTypes = {
  isAO: PropTypes.bool,
};

CycleManagement.defaultProps = {
  isAO: false,
};

export default withRouter(CycleManagement);