containers/Settings/GamesSettings.js

/* eslint-disable operator-assignment */
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { getGamesSettings, getCashoutNumberofSlots } from '../../selectors/settings';
import { PERMISSIONS, GAMES_SETTINGS_FIELDS, SVG_ICONS, CONFIRM_MODAL_CONTENT, MODALS } from '../../constants';
import { checkPermisson, getCurrency, getActiveModal, getNavigatedLink, isFetched } from '../../selectors/common';
import SVGComponent from '../../components/SVG/SVGComponent';
import { parsePoints, parseDecimal } from '../../utils/common';
import validateFields from '../../utils/validation';
import ConfirmationModal from '../../components/Modals/ConfirmationModal';
import { aSetNavigationInfo, aSetOpenModal } from '../../reducers/common';
import { aGetGamesSettings } from '../../reducers/settings';
import { aSaveGamesSettings } from '../../reducers/actions';
import { withRouterHooks } from '../../utils/router';

/**
 * @class
 * @param navigate
 * @param navigate.navigate
 * @property {object} props
 * @property {Array} gamesSettings All games settings data
 * @property {boolean} checkPermisson Check if user have
 * permission for specific action or view
 * @property {string} currency Currency sign
 * @property {string} activeModal Active modal
 * @property {object} modalData Modal data
 * @property {Function} getGamesSettings Call API for games settings
 * @property {Function} saveLeaguesSettings Call API to save changes
 * @property {Function} setOpenModal Open modal
 * @property {object} nextNavigationLink link for navigation
 * @property {Function} setNavigationInfo set navigation info for unsaved data
 */
const GamesSettings = ({ navigate }) => {
  /**
   * @member {object}
   * @property {boolean} hasChanges
   * @property {object|null} currentData
   * @property {Array|null} multipleBonusPercentages
   * @property {object} errorMgs
   */
  const [state, setState] = useState({
    hasChanges: false,
    currentData: null,
    multipleBonusPercentages: null,
    errorMgs: {},
  });

  const dispatch = useDispatch();
  const editPermission = useSelector((stateSelector) => checkPermisson(stateSelector, PERMISSIONS.GAMES_SETTINGS_EDIT));
  const gamesSettings = useSelector(getGamesSettings);
  const currency = useSelector(getCurrency);
  const activeModal = useSelector(getActiveModal);
  const nextNavigationLink = useSelector(getNavigatedLink);
  const isFetchedSelector = useSelector(isFetched);
  const cashoutNumberofSlots = useSelector(getCashoutNumberofSlots);
  /**
   * Get game settings data
   *
   * @returns {void}
   */
  useEffect(() => {
    dispatch(aGetGamesSettings());
    return () => {
      dispatch(aSetNavigationInfo({}));
    };
  }, [dispatch]);

  /**
   * Load original data
   *
   * @function
   * @returns {void}
   */
  const refresh = useCallback(() => {
    const currentData = { ...gamesSettings };
    GAMES_SETTINGS_FIELDS.AmtLimitSettings.map((field) => {
      currentData[field.code] = currentData[field.code] / 100;
    });
    currentData.cashoutNumberOfSlotsList = cashoutNumberofSlots;
    setState((prevState) => ({
      ...prevState,
      hasChanges: false,
      currentData,
      multipleBonusPercentages: [...gamesSettings.multipleBonusPercentages],
      errorMgs: {},
    }));
  }, [gamesSettings, cashoutNumberofSlots]);
  /**
   * Update current data when api is fetched
   * Update check for modified fields
   *
   * @function
   * @param {object} prevProps
   * @param {object} prevState
   * @returns {void}
   */
  useEffect(() => {
    if (state.hasChanges === false && gamesSettings && state.currentData === null) {
      refresh();
    }
  }, [gamesSettings, state.hasChanges, refresh, state.currentData]);

  useEffect(() => {
    dispatch(aSetNavigationInfo({ hasFieldDataModified: state.hasChanges }));
  }, [state.hasChanges, dispatch]);

  /**
   * Handle change on inputs
   *
   * @function
   * @param {object} field
   * @param {Event} e
   * @returns {void}
   */
  const handleInputChange = (field, e) => {
    const value = e.target.value;
    const temp = state.currentData;
    temp[field.code] = field.getValue(value);
    setState((prevState) => ({
      ...prevState,
      currentData: temp,
      hasChanges: true,
    }));
  };

  /**
   * Handle change on multiple bonus inputs
   *
   * @function
   * @param {Event} e
   * @returns {void}
   */
  const handleMultipleBonusChange = (e) => {
    const multipleBonusPercentages = [...state.multipleBonusPercentages];
    const code = parseInt(e.target.id);
    const value = e.target.value;
    multipleBonusPercentages[code] = parseInt(value.replace(/[,.]/g, '')) || 0;
    setState((prevState) => ({
      ...prevState,
      hasChanges: true,
      multipleBonusPercentages,
    }));
  };

  /**
   * Save process - validation before save
   *
   * @function
   */
  // eslint-disable-next-line sonarjs/cognitive-complexity
  const saveProcess = () => {
    const validationParams = [];
    const currentData = {};
    Object.keys(GAMES_SETTINGS_FIELDS).map((objectKey) => {
      GAMES_SETTINGS_FIELDS[objectKey].map((key) => {
        validationParams.push({
          label: key.name,
          fieldName: key.code,
          // eslint-disable-next-line max-len
          value: key.isBoostPercentage
            ? Number(parseDecimal(state?.[objectKey]?.[key.code], 100))
            : key.float
            ? parseDecimal(state.currentData[key.code], 100)
            : state.currentData[key.code] || 0,
          rules: key.validationRules,
          comparatorValue: key.comparatorValue ?? state.currentData.couponMinStake,
          comparatorMaxValue:
            key.code === 'matchLength'
              ? state.currentData.roundInterval - key.comparatorMinValue
              : key.comparatorMaxValue,
          minValue: key.minValue ?? state.currentData.couponMinStake,
        });
        currentData[key.code] = key.currency ? state.currentData[key.code] * 100 : state.currentData[key.code];
      });
    });
    validateFields(validationParams)
      .then(() => {
        currentData.multipleBonusPercentages = state.multipleBonusPercentages;
        dispatch(aSaveGamesSettings(currentData));
        setState((prevState) => ({
          ...prevState,
          hasChanges: false,
          errorMgs: {},
        }));
      })
      .catch((errorMgs) => {
        setState((prevState) => ({
          ...prevState,
          errorMgs,
        }));
      })
      .finally(() => {
        if (activeModal === MODALS.SAVE_BEFORE_NAVIGATION && Object.keys(state.errorMgs).length) {
          dispatch(aSetOpenModal({ modal: '' }));
          dispatch(aSetNavigationInfo({ hasFieldDataModified: state.hasChanges }));
        }
      });
  };

  /**
   * @function
   * @param {object} validationParams
   * @returns {object}
   */
  const processErrorMessages = (validationParams) => {
    const errorMessages = { ...state.errorMgs };
    const fieldName = validationParams[0]?.fieldName;
    delete errorMessages[fieldName];
    return errorMessages;
  };

  const validateInputField = (fieldName, key) => {
    const validationParams = [];
    const validationParam = GAMES_SETTINGS_FIELDS[key]?.find((param) => param.name === fieldName);
    if (validationParam) {
      validationParams.push({
        label: validationParam.name,
        fieldName: validationParam.code,
        // eslint-disable-next-line max-len
        value: validationParam.isBoostPercentage
          ? Number(parseDecimal(state?.[key]?.[validationParam.code], 100))
          : validationParam.float
          ? parseDecimal(state.currentData[validationParam.code], 100)
          : state.currentData[validationParam.code] || 0,
        rules: validationParam.validationRules,
        comparatorValue: validationParam.comparatorValue ?? state.currentData.couponMinStake,
        comparatorMaxValue:
          validationParam.code === 'matchLength'
            ? state.currentData.roundInterval - validationParam.comparatorMinValue
            : validationParam.comparatorMaxValue,
        minValue: validationParam.minValue ?? state.currentData.couponMinStake,
      });
    }
    validateFields(validationParams)
      .then(() => {
        const updatedErrorMessages = processErrorMessages(validationParams);
        setState((prevState) => ({
          ...prevState,
          errorMgs: updatedErrorMessages,
        }));
      })
      .catch((errorMgs) => {
        setState((prevState) => ({
          ...prevState,
          errorMgs: {
            ...prevState.errorMgs,
            ...errorMgs,
          },
        }));
      });
  };

  /**
   * Render all imput settings fields
   *
   * @function
   * @param {object} field
   * @param {string} key
   * @returns {view}
   */
  // eslint-disable-next-line sonarjs/cognitive-complexity
  const renderInputSettings = (field, key) => {
    if (state.currentData) {
      const value = field.currency
        ? parsePoints(state.currentData[field.code], ',')
        : field.decimal
        ? parseDecimal(state.currentData[field.code], 1)
        : field.float
        ? parseDecimal(state.currentData[field.code], 100)
        : state.currentData[field.code] &&
          // eslint-disable-next-line no-restricted-globals
          !isNaN(parseInt(state.currentData[field.code]))
        ? parseInt(state.currentData[field.code])
        : 0;
      return (
        <div className="user__settings-row" key={field.name}>
          <div>
            <div className="d-flex justify-content-end">
              <div className="user__settings-txt">{field.name}</div>
              <div className="user__settings-icon ml-20">
                <SVGComponent className="icon-info" src={`${SVG_ICONS.utility}#info`} />
                <div className="user__settings-icon-info">{field.info}</div>
              </div>
            </div>
          </div>
          <div className={`user__settings-input-wrap ${state.errorMgs[field.code] && 'error'}`}>
            {field.isSelectField ? (
              <select
                value={value}
                disabled={field.disabled || ''}
                className="user__settings-input"
                onChange={handleInputChange.bind(this, field)}
              >
                {state.currentData[field.codeSelectField]?.map((valueOption) => (
                  <option key={`option-${valueOption?.id}`} value={valueOption?.id}>
                    {valueOption?.label}
                  </option>
                ))}
              </select>
            ) : (
              <input
                id={`input-${field.name}`}
                className="user__settings-input"
                value={value}
                onChange={handleInputChange.bind(this, field)}
                name={field.name}
                onBlur={validateInputField.bind(this, field.name, key)}
              />
            )}
            {field.currency === true ? <div className="currency">{currency}</div> : null}
            {state.errorMgs[field.code]}
          </div>
          <div className="user__settings-info">{field.desc?.replace('%s', currency)}</div>
        </div>
      );
    }
    return null;
  };

  /**
   * Render multiple boost input
   *
   * @function
   * @returns {view}
   */
  const renderMultipleBoost = () => {
    const col = [];
    let index = 0;
    const multipleBonusPercentages = state.multipleBonusPercentages;
    if (multipleBonusPercentages) {
      for (let i = 0; i < 20; i += 5) {
        const row = [];
        for (let j = 0; j < 5; j += 1) {
          index = i + j;
          const value = parseDecimal(multipleBonusPercentages[index], 100);

          row.push(
            <div className="user__settings-row" key={index} disabled={index === 0}>
              <div className="text-right">{index + 1}</div>
              <div className={`user__settings-input-wrap sm ${state.errorMgs[index] !== undefined ? 'error' : ''}`}>
                <input
                  id={index}
                  name={`multipleBoostPercentage-${index}`}
                  className="user__settings-input"
                  value={value}
                  onChange={handleMultipleBonusChange}
                  onBlur={validateInputField.bind(this, `Percentage [${index + 1}]`, 'multipleBonusPercentages')}
                />
                {state.errorMgs?.[index] !== undefined && state.errorMgs[index]}
              </div>
              <div>%</div>
            </div>
          );
        }
        col.push(
          <div className="col-sm-3" key={i}>
            {row}
          </div>
        );
      }
    }
    return <div className="row mt-30 no-gutters">{col}</div>;
  };

  /**
   * Render buttons
   *
   * @returns {view}
   */
  const renderButtons = () => (
    <div className="d-flex justify-content-end mt-20">
      <button
        id="gameSettings_save"
        type="button"
        className="btn btn-primary"
        onClick={saveProcess}
        disabled={!state.hasChanges}
      >
        Save
      </button>
      <button
        id="gameSettings_cancel"
        type="button"
        className="btn btn-secondary ml-3"
        onClick={refresh}
        disabled={!state.hasChanges}
      >
        Cancel
      </button>
    </div>
  );

  /**
   * close modal and navigate
   *
   * @returns {void}
   */
  const unsavedNavigation = () => {
    dispatch(aSetOpenModal({ modal: '' }));
    navigate(nextNavigationLink);
  };

  /**
   * Render
   *
   * @returns {view}
   */
  if (isFetchedSelector) {
    return (
      <>
        <div className="user mt-20">
          <div className="d-flex">
            <div className="user__settings">
              <div className="user__settings-title">Time settings</div>
              <hr className="line-spacer" />
              <div className="user__settings-wrap">
                {GAMES_SETTINGS_FIELDS.TimeSettings.map((field) => renderInputSettings(field, 'TimeSettings'))}
              </div>
            </div>
            <div className="user__settings ml-20">
              <div className="user__settings-title">Cashout settings</div>
              <hr className="line-spacer" />
              <div className="user__settings-wrap" />
              {GAMES_SETTINGS_FIELDS.CashoutSettings.map((field) => renderInputSettings(field, 'CashoutSettings'))}
            </div>
          </div>
          <div className="user__settings mt-20">
            <div className="user__settings-title">Amount limit settings</div>
            <hr className="line-spacer" />
            <div className="user__settings-wrap">
              {GAMES_SETTINGS_FIELDS.AmtLimitSettings.map((field) => renderInputSettings(field, 'AmtLimitSettings'))}
            </div>
          </div>
          <div className="user__settings mt-20">
            <div className="user__settings-title">Bonus Settings</div>
            <hr className="line-spacer my-4" />
            <div className="user__settings-wrap">
              {GAMES_SETTINGS_FIELDS.BonusSettings.map((field) => renderInputSettings(field, 'BonusSettings'))}
              <hr className="line-spacer my-4" />
              <div className="d-flex justify-content-between align-items-center">
                <div className="user__settings-title">Multiple Boost percentages</div>
                <div className="user__settings-icon">
                  <SVGComponent className="icon-info" src={`${SVG_ICONS.utility}#info`} />
                  <div className="user__settings-icon-info">
                    Bonus amount percentage for number of events in a coupon.
                  </div>
                </div>
              </div>
              {renderMultipleBoost()}
            </div>
          </div>
          <hr className="line-spacer" />
          {editPermission ? renderButtons() : null}
        </div>
        {activeModal === MODALS.SAVE_BEFORE_NAVIGATION ? (
          <ConfirmationModal
            modalData={CONFIRM_MODAL_CONTENT.SAVE_CHANGES}
            cancel={unsavedNavigation}
            confirmation={saveProcess}
            cancelBtnText="Cancel"
            confirmBtnText="Save"
            logo
          />
        ) : null}
      </>
    );
  }
  return null;
};

export default withRouterHooks(GamesSettings);