MetaPhase-Consulting/State-TalentMAP

View on GitHub
src/Components/Agenda/AgendaItemResearchPane/RemarksGlossary/RemarksGlossary.jsx

Summary

Maintainability
A
0 mins
Test Coverage
F
52%
import { useEffect, useState } from 'react';
import { find, isEqual, orderBy, uniqBy } from 'lodash';
import PropTypes from 'prop-types';
import FA from 'react-fontawesome';
import InteractiveElement from 'Components/InteractiveElement';
import TextInput from 'Components/TextInput';
import { EMPTY_FUNCTION } from 'Constants/PropTypes';
import Fuse from 'fuse.js';

const RemarksGlossary = ({ isReadOnly, remarks, remarkCategories,
  userSelections, updateSelection }) => {
  const [textInputs, setTextInputs] = useState({});

  const setTextInput = (rSeq, riSeq, value) => {
    const textInputs$ = { ...textInputs };

    if (!textInputs$[rSeq.toString()]) {
      textInputs$[rSeq.toString()] = {};
      textInputs$[rSeq.toString()][riSeq.toString()] = value;
    } else {
      textInputs$[rSeq.toString()][riSeq.toString()] = value;
    }
    setTextInputs(textInputs$);
  };

  const setTextInputBulk = () => {
    const textInputs$ = {};

    remarks.forEach(r => {
      const userRemark = find(userSelections, { seq_num: r.seq_num });
      r.remark_inserts.forEach(ri => {
        const userRemarkInsert = find(userRemark?.user_remark_inserts,
          { aiririseqnum: ri.riseqnum });
        textInputs$[(r.seq_num).toString()] ??= {};
        textInputs$[(r.seq_num).toString()][(ri.riseqnum).toString()] =
          userRemarkInsert?.airiinsertiontext || ri.riinsertiontext;
      });
    });

    if (!isEqual(textInputs$, textInputs)) {
      setTextInputs(textInputs$);
    }
  };

  const getTextInputValue = (rSeq, riSeq) => textInputs?.[rSeq]?.[riSeq] || '';

  const renderText = (r, disabled) => {
    const rText = r?.text?.split(/(\s+)/) || '';
    const rInserts = r?.remark_inserts || [];

    rInserts.forEach((a) => {
      const rInsertionText = a?.riinsertiontext;
      const rTextI = rText.indexOf(rInsertionText);

      if (rTextI > -1) {
        let remarkInsertValue = getTextInputValue(a?.rirmrkseqnum, a?.riseqnum);
        remarkInsertValue = remarkInsertValue[0] === '{' ? '' : remarkInsertValue;

        // {text}, {text1}, {text2} -> text
        // {#}, {cable#} -> number, cablenumber
        // date -> MM/DD/YYYY
        const rInsertionTextSanitized = rInsertionText.replace(/[{}\d]/g, '')
          .replace(/#/g, 'number')
          .replace(/date/g, 'MM/DD/YYYY');

        rText.splice(rTextI, 1, <TextInput
          value={remarkInsertValue}
          changeText={v => setTextInput(a?.rirmrkseqnum, a?.riseqnum, v)}
          customContainerClass="remark-input"
          placeholder={rInsertionTextSanitized}
          id="remarks-custom-input"
          key={a.riseqnum}
          inputProps={{ autoComplete: 'off' }}
          disabled={disabled}
        />);
      }
    });
    return (<>
      <div className="remark-input-container">{rText}</div>
    </>);
  };

  const remarkStatus = (r) => {
    // disable if any insertions are empty
    const rInserts = r?.remark_inserts || [];
    const someAreEmpty = rInserts.some((a) => {
      const remarkInsertValue = getTextInputValue(a?.rirmrkseqnum, a?.riseqnum);
      const remarkInsertValueTrimmed = remarkInsertValue.trim();
      return (remarkInsertValueTrimmed[0] === '{' || remarkInsertValueTrimmed === '');
    });

    const selected = find(userSelections, { seq_num: r.seq_num });
    const allDisabled = isReadOnly;
    const inputDisabled = isReadOnly || (someAreEmpty && !selected);

    return { selected, allDisabled, inputDisabled };
  };

  const [remarks$, setRemarks$] = useState(remarks);

  const fuseOptions = {
    shouldSort: false,
    findAllMatches: true,
    tokenize: true,
    includeScore: false,
    threshold: 0.25,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
      'text',
    ],
  };

  const fuse$ = new Fuse(remarks, fuseOptions);

  const search = a => setRemarks$(fuse$.search(a).map(({ item }) => item));
  const [term, setTerm] = useState('');

  const remarks$$ = term ? remarks$ : remarks;

  const remarkCategoriesCodes = uniqBy(remarkCategories, 'code');
  const remarkCategories$ = orderBy(remarkCategoriesCodes, 'desc_text');

  const processClick = remark => {
    const el = document.getElementById(`remark-category-${remark.code}`);
    el.scrollIntoView();
  };

  useEffect(() => {
    setTextInputBulk();
  }, []);

  return (
    <div className="usa-grid-full remarks-glossary-container">
      <div className="filter">
        <TextInput
          changeText={e => { search(e); setTerm(e); }}
          value={term}
          labelSrOnly
          placeholder="Search for Remarks"
          id="remarks-search"
          inputProps={{
            autoComplete: 'off',
          }}
        />
        <FA className="close-fa" name={term && 'close'} onClick={() => setTerm('')} />
      </div>
      <div className="remarks-glossary-container">
        <div className="usa-grid-full remarks-categories-container">
          {remarkCategories$.map(a => (
            <a key={a.desc_text} tabIndex={0} role="button" onClick={() => processClick(a)}>{a.desc_text}</a>))}
        </div>
        {remarkCategories$.map(category => {
          const remarksInCategory = orderBy(remarks$$.filter(f => f.rc_code === category.code), 'order_num');

          return (
            <div key={category.code}>
              <div id={`remark-category-${category.code}`} className={`remark-category remark-category--${category.code}`}>
                {category.desc_text}
              </div>
              <ul>
                {remarksInCategory.map(r => {
                  const rStatus = remarkStatus(r);
                  return (<li key={r.seq_num}>
                    <InteractiveElement
                      onClick={() => rStatus?.inputDisabled ? {} : updateSelection(r, textInputs)}
                    >
                      <FA
                        name={`${rStatus?.selected ? 'minus-circle' : 'plus-circle'}`}
                        className={`${rStatus?.inputDisabled ? 'fa-disabled' : ''}`}
                      />
                    </InteractiveElement>
                    {renderText(r, rStatus?.allDisabled || rStatus?.selected)}
                  </li>);
                })}
              </ul>
            </div>
          );
        })}
      </div>
    </div>
  );
};

RemarksGlossary.propTypes = {
  isReadOnly: PropTypes.bool,
  userSelections: PropTypes.arrayOf(
    PropTypes.shape({
      seq_num: PropTypes.number,
      rc_code: PropTypes.string,
      order_num: PropTypes.number,
      short_desc_text: PropTypes.string,
      ari_insertions: PropTypes.shape({
        ri_seq_num: PropTypes.number,
        ari_insertion_text: PropTypes.string,
      }),
      text: PropTypes.string,
      active_ind: PropTypes.string,
    }),
  ),
  remarks: PropTypes.arrayOf(
    PropTypes.shape({
      seq_num: PropTypes.number,
      rc_code: PropTypes.string,
      order_num: PropTypes.number,
      short_desc_text: PropTypes.string,
      ri_insertions: PropTypes.arrayOf(
        PropTypes.shape({
          ri_seq_num: PropTypes.number,
          ri_insertion_text: PropTypes.string,
        }),
      ),
      text: PropTypes.string,
      ref_text: PropTypes.string,
      active_ind: PropTypes.string,
    }),
  ),
  remarkCategories: PropTypes.arrayOf(PropTypes.shape({})),
  updateSelection: PropTypes.func,
};

RemarksGlossary.defaultProps = {
  isReadOnly: false,
  remarks: [],
  remarkCategories: [],
  userSelections: [],
  updateSelection: EMPTY_FUNCTION,
};

export default RemarksGlossary;