MetaPhase-Consulting/State-TalentMAP

View on GitHub
src/Components/Agenda/AgendaLegFormEdit/AgendaLegFormEdit.jsx

Summary

Maintainability
D
1 day
Test Coverage
F
47%
import PropTypes from 'prop-types';
import { AI_VALIDATION, EMPTY_FUNCTION } from 'Constants/PropTypes';
import { get, includes } from 'lodash';
import FA from 'react-fontawesome';
import InteractiveElement from 'Components/InteractiveElement';
import Calendar from 'react-calendar';
import { formatDate, formatLang, formatMonthYearDate } from 'utilities';
import swal from '@sweetalert/with-react';
import { add } from 'date-fns-v2';
import { useEffect, useState } from 'react';
import { DEFAULT_TEXT } from 'Constants/SystemMessages';
import { GSA as LocationsTabID } from '../AgendaItemResearchPane/AgendaItemResearchPane';
import TodModal from './TodModal';
import MonthYearDropdown from './MonthYearDropdown';
import { formatVice } from '../Constants';

const AgendaLegFormEdit = props => {
  const {
    AIvalidation,
    isEf, // check if leg is first leg (effective leg)
    leg,
    legNum,
    updateLeg,
    onClose,
    TODs,
    legActionTypes,
    travelFunctions,
    onHover,
    rowNum,
    setLegsContainerExpanded,
    updateResearchPaneTab,
    setActiveAIL,
  } = props;

  const isSeparation = leg?.is_separation || false;
  const defaultSepText = isSeparation ? '-' : false;
  const legValidation = AIvalidation?.legs?.individualLegs?.[leg?.ail_seq_num];
  const disabled = isEf;

  const getLegActionTypes = legActionTypes.filter(lat => lat.is_separation === isSeparation);

  const isLegacyValue = (val, data) => {
    if (['', null, undefined].includes(val)) return false;
    return !data.some(a => a.code === val);
  };

  const [showLegacyAction, setShowLegacyAction] = useState(
    isLegacyValue(leg?.action_code, getLegActionTypes));
  const [showLegacyTravel, setShowLegacyTravel] = useState(
    isLegacyValue(leg?.travel_code, travelFunctions));

  const onHover$ = (row) => {
    // this should check the row number of getArrow()
    // to avoid highlighting the arrow
    if (row !== 9) {
      onHover(row);
    }
  };

  const onClose$ = () => {
    onClose(leg);
  };

  const cancel = (e) => {
    e.preventDefault();
    swal.close();
  };

  const submitCustomTod = (todArray, customTodMonths) => {
    const ted = add(new Date(leg?.eta), { months: customTodMonths });
    const todCode = todArray.map((tod, i, arr) => (i + 1 === arr.length ? tod : `${tod}/`)).join('').toString();
    updateLeg(leg?.ail_seq_num, {
      tod: 'X',
      tod_months: customTodMonths,
      tod_long_desc: todCode,
      tod_short_desc: todCode,
      tod_is_dropdown: false,
      ted,
    });
    swal.close();
  };

  const openTodModal = () => {
    swal({
      title: 'Tour of Duty',
      closeOnEsc: true,
      button: false,
      className: 'swal-aim-custom-tod',
      content: (
        <TodModal
          cancel={cancel}
          submitCustomTod={submitCustomTod}
        />
      ),
    });
  };


  const updateDropdown = (dropdown, value) => {
    if (dropdown === 'tod' && value === 'X') {
      openTodModal();
      return;
    }

    if (dropdown === 'tod') {
      const getTod = TODs.find(tod => tod.code === value);

      // Update TED to reflect ETA + TOD
      let ted = leg?.ted;
      if (!value || value === 'Y' || value === 'Z') {
        ted = '';
      } else if (leg?.eta && getTod?.months) {
        ted = add(new Date(leg.eta), { months: getTod.months });
      }

      updateLeg(leg?.ail_seq_num, {
        tod: getTod?.code,
        tod_long_desc: getTod?.long_description,
        tod_short_desc: getTod?.short_description,
        tod_months: null, // only custom/other TOD will have months
        // only legacy and custom/other TOD Agenda Item Legs will render as a dropdown
        tod_is_dropdown: true,
        ted,
      });
      return;
    }

    if (dropdown === 'eta') {
      // Update TED to reflect ETA + TOD
      let ted = leg?.ted;
      const getTod = TODs.find(tod => tod.code === leg.tod);
      const tod_ref_months = getTod?.months;

      if (leg?.tod === 'X') {
        // only custom/other TOD will have a tod_months value
        ted = add(new Date(value), { months: leg?.tod_months });
      } else if (leg?.tod === 'Y' || leg?.tod === 'Z' || !tod_ref_months) {
        // Legacy, Indefinite, and N/A TOD
        ted = '';
      } else {
        ted = add(new Date(value), { months: tod_ref_months });
      }
      updateLeg(leg?.ail_seq_num, {
        eta: value,
        ted,
      });
      return;
    }

    if (dropdown === 'ted' && isSeparation) {
      swal.close();
    }

    updateLeg(get(leg, 'ail_seq_num'), { [dropdown]: value });
  };

  const setShowLegacyActionToFalse = (key) => {
    setShowLegacyAction(false);
    updateDropdown(key, '');
  };
  const setShowLegacyTravelToFalse = (key) => {
    setShowLegacyTravel(false);
    updateDropdown(key, '');
  };

  useEffect(() => {
    if (!isEf) {
      updateLeg(get(leg, 'ail_seq_num'),
        { action_code: get(leg, 'action_code') || '', travel_code: get(leg, 'travel_code') || '' });
    }
  }, []);

  const clearTED = () => {
    updateLeg(leg?.ail_seq_num, { ted: '' });
    swal.close();
  };

  const calendarModalTED = () => {
    // TO DO: Update class names
    swal({
      title: 'Tour End Date (TED)',
      closeOnEsc: true,
      button: false,
      className: 'swal-aim-ted-calendar',
      content: (
        <div className="ted-modal-content-container">
          <div>
            <Calendar
              className="ted-react-calendar"
              onChange={(e) => updateDropdown('ted', e)}
            />
          </div>
          <div className="ted-buttons">
            <button onClick={cancel}>Cancel</button>
            <button onClick={clearTED}>Clear TED</button>
          </div>
        </div>
      ),
    });
  };

  const getDropdown = (type) => {
    // Attribute and constants to handle Travel dropdown
    let showLegacy = showLegacyTravel;
    let setShowLegacy = setShowLegacyTravelToFalse;
    let refArray = travelFunctions;
    let codeAttr = 'travel_code';
    let descAttr = 'travel_desc';
    let nullText = 'No Travel';
    // Attribute and constants to handle Action dropdown
    if (type === 'action') {
      showLegacy = showLegacyAction;
      setShowLegacy = setShowLegacyActionToFalse;
      refArray = getLegActionTypes;
      codeAttr = 'action_code';
      descAttr = 'action';
      nullText = 'Keep Unselected';
    }

    const noValidationRequired = ['travel_code'].includes(codeAttr);

    if (isEf) {
      return (<div className="read-only">{leg?.[descAttr] || 'None listed'}</div>);
    }

    return (showLegacy ?
      <div>
        {leg?.[descAttr]}
        <FA name="times" className="" onClick={() => setShowLegacy(codeAttr)} />
      </div> :
      <div className={noValidationRequired ? '' : 'error-message-wrapper'}>
        <div className={noValidationRequired ? '' : 'validation-error-message-label validation-error-message'}>
          {legValidation?.[codeAttr]?.errorMessage}
        </div>
        <div>
          <select
            className={`leg-dropdown ${(legValidation?.[codeAttr]?.valid || noValidationRequired) ? '' : 'validation-error-border'}`}
            value={leg?.[codeAttr] || ''}
            onChange={(e) => updateDropdown(codeAttr, e.target.value)}
            disabled={disabled}
          >
            <option key={null} value={''}>
              {nullText}
            </option>
            {refArray.map((a, i) => {
              const keyId = `${a?.code}-${i}`;
              return <option key={keyId} value={a?.code}>{a?.desc_text}</option>;
            })}
          </select>
        </div>
      </div>
    );
  };


  const closeOtherTod = () => {
    updateLeg(leg?.ail_seq_num, {
      tod: null,
      tod_months: null,
      tod_long_desc: null,
      tod_short_desc: null,
      tod_is_dropdown: true,
    });
  };

  const getTodDropdown = () => {
    const getTod = TODs.find(tod => tod.code === leg?.tod);
    if (isEf) {
      return <div className="read-only">{leg.tod_long_desc || 'None listed'}</div>;
    }

    if (!leg.tod_is_dropdown) {
      return (
        <div className="other-tod-wrapper">
          <div className="other-tod">
            {leg.tod_long_desc}
            {<FA name="times" className="other-tod-icon" onClick={closeOtherTod} />}
          </div>
        </div>
      );
    }

    return (
      <div className="error-message-wrapper">
        <div className="validation-error-message-label validation-error-message">
          {legValidation?.tod?.errorMessage}
        </div>
        <div>
          <select
            className={`leg-dropdown ${legValidation?.tod?.valid ? '' : 'validation-error-border'}`}
            value={getTod?.code || ''}
            onChange={(e) => updateDropdown('tod', e.target.value)}
            disabled={disabled}
          >
            <option key={null} disabled value={''}>
              Select TOD
            </option>
            {
              TODs.map((tod, i) => {
                const { code, long_description } = tod;
                const todKey = `${code}-${i}`; // custom tods will have the same code as other
                return <option key={todKey} value={code}>{long_description}</option>;
              })
            }
          </select>
        </div>
      </div>
    );
  };

  const onAddLocationClick = () => {
    setActiveAIL(leg?.ail_seq_num);
    setLegsContainerExpanded(false);
    updateResearchPaneTab(LocationsTabID);
  };

  const getCalendar = (value) => (
    disabled ?
      // Read only
      <div className="read-only">{formatMonthYearDate(leg?.[value]) || DEFAULT_TEXT}</div>
      :
      // Edit
      <div className="error-message-wrapper ail-form-ted">
        <div className="validation-error-message-label validation-error-message">
          {legValidation?.[value]?.errorMessage}
        </div>
        <div className={`${legValidation?.[value]?.valid ? '' : 'validation-error-border'}`}>
          {
            value === 'ted' && isSeparation ?
              <>
                {formatDate(leg?.[value]) || DEFAULT_TEXT}
                <FA name="calendar" onClick={calendarModalTED} />
              </>
              :
              <MonthYearDropdown
                date={leg?.[value]}
                updateDropdown={updateDropdown}
                dropdownType={value}
              />
          }
        </div>
      </div>
  );

  const getArrows = () => (
    <div className="aim-form-arrow-edit">
      {
        !isSeparation &&
        <FA name="arrow-down" />
      }
    </div>
  );

  const removeLocation = () => {
    updateLeg(leg?.ail_seq_num, {
      separation_location: null,
    });
  };

  const handleTED = () => {
    if (isSeparation) {
      return getCalendar('ted');
    }
    return (<div className="read-only">{ !leg?.ted ? DEFAULT_TEXT : formatMonthYearDate(leg.ted)}</div>);
  };

  const getLocation = () => {
    const location = leg?.separation_location;
    let displayText;

    if (location) {
      const { city, country, code } = location;
      displayText = (city && country) ? `${city}, ${country}` : city || country || code || '';
    }

    return (
      <div className="error-message-wrapper ail-form-ted">
        <div className="validation-error-message-label validation-error-message">
          {
            AIvalidation
              ?.legs
              ?.individualLegs?.[leg?.ail_seq_num]?.separation_location?.errorMessage
          }
        </div>
        {
          !isEf ?
            <div className={`${legValidation?.separation_location?.valid ? '' : 'validation-error-border'}`}>
              {displayText || DEFAULT_TEXT}
              {
                displayText ?
                  <FA name="times" className="" onClick={removeLocation} />
                  :
                  <FA name="globe" onClick={onAddLocationClick} />
              }
            </div>
            :
            <div className="read-only">{displayText || DEFAULT_TEXT}</div>
        }
      </div>
    );
  };

  const columnData = [
    {
      title: 'Action',
      content: (getDropdown('action')),
    },
    {
      title: 'Position Title',
      content: (<div>{defaultSepText || get(leg, 'pos_title') || DEFAULT_TEXT}</div>),
    },
    {
      title: 'Position Number',
      content: (<div>{defaultSepText || get(leg, 'pos_num') || DEFAULT_TEXT}</div>),
    },
    {
      title: 'Location/Org',
      content: isSeparation ?
        getLocation()
        :
        (<div className="read-only">{leg?.org || DEFAULT_TEXT}</div>),
    },
    {
      title: 'Languages',
      content: (<div>{defaultSepText || formatLang(leg?.languages || []) || DEFAULT_TEXT}</div>),
    },
    {
      title: 'Skills',
      content: (<div>{defaultSepText || leg?.custom_skills_description || DEFAULT_TEXT}</div>),
    },
    {
      title: 'ETA',
      content: (isSeparation ? <div className="read-only">{defaultSepText}</div> : getCalendar('eta')),
    },
    {
      title: '',
      content: (getArrows()),
    },
    {
      title: 'TED',
      content: (handleTED()),
    },
    {
      title: 'TOD',
      content: (isSeparation ? <div className="read-only">{defaultSepText}</div> : getTodDropdown()),
    },
    {
      title: 'Travel',
      content: (getDropdown()),
    },
    {
      title: 'Vice',
      content: formatVice(leg?.vice),
    },
    {
      title: 'PP/Grade',
      content: (<div>{leg?.combined_pp_grade}</div>),
    },
  ];

  const dropdowns = ['TOD', 'Action', 'Travel'];

  return (
    <>
      <div className={`grid-col-${legNum} grid-row-1`}>
        {
          !disabled &&
          <InteractiveElement className="remove-leg-button" onClick={() => onClose$(leg)} title="Remove leg">
            <FA name="times" />
          </InteractiveElement>
        }
      </div>
      {
        columnData.map((cData, i) => (
          <InteractiveElement
            className={`grid-col-${legNum} grid-row-${i + 2}${rowNum === (i + 2) ? ' grid-row-hover' : ''}${(includes(dropdowns, cData.title) && isEf) ? ' ef-pos-dropdown' : ''}`}
            onMouseOver={() => onHover$(i + 2)}
            onMouseLeave={() => onHover$('')}
            key={cData.title}
          >
            {cData.content}
          </InteractiveElement>
        ))
      }
    </>
  );
};

AgendaLegFormEdit.propTypes = {
  AIvalidation: AI_VALIDATION,
  isEf: PropTypes.bool,
  leg: PropTypes.shape({
    ail_seq_num: PropTypes.number,
    tod_other_text: PropTypes.string,
    tod_long_desc: PropTypes.string,
    tod: PropTypes.string,
    tod_months: PropTypes.number,
    tod_is_dropdown: PropTypes.bool,
    action: PropTypes.string,
    action_code: PropTypes.string,
    travel_code: PropTypes.string,
    travel_desc: PropTypes.string,
    vice: PropTypes.shape({}),
    ted: PropTypes.string,
    eta: PropTypes.string,
    is_separation: PropTypes.bool,
    separation_location: PropTypes.shape({}),
    org: PropTypes.string,
    pay_plan: PropTypes.string,
    grade: PropTypes.string,
    combined_pp_grade: PropTypes.string,
    languages: PropTypes.shape([]),
    custom_skills_description: PropTypes.string,
  }),
  legNum: PropTypes.number.isRequired,
  TODs: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
  legActionTypes: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
  travelFunctions: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
  onClose: PropTypes.func.isRequired,
  updateLeg: PropTypes.func.isRequired,
  setLegsContainerExpanded: PropTypes.func.isRequired,
  updateResearchPaneTab: PropTypes.func.isRequired,
  setActiveAIL: PropTypes.func.isRequired,
  onHover: PropTypes.func.isRequired,
  rowNum: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

AgendaLegFormEdit.defaultProps = {
  AIvalidation: {},
  isEf: false,
  leg: {},
  onClose: EMPTY_FUNCTION,
  updateLeg: EMPTY_FUNCTION,
  onHover: EMPTY_FUNCTION,
  rowNum: null,
};

export default AgendaLegFormEdit;