diff --git a/CHANGELOG.md b/CHANGELOG.md index 2928e419e49..869a6d4576a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ requesting a new account and will be displayed in the User Accounts module (PR # ### Modules #### Issue Tracker - Readability of comments and history was improved. (PR #6138) +#### Candidate Parameters +- Consents may now be grouped in UI of consent tab (PR #6042, PR #6044) ### Clean Up - *Add item here* ### Notes For Existing Projects diff --git a/modules/candidate_parameters/ajax/getData.php b/modules/candidate_parameters/ajax/getData.php index 22c6bdbf9ef..42a6642f2dd 100644 --- a/modules/candidate_parameters/ajax/getData.php +++ b/modules/candidate_parameters/ajax/getData.php @@ -423,6 +423,9 @@ function getConsentStatusFields() $date = []; $withdrawalDate = []; + // Get consent groups + $consentGroups = \Utility::getConsentGroups(); + // Get list of all consent types $consentDetails = \Utility::getConsentList(); // Get list of consents for candidate @@ -431,6 +434,10 @@ function getConsentStatusFields() foreach ($consentDetails as $consentID=>$consent) { $consentName = $consent['Name']; $consentList[$consentName] = $consent['Label']; + $groupID = $consent['ConsentGroupID']; + + // Append consent as a child to its group + $consentGroups[$groupID]['Children'][] = $consentName; if (isset($candidateConsent[$consentID])) { $candidateConsentID = $candidateConsent[$consentID]; @@ -453,6 +460,7 @@ function getConsentStatusFields() 'withdrawals' => $withdrawalDate, 'consents' => $consentList, 'history' => $history, + 'consentGroups' => $consentGroups, ]; return $result; @@ -469,7 +477,7 @@ function getConsentStatusFields() */ function getConsentStatusHistory($pscid) { - $db = \Database::singleton(); + $db = (\NDB_Factory::singleton())->database(); $historyData = $db->pselect( "SELECT EntryDate, DateGiven, DateWithdrawn, PSCID, @@ -481,23 +489,16 @@ function getConsentStatusHistory($pscid) ); $formattedHistory = []; - foreach ($historyData as $key => $entry) { - $consentName = $entry['ConsentName']; - $consentLabel = $entry['ConsentLabel']; - - $history = [ - 'data_entry_date' => $entry['EntryDate'], - 'entry_staff' => $entry['EntryStaff'], - $consentName => $entry['Status'], - $consentName . '_date' => $entry['DateGiven'], - $consentName . '_withdrawal' => $entry['DateWithdrawn'], - ]; - $consentHistory = [ - $key => $history, - 'label' => $consentLabel, - 'consentType' => $consentName, + foreach ($historyData as $entry) { + $formattedHistory[] = [ + 'data_entry_date' => $entry['EntryDate'], + 'entry_staff' => $entry['EntryStaff'], + 'consentStatus' => $entry['Status'], + 'date' => $entry['DateGiven'], + 'withdrawal' => $entry['DateWithdrawn'], + 'label' => $entry['ConsentLabel'], + 'consentType' => $entry['ConsentName'], ]; - $formattedHistory[$key] = $consentHistory; } return $formattedHistory; } diff --git a/modules/candidate_parameters/jsx/ConsentStatus.js b/modules/candidate_parameters/jsx/ConsentStatus.js index 5dee37e8e40..6493739bcac 100644 --- a/modules/candidate_parameters/jsx/ConsentStatus.js +++ b/modules/candidate_parameters/jsx/ConsentStatus.js @@ -1,6 +1,8 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import swal from 'sweetalert2'; + +import {VerticalTabs, TabPane} from 'Tabs'; import Loader from 'Loader'; /** @@ -20,7 +22,8 @@ class ConsentStatus extends Component { formData: {}, error: false, isLoaded: false, - loadedData: 0, + submitDisabled: false, + showHistory: false, }; /** @@ -29,6 +32,9 @@ class ConsentStatus extends Component { this.fetchData = this.fetchData.bind(this); this.setFormData = this.setFormData.bind(this); this.handleSubmit = this.handleSubmit.bind(this); + this.showHistory = this.showHistory.bind(this); + this.renderFormattedHistory = this.renderFormattedHistory.bind(this); + this.renderConsent = this.renderConsent.bind(this); } componentDidMount() { @@ -173,6 +179,10 @@ class ConsentStatus extends Component { } formData.append('tab', this.props.tabName); formData.append('candID', this.state.Data.candID); + + // Disable submit button to prevent form resubmission + this.setState({submitDisabled: true}); + $.ajax({ type: 'POST', url: this.props.action, @@ -181,221 +191,252 @@ class ConsentStatus extends Component { contentType: false, processData: false, success: (data) => { - swal.fire('Success!', 'Update successful.', 'success'); + swal.fire('Success!', 'Update successful.', 'success') + .then((result) => { + if (result.value) { + this.setState({submitDisabled: false}); + this.fetchData(); + } + } + ); this.fetchData(); }, error: (error) => { console.error(error); + // Enable submit button for form resubmission + this.setState({submitDisabled: false}); let errorMessage = error.responseText || 'Update failed.'; swal.fire('Error!', errorMessage, 'error'); }, }); } + showHistory() { + this.setState({showHistory: !this.state.showHistory}); + } + + renderFormattedHistory() { + const historyBtnLabel = this.state.showHistory ? + 'Hide Consent History' : 'Show Consent History'; + + const formattedHistory = this.state.Data.history.map((info, key) => { + const label = info.label; + const dataEntry = info.data_entry_date; + const user = info.entry_staff; + const consentStatus = info.consentStatus; + const consentDate = info.date; + const withdrawal = info.withdrawal; + const dateHistory = consentDate ? ( + + , Date of Consent to {consentDate} + + ) : null; + const withdrawalHistory = withdrawal ? ( + + , Date of Consent Withdrawal to {withdrawal} + + ) : null; + + return ( +
+
+

+ + {dataEntry} - {user} + updated for {label}: + Status to {' '} + {this.state.consentOptions[consentStatus]} + {dateHistory} + {withdrawalHistory} +

+
+ ); + }); + + return ( +
+ + +
+ ); + } + + renderConsent(consentName) { + // Allow editing if user has permission + let disabled = loris.userHasPermission('candidate_parameter_edit') + ? false : true; + + // Set up props for front-end validation + const oldConsent = this.state.Data.consentStatuses[consentName]; + const newConsent = this.state.formData[consentName]; + const withdrawalDate = this.state.Data.withdrawals[consentName]; + // Define defaults + let emptyOption = true; + let dateRequired = false; + let withdrawalRequired = false; + // Let date of withdrawal field be disabled until it is needed + let withdrawalDisabled = true; + + // If answer to consent is 'yes', require date of consent + if (newConsent === 'yes') { + dateRequired = true; + } + // If answer to consent is 'no', require date of consent + if (newConsent === 'no') { + dateRequired = true; + // If answer was previously 'yes' and consent is now being withdrawn, enable and require withdrawal date + // If consent was previously withdrawn and stays withdrawn, enable and require withdrawal date + if (oldConsent === 'yes' || + (oldConsent === 'no' && withdrawalDate) + ) { + withdrawalDisabled = false; + withdrawalRequired = true; + } + } + // Disallow clearing a valid consent status by removing empty option + if (oldConsent === 'no' || oldConsent === 'yes') { + emptyOption = false; + } + + // Set up elements + const label = this.state.Data.consents[consentName]; + const statusLabel = 'Response'; + const consentDate = consentName + '_date'; + const consentDate2 = consentName + '_date2'; + const consentDateLabel = 'Date of Response'; + const consentDateConfirmationLabel = 'Confirmation Date of Response'; + const consentWithdrawal = consentName + '_withdrawal'; + const consentWithdrawal2 = consentName + '_withdrawal2'; + const consentWithdrawalLabel = 'Date of Withdrawal of Consent'; + const consentWithdrawalConfirmationLabel = + 'Confirmation Date of Withdrawal of Consent'; + + return ( +
+ + + + + + +
+
+ ); + } + render() { // If error occurs, return a message. // XXX: Replace this with a UI component for 500 errors. if (this.state.error) { - return

An error occured while loading the page.

; + return

An error occurred while loading the page.

; } if (!this.state.isLoaded) { return ; } - let disabled = true; - let updateButton = null; - if (loris.userHasPermission('candidate_parameter_edit')) { - disabled = false; - updateButton = ; - } - const emptyOption = []; - const dateRequired = []; - const withdrawalRequired = []; - const withdrawalDisabled = []; - let i = 0; - for (let consent in this.state.Data.consents) { - if (this.state.Data.consents.hasOwnProperty(consent)) { - const oldConsent = this.state.Data.consentStatuses[consent]; - const newConsent = this.state.formData[consent]; - const withdrawalDate = this.state.Data.withdrawals[consent]; - // Set defaults - emptyOption[i] = true; - dateRequired[i] = false; - withdrawalRequired[i] = false; - // Let date of withdrawal field be disabled until it is needed - withdrawalDisabled[i] = true; - // If answer to consent is 'yes', require date of consent - if (newConsent === 'yes') { - dateRequired[i] = true; - } - // If answer to consent is 'no', require date of consent - if (newConsent === 'no') { - dateRequired[i] = true; - // If answer was previously 'yes' and consent is now being withdrawn, enable and require withdrawal date - // If consent was previously withdrawn and stays withdrawn, enable and require withdrawal date - if (oldConsent === 'yes' - || (oldConsent === 'no' && withdrawalDate)) { - withdrawalDisabled[i] = false; - withdrawalRequired[i] = true; - } - } - // Disallow clearing a valid consent status by removing empty option - if (oldConsent === 'no' || oldConsent === 'yes') { - emptyOption[i] = false; - } - i++; - } - } + // Allow editing if user has permission + let updateButton = loris.userHasPermission('candidate_parameter_edit') ? + () : null; - let consents = []; - i = 0; - for (let consentStatus in this.state.Data.consents) { - if (this.state.Data.consents.hasOwnProperty(consentStatus)) { - let consentLabel = this.state.Data.consents[consentStatus]; - let statusLabel = 'Response'; - let consentDate = consentStatus + '_date'; - let consentDate2 = consentStatus + '_date2'; - let consentDateLabel = 'Date of ' + statusLabel; - let consentDateConfirmationLabel = 'Confirmation Date of ' - + statusLabel; - let consentWithdrawal = consentStatus + '_withdrawal'; - let consentWithdrawal2 = consentStatus + '_withdrawal2'; - let consentWithdrawalLabel = 'Date of Withdrawal of Consent'; - let consentWithdrawalConfirmationLabel = - 'Confirmation Date of Withdrawal of Consent'; - - const consent = ( -
- - - - - - -
-
- ); - consents.push(consent); - i++; - } - } - - let formattedHistory = []; - const history = this.state.Data.history; - for (let consentKey in history) { - if (history.hasOwnProperty(consentKey)) { - let consentLabel = history[consentKey].label; - let consentType = history[consentKey].consentType; - for (let field in history[consentKey]) { - if (history[consentKey].hasOwnProperty(field)) { - let line = ''; - let historyConsent = history[consentKey][field]; - for (let field2 in historyConsent) { - if (historyConsent.hasOwnProperty(field2)) { - let current = historyConsent[field2]; - if (current !== null) { - switch (field2) { - case 'data_entry_date': - line += '['; - line += current; - line += '] '; - break; - case 'entry_staff': - line += current; - line += ' '; - break; - case consentType: - line += consentLabel + ' Status: '; - line += current; - line += ' '; - break; - case consentType + '_date': - line += 'Date of Consent: '; - line += current; - line += ' '; - break; - case consentType + '_withdrawal': - line += 'Date of ' - + 'Consent Withdrawal: '; - line += current; - line += ' '; - break; - default: - } - } + // Set up vertical tabs to group consent by consent groups + const tabList = []; + const tabPanes = Object.keys(this.state.Data.consentGroups).map( + (consentID) => { + if (this.state.Data.consentGroups[consentID].Children) { + tabList.push({ + id: consentID, + label: this.state.Data.consentGroups[consentID].Label, + }); + return ( + + + + + {this.state.Data.consentGroups[consentID].Children + .map((consentName) => { + return this.renderConsent(consentName); } - } - formattedHistory.push(

{line}

); - } - } + )} + {updateButton} +
+
+ ); } - } - + } + ); return (
- - - - {consents} - {updateButton} - {formattedHistory} - + {tabPanes} + + {this.renderFormattedHistory()}
); } diff --git a/php/libraries/Utility.class.inc b/php/libraries/Utility.class.inc index 9015b727e42..ab649b833dc 100644 --- a/php/libraries/Utility.class.inc +++ b/php/libraries/Utility.class.inc @@ -97,6 +97,25 @@ class Utility return $age; } + /** + * Returns list of consent groups in the database + * + * @return array An associative array of consent grouping, + * with their names and labels. The keys of + * the arrays are the IDs of the groups. + */ + static function getConsentGroups(): array + { + $factory = NDB_Factory::singleton(); + $DB = $factory->database(); + + $query = "SELECT ConsentGroupID, Name, Label FROM consent_group"; + $key = "ConsentGroupID"; + + $result = $DB->pselectWithIndexKey($query, [], $key); + + return $result; + } /** * Returns list of consents in the database *