From e8d6908b47973641cfb2d0db3a770786de5d674e Mon Sep 17 00:00:00 2001
From: Alexandru Munteanu <alexandru.munt@gmail.com>
Date: Thu, 4 Oct 2018 11:40:51 +0300
Subject: [PATCH] feat(HE-recommendation): add contextual box with HE form

---
 .../component-faraday-selectors/src/index.js  | 22 ++++--
 .../component-faraday-ui/src/RemoteOpener.md  |  2 +-
 .../EditorialRecommendation.js                | 72 ++++++++++++++-----
 .../src/contextualBoxes/ReviewerDetails.js    |  6 +-
 .../src/components/ManuscriptLayout.js        | 24 ++++---
 .../src/components/ManuscriptPage.js          | 46 +++++++++++-
 .../src/redux/recommendations.js              |  1 -
 .../src/redux/recommendations.js              |  2 +
 8 files changed, 141 insertions(+), 34 deletions(-)

diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js
index e852fc3b0..226cee8a4 100644
--- a/packages/component-faraday-selectors/src/index.js
+++ b/packages/component-faraday-selectors/src/index.js
@@ -2,10 +2,12 @@ import { get, has, last, chain } from 'lodash'
 import { selectCurrentUser } from 'xpub-selectors'
 
 export const isHEToManuscript = (state, collectionId) => {
-  const currentUserId = get(state, 'currentUser.user.id', '')
-  const collections = get(state, 'collections', [])
-  const collection = collections.find(c => c.id === collectionId) || {}
-  return get(collection, 'handlingEditor.id') === currentUserId
+  const { id = '', isAccepted = false } = chain(state)
+    .get('collections', [])
+    .find(c => c.id === collectionId)
+    .get('handlingEditor.id', '')
+    .value()
+  return isAccepted && id === get(state, 'currentUser.user.id')
 }
 
 export const currentUserIs = ({ currentUser: { user } }, role) => {
@@ -217,6 +219,18 @@ export const getInvitationsWithReviewersForFragment = (state, fragmentId) =>
     }))
     .value()
 
+export const canMakeHERecommendation = (state, { collection, statuses }) => {
+  const { isAccepted, id: heId } = get(collection, 'handlingEditor', {})
+  const validHe = isAccepted && get(state, 'currentUser.user.id', '') === heId
+  const statusImportance = get(
+    statuses,
+    `${get(collection, 'status', 'draft')}.importance`,
+    1,
+  )
+
+  return statusImportance > 1 && statusImportance < 9 && validHe
+}
+
 // #region Editorial and reviewer recommendations
 export const getFragmentRecommendations = (state, fragmentId) =>
   get(state, `fragments.${fragmentId}.recommendations`, [])
diff --git a/packages/component-faraday-ui/src/RemoteOpener.md b/packages/component-faraday-ui/src/RemoteOpener.md
index 283c505e2..173a86f04 100644
--- a/packages/component-faraday-ui/src/RemoteOpener.md
+++ b/packages/component-faraday-ui/src/RemoteOpener.md
@@ -2,7 +2,7 @@ Toggle a boolean flag and pass it around in your React components tree.
 
 ```js
 <RemoteOpener>
-  {(expanded, toggle) => (
+  {({ expanded, toggle }) => (
     <div>
       <button onClick={toggle}>Toggle</button>
       <span>{expanded ? 'Collapse me!' : 'Expand me!'}</span>
diff --git a/packages/component-faraday-ui/src/contextualBoxes/EditorialRecommendation.js b/packages/component-faraday-ui/src/contextualBoxes/EditorialRecommendation.js
index 679610502..beb1fcb68 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/EditorialRecommendation.js
+++ b/packages/component-faraday-ui/src/contextualBoxes/EditorialRecommendation.js
@@ -1,5 +1,5 @@
 import React from 'react'
-import { get } from 'lodash'
+import { get, tail } from 'lodash'
 import { reduxForm } from 'redux-form'
 import styled from 'styled-components'
 import { th } from '@pubsweet/ui-toolkit'
@@ -22,18 +22,41 @@ import {
 const options = [
   { value: 'publish', label: 'Publish' },
   { value: 'reject', label: 'Reject' },
-  { value: 'minor-revision', label: 'Request Minor Revision' },
-  { value: 'major-revision', label: 'Request Major Revision' },
+  { value: 'minor', label: 'Request Minor Revision' },
+  { value: 'major', label: 'Request Major Revision' },
 ]
 
-const EditorialRecommendation = ({ formValues, handleSubmit }) => (
-  <ContextualBox highlight label="Your Editorial Recommendation" startExpanded>
+const parseFormValues = ({ recommendation, ...rest }) => {
+  const comments = Object.entries(rest).map(([key, value]) => ({
+    content: value,
+    public: key === 'public',
+    files: [],
+  }))
+
+  return {
+    comments,
+    recommendation,
+    recommendationType: 'editorRecommendation',
+  }
+}
+
+const EditorialRecommendation = ({
+  formValues,
+  handleSubmit,
+  hasReviewerReports,
+}) => (
+  <ContextualBox highlight label="Your Editorial Recommendation" mb={2}>
     <Root>
       <Row justify="flex-start">
         <ItemOverrideAlert flex={0} vertical>
           <Label required>Recommendation</Label>
           <ValidatedField
-            component={input => <Menu options={options} {...input} />}
+            component={input => (
+              <Menu
+                options={hasReviewerReports ? options : tail(options)}
+                {...input}
+              />
+            )}
             name="recommendation"
             validate={[required]}
           />
@@ -47,27 +70,27 @@ const EditorialRecommendation = ({ formValues, handleSubmit }) => (
             <Label required>Message for Author</Label>
             <ValidatedField
               component={Textarea}
-              name="message"
+              name="public"
               validate={[required]}
             />
           </ItemOverrideAlert>
         </Row>
       ) : (
-        <Row mt={2}>
-          <ItemOverrideAlert mr={1} vertical>
+        <ResponsiveRow mt={2}>
+          <ResponsiveItem mr={1} vertical>
             <Label>
               Message for Author <Text secondary>Optional</Text>
             </Label>
-            <ValidatedField component={Textarea} name="author-message" />
-          </ItemOverrideAlert>
+            <ValidatedField component={Textarea} name="public" />
+          </ResponsiveItem>
 
-          <ItemOverrideAlert ml={1} vertical>
+          <ResponsiveItem ml={1} vertical>
             <Label>
               Message for Editor in Chief <Text secondary>Optional</Text>
             </Label>
-            <ValidatedField component={Textarea} name="eic-message" />
-          </ItemOverrideAlert>
-        </Row>
+            <ValidatedField component={Textarea} name="private" />
+          </ResponsiveItem>
+        </ResponsiveRow>
       )}
 
       <Row justify="flex-end" mt={2}>
@@ -100,7 +123,10 @@ export default compose(
       showModal({
         title: `${modalTitle}?`,
         onConfirm: props => {
-          onRecommendationSubmit(values, { ...props, setFetching })
+          onRecommendationSubmit(parseFormValues(values), {
+            ...props,
+            setFetching,
+          })
         },
       })
     },
@@ -113,4 +139,18 @@ const Root = styled.div`
   flex-direction: column;
   padding: ${th('gridUnit')};
 `
+
+const ResponsiveRow = styled(Row)`
+  @media (max-width: 800px) {
+    flex-direction: column;
+  }
+`
+
+const ResponsiveItem = styled(ItemOverrideAlert)`
+  @media (max-width: 800px) {
+    margin-right: 0;
+    margin-left: 0;
+    width: 100%;
+  }
+`
 // #endregion
diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
index 153dbfa01..e0b21c0c3 100644
--- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
+++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js
@@ -31,11 +31,15 @@ const ReviewerDetails = ({
   onInviteReviewer,
   onResendReviewerInvite,
   onRevokeReviewerInvite,
+  toggle,
+  expanded,
 }) => (
   <ContextualBox
+    expanded={expanded}
     label="Reviewer details"
     rightChildren={<ReviewerBreakdown fitContent fragment={fragment} mr={1} />}
-    startExpanded
+    scrollIntoView
+    toggle={toggle}
   >
     <Tabs>
       {({ selectedTab, changeTab }) => (
diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js
index 4e2471f1d..d0178984f 100644
--- a/packages/component-manuscript/src/components/ManuscriptLayout.js
+++ b/packages/component-manuscript/src/components/ManuscriptLayout.js
@@ -62,7 +62,12 @@ const ManuscriptLayout = ({
   reviewerRecommendationExpanded,
   shouldReview,
   submittedOwnRecommendation,
+  heAccepted,
   reviewerReports,
+  onEditorialRecommendation,
+  reviewerRecommendations,
+  toggleReviewerDetails,
+  reviewerDetailsExpanded,
 }) => (
   <Root pb={30}>
     {!isEmpty(collection) && !isEmpty(fragment) ? (
@@ -88,14 +93,6 @@ const ManuscriptLayout = ({
           revokeInvitation={revokeHE}
         />
 
-        <EditorialRecommendation
-          formValues={get(formValues, 'editorialRecommendation', {})}
-          modalKey="heRecommendation"
-          onRecommendationSubmit={(values, props) => {
-            props.setFetching(true)
-          }}
-        />
-
         <ManuscriptMetadata
           currentUser={currentUser}
           fragment={fragment}
@@ -171,9 +168,19 @@ const ManuscriptLayout = ({
           />
         )}
 
+        {get(currentUser, 'permissions.canMakeHERecommendation', false) && (
+          <EditorialRecommendation
+            formValues={get(formValues, 'editorialRecommendation', {})}
+            hasReviewerReports={reviewerRecommendations.length > 0}
+            modalKey="heRecommendation"
+            onRecommendationSubmit={onEditorialRecommendation}
+          />
+        )}
+
         {get(currentUser, 'permissions.canInviteReviewers', false) && (
           <ReviewerDetails
             currentUser={currentUser}
+            expanded={reviewerDetailsExpanded}
             fragment={fragment}
             getSignedUrl={getSignedUrl}
             invitations={invitationsWithReviewers}
@@ -182,6 +189,7 @@ const ManuscriptLayout = ({
             onResendReviewerInvite={onResendReviewerInvite}
             onRevokeReviewerInvite={onRevokeReviewerInvite}
             reviewerReports={reviewerReports}
+            toggle={toggleReviewerDetails}
           />
         )}
       </Fragment>
diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js
index ce588ddfe..6481bd89d 100644
--- a/packages/component-manuscript/src/components/ManuscriptPage.js
+++ b/packages/component-manuscript/src/components/ManuscriptPage.js
@@ -36,11 +36,13 @@ import {
   currentUserIs,
   canMakeRevision,
   canMakeDecision,
+  isHEToManuscript,
   canEditManuscript,
   canInviteReviewers,
   pendingHEInvitation,
   currentUserIsReviewer,
   parseCollectionDetails,
+  canMakeHERecommendation,
   pendingReviewerInvitation,
   canOverrideTechnicalChecks,
   getOwnPendingRecommendation,
@@ -126,6 +128,7 @@ export default compose(
       state,
       {
         match,
+        journal,
         fragment,
         collection,
         currentUser,
@@ -139,10 +142,18 @@ export default compose(
         token: getUserToken(state),
         isHE: currentUserIs(state, 'isHE'),
         isEIC: currentUserIs(state, 'adminEiC'),
-        isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')),
         isInvitedHE: !isUndefined(pendingHEInvitation),
         isInvitedToReview: !isUndefined(pendingReviewerInvitation),
+        isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')),
+        isHEToManuscript: isHEToManuscript(state, {
+          collectionId: get(collection, 'id', ''),
+          statuses: get(journal, 'statuses', {}),
+        }),
         permissions: {
+          canMakeHERecommendation: canMakeHERecommendation(state, {
+            collection,
+            statuses: get(journal, 'statuses', {}),
+          }),
           canAssignHE: canAssignHE(state, match.params.project),
           canInviteReviewers: canInviteReviewers(state, collection),
           canMakeRecommendation: !isUndefined(pendingOwnRecommendation),
@@ -373,6 +384,27 @@ export default compose(
           handleError(setModalError)(err)
         })
     },
+    onEditorialRecommendation: ({
+      fragment,
+      collection,
+      fetchUpdatedCollection,
+    }) => (recommendation, { hideModal, setFetching, setModalError }) => {
+      setFetching(true)
+      createRecommendation({
+        recommendation,
+        fragmentId: fragment.id,
+        collectionId: collection.id,
+      })
+        .then(r => {
+          setFetching(false)
+          hideModal()
+          fetchUpdatedCollection()
+        })
+        .catch(e => {
+          setFetching(false)
+          handleError(setModalError)(e)
+        })
+    },
   }),
   fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
     toggleAssignHE: toggle,
@@ -390,7 +422,11 @@ export default compose(
     toggleReviewerRecommendations: toggle,
     reviewerRecommendationExpanded: expanded,
   })),
-  withProps(({ currentUser, submittedOwnRecommendation }) => ({
+  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
+    toggleReviewerDetails: toggle,
+    reviewerDetailsExpanded: expanded,
+  })),
+  withProps(({ currentUser, collection, submittedOwnRecommendation }) => ({
     getSignedUrl,
     shouldReview:
       get(currentUser, 'isReviewer', false) &&
@@ -407,7 +443,7 @@ export default compose(
         clearCustomError,
         hasManuscriptFailure,
         fetchUpdatedCollection,
-        currentUser: { isInvitedHE, isInvitedToReview },
+        currentUser: { isInvitedHE, isInvitedToReview, isHEToManuscript },
       } = this.props
       if (hasManuscriptFailure) {
         history.push('/not-found')
@@ -439,6 +475,10 @@ export default compose(
       if (shouldReview) {
         this.props.toggleReviewerRecommendations()
       }
+
+      if (isHEToManuscript) {
+        this.props.toggleReviewerDetails()
+      }
     },
   }),
 )(ManuscriptLayout)
diff --git a/packages/component-manuscript/src/redux/recommendations.js b/packages/component-manuscript/src/redux/recommendations.js
index bb55354a5..2f57b4f37 100644
--- a/packages/component-manuscript/src/redux/recommendations.js
+++ b/packages/component-manuscript/src/redux/recommendations.js
@@ -23,7 +23,6 @@ export const recommendationsFetching = state =>
 // #endregion
 
 // #region Actions
-// error handling and fetching is handled by the autosave reducer
 export const createRecommendation = ({
   fragmentId,
   collectionId,
diff --git a/packages/components-faraday/src/redux/recommendations.js b/packages/components-faraday/src/redux/recommendations.js
index fb3d21fef..6664e1358 100644
--- a/packages/components-faraday/src/redux/recommendations.js
+++ b/packages/components-faraday/src/redux/recommendations.js
@@ -4,10 +4,12 @@ import { create, update } from 'pubsweet-client/src/helpers/api'
 // #region Selectors
 export const selectRecommendations = (state, fragmentId) =>
   get(state, `fragments.${fragmentId}.recommendations`, [])
+
 export const selectEditorialRecommendations = (state, fragmentId) =>
   selectRecommendations(state, fragmentId).filter(
     r => r.recommendationType === 'editorRecommendation' && r.comments,
   )
+
 export const selectReviewRecommendations = (state, fragmentId) =>
   selectRecommendations(state, fragmentId).filter(
     r => r.recommendationType === 'review',
-- 
GitLab