/* eslint-disable max-len */
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable operator-assignment */
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getGamesShopSettings } from '../../selectors/settings';
import {
PERMISSIONS,
SVG_ICONS,
CONFIRM_MODAL_CONTENT,
MODALS,
GAMES_SETTINGS_SHOP_FIELDS,
GAME_SETTINGS_SHOP_LEAGUES,
LEAGUE_POSITION_MAP,
VALIDATION_RULES,
SUPERVISOR_CONFIG_CODE,
} 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 RoundShiftDetails from '../../components/RoundShiftDetails';
import { aSetNavigationInfo, aSetOpenModal } from '../../reducers/common';
import { aGetGamesShopSettings } from '../../reducers/settings';
import { aSaveGamesShopSettings } from '../../reducers/actions';
import { withRouterHooks } from '../../utils/router';
/**
* @class
* @param navigate
* @param navigate.navigate
* @property {Array} gamesShopSettings All games shop 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} getGamesShopSettings Call API for games shop settings
* @property {Function} setOpenModal Open modal
* @property {object} nextNavigationLink link for navigation
* @property {Function} setNavigationInfo set navigation info for unsaved data
*/
const GamesSettingsShop = ({ navigate }) => {
/**
* @member {object}
* @property {boolean} hasChanges
* @property {object|null} currentData
* @property {Array|null} multipleBonusPercentages
* @property {object} errorMgs
* @property {string} roundShifts
*/
const [state, setState] = useState({
hasChanges: false,
currentData: null,
multipleBonusPercentages: null,
errorMgs: {},
roundShifts: null,
shopFieldConfigs: { ...GAMES_SETTINGS_SHOP_FIELDS },
});
const dispatch = useDispatch();
const editPermission = useSelector((stateSelector) =>
checkPermisson(stateSelector, PERMISSIONS.SHOP_GAMES_SETTINGS_EDIT)
);
const gamesShopSettings = useSelector(getGamesShopSettings);
const currency = useSelector(getCurrency);
const activeModal = useSelector(getActiveModal);
const nextNavigationLink = useSelector(getNavigatedLink);
const isFetchedSelector = useSelector(isFetched);
/**
* Get game settings data
*
* @returns {void}
*/
useEffect(() => {
dispatch(aGetGamesShopSettings());
return () => {
dispatch(aSetNavigationInfo({}));
};
}, [dispatch]);
/**
* Load original data
*
* @function
* @returns {void}
*/
const refresh = useCallback(() => {
const { shopSelfService, ...otherShopSettings } = gamesShopSettings;
const newSscConfigs = GAMES_SETTINGS_SHOP_FIELDS.sscConfig.map((config) => {
const newConfig = { ...config };
if (newConfig.code === SUPERVISOR_CONFIG_CODE) {
newConfig.desc = `(Should be greater or equal to No Placebet Interval (${gamesShopSettings.noPlaceBetInterval}))`;
newConfig.validationRules = [VALIDATION_RULES.CUSTOM];
newConfig.customValidation = (val) => parseInt(val, 10) >= parseInt(gamesShopSettings.noPlaceBetInterval, 10);
newConfig.customErrorMessage = `'Supervisor No Bet Time should be greater or equal to No Placebet Interval (${gamesShopSettings.noPlaceBetInterval})'`;
}
return newConfig;
});
const currentData = {
...otherShopSettings,
...shopSelfService.ssc,
noBetTimeSupervisor: shopSelfService.supervisor.noBetTime,
};
GAMES_SETTINGS_SHOP_FIELDS.AmtLimitSettings.map((field) => {
currentData[field.code] = currentData[field.code] / 100;
});
const roundShifts = gamesShopSettings?.roundShifts?.replaceAll('-', '_');
setState((prevState) => ({
...prevState,
shopFieldConfigs: { ...prevState.shopFieldConfigs, sscConfig: newSscConfigs },
hasChanges: false,
currentData,
multipleBonusPercentages: [...gamesShopSettings.multipleBonusPercentages],
roundShifts,
errorMgs: {},
}));
}, [gamesShopSettings]);
/**
* 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 && gamesShopSettings && state.currentData === null) {
refresh();
}
}, [gamesShopSettings, state.hasChanges, refresh, state.currentData]);
useEffect(() => {
dispatch(aSetNavigationInfo({ hasFieldDataModified: state.hasChanges }));
}, [state.hasChanges, dispatch]);
useEffect(
function onChangeNoPlaceBetInterval() {
// If noPlaceBetInterval is changed, update the sscConfig validation rules
// to ensure noBetTimeSupervisor is greater than noPlaceBetInterval
if (state.currentData?.noPlaceBetInterval !== undefined && state.currentData?.noPlaceBetInterval !== null) {
setState((prevState) => {
const newSscConfigs = (prevState.shopFieldConfigs.sscConfig || []).map((config) => {
const newConfig = { ...config };
if (newConfig.code === SUPERVISOR_CONFIG_CODE) {
newConfig.desc = `(Should be greater or equal to No Placebet Interval (${prevState.currentData.noPlaceBetInterval}))`;
newConfig.validationRules = [VALIDATION_RULES.CUSTOM];
newConfig.customValidation = (val) =>
parseInt(val, 10) >= parseInt(prevState.currentData.noPlaceBetInterval, 10);
newConfig.customErrorMessage = `'Supervisor No Bet Time should be greater or equal to No Placebet Interval (${prevState.currentData.noPlaceBetInterval})'`;
}
return newConfig;
});
return {
...prevState,
shopFieldConfigs: { ...prevState.shopFieldConfigs, sscConfig: newSscConfigs },
};
});
}
},
[state.currentData?.noPlaceBetInterval]
);
/**
* Handle change on inputs
*
* @function
* @param {object} field
* @param {Event} e
* @param {any} customValue
* @returns {void}
*/
const handleInputChange = (field, e, customValue) => {
// if event is not passed, that means we want a custom value from input
const value = e === null ? customValue : 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
*/
const saveProcess = () => {
const validationParams = [];
const currentData = {};
Object.keys(state.shopFieldConfigs).map((objectKey) => {
state.shopFieldConfigs[objectKey].map((key) => {
const roundShiftInputCheck = GAME_SETTINGS_SHOP_LEAGUES.includes(key.code);
const dataPosition = LEAGUE_POSITION_MAP[key.code];
const roundShiftValue = parseInt(state.roundShifts.split('_')[dataPosition]);
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.roundShift
? roundShiftValue
: key.float
? parseDecimal(state.currentData[key.code], 100)
: state.currentData[key.code] || 0,
rules: key.validationRules,
comparatorValue: roundShiftInputCheck
? state.currentData.roundInterval
: key.comparatorValue ?? state.currentData.couponMinStake,
comparatorMaxValue:
key.code === 'matchLength'
? state.currentData.roundInterval - key.comparatorMinValue
: key.comparatorMaxValue || 0,
minValue: key.minValue ?? state.currentData.couponMinStake,
formValues: state.currentData,
customValidation: key.customValidation,
customErrorMessage: key.customErrorMessage,
});
if (!roundShiftInputCheck) {
currentData[key.code] = key.currency ? state.currentData[key.code] * 100 : state.currentData[key.code];
}
});
});
validateFields(validationParams)
.then(() => {
// extract ssc properties from data
const {
idleTimeAllowed,
maxBetsPerBooking,
noBetTime,
noBetTimeSupervisor,
postBookLogoutTime,
prepaidNoBetTime,
...dataToSave
} = currentData;
dataToSave.shopSelfService = {
ssc: {
idleTimeAllowed,
maxBetsPerBooking,
noBetTime,
postBookLogoutTime,
prepaidNoBetTime,
},
supervisor: {
noBetTime: noBetTimeSupervisor,
},
};
dataToSave.multipleBonusPercentages = state.multipleBonusPercentages;
dataToSave.roundShifts = state.roundShifts?.replaceAll('_', '-');
dispatch(aSaveGamesShopSettings(dataToSave));
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 = state.shopFieldConfigs[key]?.find((param) => param.name === fieldName);
if (validationParam) {
const roundShiftInputCheck = GAME_SETTINGS_SHOP_LEAGUES.includes(validationParam.code);
const dataPosition = LEAGUE_POSITION_MAP[validationParam.code];
const roundShiftValue = parseInt(state.roundShifts.split('_')[dataPosition]);
validationParams.push({
label: validationParam.name,
fieldName: validationParam.code,
value: validationParam.isBoostPercentage
? Number(parseDecimal(state?.[key]?.[validationParam.code], 100))
: validationParam.roundShift
? roundShiftValue
: validationParam.float
? parseDecimal(state.currentData[validationParam.code], 100)
: state.currentData[validationParam.code] || 0,
rules: validationParam.validationRules,
comparatorValue: roundShiftInputCheck
? state.currentData.roundInterval
: validationParam.comparatorValue ?? state.currentData.couponMinStake,
comparatorMaxValue:
validationParam.code === 'matchLength'
? state.currentData.roundInterval - validationParam.comparatorMinValue
: validationParam.comparatorMaxValue || 0,
minValue: validationParam.minValue ?? state.currentData.couponMinStake,
formValues: state.currentData,
customValidation: validationParam.customValidation,
customErrorMessage: validationParam.customErrorMessage,
});
}
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}
*/
const renderInputSettings = (field, key) => {
if (state.currentData) {
const value = field.currency
? parsePoints(state.currentData[field.code], ',')
: 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'}`}>
<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}
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);
};
/**
* Handle change on roundShifts
*
* @function
* @param {Event} e
* @returns {void}
*/
const handleRoundShitsChange = (e) => {
const position = parseInt(e.target.name);
const value = Number(e.target.value) || '0';
let roundShifts = state.roundShifts?.split('_');
if (!roundShifts) return;
roundShifts[position] = value;
roundShifts = roundShifts.join('_');
setState((prevState) => ({
...prevState,
hasChanges: true,
roundShifts,
}));
};
/**
* Handle validation for round shift fields
*
* @function
* @param {string} fieldName
* @returns {void}
*/
const handleRoundShiftValidation = (fieldName) => {
validateInputField(fieldName, 'RoundShift');
};
/**
* Render
*
* @returns {view}
*/
return (
<>
{isFetchedSelector ? (
<div className="user mt-20">
<div className="user__settings">
<div className="user__settings-title">Time settings</div>
<hr className="line-spacer" />
<div className="user__settings-wrap">
{state.shopFieldConfigs.TimeSettings.map((field) => renderInputSettings(field, 'TimeSettings'))}
</div>
</div>
<div className="user__settings mt-20">
<div className="user__settings-title">Round shifts</div>
<hr className="line-spacer" />
<div className="user__settings-wrap">
<RoundShiftDetails
roundShifts={state.roundShifts}
errorMessages={state.errorMgs}
onChange={handleRoundShitsChange}
onValidate={handleRoundShiftValidation}
disabled={!editPermission}
/>
</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">
{state.shopFieldConfigs.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">
{state.shopFieldConfigs.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>
<div className="user__settings mt-20">
<div className="user__settings-title">SSC Config</div>
<hr className="line-spacer" />
<div className="user__settings-wrap">
{state.shopFieldConfigs.sscConfig.map((field) => renderInputSettings(field, 'sscConfig'))}
</div>
</div>
<hr className="line-spacer" />
{editPermission ? renderButtons() : null}
</div>
) : null}
{activeModal === MODALS.SAVE_BEFORE_NAVIGATION ? (
<ConfirmationModal
modalData={CONFIRM_MODAL_CONTENT.SAVE_CHANGES}
cancel={unsavedNavigation}
confirmation={saveProcess}
cancelBtnText="Cancel"
confirmBtnText="Save"
logo
/>
) : null}
</>
);
};
export default withRouterHooks(GamesSettingsShop);