MetaPhase-Consulting/State-TalentMAP

View on GitHub
src/Components/AdministratorPage/PanelAdmin/PanelMeetingAdmin.jsx

Summary

Maintainability
C
7 hrs
Test Coverage
F
2%
import { useEffect, useRef, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import DatePicker from 'react-datepicker';
import { subMinutes } from 'date-fns';
import PropTypes from 'prop-types';
import FA from 'react-fontawesome';
import { convertQueryToString, userHasPermissions } from 'utilities';
import Spinner from 'Components/Spinner';
import Alert from 'Components/Alert';
import { HISTORY_OBJECT } from 'Constants/PropTypes';
import { panelMeetingsFetchData, panelMeetingsFiltersFetchData } from 'actions/panelMeetings';
import { runPanelMeeting } from 'actions/panelMeetingAdmin';
import { submitPanelMeeting } from '../../Panel/helpers';
import { panelMeetingAgendasFetchData } from '../../../actions/panelMeetingAgendas';
import { useDataLoader } from '../../../hooks';
import api from '../../../api';

const PanelMeetingAdmin = (props) => {
  const { history, panelMeetingsResults, panelMeetingsIsLoading, pmSeqNum } = props;
  const isCreate = !pmSeqNum;

  const dispatch = useDispatch();

  const userProfile = useSelector(state => state.userProfile);
  const isFsbidAdmin = userHasPermissions(['fsbid_admin'], userProfile?.permission_groups);
  const isPanelAdmin = userHasPermissions(['panel_admin'], userProfile?.permission_groups);

  // ============= Retrieve Data =============

  const { pmt_code, pms_code, panelMeetingDates } = panelMeetingsResults;

  const panelMeetingDate$ = panelMeetingDates?.find(x => x.mdt_code === 'MEET');
  const prelimCutoff$ = panelMeetingDates?.find(x => x.mdt_code === 'CUT');
  const prelimRunTime$ = panelMeetingDates?.find(x => x.mdt_code === 'OFF');
  const addendumCutoff$ = panelMeetingDates?.find(x => x.mdt_code === 'ADD');
  const addendumRunTime$ = panelMeetingDates?.find(x => x.mdt_code === 'OFFA');

  const panelMeetingsFilters = useSelector(state => state.panelMeetingsFilters);
  const panelMeetingsFiltersIsLoading = useSelector(state =>
    state.panelMeetingsFiltersFetchDataLoading);

  const savePanelSuccess = useSelector(state => state.createPanelMeetingSuccess);
  const runPreliminarySuccess = useSelector(state => state.runOfficialPreliminarySuccess);
  const runAddendumSuccess = useSelector(state => state.runOfficialAddendumSuccess);

  const agendas = useSelector(state => state.panelMeetingAgendas);
  const agendasIsLoading = useSelector(state => state.panelMeetingAgendasFetchDataLoading);
  const agendas$ = agendas?.results || [];

  useEffect(() => {
    dispatch(panelMeetingsFiltersFetchData());
    dispatch(panelMeetingAgendasFetchData({}, pmSeqNum));
  }, []);

  // ============= Input Management =============

  const datePickerRef = useRef(null);
  const openDatePicker = () => {
    datePickerRef.current.setOpen(true);
  };

  const [panelMeetingType, setPanelMeetingType] = useState('ID');
  const [panelMeetingDate, setPanelMeetingDate] = useState();
  const [panelMeetingStatus, setPanelMeetingStatus] = useState('I');
  const [prelimCutoff, setPrelimCutoff] = useState();
  const [addendumCutoff, setAddendumCutoff] = useState();
  const [prelimRuntime, setPrelimRuntime] = useState();
  const [addendumRuntime, setAddendumRuntime] = useState();

  const setInitialInputResults = () => {
    setPanelMeetingType(pmt_code);
    setPanelMeetingDate(new Date(panelMeetingDate$.pmd_dttm));
    setPanelMeetingStatus(pms_code);

    setPrelimCutoff(new Date(prelimCutoff$.pmd_dttm));
    setAddendumCutoff(new Date(addendumCutoff$.pmd_dttm));

    if (prelimRunTime$) {
      setPrelimRuntime(new Date(prelimRunTime$.pmd_dttm));
    }
    if (addendumRunTime$) {
      setAddendumRuntime(new Date(addendumRunTime$.pmd_dttm));
    }
  };

  useEffect(() => {
    if (!isCreate && !!Object.keys(panelMeetingsResults).length && !panelMeetingsIsLoading) {
      setInitialInputResults();
    }
  }, [panelMeetingsResults]);

  const selectPanelMeetingDate = (date) => {
    setPanelMeetingDate(date);
    if (!prelimCutoff) {
      const prelimCutoffMins = 2875;
      setPrelimCutoff(subMinutes(date, prelimCutoffMins));
    }
    if (!addendumCutoff) {
      const addendumCutoffMins = 1435;
      setAddendumCutoff(subMinutes(date, addendumCutoffMins));
    }
  };

  const clear = () => {
    if (!isCreate && !!Object.keys(panelMeetingsResults).length && !panelMeetingsIsLoading) {
      setInitialInputResults();
    } else {
      setPanelMeetingType('ID');
      setPanelMeetingDate('');
      setPrelimCutoff('');
      setAddendumCutoff('');
      setPrelimRuntime('');
      setAddendumRuntime('');
      setPanelMeetingStatus('I');
    }
  };


  // ============= Submission Management =============

  const createMeetingResults = useSelector(state => state.createPanelMeetingSuccess);
  const createMeetingLoading = useSelector(state => state.createPanelMeetingIsLoading);
  const createMeetingErrored = useSelector(state => state.createPanelMeetingHasErrored);

  useEffect(() => {
    if (!createMeetingLoading && !createMeetingErrored && createMeetingResults.length) {
      history.push('/profile/ao/panelmeetings');
    }
  }, [createMeetingResults]);

  // Submit current timestamp for specified field without saving other pending changes
  const handleRun = (field) => {
    if (field === 'prelimRuntime') {
      dispatch(runPanelMeeting(pmSeqNum, 'preliminary'));
    }
    if (field === 'addendumRuntime') {
      dispatch(runPanelMeeting(pmSeqNum, 'addendum'));
    }
    if (runPreliminarySuccess || runAddendumSuccess) {
      dispatch(panelMeetingsFetchData({ id: pmSeqNum }));
      dispatch(panelMeetingAgendasFetchData({}, pmSeqNum));
    }
  };

  const submit = () => {
    dispatch(submitPanelMeeting(panelMeetingsResults,
      {
        panelMeetingType,
        panelMeetingDate,
        prelimCutoff,
        addendumCutoff,
        prelimRuntime,
        addendumRuntime,
        panelMeetingStatus,
      },
    ));
    if (savePanelSuccess.length !== 0) {
      if (isCreate) {
        clear();
      } else {
        dispatch(panelMeetingsFetchData({ id: pmSeqNum }));
        dispatch(panelMeetingAgendasFetchData({}, pmSeqNum));
      }
    }
  };

  // ============= Form Conditions =============

  // Logic to determine if there is a subsequent panel with same meeting type
  // in initiated status for agenda itmes in NR status to roll over to

  const panelDateStart = new Date(panelMeetingDate$?.pmd_dttm);
  const panelDateEnd = new Date(panelMeetingDate$?.pmd_dttm);
  panelDateEnd.setFullYear(panelDateStart.getFullYear() + 10);
  const {
    data: subsequentPanels,
    loading: subsequentPanelsIsLoading,
  } = useDataLoader(api().get,
    `/fsbid/panel/meetings/?${convertQueryToString({
      limit: 1,
      page: 1,
      ordering: '-panel_date',
      type: pmt_code,
      status: 'I',
      'panel-date-start': panelDateStart.toJSON(),
      'panel-date-end': panelDateEnd.toJSON(),
    })}`,
  );
  const subsequentPanel = subsequentPanels?.data?.results?.length > 0
    ? subsequentPanels.data.results[0] : undefined;

  // Helpers for input disabling conditions

  const beforePanelMeetingDate = (
    panelMeetingDate$ ? (new Date(panelMeetingDate$.pmd_dttm) - new Date() > 0) : true
  );
  const beforePrelimCutoff = (
    prelimCutoff$ ? (new Date(prelimCutoff$.pmd_dttm) - new Date() > 0) : true
  );
  const beforeAddendumCutoff = (
    addendumCutoff$ ? (new Date(addendumCutoff$.pmd_dttm) - new Date() > 0) : true
  );

  // Only admins can access editable fields and run buttons
  // Additional business rules must be followed depending on the stage of the panel meeting

  const disableMeetingType = !isFsbidAdmin ||
    (!isCreate && !beforeAddendumCutoff);

  const disableStatus = !isFsbidAdmin ||
    (isCreate || !beforeAddendumCutoff);

  const disablePanelMeetingDate = !isFsbidAdmin ||
    (!isCreate && !beforePanelMeetingDate);

  const disablePrelimCutoff = !isFsbidAdmin ||
    (!isCreate && !beforePrelimCutoff);

  const disableAddendumCutoff = !isFsbidAdmin ||
    (!isCreate && !beforeAddendumCutoff);

  const disableRunPrelim = () => {
    let preconditioned = true;
    agendas$.forEach(a => {
      // Approved Agenda Items must be in Off-Panel Meeting Category
      if (a.status_short === 'APR' && a.pmi_mic_code !== 'O') {
        preconditioned = false;
      }
      // Agenda Items must be Approved, Ready, Not Ready, or Deferred
      if (
        a.status_short !== 'APR' &&
        a.status_short !== 'RDY' &&
        a.status_short !== 'NR' &&
        a.status_short !== 'DEF'
      ) {
        preconditioned = false;
      }
    });
    return (
      isCreate ||
      beforePrelimCutoff ||
      !beforePanelMeetingDate ||
      !preconditioned ||
      !subsequentPanel ||
      prelimRunTime$
    );
  };

  const disableRunAddendum = () => {
    let preconditioned = true;
    agendas$.forEach(a => {
      // Agenda Items must not be Disapproved or Not Ready
      if (a.status_short === 'DIS' || a.status_short === 'NR') {
        preconditioned = false;
      }
    });
    return (
      isCreate ||
      beforeAddendumCutoff ||
      !beforePanelMeetingDate ||
      !preconditioned ||
      !subsequentPanel ||
      addendumRunTime$
    );
  };

  const disableClear = !isFsbidAdmin ||
    (!isCreate && !beforePanelMeetingDate);

  const disableSave = !isFsbidAdmin ||
    (!isCreate && !beforePanelMeetingDate);

  const isLoading =
    panelMeetingsIsLoading ||
    panelMeetingsFiltersIsLoading ||
    agendasIsLoading ||
    subsequentPanelsIsLoading;

  if (isCreate && isPanelAdmin) {
    return <Alert type="error" title="Permission Denied" messages={[{ body: 'Additional permissions are required to access this feature.' }]} />;
  }
  return (
    (isLoading) ?
      <Spinner type="panel-admin-remarks" size="small" /> :
      <div className="admin-panel-meeting">
        <div>
          <div className="admin-panel-grid-row">
            <div className="panel-meeting-field">
              <label htmlFor="meeting-type">Meeting Type</label>
              <select
                disabled={disableMeetingType}
                className="select-dropdown"
                value={panelMeetingType}
                onChange={(e) => setPanelMeetingType(e.target.value)}
              >
                {
                  panelMeetingsFilters?.panelTypes?.map(a => (
                    <option value={a.code} key={a.text}>{a.text}</option>
                  ))
                }
              </select>
            </div>
            <div className="panel-meeting-field">
              <label htmlFor="status">Status</label>
              <select
                disabled={disableStatus}
                className="select-dropdown"
                value={panelMeetingStatus}
                onChange={(e) => setPanelMeetingStatus(e.target.value)}
              >
                {
                  panelMeetingsFilters?.panelStatuses?.map(a => (
                    <option value={a.code} key={a.text}>{a.text}</option>
                  ))
                }
              </select>
            </div>
            <div className="panel-meeting-field">
              <label htmlFor="panel-meeting-date">Panel Meeting Date</label>
              <div className="date-picker-wrapper larger-date-picker">
                <FA name="fa fa-calendar" onClick={() => openDatePicker()} />
                <DatePicker
                  disabled={disablePanelMeetingDate}
                  selected={panelMeetingDate}
                  onChange={selectPanelMeetingDate}
                  showTimeSelect
                  timeFormat="HH:mm"
                  timeIntervals={5}
                  timeCaption="time"
                  dateFormat="MM/dd/yyyy h:mm aa"
                  ref={datePickerRef}
                />
              </div>
            </div>
          </div>
          <div className="admin-panel-grid-row">
            <div className="panel-meeting-field">
              <label htmlFor="preliminary-cutoff-date">Official Preliminary Cutoff</label>
              <div className="date-picker-wrapper larger-date-picker">
                <FA name="fa fa-calendar" onClick={() => openDatePicker()} />
                <DatePicker
                  disabled={disablePrelimCutoff}
                  selected={prelimCutoff}
                  onChange={(date) => setPrelimCutoff(date)}
                  showTimeSelect
                  timeFormat="HH:mm"
                  timeIntervals={5}
                  timeCaption="time"
                  dateFormat="MM/dd/yyyy h:mm aa"
                  ref={datePickerRef}
                />
              </div>
            </div>
            <div className="panel-meeting-field">
              <label htmlFor="addendum-cutoff-date">Official Addendum Cutoff</label>
              <div className="date-picker-wrapper larger-date-picker">
                <FA name="fa fa-calendar" onClick={() => openDatePicker()} />
                <DatePicker
                  disabled={disableAddendumCutoff}
                  selected={addendumCutoff}
                  onChange={(date) => setAddendumCutoff(date)}
                  showTimeSelect
                  timeFormat="HH:mm"
                  timeIntervals={5}
                  timeCaption="time"
                  dateFormat="MM/dd/yyyy h:mm aa"
                  ref={datePickerRef}
                />
              </div>
            </div>
          </div>
          <div className="admin-panel-grid-row">
            <div className="panel-meeting-field">
              <label htmlFor="addendum-cutoff-date">Official Preliminary Run Time</label>
              <div className="date-picker-wrapper larger-date-picker">
                <FA name="fa fa-calendar" onClick={() => openDatePicker()} />
                <DatePicker
                  disabled
                  selected={prelimRuntime}
                  onChange={(date) => setPrelimRuntime(date)}
                  showTimeSelect
                  timeFormat="HH:mm"
                  timeIntervals={5}
                  timeCaption="time"
                  dateFormat="MM/dd/yyyy h:mm aa"
                  ref={datePickerRef}
                />
              </div>
              {isPanelAdmin &&
                <button
                  disabled={disableRunPrelim()}
                  className="text-button"
                  onClick={() => { handleRun('prelimRuntime'); }}
                >
                  Run Official Preliminary
                </button>
              }
            </div>
            <div className="panel-meeting-field">
              <label htmlFor="addendum-cutoff-date">Official Addendum Run Time</label>
              <div className="date-picker-wrapper larger-date-picker">
                <FA name="fa fa-calendar" onClick={() => openDatePicker()} />
                <DatePicker
                  disabled
                  selected={addendumRuntime}
                  onChange={(date) => setAddendumRuntime(date)}
                  showTimeSelect
                  timeFormat="HH:mm"
                  timeIntervals={5}
                  timeCaption="time"
                  dateFormat="MM/dd/yyyy h:mm aa"
                  ref={datePickerRef}
                />
              </div>
              {isPanelAdmin &&
                <button
                  disabled={disableRunAddendum()}
                  className="text-button"
                  onClick={() => { handleRun('addendumRuntime'); }}
                >
                  Run Official Addendum
                </button>
              }
            </div>
          </div>
        </div>
        {isFsbidAdmin &&
          <div className="position-form--actions">
            <button onClick={clear} disabled={disableClear}>Clear</button>
            <button onClick={submit} disabled={disableSave}>{isCreate ? 'Create' : 'Save'}</button>
          </div>
        }
      </div>
  );
};

PanelMeetingAdmin.propTypes = {
  history: HISTORY_OBJECT.isRequired,
  pmSeqNum: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.bool,
  ]),
  panelMeetingsResults: PropTypes.shape(),
  panelMeetingsIsLoading: PropTypes.bool,
};

PanelMeetingAdmin.defaultProps = {
  match: {},
  pmSeqNum: false,
  panelMeetingsResults: undefined,
  panelMeetingsIsLoading: false,
};

export default withRouter(PanelMeetingAdmin);