diff --git a/packages/component-faraday-ui/src/ReviewerReport.js b/packages/component-faraday-ui/src/ReviewerReport.js index 212ded253217b36bc07f1dda42ba9998adf6c14d..12391694d8b8032f53d43932629ba1f4d20a12aa 100644 --- a/packages/component-faraday-ui/src/ReviewerReport.js +++ b/packages/component-faraday-ui/src/ReviewerReport.js @@ -13,6 +13,8 @@ const ReviewerReport = ({ reportFile, publicReport, privateReport, + reviewerName, + reviewerIndex, recommendation, showOwner = false, report: { submittedOn }, @@ -27,9 +29,9 @@ const ReviewerReport = ({ <Item justify="flex-end"> {showOwner && ( <Fragment> - <Text>Reviewer name</Text> + <Text>{reviewerName}</Text> <Text customId ml={1} mr={1}> - {`Reviewer ${1}`} + {`Reviewer ${reviewerIndex}`} </Text> </Fragment> )} @@ -82,11 +84,18 @@ export default withProps(({ report, journal: { recommendations = [] } }) => ({ reportFile: get(report, 'comments.0.files.0'), publicReport: get(report, 'comments.0.content'), privateReport: get(report, 'comments.1.content'), + reviewerName: `${get(report, 'reviewer.firstName', '')} ${get( + report, + 'reviewer.lastName', + '', + )}`, }))(ReviewerReport) // #region styles const Root = styled.div` - box-shadow: ${th('boxShadow')}; + background-color: ${th('colorBackgroundHue')}; + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBackgroundHue3')}; + border-radius: ${th('borderRadius')}; padding: calc(${th('gridUnit')} * 2); margin: ${th('gridUnit')}; ` diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js index 4da6d58f7f350ba5b3e773b33a1f3429e033c379..9dc2a45f11c5a6d5d9f96eb37bb611d0ca73e83e 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.js +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -65,7 +65,7 @@ const ReviewersTable = ({ </Fragment> </td> <td> - <DateParser timestamp={get(invitation, 'review.submittedOn')}> + <DateParser timestamp={get(invitation, 'review.submittedOn', '')}> {timestamp => <Text>{timestamp}</Text>} </DateParser> </td> @@ -115,6 +115,7 @@ export default compose( // #region styles const Table = styled.table` border-collapse: collapse; + width: 100%; & thead { border-bottom: 1px solid ${th('colorBorder')}; diff --git a/packages/component-faraday-ui/src/ReviewersTable.md b/packages/component-faraday-ui/src/ReviewersTable.md index dbab1725a9cb9c5744460e1f95dcd814a378b7fa..a6646bf04b38919a484dff9281bd372600c8722c 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.md +++ b/packages/component-faraday-ui/src/ReviewersTable.md @@ -1,5 +1,18 @@ A list of reviewers. ```js -<ReviewersTable /> +const invitations = [ + { + "id": "0078cc96-6daf-4171-8e57-dc84d85600ee", + "role": "reviewer", + "type": "invitation", + "userId": "9ac5b5b5-252c-4933-9e66-72ec7c644a5c", + "hasAnswer": true, + "invitedOn": 1538461178587, + "isAccepted": true, + "respondedOn": 1538461646415 + } +]; + +<ReviewersTable invitations={invitations} /> ``` diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index c9f0c279bd83899b6ee723a859cb6529420df9b7..153dbfa0108685f4d864da15558aa52b36652347 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -1,21 +1,33 @@ import React, { Fragment } from 'react' +import { get } from 'lodash' import { H4 } from '@pubsweet/ui' -import { withProps } from 'recompose' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' +import { withProps, compose } from 'recompose' import { + Tag, + Text, Tabs, marginHelper, ContextualBox, ReviewersTable, + ReviewerReport, InviteReviewers, ReviewerBreakdown, + withFilePreview, + withFileDownload, } from '../' const ReviewerDetails = ({ + journal, + reports, fragment, invitations, + previewFile, + currentUser, + downloadFile, + canSeeReports, onInviteReviewer, onResendReviewerInvite, onRevokeReviewerInvite, @@ -37,32 +49,74 @@ const ReviewerDetails = ({ > <H4>Reviewer Details</H4> </TabButton> + {canSeeReports && ( + <TabButton + ml={1} + mr={1} + onClick={() => changeTab(1)} + selected={selectedTab === 1} + > + <H4>Reviewer Reports</H4> + <Tag mr={1}>{reports.length}</Tag> + </TabButton> + )} </TabsHeader> - {selectedTab === 0 && ( - <Fragment> - <InviteReviewers - modalKey="invite-reviewers" - onInvite={onInviteReviewer} - /> - <ReviewersTable - invitations={invitations} - onResendReviewerInvite={onResendReviewerInvite} - onRevokeReviewerInvite={onRevokeReviewerInvite} - /> - </Fragment> - )} + <TabContent> + {selectedTab === 0 && ( + <Fragment> + <InviteReviewers + modalKey="invite-reviewers" + onInvite={onInviteReviewer} + /> + <ReviewersTable + invitations={invitations} + onResendReviewerInvite={onResendReviewerInvite} + onRevokeReviewerInvite={onRevokeReviewerInvite} + /> + </Fragment> + )} + {canSeeReports && + selectedTab === 1 && ( + <Fragment> + {reports.length === 0 && ( + <Text align="center">No reports submitted yet.</Text> + )} + {reports.map((report, index) => ( + <ReviewerReport + journal={journal} + key={report.id} + onDownload={downloadFile} + onPreview={previewFile} + report={report} + reviewerIndex={index + 1} + showOwner + /> + ))} + </Fragment> + )} + </TabContent> </Fragment> )} </Tabs> </ContextualBox> ) -export default withProps(({ invitations, reviewerReports = [] }) => ({ - invitations: invitations.map(i => ({ - ...i, - review: reviewerReports.find(r => r.userId === i.userId), +export default compose( + withFilePreview, + withFileDownload, + withProps(({ invitations = [], reviewerReports = [], currentUser }) => ({ + token: get(currentUser, 'token', ''), + invitations: invitations.map(i => ({ + ...i, + review: reviewerReports.find(r => r.userId === i.userId), + })), + reports: reviewerReports.filter(r => r.submittedOn), })), -}))(ReviewerDetails) + withProps(({ currentUser }) => ({ + canSeeReports: + get(currentUser, 'isEIC', false) || get(currentUser, 'isHE', false), + })), +)(ReviewerDetails) // #region styles const TabButton = styled.div` @@ -94,4 +148,13 @@ const TabsHeader = styled.div` padding: 0 calc(${th('gridUnit')} * 3); ` + +const TabContent = styled.div` + align-items: stretch; + background-color: ${th('colorBackgroundHue2')}; + display: flex; + justify-content: center; + flex-direction: column; + min-height: calc(${th('gridUnit')} * 10); +` // #endregion diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md index 8bfbd999d963d485d270504363724999d681034a..bbc8d973620151b1aadddbeee315ffea83b866a0 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md @@ -78,8 +78,81 @@ const fragment = { ], } +const invitations = [ + { + id: '590db43d-f270-4cde-9452-d3cd94a59001', + role: 'reviewer', + type: 'invitation', + userId: '9ac5b5b5-252c-4933-9e66-72ec7c644a5c', + hasAnswer: true, + invitedOn: 1538053490366, + isAccepted: true, + respondedOn: 1538053549407, + person: { + id: '9ac5b5b5-252c-4933-9e66-72ec7c644a5c', + type: 'user', + admin: false, + email: 'alexandru.munteanu+rev1@thinslices.com', + teams: [ + '54d5ec8b-4ff0-4483-b725-4e06546cd98b', + '02ac276c-447d-46fc-949b-a6581856b7fd', + '2d30f34f-bdb9-4f55-a21f-a1cbcda3453b', + ], + isActive: true, + lastName: 'REV1', + username: 'alexandru.munteanu+rev1@thinslices.com', + firstName: 'AlexREV1', + fragments: [], + affiliation: 'TSD', + collections: [], + isConfirmed: true, + editorInChief: false, + notifications: { + email: { + user: true, + system: true, + }, + }, + handlingEditor: false, + }, + }, + { + id: 'fbff261b-6f6e-433f-aaa9-0fa724bc32f3', + role: 'reviewer', + type: 'invitation', + userId: 'afb42359-3ece-42db-b55a-b4d638dd7031', + hasAnswer: false, + invitedOn: 1538122139200, + isAccepted: false, + respondedOn: null, + person: { + id: 'afb42359-3ece-42db-b55a-b4d638dd7031', + type: 'user', + admin: false, + email: 'alexandru.munteanu+rev2@thinslices.com', + teams: ['02ac276c-447d-46fc-949b-a6581856b7fd'], + isActive: true, + lastName: 'REV2', + username: 'alexandru.munteanu+rev2@thinslices.com', + firstName: 'alexREv2', + fragments: [], + affiliation: 'TSD', + collections: [], + isConfirmed: false, + editorInChief: false, + notifications: { + email: { + user: true, + system: true, + }, + }, + handlingEditor: false, + }, + }, +] ;<ReviewerDetails fragment={fragment} + invitations={invitations} onInviteReviewer={(reviewer, modalProps) => { modalProps.setFetching(true) }} diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index 0a4da8cc2036d1e8b0d565ba94c966345f595ac9..a13df8c3461d0c10f2571cb79d138772d45985c4 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -164,8 +164,11 @@ const ManuscriptLayout = ({ {get(currentUser, 'permissions.canInviteReviewers', false) && ( <ReviewerDetails + currentUser={currentUser} fragment={fragment} + getSignedUrl={getSignedUrl} invitations={invitationsWithReviewers} + journal={journal} onInviteReviewer={onInviteReviewer} onResendReviewerInvite={onResendReviewerInvite} onRevokeReviewerInvite={onRevokeReviewerInvite} diff --git a/packages/component-manuscript/src/redux/recommendations.js b/packages/component-manuscript/src/redux/recommendations.js index 154e043de88b4100daca4a96da85243fa26ebb20..bb55354a57d734632a243ada5f91d7fcda7c0022 100644 --- a/packages/component-manuscript/src/redux/recommendations.js +++ b/packages/component-manuscript/src/redux/recommendations.js @@ -9,9 +9,14 @@ export const selectEditorialRecommendations = (state, fragmentId) => r => r.recommendationType === 'editorRecommendation' && r.comments, ) export const selectReviewRecommendations = (state, fragmentId) => - selectRecommendations(state, fragmentId).filter( - r => r.recommendationType === 'review', - ) + selectRecommendations(state, fragmentId) + .filter(r => r.recommendationType === 'review') + .map(r => ({ + ...r, + reviewer: get(state, 'users.users', []).find( + user => user.id === r.userId, + ), + })) export const recommendationsFetching = state => get(state, 'recommendations', false)