diff --git a/packages/component-faraday-ui/src/PersonInvitation.js b/packages/component-faraday-ui/src/PersonInvitation.js index 563573c38875e01e88d532f813c9f57cb92b522c..89676c26a54b867889d5f10c65ca33741fcc62ea 100644 --- a/packages/component-faraday-ui/src/PersonInvitation.js +++ b/packages/component-faraday-ui/src/PersonInvitation.js @@ -5,6 +5,7 @@ import { compose, withHandlers, defaultProps, setDisplayName } from 'recompose' import { Text, OpenModal, IconButton, marginHelper } from './' const PersonInvitation = ({ + withName, hasAnswer, isFetching, person: { name }, @@ -13,7 +14,7 @@ const PersonInvitation = ({ ...rest }) => ( <Root {...rest}> - <Text>{name}</Text> + {withName && <Text>{name}</Text>} {!hasAnswer && name !== 'Unassigned' && ( <Fragment> diff --git a/packages/component-faraday-ui/src/ReviewerBreakdown.js b/packages/component-faraday-ui/src/ReviewerBreakdown.js index 6c64361e64408acca148bbf44e58eedf5d4478b1..7c181f90968c9751a32cc48458241fda72bbd246 100644 --- a/packages/component-faraday-ui/src/ReviewerBreakdown.js +++ b/packages/component-faraday-ui/src/ReviewerBreakdown.js @@ -1,11 +1,8 @@ -import React, { Fragment } from 'react' +import React from 'react' import { get } from 'lodash' -import { H4 } from '@pubsweet/ui' -import styled from 'styled-components' -import { th } from '@pubsweet/ui-toolkit' import { compose, withHandlers, withProps } from 'recompose' -import { Text } from './' +import { Text, Row } from './' const ReviewerBreakdown = ({ getReportBreakdown }) => getReportBreakdown() @@ -26,11 +23,7 @@ export default compose( recommendations: get(fragment, 'recommendations', []), })), withHandlers({ - getReportBreakdown: ({ - invitations, - recommendations, - label = '', - }) => () => { + getReportBreakdown: ({ invitations, recommendations, ...rest }) => () => { const reviewerInvitations = invitations.filter(roleFilter('reviewer')) const invitationsWithRecommendations = reviewerInvitations.map(r => ({ ...r, @@ -41,27 +34,31 @@ export default compose( declined: 0, submitted: 0, }) - return ( - <Fragment> - {!!label && <Label>{label}</Label>} - {reviewerInvitations.length ? ( - <Text> - {`${reviewerInvitations.length} invited, ${ - report.accepted - } agreed, ${report.declined} declined, ${ - report.submitted - } submitted`} - </Text> - ) : ( - <Text> {`${reviewerInvitations.length} invited`}</Text> - )} - </Fragment> + return reviewerInvitations.length ? ( + <Row justify="flex-end" {...rest}> + <Text customId mr={1 / 2}> + {reviewerInvitations.length} + </Text> + <Text mr={1 / 2}> invited,</Text> + + <Text customId mr={1 / 2}> + {report.accepted} + </Text> + <Text mr={1 / 2}> agreed,</Text> + + <Text customId mr={1 / 2}> + {report.declined} + </Text> + <Text mr={1 / 2}> declined,</Text> + + <Text customId mr={1 / 2}> + {report.submitted} + </Text> + <Text mr={1 / 2}> submitted</Text> + </Row> + ) : ( + <Text> {`${reviewerInvitations.length} invited`}</Text> ) }, }), )(ReviewerBreakdown) - -const Label = styled(H4)` - display: inline-block; - margin-right: ${th('gridUnit')}; -` diff --git a/packages/component-faraday-ui/src/ReviewerBreakdown.md b/packages/component-faraday-ui/src/ReviewerBreakdown.md index d14eaaf2ce307b2ebb091b25cd220ac2084906ba..64df75b1fe57fee03dcc52137520dd676415a0d5 100644 --- a/packages/component-faraday-ui/src/ReviewerBreakdown.md +++ b/packages/component-faraday-ui/src/ReviewerBreakdown.md @@ -85,8 +85,3 @@ Without invitations ```js <ReviewerBreakdown fragment={{}} /> ``` - -With label -```js -<ReviewerBreakdown fragment={{}} label='Reviewer Reports' /> -``` diff --git a/packages/component-faraday-ui/src/ReviewerReport.js b/packages/component-faraday-ui/src/ReviewerReport.js new file mode 100644 index 0000000000000000000000000000000000000000..401f3818edffd6781810f1e5f2e7c3456d984f32 --- /dev/null +++ b/packages/component-faraday-ui/src/ReviewerReport.js @@ -0,0 +1,69 @@ +import React from 'react' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { DateParser } from '@pubsweet/ui' + +import { Label, Item, FileItem, Row, Text } from './' + +const ReviewerReport = ({ + report: { + report, + files = [], + submittedOn, + recommendation, + confidentialNote, + reviewer: { fullName, reviewerNumber }, + }, +}) => ( + <Root> + <Row justify="space-between" mb={2}> + <Item vertical> + <Label mb={1 / 2}>Recommendation</Label> + <Text>{recommendation}</Text> + </Item> + + <Item justify="flex-end"> + <Text>{fullName}</Text> + <Text customId ml={1} mr={1}> + {`Reviewer ${reviewerNumber}`} + </Text> + <DateParser timestamp={submittedOn}> + {date => <Text>{date}</Text>} + </DateParser> + </Item> + </Row> + + <Row mb={2}> + <Item vertical> + <Label mb={1 / 2}>Report</Label> + <Text>{report}</Text> + </Item> + </Row> + + <Label mb={1 / 2}>Files</Label> + <Row justify="flex-start" mb={2}> + {files.map(file => ( + <Item flex={0} key={file.id} mr={1}> + <FileItem item={file} /> + </Item> + ))} + </Row> + + <Row mb={2}> + <Item vertical> + <Label mb={1 / 2}>Confidential note for the Editorial Team</Label> + <Text>{confidentialNote}</Text> + </Item> + </Row> + </Root> +) + +export default ReviewerReport + +// #region styles +const Root = styled.div` + box-shadow: ${th('boxShadow')}; + padding: calc(${th('gridUnit')} * 2); + margin: ${th('gridUnit')}; +` +// #endregion diff --git a/packages/component-faraday-ui/src/ReviewerReport.md b/packages/component-faraday-ui/src/ReviewerReport.md new file mode 100644 index 0000000000000000000000000000000000000000..e16f2bc1a28bc427029e6c9388d8f8288f031112 --- /dev/null +++ b/packages/component-faraday-ui/src/ReviewerReport.md @@ -0,0 +1,26 @@ +Reviewer report. + +```js +<ReviewerReport + report={{ + submittedOn: Date.now(), + recommendation: 'Reject', + report: `Of all of the celestial bodies that capture our attention and + fascination as astronomers, none has a greater influence on life on + planet Earth than it’s own satellite, the moon. When you think about + it, we regard the moon with such powerful significance that unlike the + moons of other planets which we give names, we only refer to our one + and only orbiting orb as THE moon. It is not a moon. To us, it is the + one and only moon.`, + reviewer: { + fullName: 'Kenny Hudson', + reviewerNumber: 1, + }, + confidentialNote: `First 10 pages feel very familiar, you should check for plagarism.`, + files: [ + { id: 'file1', name: 'file1.pdf', size: 12356 }, + { id: 'file2', name: 'file2.pdf', size: 76421 }, + ], + }} +/> +``` diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js new file mode 100644 index 0000000000000000000000000000000000000000..e0bdabb2722aa52c0e0fcf9bd5382c43bcba6bb9 --- /dev/null +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -0,0 +1,207 @@ +import React from 'react' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { DateParser } from '@pubsweet/ui' + +import { Label, PersonInvitation, Text } from '../' + +const invitation = { + id: 'b4305ab6-84e6-48a3-9eb9-fbe0ec80c694', + role: 'handlingEditor', + type: 'invitation', + reason: 'because', + userId: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + hasAnswer: false, + invitedOn: 713919119, + isAccepted: false, + respondedOn: 1533714034932, + person: { + id: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + name: 'Toto Schilacci', + }, +} + +const reviewers = [ + { + id: 1, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 2, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 11, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 21, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 12, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 22, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 13, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 23, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 113, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 213, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 123, + fullName: 'Gica Hagi', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, + { + id: 223, + fullName: 'Cosmin Contra', + invitedOn: Date.now(), + respondedOn: Date.now(), + submittedOn: Date.now() - 3000000000, + }, +] + +const ReviewersTable = () => ( + <Table> + <thead> + <tr> + <th> + <Label>Full Name</Label> + </th> + <th> + <Label>Invited on</Label> + </th> + <th> + <Label>Responded on</Label> + </th> + <th> + <Label>Submitted on</Label> + </th> + <th> </th> + </tr> + </thead> + <tbody> + {reviewers.map((r, index) => ( + <TableRow key={r.id}> + <td> + <Text>{r.fullName}</Text> + <Text customId ml={1}>{`Reviewer ${index + 1}`}</Text> + </td> + <td> + <DateParser timestamp={r.invitedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + </td> + <td> + <DateParser timestamp={r.respondedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + <Text ml={1} secondary> + ACCEPTED + </Text> + </td> + <td> + <DateParser timestamp={r.submittedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + </td> + <HiddenCell> + <PersonInvitation {...invitation} /> + </HiddenCell> + </TableRow> + ))} + </tbody> + </Table> +) + +export default ReviewersTable + +// #region styles +const Table = styled.table` + border-collapse: collapse; + padding: ${th('gridUnit')}; + + & thead { + border-bottom: 1px solid ${th('colorBorder')}; + } + + & th, + & td { + border: none; + padding-left: ${th('gridUnit')}; + text-align: start; + vertical-align: middle; + + height: calc(${th('gridUnit')} * 5); + min-width: calc(${th('gridUnit')} * 12); + } +` + +const HiddenCell = styled.td` + opacity: 0; +` + +const TableRow = styled.tr` + background-color: ${th('colorBackgroundHue2')}; + border-bottom: 1px solid ${th('colorBorder')}; + + & td:first-child { + min-width: calc(${th('gridUnit')} * 30); + } + + &:hover { + background-color: #eeeeee; + + ${HiddenCell} { + opacity: 1; + } + } +` +// #endregion diff --git a/packages/component-faraday-ui/src/ReviewersTable.md b/packages/component-faraday-ui/src/ReviewersTable.md new file mode 100644 index 0000000000000000000000000000000000000000..dbab1725a9cb9c5744460e1f95dcd814a378b7fa --- /dev/null +++ b/packages/component-faraday-ui/src/ReviewersTable.md @@ -0,0 +1,5 @@ +A list of reviewers. + +```js +<ReviewersTable /> +``` diff --git a/packages/component-faraday-ui/src/Tabs.js b/packages/component-faraday-ui/src/Tabs.js new file mode 100644 index 0000000000000000000000000000000000000000..76d0699c166690707ae38dc344f0cfa5f0833d44 --- /dev/null +++ b/packages/component-faraday-ui/src/Tabs.js @@ -0,0 +1,15 @@ +import { compose, withStateHandlers } from 'recompose' + +const Tabs = ({ items, selectedTab, changeTab, children }) => + children({ selectedTab, changeTab }) + +export default compose( + withStateHandlers( + { selectedTab: 0 }, + { + changeTab: () => selectedTab => ({ + selectedTab, + }), + }, + ), +)(Tabs) diff --git a/packages/component-faraday-ui/src/Tabs.md b/packages/component-faraday-ui/src/Tabs.md new file mode 100644 index 0000000000000000000000000000000000000000..a213cb4aec658c0d3f50dea84688c2f0ec7c07aa --- /dev/null +++ b/packages/component-faraday-ui/src/Tabs.md @@ -0,0 +1,22 @@ +A component to render tabs. + +```js +const tabItems = [ + { content: () => <div>Tab one content</div> }, + { content: () => <div>Tab two content</div> }, + { content: () => <div>Tab three content</div> }, +] +;<Tabs> + {({ selectedTab, changeTab }) => ( + <div> + <div> + <button onClick={() => changeTab(0)}>Tab 1</button> + <button onClick={() => changeTab(1)}>Tab 2</button> + <button onClick={() => changeTab(2)}>Tab 3</button> + </div> + + {tabItems[selectedTab].content()} + </div> + )} +</Tabs> +``` diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js new file mode 100644 index 0000000000000000000000000000000000000000..cca9baae989cda6722630bc6a91e37984aa23b3b --- /dev/null +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -0,0 +1,107 @@ +import React, { Fragment } from 'react' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' + +import { + Tag, + Tabs, + Label, + marginHelper, + ContextualBox, + ReviewersTable, + ReviewerReport, + ReviewerBreakdown, +} from '../' + +const report = { + submittedOn: Date.now(), + recommendation: 'Reject', + report: `Of all of the celestial bodies that capture our attention and + fascination as astronomers, none has a greater influence on life on + planet Earth than it’s own satellite, the moon. When you think about + it, we regard the moon with such powerful significance that unlike the + moons of other planets which we give names, we only refer to our one + and only orbiting orb as THE moon. It is not a moon. To us, it is the + one and only moon.`, + reviewer: { + fullName: 'Kenny Hudson', + reviewerNumber: 1, + }, + confidentialNote: `First 10 pages feel very familiar, you should check for plagarism.`, + files: [ + { id: 'file1', name: 'file1.pdf', size: 12356 }, + { id: 'file2', name: 'file2.pdf', size: 76421 }, + ], +} + +const ReviewerDetails = ({ fragment }) => ( + <ContextualBox + label="Reviewer details" + rightChildren={<ReviewerBreakdown fitContent fragment={fragment} mr={1} />} + startExpanded + > + <Tabs> + {({ selectedTab, changeTab }) => ( + <Fragment> + <TabsHeader> + <TabButton + ml={1} + mr={1} + onClick={() => changeTab(0)} + selected={selectedTab === 0} + > + <Label>Reviewer Details</Label> + </TabButton> + <TabButton + ml={1} + mr={1} + onClick={() => changeTab(1)} + selected={selectedTab === 1} + > + <Label>Reviewer Reports</Label> + <Tag ml={1}>12</Tag> + </TabButton> + </TabsHeader> + {selectedTab === 0 && <ReviewersTable />} + {selectedTab === 1 && ( + <Fragment> + <ReviewerReport report={report} /> + <ReviewerReport report={report} /> + <ReviewerReport report={report} /> + </Fragment> + )} + </Fragment> + )} + </Tabs> + </ContextualBox> +) + +export default ReviewerDetails + +// #region styles +const TabButton = styled.div` + align-items: center; + border-bottom: ${props => + props.selected + ? `4px solid ${props.theme.colorFurnitureHue}` + : '4px solid transparent'}; + box-sizing: border-box; + cursor: pointer; + display: flex; + justify-content: center; + height: calc(${th('gridUnit')} * 5); + + ${marginHelper}; +` + +const TabsHeader = styled.div` + align-items: center; + border-bottom: 1px solid ${th('colorFurniture')}; + box-sizing: border-box; + display: flex; + justify-content: flex-start; + + margin-bottom: ${th('gridUnit')}; + padding: 0 calc(${th('gridUnit')} * 3); +` +// #endregion diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md new file mode 100644 index 0000000000000000000000000000000000000000..49bb54a7d348719cc44c2b822edcaf016d163d5c --- /dev/null +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md @@ -0,0 +1,83 @@ +The ReviewerDetails contextual box. + +```js +const fragment = { + invitations: [ + { + id: 'a69c1273-4073-4529-9e65-5424ad8034ea', + role: 'reviewer', + type: 'invitation', + userId: '9c79c3bf-ccba-4540-aad8-ce4609325826', + hasAnswer: false, + invitedOn: 1534506511008, + isAccepted: false, + respondedOn: null, + }, + { + id: 'ca1c08bb-4da6-4cb4-972d-d16e3b66b884', + role: 'reviewer', + type: 'invitation', + userId: 'd3ab9a0b-d8e8-41e5-ab3b-72b13f84fba1', + hasAnswer: true, + invitedOn: 1534506542522, + isAccepted: true, + respondedOn: 1534506569565, + }, + ], + recommendations: [ + { + id: '87fb2c45-2685-47cc-9952-d58435ff8f3a', + userId: 'd3ab9a0b-d8e8-41e5-ab3b-72b13f84fba1', + comments: [ + { + files: [], + public: true, + content: 'This is a great manuscript', + }, + ], + createdOn: 1534506583815, + updatedOn: 1534506592527, + submittedOn: 1534506591684, + recommendation: 'publish', + recommendationType: 'review', + }, + { + id: '9853453f-1049-4369-8543-1b812930430d', + userId: 'ede6770d-8dbf-4bf9-bbe2-98facfd0a114', + comments: [ + { + public: true, + content: 'nice job', + }, + { + public: false, + content: 'please publish this', + }, + ], + createdOn: 1534506624583, + updatedOn: 1534506624583, + recommendation: 'publish', + recommendationType: 'editorRecommendation', + }, + { + id: '64c5b596-fbfc-485c-9068-f3a58306efd7', + userId: '5b53da0d-3f88-4e94-b8f9-7eae6a754168', + createdOn: 1534506644873, + updatedOn: 1534506644873, + recommendation: 'publish', + recommendationType: 'editorRecommendation', + }, + { + id: '3d43ff74-9a20-479d-a218-23bf8eac0b6a', + userId: '5b53da0d-3f88-4e94-b8f9-7eae6a754168', + createdOn: 1534506813446, + updatedOn: 1534506813446, + recommendation: 'publish', + recommendationType: 'editorRecommendation', + }, + ], +} +; + +<ReviewerDetails fragment={fragment} /> +``` diff --git a/packages/component-faraday-ui/src/contextualBoxes/index.js b/packages/component-faraday-ui/src/contextualBoxes/index.js index 671406c817a82b42d530a58972edea909458800f..d8e24b485b0ed222d8380682efb5fd3f90b838fc 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/index.js +++ b/packages/component-faraday-ui/src/contextualBoxes/index.js @@ -1 +1,2 @@ export { default as AssignHE } from './AssignHE' +export { default as ReviewerDetails } from './ReviewerDetails' diff --git a/packages/component-faraday-ui/src/gridItems/Row.js b/packages/component-faraday-ui/src/gridItems/Row.js index c39f27891ecc259a8bfc6de114038e673463e9ab..794ed2b3f00faf6c82f720745b3826eed63bc3f2 100644 --- a/packages/component-faraday-ui/src/gridItems/Row.js +++ b/packages/component-faraday-ui/src/gridItems/Row.js @@ -14,7 +14,7 @@ export default styled.div.attrs({ justify-content: ${props => get(props, 'justify', 'space-evenly')}; height: ${props => get(props, 'height', 'auto')}; - width: 100%; + width: ${props => (props.fitContent ? 'fit-content' : '100%')}; ${heightHelper}; ${marginHelper}; diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 0d6361a425da1a76ad28d1a2eca5eecc99a32dd3..b186e2a8a42a0312a827520948661dfa5d57c348 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -30,10 +30,13 @@ export { default as PersonInfo } from './PersonInfo' export { default as PersonInvitation } from './PersonInvitation' export { default as PreviewFile } from './PreviewFile' export { default as RadioWithComments } from './RadioWithComments' +export { default as ReviewerReport } from './ReviewerReport' +export { default as ReviewersTable } from './ReviewersTable' export { default as RemoteOpener } from './RemoteOpener' export { default as ShadowedBox } from './ShadowedBox' export { default as SortableList } from './SortableList' export { default as Tag } from './Tag' +export { default as Tabs } from './Tabs' export { default as Text } from './Text' export { default as Textarea } from './Textarea' export { default as UserProfile } from './UserProfile' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js index 267ab5774524680b6d9e435f0a48efe8d7185ca1..6927e7ba41b1ffb70b1158a9382b06651d4c0c46 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js @@ -115,6 +115,7 @@ export default compose( <PersonInvitation isFetching={isFetching} ml={1} + withName {...pendingInvitation} onResend={resendInvitation} onRevoke={revokeInvitation}