diff --git a/src/clincoded/static/components/experimental_curation.js b/src/clincoded/static/components/experimental_curation.js index 79c261b61..262e02b90 100644 --- a/src/clincoded/static/components/experimental_curation.js +++ b/src/clincoded/static/components/experimental_curation.js @@ -2551,7 +2551,8 @@ const ExperimentalViewer = createReactClass({ userScoreObj: {}, // Logged-in user's score object submitBusy: false, // True while form is submitting experimentalEvidenceType: null, - formError: false + scoreError: false, + scoreErrorMsg: '' }; }, @@ -2559,7 +2560,7 @@ const ExperimentalViewer = createReactClass({ handleUserScoreObj: function(newUserScoreObj) { this.setState({userScoreObj: newUserScoreObj}, () => { if (!newUserScoreObj.hasOwnProperty('score') || (newUserScoreObj.hasOwnProperty('score') && newUserScoreObj.score !== false && newUserScoreObj.scoreExplanation)) { - this.setState({formError: false}); + this.setState({scoreError: false, scoreErrorMsg: ''}); } }); }, @@ -2582,7 +2583,7 @@ const ExperimentalViewer = createReactClass({ if (Object.keys(newUserScoreObj).length) { if(newUserScoreObj.hasOwnProperty('score') && newUserScoreObj.score !== false && !newUserScoreObj.scoreExplanation) { - this.setState({formError: true}); + this.setState({scoreError: true, scoreErrorMsg: 'A reason is required for the changed score.'}); return false; } this.setState({submitBusy: true}); @@ -3268,7 +3269,7 @@ const ExperimentalViewer = createReactClass({ {isEvidenceScored || (!isEvidenceScored && affiliation && affiliatedExperimental) || (!isEvidenceScored && !affiliation && userExperimental) ? + scoreError={this.state.scoreError} scoreErrorMsg={this.state.scoreErrorMsg} affiliation={affiliation} /> : null} {!isEvidenceScored && ((affiliation && !affiliatedExperimental) || (!affiliation && !userExperimental)) ?
diff --git a/src/clincoded/static/components/score/case_control_score.js b/src/clincoded/static/components/score/case_control_score.js index 76c6d698a..67c4c99fb 100644 --- a/src/clincoded/static/components/score/case_control_score.js +++ b/src/clincoded/static/components/score/case_control_score.js @@ -18,6 +18,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ handleUserScoreObj: PropTypes.func, // Function to call create/update score object scoreSubmit: PropTypes.func, // Function to call when Save button is clicked; This prop's existence makes the Save button exist submitBusy: PropTypes.bool, // TRUE while the form submit is running + scoreError: PropTypes.bool, // TRUE if score selection is not changed + scoreErrorMsg: PropTypes.string, // Text string in response to the type of score error affiliation: PropTypes.object, // Affiliation object passed from parent isDisabled: PropTypes.bool // Boolean for the state (disabled or not) of the input field }, @@ -28,6 +30,9 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ modifiedScore: null, // Score that is selected by curator userScoreUuid: null, // Pre-existing logged-in user's score uuuid submitBusy: false, // TRUE while form is submitting + userOrigScore: null, // User originally selected score + scoreError: this.props.scoreError, // TRUE if score selection is not changed + scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error scoreAffiliation: null // Affiliation associated with the score }; }, @@ -43,6 +48,7 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ this.updateUserScoreObj(); }); } + this.setState({scoreError: nextProps.scoreError, scoreErrorMsg: nextProps.scoreErrorMsg}); }, loadData() { @@ -69,6 +75,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ this.refs.scoreRange.setValue(modifiedScore ? modifiedScore : 'none'); this.updateUserScoreObj(); }); + // Save original score for checking if change has been made + this.setState({userOrigScore: !isNaN(parseFloat(modifiedScore)) ? modifiedScore : null}); } } }); @@ -165,9 +173,20 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ return affiliatedScore; }, + // Check if score has been changed before saving the score object + saveScore(e) { + if (this.state.modifiedScore === this.state.userOrigScore || + this.state.modifiedScore === 'none' && this.state.userOrigScore === null) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score is not changed. Please select a new score then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + render() { let modifiedScore = this.state.modifiedScore ? this.state.modifiedScore : 'none'; - + let scoreError = this.state.scoreError; return (
@@ -190,6 +209,11 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({ + {scoreError ? +
+

{this.state.scoreErrorMsg}

+
+ : null} {this.props.isDisabled ?

A Study type must be selected @@ -199,8 +223,8 @@ var ScoreCaseControl = module.exports.ScoreCaseControl = createReactClass({

{this.props.scoreSubmit ?
- +
: null}
diff --git a/src/clincoded/static/components/score/experimental_score.js b/src/clincoded/static/components/score/experimental_score.js index 94c6028b2..a806005c4 100644 --- a/src/clincoded/static/components/score/experimental_score.js +++ b/src/clincoded/static/components/score/experimental_score.js @@ -23,7 +23,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ handleUserScoreObj: PropTypes.func, // Function to call create/update score object scoreSubmit: PropTypes.func, // Function to call when Save button is clicked; This prop's existence makes the Save button exist submitBusy: PropTypes.bool, // TRUE while the form submit is running - formError: PropTypes.bool, // TRUE if no explanation is given for a different score + scoreError: PropTypes.bool, // TRUE if no explanation is given for a different score or no change is made + scoreErrorMsg: PropTypes.string, // Text string in response to the type of score error scoreDisabled: PropTypes.bool, // FALSE if the matched checkbox is selected affiliation: PropTypes.object // Affiliation object passed from parent }, @@ -45,8 +46,12 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ submitBusy: false, // TRUE while form is submitting disableScoreStatus: this.props.scoreDisabled, // FALSE if the matched checkbox is selected willNotCountScore: false, // TRUE if 'Review' is selected when Mode of Inheritance is not AD, AR, or X-Linked - formError: false, // TRUE if no explanation is given for a different score - scoreAffiliation: null // Affiliation associated with the score + scoreAffiliation: null, // Affiliation associated with the score + scoreError: this.props.scoreError, // TRUE if no explanation is given for a different score or no change is made + scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error + origStatus: null, // User originally selected status + origScore: null, // User originally selected score + origScoreExplanation: null // User originally entered explanation for selected score }; }, @@ -65,9 +70,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ this.refs.scoreStatus.resetValue(); }); } - if (nextProps.formError && nextProps.formError !== this.props.formError) { - this.setState({formError: true}); - } + this.setState({scoreError: nextProps.scoreError, scoreErrorMsg: nextProps.scoreErrorMsg}); this.setState({disableScoreStatus: nextProps.scoreDisabled}, () => { if (this.state.disableScoreStatus) { this.setState({showScoreInput: false}, () => { @@ -102,6 +105,14 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ modifiedScore = matchedScore.hasOwnProperty('score') ? matchedScore.score.toString() : null, scoreExplanation = matchedScore.scoreExplanation, calcScoreRange = this.getScoreRange(experimentalEvidenceType, parseFloat(defaultScore)); + + // Save original data for checking if changes has been made + this.setState({ + origStatus: scoreStatus, + origScore: modifiedScore, + origScoreExplanation: scoreExplanation === undefined ? null : scoreExplanation + }); + /**************************************************************************************/ /* Curators are allowed to access the score form fields when the 'Score' is selected, */ /* or when 'Review' is selected given the matched Mode of Inheritance types */ @@ -160,7 +171,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ modifiedScore: null, scoreExplanation: null, requiredScoreExplanation: false, - formError: false, + scoreError: false, + scoreErrorMsg: '', updateDefaultScore: true }, () => { let calcScoreRange = this.getScoreRange(experimentalEvidenceType, calcDefaultScore); @@ -183,7 +195,8 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ scoreRange: [], scoreExplanation: null, requiredScoreExplanation: false, - formError: false + scoreError: false, + scoreErrorMsg: '' }, () => { if (this.refs.scoreExplanation && this.refs.scoreExplanation.getValue()) { this.refs.scoreExplanation.resetValue(); @@ -209,7 +222,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ }); } else { // Reset explanation if default score is kept - this.setState({scoreExplanation: null, requiredScoreExplanation: false, formError: false}, () => { + this.setState({scoreExplanation: null, requiredScoreExplanation: false, scoreError: false, scoreErrorMsg: ''}, () => { this.refs.scoreExplanation.resetValue(); this.updateUserScoreObj(); }); @@ -221,12 +234,27 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ if (this.refs.scoreExplanation) { // Parse the score explanation entered by the curator let scoreExplanation = this.refs.scoreExplanation.getValue(); - this.setState({scoreExplanation: scoreExplanation, formError: false}, () => { + this.setState({scoreExplanation: scoreExplanation, scoreError: false, scoreErrorMsg: ''}, () => { this.updateUserScoreObj(); }); } }, + // Check if changes has been made before saving the score object. + saveScore(e) { + if ((this.state.scoreStatus === this.state.origStatus || + this.state.scoreStatus === 'none' && this.state.origStatus === null) && + (this.state.modifiedScore === this.state.origScore || + this.state.modifiedScore === 'none' && this.state.origScore === null) && + (this.state.scoreExplanation === this.state.origScoreExplanation || + this.state.scoreExplanation === '' && this.state.origScoreExplanation === null)) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score/explanation has not been modified. Please make your changes then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + // Put together the score object based on the form values for // the currently logged-in user updateUserScoreObj() { @@ -413,7 +441,7 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ let requiredScoreExplanation = this.state.requiredScoreExplanation; let disableScoreStatus = this.state.disableScoreStatus; let willNotCountScore = this.state.willNotCountScore; - let formError = this.state.formError; + let scoreError = this.state.scoreError; return (
@@ -457,17 +485,17 @@ var ScoreExperimental = module.exports.ScoreExperimental = createReactClass({ value={scoreExplanation} handleChange={this.handleScoreExplanation} placeholder="Note: If you selected a score different from the default score, you must provide a reason for the change here." rows="3" labelClassName="col-sm-5 control-label" wrapperClassName="col-sm-7" groupClassName="form-group" /> - {formError ? -
-

A reason is required for the changed score.

-
- : null}
: null} + {scoreError ? +
+

{this.state.scoreErrorMsg}

+
+ : null}
{this.props.scoreSubmit ?
-
: null} diff --git a/src/clincoded/static/components/score/individual_score.js b/src/clincoded/static/components/score/individual_score.js index c4676f9a1..22c776cfa 100644 --- a/src/clincoded/static/components/score/individual_score.js +++ b/src/clincoded/static/components/score/individual_score.js @@ -60,7 +60,11 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ scoreError: this.props.scoreError, // TRUE if no explanation is given for modified score or no case info type scoreErrorMsg: this.props.scoreErrorMsg, // Text string in response to the type of score error scoreAffiliation: null, // Affiliation associated with the score - priorScoreStatus: undefined // Placeholder score status for clearing explanation text field given the comparison + priorScoreStatus: undefined, // Placeholder score status for clearing explanation text field given the comparison + origStatus: null, // User originally selected status + origCaseInfoType: null, // User originally selected case information type + origScore: null, // User originally selected score + origScoreExplanation: null // User originally entered explanation for selected score }; }, @@ -127,6 +131,14 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ modifiedScore = matchedScore.hasOwnProperty('score') ? matchedScore.score.toString() : null, scoreExplanation = matchedScore.scoreExplanation, calcScoreRange = this.getScoreRange(modeInheritanceType, caseInfoType, parseFloat(defaultScore)); + + // Save original data for checking if changes has been made + this.setState({ + origStatus: scoreStatus, + origCaseInfoType: caseInfoType === undefined ? null : caseInfoType, + origScore: modifiedScore, + origScoreExplanation: scoreExplanation === undefined ? null : scoreExplanation + }); /**************************************************************************************/ /* Curators are allowed to access the score form fields when the 'Score' is selected, */ /* or when 'Review' is selected given the matched Mode of Inheritance types */ @@ -321,6 +333,22 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ } }, + // Check if changes has been made before saving the score object. + saveScore(e) { + if ((this.state.scoreStatus === this.state.origStatus || + this.state.scoreStatus === 'none' && this.state.origStatus === null) && + (this.state.modifiedScore === this.state.origScore || + this.state.modifiedScore === 'none' && this.state.origScore === null) && + (this.state.scoreExplanation === this.state.origScoreExplanation || + this.state.scoreExplanation === '' && this.state.origScoreExplanation === null) && + this.state.caseInfoType === this.state.origCaseInfoType) { + this.setState({scoreError: true, scoreErrorMsg: 'Cannot save because score/explanation has not been modified. Please make your changes then save.'}); + } + else { + this.props.scoreSubmit(e); + } + }, + // Put together the score object based on the form values for // the currently logged-in user updateUserScoreObj() { @@ -639,18 +667,18 @@ const ScoreIndividual = module.exports.ScoreIndividual = createReactClass({ 'Note: If you selected a score different from the default score, you must provide a reason for the change here.' : null} rows="3" labelClassName="col-sm-5 control-label" wrapperClassName="col-sm-7" groupClassName="form-group" /> - {scoreError ? -
-

{this.state.scoreErrorMsg}

-
- : null} +
+ : null} + {scoreError ? +
+

{this.state.scoreErrorMsg}

: null} {this.props.scoreSubmit ?
- +
: null} diff --git a/src/clincoded/static/scss/clincoded/modules/_curator.scss b/src/clincoded/static/scss/clincoded/modules/_curator.scss index 1e3709775..445f34a83 100644 --- a/src/clincoded/static/scss/clincoded/modules/_curator.scss +++ b/src/clincoded/static/scss/clincoded/modules/_curator.scss @@ -3444,7 +3444,7 @@ table.login-users-interpretations { .required-field { position: absolute; top: -38px; - left: 198px; + left: 86px; } }