diff --git a/public/globals.js b/public/globals.js index ce457ba21..96fc298a0 100644 --- a/public/globals.js +++ b/public/globals.js @@ -67,13 +67,6 @@ window.pkp = { SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS: 2, SUBMISSION_REVIEW_METHOD_OPEN: 3, - // SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT: 1, - // SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS: 2, - // SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE: 3, - // SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE: 4, - // SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE: 5, - // SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS: 6, - ROLE_ID_MANAGER: 16, ROLE_ID_SITE_ADMIN: 1, ROLE_ID_AUTHOR: 65536, @@ -747,6 +740,7 @@ window.pkp = { "Invitation not accepted. You're logged in as a different user.", 'acceptInvitation.authorization.message': 'Please log out and sign in with the correct account to accept this invitation.', + 'reviewer.recommendation.management.options': 'Recommendation management options', }, tinyMCE: { diff --git a/src/components/ListPanel/reviewerRecommendations/ReviewerRecommendationsListPanel.vue b/src/components/ListPanel/reviewerRecommendations/ReviewerRecommendationsListPanel.vue index ca242cf6d..e70cd470a 100644 --- a/src/components/ListPanel/reviewerRecommendations/ReviewerRecommendationsListPanel.vue +++ b/src/components/ListPanel/reviewerRecommendations/ReviewerRecommendationsListPanel.vue @@ -13,24 +13,37 @@ + {{ localize(item.title) }} - - {{ item.status ? t('common.active') : t('common.inactive') }} - + - - {{ t('common.edit') }} - - - {{ t('common.delete') }} - + + + openStatusToggleConfirmationModal(item.id, event) + " + /> + + + + + handleAction(actionName, item)" + /> + + import PkpButton from '@/components/Button/Button.vue'; -import Badge from '@/components/Badge/Badge.vue'; import Spinner from '@/components/Spinner/Spinner.vue'; import ListPanel from '@/components/ListPanel/ListPanel.vue'; import Pagination from '@/components/Pagination/Pagination.vue'; import PkpHeader from '@/components/Header/Header.vue'; +import DropdownActions from '@/components/DropdownActions/DropdownActions.vue'; import ajaxError from '@/mixins/ajaxError'; import dialog from '@/mixins/dialog.js'; import fetch from '@/mixins/fetch'; import cloneDeep from 'clone-deep'; import ReviewerRecommendationsEditModal from './ReviewerRecommendationsEditModal.vue'; import {useModal} from '@/composables/useModal'; +import {useLocalize} from '@/composables/useLocalize'; + +const {t} = useLocalize(); export default { components: { @@ -66,7 +82,7 @@ export default { ListPanel, Pagination, PkpHeader, - Badge, + DropdownActions, }, mixins: [dialog, fetch, ajaxError], props: { @@ -90,6 +106,26 @@ export default { type: String, required: true, }, + /** A localized string for title when activating the recommendation on modal confirmation. */ + activateTitle: { + type: String, + required: true, + }, + /** A localized string to confirm activating recommendation. */ + confirmActivateMessage: { + type: String, + required: true, + }, + /** A localized string for title when deactivating the recommendation on modal confirmation. */ + deactivateTitle: { + type: String, + required: true, + }, + /** A localized string to confirm deactivating recommendation. */ + confirmDeactivateMessage: { + type: String, + required: true, + }, /** The Form to edit an recommendation. */ form: { type: Object, @@ -135,6 +171,108 @@ export default { }; }, methods: { + /** + * Get the actions for recommendation items + * + * @param {Object} recommendation + */ + getActions(recommendation) { + let actions = [ + { + label: t('common.edit'), + name: 'openEditModal', + icon: 'Edit', + }, + ]; + + if (recommendation.removable) { + actions.push({ + label: t('common.delete'), + name: 'openDeleteModal', + icon: 'Delete', + isWarnable: true, + }); + } + + return actions; + }, + + /** + * Handle action on recommendation item + * + * @param {String} actionName + * @param {Object} recommendation + */ + handleAction(actionName, recommendation) { + this[actionName](recommendation.id); + }, + + /** + * Show the confirmation modal to activate/deactivate recommendation + * + * @param {Number} id + * @param {Object} event + */ + openStatusToggleConfirmationModal(id, event) { + const recommendation = this.items.find((a) => a.id === id); + + if (typeof recommendation === 'undefined') { + this.ajaxErrorCallback({}); + return; + } + + this.openDialog({ + name: 'edit', + title: recommendation.status + ? this.deactivateTitle + : this.activateTitle, + message: this.replaceLocaleParams( + recommendation.status + ? this.confirmDeactivateMessage + : this.confirmActivateMessage, + { + title: this.localize(recommendation.title), + }, + ), + actions: [ + { + label: this.t('common.yes'), + isPrimary: true, + callback: (close) => { + var self = this; + $.ajax({ + url: this.apiUrl + '/' + id + '/status', + type: 'POST', + headers: { + 'X-Csrf-Token': pkp.currentUser.csrfToken, + 'X-Http-Method-Override': 'PUT', + }, + data: { + status: Number(!recommendation.status), + }, + error: self.ajaxErrorCallback, + success: function (r) { + self.items.forEach((item) => { + if (item.id == recommendation.id) { + item.status = !recommendation.status; + $(event.target).prop('checked', !recommendation.status); + } + }); + close(); + self.setFocusIn(self.$el); + }, + }); + }, + }, + { + label: this.t('common.no'), + isWarnable: recommendation.status ? true : false, + callback: (close) => close(), + }, + ], + }); + }, + /** * Clear the active form when the modal is closed * @@ -323,5 +461,22 @@ export default { .listPanel__itemTitle { font-weight: @normal; } + + .recommendationListItem__selectWrapper { + display: flex; + align-items: center; + margin-left: -1rem; + } + + .recommendationListItem__selector { + line-height: 100%; + width: 3rem; + padding-left: 1rem; + padding-right: 7rem; + } + + .recommendationList__actions { + padding-right: 2rem; + } } diff --git a/src/composables/useSubmission.js b/src/composables/useSubmission.js index 32fddca0c..af8f38d16 100644 --- a/src/composables/useSubmission.js +++ b/src/composables/useSubmission.js @@ -25,27 +25,6 @@ export const ExtendedStagesLabels = { declined: tk('submissions.declined'), }; -// export const RecommendationTranslations = { -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_ACCEPT]: tk( -// 'reviewer.article.decision.accept', -// ), -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_PENDING_REVISIONS]: tk( -// 'reviewer.article.decision.pendingRevisions', -// ), -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_HERE]: tk( -// 'reviewer.article.decision.resubmitHere', -// ), -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_RESUBMIT_ELSEWHERE]: tk( -// 'reviewer.article.decision.resubmitElsewhere', -// ), -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_DECLINE]: tk( -// 'reviewer.article.decision.decline', -// ), -// [pkp.const.SUBMISSION_REVIEWER_RECOMMENDATION_SEE_COMMENTS]: tk( -// 'reviewer.article.decision.seeComments', -// ), -// }; - const InProgressReviewAssignmentStatuses = [ pkp.const.REVIEW_ASSIGNMENT_STATUS_ACCEPTED, pkp.const.REVIEW_ASSIGNMENT_STATUS_REVIEW_OVERDUE, diff --git a/src/managers/ReviewerManager/useReviewerManagerConfig.js b/src/managers/ReviewerManager/useReviewerManagerConfig.js index 87962e32d..d224fd198 100644 --- a/src/managers/ReviewerManager/useReviewerManagerConfig.js +++ b/src/managers/ReviewerManager/useReviewerManagerConfig.js @@ -1,6 +1,6 @@ import {useLocalize} from '@/composables/useLocalize'; import {useDate} from '@/composables/useDate'; -// import {RecommendationTranslations} from '@/composables/useSubmission'; + export function useReviewerManagerConfig(recommendations) { const {t, localize} = useLocalize(); const {formatShortDate} = useDate(); @@ -9,18 +9,6 @@ export function useReviewerManagerConfig(recommendations) { const items = []; function getRecommendationString(reviewAssignment) { - // const recommendationString = reviewAssignment.recommendation - // ? t(RecommendationTranslations[reviewAssignment.recommendation]) - // : null; - - // if (recommendationString) { - // return t('submission.recommendation', { - // recommendation: recommendationString, - // }); - // } - - // return null; - const recommendation = recommendations.filter( (r) => r.value === reviewAssignment.recommendation, )[0];