From 12ab3242241af90424f86239fd1bd9f8f6e75abf Mon Sep 17 00:00:00 2001 From: Blake Manus <33578594+Blake-Manus@users.noreply.github.com> Date: Thu, 21 Dec 2023 22:51:16 -0500 Subject: [PATCH] Ricky/appeals-34350-Error-and-Validation-Handling (#20288) * Updated LeverModal button and reducer to fix error handling * Implemented new error handling in DocketTimeGoals and BatchSize Lever groups * Updated error handling for lever inputs for affinity days * Fixed incorrect check and messages in CheckIsInRange function * Added first part of input validation for duplicate values * Removed leftover code * Updated logic to handle save button enabling if duplicate values are put in fields --------- Co-authored-by: 631966 --- .../components/AffinityDays.jsx | 89 +++++++++--- .../components/BatchSize.jsx | 88 ++++++++++-- .../components/DocketTimeGoals.jsx | 127 ++++++++++++++---- .../components/LeverInputValidation.jsx | 106 +++++++++++++++ .../components/LeverModal.jsx | 5 +- .../reducers/Levers/leversReducer.js | 8 +- 6 files changed, 359 insertions(+), 64 deletions(-) create mode 100644 client/app/caseflowDistribution/components/LeverInputValidation.jsx diff --git a/client/app/caseflowDistribution/components/AffinityDays.jsx b/client/app/caseflowDistribution/components/AffinityDays.jsx index ffb6870c446..d0bdb82c273 100644 --- a/client/app/caseflowDistribution/components/AffinityDays.jsx +++ b/client/app/caseflowDistribution/components/AffinityDays.jsx @@ -7,12 +7,31 @@ import styles from 'app/styles/caseDistribution/InteractableLevers.module.scss'; import NumberField from 'app/components/NumberField'; import TextField from 'app/components/TextField'; import COPY from '../../../COPY'; +import leverInputValidation from './LeverInputValidation'; const AffinityDays = (props) => { const { leverList, leverStore } = props; const filteredLevers = leverList.map((item) => { return leverStore.getState().levers.find((lever) => lever.item === item); }); + + const checkIfOtherChangesExist = (currentLever) => { + + let leversWithChangesList = []; + + leverStore.getState().levers.map((lever) => { + if (lever.hasValueChanged === true && lever.item !== currentLever.item) { + leversWithChangesList.push(lever); + } + }); + + if (leversWithChangesList.length > 0) { + return true; + } + + return false; + }; + const leverNumberDiv = css({ '& .cf-form-int-input': { width: 'auto', display: 'inline-block', position: 'relative' }, '& .cf-form-int-input .input-container': { width: 'auto', display: 'inline-block', verticalAlign: 'middle' }, @@ -22,31 +41,66 @@ const AffinityDays = (props) => { const errorMessages = {}; const [affinityLevers, setAffinityLevers] = useState(filteredLevers); const [errorMessagesList, setErrorMessages] = useState(errorMessages); - const leverInputValidation = (lever, event) => { // eslint-disable-line no-unused-vars - let rangeError = !(/^\d{1,3}$/).test(event); - if (rangeError) { - setErrorMessages({ ...errorMessagesList, [lever.item]: 'Please enter a value less than or equal to 999' }); - - return 'FAIL'; - } - setErrorMessages({ ...errorMessagesList, [lever.item]: null }); - - return 'SUCCESS'; - }; const updatedLever = (lever, option) => (event) => { const levers = affinityLevers.map((individualLever) => { if (individualLever.item === lever.item) { const updatedOptions = individualLever.options.map((op) => { if (op.item === option.item) { - let validationResponse = leverInputValidation(individualLever, event); + + let initialLever = leverStore.getState().initial_levers.find((original) => original.item === lever.item); + + let validationResponse = leverInputValidation(lever, event, errorMessagesList, initialLever, op); + const newValue = isNaN(event) ? event : individualLever.value; - if (validationResponse === 'SUCCESS') { + if (validationResponse.statement === 'DUPLICATE') { + + if (checkIfOtherChangesExist(lever)) { + op.value = event; + op.errorMessage = validationResponse.updatedMessages[`${lever.item}-${option.item}`]; + setErrorMessages(validationResponse.updatedMessages[`${lever.item}-${option.item}`]); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: individualLever.item, value: newValue }, + hasValueChanged: false, + validChange: true + + }); + } else { + op.value = event; + op.errorMessage = validationResponse.updatedMessages[`${lever.item}-${option.item}`]; + setErrorMessages(validationResponse.updatedMessages[`${lever.item}-${option.item}`]); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: individualLever.item, value: newValue }, + hasValueChanged: false, + validChange: false + + }); + } + + } + if (validationResponse.statement === 'SUCCESS') { + op.value = event; + op.errorMessage = validationResponse.updatedMessages[`${lever.item}-${option.item}`]; + setErrorMessages(validationResponse.updatedMessages[`${lever.item}-${option.item}`]); + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: individualLever.item, value: newValue }, + validChange: true + }); + } + if (validationResponse.statement === 'FAIL') { op.value = event; + op.errorMessage = validationResponse.updatedMessages[`${lever.item}-${option.item}`]; + setErrorMessages(validationResponse.updatedMessages[`${lever.item}-${option.item}`]); leverStore.dispatch({ type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: individualLever.item, value: newValue } + updated_lever: { item: individualLever.item, value: newValue }, + validChange: false }); } } @@ -76,13 +130,14 @@ const AffinityDays = (props) => { setAffinityLevers(updatedLevers); leverStore.dispatch({ type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: lever.item, value: option.item } + updated_lever: { item: lever.item, value: option.item }, + validChange: true }); } }; const generateFields = (dataType, option, lever) => { const useAriaLabel = !lever.is_disabled; - const tabIndex = lever.is_disabled ? -1 : 'undefined'; + const tabIndex = lever.is_disabled ? -1 : null; if (dataType === 'number') { return ( @@ -128,7 +183,7 @@ const AffinityDays = (props) => {
diff --git a/client/app/caseflowDistribution/components/BatchSize.jsx b/client/app/caseflowDistribution/components/BatchSize.jsx index c6f1e126d94..d79daabb158 100644 --- a/client/app/caseflowDistribution/components/BatchSize.jsx +++ b/client/app/caseflowDistribution/components/BatchSize.jsx @@ -4,6 +4,7 @@ import * as Constants from 'app/caseflowDistribution/reducers/Levers/leversActio import { css } from 'glamor'; import styles from 'app/styles/caseDistribution/InteractableLevers.module.scss'; import NumberField from 'app/components/NumberField'; +import leverInputValidation from './LeverInputValidation'; import COPY from '../../../COPY'; const BatchSize = (props) => { @@ -20,26 +21,85 @@ const BatchSize = (props) => { '& .usa-input-error label': { bottom: '15px', left: '89px' } }); - const [batchSizeLevers, setLever] = useState(filteredLevers); - const updateLever = (index, changedItem) => (event) => { + const checkIfOtherChangesExist = (currentLever) => { + + let leversWithChangesList = []; - leverStore.dispatch({ - type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: changedItem, value: event } + leverStore.getState().levers.map((lever) => { + if (lever.hasValueChanged === true && lever.item !== currentLever.item) { + leversWithChangesList.push(lever); + } }); + if (leversWithChangesList.length > 0) { + return true; + } + + return false; + }; + + const errorMessages = {}; + const [batchSizeLevers, setLever] = useState(filteredLevers); + const [errorMessagesList, setErrorMessages] = useState(errorMessages); + const updateLever = (index) => (event) => { + const levers = batchSizeLevers.map((lever, i) => { if (index === i) { - let errorResult = !(/^\d{0,3}$/).test(event); - if (errorResult) { - lever.errorMessage = 'Please enter a value less than or equal to 999'; - } else { - lever.errorMessage = null; + let initialLever = leverStore.getState().initial_levers.find((original) => original.item === lever.item); + + let validationResponse = leverInputValidation(lever, event, errorMessagesList, initialLever); + + if (validationResponse.statement === 'DUPLICATE') { + + if (checkIfOtherChangesExist(lever)) { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: true + }); + } else { + + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: false + }); + } + } - lever.value = event; + if (validationResponse.statement === 'SUCCESS') { - return lever; + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + validChange: true + }); + + return lever; + } + if (validationResponse.statement === 'FAIL') { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + validChange: false + }); + + return lever; + } } return lever; @@ -75,9 +135,9 @@ const BatchSize = (props) => { isInteger readOnly={lever.is_disabled} value={lever.value} - errorMessage={lever.errorMessage} + errorMessage={errorMessagesList[lever.item]} onChange={updateLever(index, lever.item, lever.item)} - tabIndex={lever.is_disabled ? -1 : 'undefined'} + tabIndex={lever.is_disabled ? -1 : null} /> } diff --git a/client/app/caseflowDistribution/components/DocketTimeGoals.jsx b/client/app/caseflowDistribution/components/DocketTimeGoals.jsx index 97b348176c1..90efcf26bd9 100644 --- a/client/app/caseflowDistribution/components/DocketTimeGoals.jsx +++ b/client/app/caseflowDistribution/components/DocketTimeGoals.jsx @@ -6,17 +6,21 @@ import styles from 'app/styles/caseDistribution/InteractableLevers.module.scss'; import * as Constants from 'app/caseflowDistribution/reducers/Levers/leversActionTypes'; import ToggleSwitch from 'app/components/ToggleSwitch/ToggleSwitch'; import NumberField from 'app/components/NumberField'; +import leverInputValidation from './LeverInputValidation'; import COPY from '../../../COPY'; const DocketTimeGoals = (props) => { + const { leverList, leverStore } = props; const filteredDistributionLevers = leverList.docketDistributionPriorLevers.map((item) => { return leverStore.getState().levers.find((lever) => lever.item === item); }); + const filteredTimeGoalLevers = leverList.docketTimeGoalLevers.map((item) => { return leverStore.getState().levers.find((lever) => lever.item === item); }); + const leverNumberDiv = css({ '& .cf-form-int-input': { width: 'auto', display: 'inline-block', position: 'relative' }, '& .cf-form-int-input .input-container': { width: 'auto', display: 'inline-block', verticalAlign: 'middle' }, @@ -30,18 +34,21 @@ const DocketTimeGoals = (props) => { const [docketTimeGoalLevers, setTimeGoalLever] = useState(filteredTimeGoalLevers); const [errorMessagesList, setErrorMessages] = useState(errorMessages); - const leverInputValidation = (lever, event) => { + const checkIfOtherChangesExist = (currentLever) => { - let rangeError = !(/^\d{1,3}$/).test(event); + let leversWithChangesList = []; - if (rangeError) { - setErrorMessages({ ...errorMessagesList, [lever.item]: 'Please enter a value less than or equal to 999' }); + leverStore.getState().levers.map((lever) => { + if (lever.hasValueChanged === true && lever.item !== currentLever.item) { + leversWithChangesList.push(lever); + } + }); - return 'FAIL'; + if (leversWithChangesList.length > 0) { + return true; } - setErrorMessages({ ...errorMessagesList, [lever.item]: null }); - return 'SUCCESS'; + return false; }; const updateLever = (index, leverType) => (event) => { @@ -50,25 +57,58 @@ const DocketTimeGoals = (props) => { const levers = docketDistributionLevers.map((lever, i) => { if (index === i) { - let validationResponse = leverInputValidation(lever, event); + let initialLever = leverStore.getState().initial_levers.find((original) => original.item === lever.item); + let validationResponse = leverInputValidation(lever, event, errorMessagesList, initialLever); + + if (validationResponse.statement === 'DUPLICATE') { + + if (checkIfOtherChangesExist(lever)) { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: true + }); + } else { + + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); - if (validationResponse === 'SUCCESS') { + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: false + }); + } + + } + if (validationResponse.statement === 'SUCCESS') { lever.value = event; + setErrorMessages(validationResponse.updatedMessages); leverStore.dispatch({ type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: lever.item, value: event } + updated_lever: { item: lever.item, value: event }, + validChange: true }); return lever; } - lever.value = event; + if (validationResponse.statement === 'FAIL') { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); - leverStore.dispatch({ - type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: lever.item, value: event } - }); + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + validChange: false + }); - return lever; + return lever; + } } return lever; @@ -80,26 +120,59 @@ const DocketTimeGoals = (props) => { const levers = docketTimeGoalLevers.map((lever, i) => { if (index === i) { - let validationResponse = leverInputValidation(lever, event); + let initialLever = leverStore.getState().initial_levers.find((original) => original.item === lever.item); - if (validationResponse === 'SUCCESS') { + let validationResponse = leverInputValidation(lever, event, errorMessagesList, initialLever); + + if (validationResponse.statement === 'DUPLICATE') { + + if (checkIfOtherChangesExist(lever)) { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: true + }); + } else { + + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + hasValueChanged: false, + validChange: false + }); + } + + } + + if (validationResponse.statement === 'SUCCESS') { lever.value = event; + setErrorMessages(validationResponse.updatedMessages); leverStore.dispatch({ type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: lever.item, value: event } + updated_lever: { item: lever.item, value: event }, + validChange: true }); return lever; } + if (validationResponse.statement === 'FAIL') { + lever.value = event; + setErrorMessages(validationResponse.updatedMessages); + leverStore.dispatch({ + type: Constants.UPDATE_LEVER_VALUE, + updated_lever: { item: lever.item, value: event }, + validChange: false + }); - lever.value = event; - - leverStore.dispatch({ - type: Constants.UPDATE_LEVER_VALUE, - updated_lever: { item: lever.item, value: event } - }); - - return lever; + return lever; + } } return lever; diff --git a/client/app/caseflowDistribution/components/LeverInputValidation.jsx b/client/app/caseflowDistribution/components/LeverInputValidation.jsx new file mode 100644 index 00000000000..6449c997505 --- /dev/null +++ b/client/app/caseflowDistribution/components/LeverInputValidation.jsx @@ -0,0 +1,106 @@ +/* eslint-disable eqeqeq */ + +import PropTypes from 'prop-types'; + +const leverInputValidation = (lever, event, currentMessageState, initialLever, option) => { + let maxValue = 999; + + const checkIsInRange = () => { + let validNumber = (/^\d{1,10}$/).test(event); + let withinLimits = ((lever.min_value) <= event); + + // Max value to override lever database maximums on majority levers + + if (lever.data_type === 'radio') { + + withinLimits = ((option.min_value) <= event && event <= maxValue); + } else { + + withinLimits = ((lever.min_value) <= event); + } + + if (validNumber && withinLimits) { + return true; + } + + return false; + }; + + let updatedMessages = {}; + let response = {}; + + // Checks if value is a valid digit and within the min / max value for the lever requirements. + if (checkIsInRange()) { + if (lever.data_type === 'radio') { + if (updatedMessages) { + updatedMessages = {}; + } else { + updatedMessages = { ...currentMessageState, [`${lever.item}-${option.item}`]: null }; + } + } else { + updatedMessages = { ...currentMessageState, [lever.item]: null }; + } + } else if (lever.data_type === 'radio') { + updatedMessages = { ...currentMessageState, + [`${lever.item}-${option.item}`]: `Please enter a value from ${ option.min_value } to ${ maxValue }` + }; + } else { + updatedMessages = { ...currentMessageState, + [lever.item]: `Please enter a value greater than or equal to ${ lever.min_value }` + }; + } + + let messageValues = Object.values(updatedMessages); + let hasErrorMessage = (message) => message !== null; + let messageFilter = messageValues.filter(hasErrorMessage); + + if (lever.data_type === 'radio') { + let initialOption = initialLever.options.find((original) => original.item === option.item); + + if (event === initialOption.value) { + response = { + updatedMessages, + statement: 'DUPLICATE', + value: event, + }; + + return response; + } + } else if (event == initialLever.value) { + response = { + updatedMessages, + statement: 'DUPLICATE', + value: event, + }; + + return response; + } + + if (messageFilter < 1) { + response = { + updatedMessages, + statement: 'SUCCESS', + value: event, + }; + + return response; + } + + response = { + updatedMessages, + statement: 'FAIL', + value: event, + }; + + return response; +}; + +leverInputValidation.propTypes = { + lever: PropTypes.object, + event: PropTypes.integer, + currentMessageState: PropTypes.object, + initialLever: PropTypes.object, + option: PropTypes.object, +}; + +export default leverInputValidation; diff --git a/client/app/caseflowDistribution/components/LeverModal.jsx b/client/app/caseflowDistribution/components/LeverModal.jsx index 160bd3c2136..f91b8e7b538 100644 --- a/client/app/caseflowDistribution/components/LeverModal.jsx +++ b/client/app/caseflowDistribution/components/LeverModal.jsx @@ -188,10 +188,9 @@ export function LeverSaveButton({ leverStore }) { const leversString = JSON.stringify(state.levers); const initialLeversString = JSON.stringify(state.initial_levers); + const validChangeOccurred = state.changesOccurred; - const leverChangesOccurred = leversString !== initialLeversString; - - setChangesOccurred(leverChangesOccurred); + setChangesOccurred(validChangeOccurred); }); diff --git a/client/app/caseflowDistribution/reducers/Levers/leversReducer.js b/client/app/caseflowDistribution/reducers/Levers/leversReducer.js index 33631f93b75..824723b8d98 100644 --- a/client/app/caseflowDistribution/reducers/Levers/leversReducer.js +++ b/client/app/caseflowDistribution/reducers/Levers/leversReducer.js @@ -17,12 +17,13 @@ const leversReducer = (state = initialState, action = {}) => { formatted_history: formatLeverHistory(action.history) } case Constants.UPDATE_LEVER_VALUE: - const updatedLevers = updateLevers(state.levers, action.updated_lever); + const updatedLevers = updateLevers(state.levers, action.updated_lever, action.hasValueChanged); const changesOccurred = JSON.stringify(updatedLevers) !== JSON.stringify(state.initial_levers) return { ...state, levers: updatedLevers, - changesOccurred, + changesOccurred: action.validChange, + saveChangesActivated: !changesOccurred } case Constants.SAVE_LEVERS: return { @@ -72,7 +73,7 @@ export const formatLeverHistory = (lever_history_list) => { return formatted_lever_history; }; -export const updateLevers = (current_levers, updated_lever) => { +export const updateLevers = (current_levers, updated_lever, hasValueChanged) => { const leverIndex = current_levers.findIndex((lever => lever.item == updated_lever.item)); if (leverIndex !== -1) { @@ -82,6 +83,7 @@ export const updateLevers = (current_levers, updated_lever) => { updatedLevers[leverIndex] = { ...updatedLevers[leverIndex], value: updated_lever.value, + hasValueChanged }; return updatedLevers