department-of-veterans-affairs/vets-website

View on GitHub
src/applications/financial-status-report/components/householdExpenses/InstallmentContract.jsx

Summary

Maintainability
F
4 days
Test Coverage
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setData } from 'platform/forms-system/src/js/actions';
import {
  VaTextInput,
  VaDate,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { parseISODate } from 'platform/forms-system/src/js/helpers';
import { isValidCurrency } from '../../utils/validations';
import ContractsExplainer from './ContractsExplainer';
import ButtonGroup from '../shared/ButtonGroup';

const defaultRecord = [
  {
    purpose: '',
    creditorName: '',
    originalAmount: '',
    unpaidBalance: '',
    amountDueMonthly: '',
    dateStarted: '',
    amountPastDue: '',
  },
];

export const SUMMARY_PATH = '/installment-contracts-summary';
export const START_PATH = '/installment-contracts';

const InstallmentContract = props => {
  const { data, goToPath, setFormData } = props;

  const { installmentContracts = [] } = data;

  const searchIndex = new URLSearchParams(window.location.search);
  let editIndex = parseInt(searchIndex.get('index'), 10);
  if (Number.isNaN(editIndex)) {
    editIndex = installmentContracts?.length ?? 0;
  }
  const isEditing = !Number.isNaN(editIndex) && editIndex >= 0;

  const index = isEditing ? Number(editIndex) : 0;

  const MAXIMUM_INSTALLMENT_AMOUNT = 1000000;

  // if we have creditCardBills and plan to edit, we need to get it from the creditCardBills
  const specificRecord = installmentContracts?.length
    ? installmentContracts[index]
    : defaultRecord[0];

  const [contractRecord, setContractRecord] = useState({
    ...(isEditing ? specificRecord : defaultRecord[0]),
  });

  const [purpose, setPurpose] = useState(contractRecord.purpose || null);

  const [creditorName, setCreditorName] = useState(
    contractRecord.creditorName || null,
  );

  // if valid returns true, if invalid returns false
  const validateLoanBegan = monthYear => {
    if (!monthYear || typeof monthYear !== 'string') return false;

    const [year] = monthYear.split('-');
    const todayYear = new Date().getFullYear();
    const isComplete = /\d{4}-\d{1,2}/.test(monthYear);

    return !(
      !isComplete ||
      (!!year && (parseInt(year, 10) > todayYear || parseInt(year, 10) < 1900))
    );
  };

  const { dateStarted } = contractRecord;

  const { month: fromMonth, year: fromYear } = parseISODate(dateStarted);

  const [submitted, setSubmitted] = useState(false);
  const [fromDateError, setFromDateError] = useState(null);

  const amountDueMonthlyError =
    !isValidCurrency(contractRecord.amountDueMonthly) ||
    (contractRecord.amountDueMonthly > MAXIMUM_INSTALLMENT_AMOUNT ||
      contractRecord.amountDueMonthly < 0)
      ? 'Please enter a minimum monthly payment amount less than $1,000,000'
      : null;

  const typeError = !purpose ? 'Please enter the contract type' : null;

  const handleChange = (key, value) => {
    setContractRecord(prevRecord => ({
      ...prevRecord,
      [key]: value,
    }));
  };

  const handlePurposeChange = event => {
    handleChange('purpose', event.target.value);
    setPurpose(event.target.value);
  };

  const handleCreditorNameChange = event => {
    handleChange('creditorName', event.target.value);
    setCreditorName(event.target.value);
  };

  const handleOriginalLoanAmountChange = event => {
    handleChange('originalAmount', event.target.value);
  };

  const handleUnpaidBalanceChange = event => {
    handleChange('unpaidBalance', event.target.value);
  };

  const handleAmountDueMonthlyChange = event => {
    handleChange('amountDueMonthly', event.target.value);
  };

  const handleAmountOverdueChange = event => {
    handleChange('amountPastDue', event.target.value);
  };

  const updateFormData = e => {
    setSubmitted(true);
    e.preventDefault();

    const loanBeganContainsGoodValue = validateLoanBegan(
      contractRecord.dateStarted,
    );

    if (!loanBeganContainsGoodValue) {
      setFromDateError('Please enter a valid date.');
      return;
    }

    if (typeError || amountDueMonthlyError) {
      return;
    }

    if (contractRecord.purpose && contractRecord.amountDueMonthly) {
      const updatedContractRecord = {
        ...contractRecord,
        amountPastDue: isValidCurrency(contractRecord.amountPastDue)
          ? contractRecord.amountPastDue
          : 0,
        originalAmount: isValidCurrency(contractRecord.originalAmount)
          ? contractRecord.originalAmount
          : 0,
        unpaidBalance: isValidCurrency(contractRecord.unpaidBalance)
          ? contractRecord.unpaidBalance
          : 0,
      };

      const newInstallmentContractArray = [...installmentContracts];
      newInstallmentContractArray[index] = updatedContractRecord;

      // update form data
      setFormData({
        ...data,
        installmentContracts: newInstallmentContractArray,
      });

      goToPath(SUMMARY_PATH);
    }
  };

  const handlers = {
    onSubmit: event => event.preventDefault(),
    onCancel: event => {
      event.preventDefault();
      if (installmentContracts.length === 0) {
        goToPath(START_PATH);
      } else {
        goToPath(SUMMARY_PATH);
      }
    },
    onUpdate: event => {
      event.preventDefault();
      updateFormData(event);
    },
    onBack: event => {
      event.preventDefault();
      goToPath(SUMMARY_PATH);
    },
    handleDateChange: (key, monthYear) => {
      const dateString = `${monthYear}-XX`;
      handleChange(key, dateString);
    },
  };

  const addUpdateButtonsText =
    installmentContracts.length === index ? 'Add' : 'Update';

  const renderAddCancelButtons = () => {
    return (
      <>
        <ButtonGroup
          buttons={[
            {
              label: 'Cancel',
              onClick: handlers.onCancel,
              isSecondary: true,
            },
            {
              label: `${addUpdateButtonsText} installment contract`,
              onClick: handlers.onUpdate,
              isSubmitting: 'prevent',
            },
          ]}
        />
      </>
    );
  };

  const renderContinueBackButtons = () => {
    return (
      <>
        <ButtonGroup
          buttons={[
            {
              label: 'Back',
              onClick: handlers.onCancel,
              isSecondary: true,
            },
            {
              label: 'Continue',
              onClick: updateFormData,
              isSubmitting: 'prevent',
            },
          ]}
        />
      </>
    );
  };

  return (
    <form onSubmit={updateFormData}>
      <fieldset className="vads-u-margin-y--2">
        <legend className="schemaform-block-title">
          <h3 className="vads-u-margin--0">
            {`${
              installmentContracts.length === index ? 'Add' : 'Update'
            } an installment contract or other debt`}
          </h3>
          <p className="vads-u-margin-bottom--neg1 vads-u-margin-top--3 vads-u-padding-bottom--0p25 vads-u-font-family--sans vads-u-font-weight--normal vads-u-font-size--base">
            If you have more than one installment contract or other debt, enter
            the information for one contract or debt below.
          </p>
        </legend>
        <ContractsExplainer />

        <VaTextInput
          width="xl"
          id="contractType"
          error={(submitted && typeError) || null}
          label="Type of contract or debt"
          name="contract-type"
          onInput={handlePurposeChange}
          required
          type="text"
          value={purpose || ''}
        />

        <VaTextInput
          width="xl"
          id="creditorName"
          label="Name of creditor who holds the contract or debt"
          name="creditor-name"
          onInput={handleCreditorNameChange}
          type="text"
          value={creditorName || ''}
        />

        <va-text-input
          hint={null}
          currency
          inputmode="decimal"
          label="Original loan amount"
          name="originalAmount"
          id="originalAmount"
          onInput={handleOriginalLoanAmountChange}
          type="decimal"
          value={contractRecord.originalAmount}
          width="md"
        />

        <va-text-input
          hint={null}
          currency
          inputmode="decimal"
          label="Unpaid balance"
          name="unpaidBalance"
          id="unpaidBalance"
          min={0}
          max={MAXIMUM_INSTALLMENT_AMOUNT}
          onInput={handleUnpaidBalanceChange}
          type="decimal"
          value={contractRecord.unpaidBalance}
          width="md"
        />

        <va-text-input
          error={(submitted && amountDueMonthlyError) || null}
          hint={null}
          currency
          required
          inputmode="decimal"
          label="Minimum monthly payment amount"
          name="amountDueMonthly"
          id="amountDueMonthly"
          min={0}
          max={MAXIMUM_INSTALLMENT_AMOUNT}
          onInput={handleAmountDueMonthlyChange}
          type="decimal"
          value={contractRecord.amountDueMonthly}
          width="md"
        />

        <VaDate
          monthYearOnly
          data-testid="loanBegan"
          value={`${fromYear}-${fromMonth}`}
          label="Date the loan began"
          name="loanBegan"
          onDateChange={e =>
            handlers.handleDateChange('dateStarted', e.target.value)
          }
          onDateBlur={e =>
            setFromDateError(
              validateLoanBegan(e.target.value)
                ? null
                : 'Please enter a valid date',
            )
          }
          required
          uswds
          error={(submitted && fromDateError) || null}
        />

        <va-text-input
          hint={null}
          currency
          inputmode="decimal"
          label="Amount overdue"
          name="amountPastDue"
          id="amountPastDue"
          onInput={handleAmountOverdueChange}
          type="decimal"
          value={contractRecord.amountPastDue}
          width="md"
        />
      </fieldset>
      <div>
        {installmentContracts.length > 0
          ? renderAddCancelButtons()
          : renderContinueBackButtons()}
      </div>
    </form>
  );
};

const mapStateToProps = ({ form }) => {
  return {
    formData: form.data,
  };
};

const mapDispatchToProps = {
  setFormData: setData,
};

InstallmentContract.propTypes = {
  data: PropTypes.shape({
    installmentContracts: PropTypes.array,
  }).isRequired,
  goToPath: PropTypes.func.isRequired,
  setFormData: PropTypes.func.isRequired,
};

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(InstallmentContract);