diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js index 99cb6ff80f674da0003f56d42e6b57b74f988944..3a6af650a239f9dddda286297219b811b45e05cc 100644 --- a/app/components/component-review/src/components/ReviewPage.js +++ b/app/components/component-review/src/components/ReviewPage.js @@ -145,10 +145,11 @@ const query = gql` } ` -const updateTeamMutation = gql` - mutation($id: ID!, $input: TeamInput) { - updateTeam(id: $id, input: $input) { - ${teamFields} +const completeReviewMutation = gql` + mutation($id: ID!) { + completeReview(id: $id) { + id + status } } ` @@ -187,12 +188,11 @@ const createFileMutation = gql` export default ({ match, ...props }) => { const currentUser = useCurrentUser() const [updateReviewMutation] = useMutation(updateReviewMutationQuery) + const [completeReview] = useMutation(completeReviewMutation) // File upload // const [uploadReviewFiles] = useMutation(uploadReviewFilesMutation) - const [updateTeam] = useMutation(updateTeamMutation) - const [createFileM] = useMutation(createFileMutation) const createFile = file => createFileM({ @@ -269,24 +269,24 @@ export default ({ match, ...props }) => { id: review.id || undefined, input: reviewData, }, - update: (proxy, { data: { updateReview } }) => { - const data = JSON.parse( - JSON.stringify( - proxy.readQuery({ - query, - variables: { - id: manuscript.id, - }, - }), - ), - ) - let reviewIndex = data.manuscript.reviews.findIndex( - review => review.id === updateReview.id, - ) - reviewIndex = reviewIndex < 0 ? 0 : reviewIndex - data.manuscript.reviews[reviewIndex] = updateReview - proxy.writeQuery({ query, data }) - }, + // update: (proxy, { data: { updateReview } }) => { + // const data = JSON.parse( + // JSON.stringify( + // proxy.readQuery({ + // query, + // variables: { + // id: manuscript.id, + // }, + // }), + // ), + // ) + // let reviewIndex = data.manuscript.reviews.findIndex( + // review => review.id === updateReview.id, + // ) + // reviewIndex = reviewIndex < 0 ? 0 : reviewIndex + // data.manuscript.reviews[reviewIndex] = updateReview + // proxy.writeQuery({ query, data }) + // }, }) } @@ -308,23 +308,10 @@ export default ({ match, ...props }) => { createFile(newFile) }) - const completeReview = history => { - const team = cloneDeep(manuscript.teams).find( - team => team.role === 'reviewer', - ) - team.members = team.members.map(m => { - if (m.user.id === currentUser.id) { - return { user: { id: m.user.id }, status: 'completed' } - } - return { user: { id: m.user.id }, status: m.status } - }) - - updateTeam({ + const handleSubmit = ({ reviewId, history }) => { + completeReview({ variables: { - id: team.id, - input: { - members: team.members, - }, + id: reviewId, }, }).then(() => { history.push('/journal/dashboard') @@ -343,7 +330,9 @@ export default ({ match, ...props }) => { recommendation: null, } } - onSubmit={values => completeReview(props.history)} + onSubmit={values => + handleSubmit({ reviewId: review.id, history: props.history }) + } validateOnMount={review => { if (!review.id) return false const hasRecommendation = review.recommendation !== null diff --git a/app/components/component-review/src/components/ReviewersPage.js b/app/components/component-review/src/components/ReviewersPage.js index 6331ff1f7fb497afcc8ad5f89de5ad5bb67da522..c88a873aabdb7c2a8de2865261beaf03cc80251d 100644 --- a/app/components/component-review/src/components/ReviewersPage.js +++ b/app/components/component-review/src/components/ReviewersPage.js @@ -1,12 +1,9 @@ -import { compose, withProps } from 'recompose' -import { withFormik } from 'formik' -import { graphql } from '@apollo/client/react/hoc' -import gql from 'graphql-tag' -import { withLoader } from 'pubsweet-client' -import { omit } from 'lodash' - +import React from 'react' +import { Formik } from 'formik' +import { gql, useQuery, useMutation } from '@apollo/client' import Reviewers from '../components/reviewers/Reviewers' import ReviewerContainer from '../components/reviewers/ReviewerContainer' +import { Spinner } from '../../../shared' const teamFields = ` id @@ -23,6 +20,10 @@ const teamFields = ` username profilePicture online + defaultIdentity { + id + name + } } status } @@ -68,17 +69,17 @@ const fragmentFields = ` status ` -const createTeamMutation = gql` - mutation($input: TeamInput!) { - createTeam(input: $input) { +const addReviewerMutation = gql` + mutation($manuscriptId: ID!, $userId: ID!) { + addReviewer(manuscriptId: $manuscriptId, userId: $userId) { ${teamFields} } } ` -const updateTeamMutation = gql` - mutation($id: ID, $input: TeamInput) { - updateTeam(id: $id, input: $input) { +const removeReviewerMutation = gql` + mutation($manuscriptId: ID!, $userId: ID!) { + removeReviewer(manuscriptId: $manuscriptId, userId: $userId) { ${teamFields} } } @@ -86,18 +87,16 @@ const updateTeamMutation = gql` const query = gql` query($id: ID!) { - currentUser { - id - username - admin - } - users { id username profilePicture online admin + defaultIdentity { + id + name + } } teams { @@ -110,121 +109,52 @@ const query = gql` } ` -const update = match => (proxy, { data: { updateTeam, createTeam } }) => { - const data = JSON.parse( - JSON.stringify( - proxy.readQuery({ - query, - variables: { - id: match.params.version, - }, - }), - ), - ) +const ReviewersPage = ({ match, history }) => { + const { data, error, loading } = useQuery(query, { + variables: { id: match.params.version }, + }) - if (updateTeam) { - const teamIndex = data.teams.findIndex(team => team.id === updateTeam.id) - const manuscriptTeamIndex = data.manuscript.teams.findIndex( - team => team.id === updateTeam.id, - ) - data.teams[teamIndex] = updateTeam - data.manuscript.teams[manuscriptTeamIndex] = updateTeam - } + const [addReviewer] = useMutation(addReviewerMutation) + const [removeReviewer] = useMutation(removeReviewerMutation) - if (createTeam) { - data.teams.push(createTeam) - data.manuscript.teams.push(createTeam) - } - proxy.writeQuery({ query, data }) -} - -const handleSubmit = ( - { user }, - { props: { manuscript, updateTeamMutation, createTeamMutation, match } }, -) => { - const team = manuscript.teams.find(team => team.role === 'reviewer') || {} - - const teamAdd = { - objectId: manuscript.id, - objectType: 'Manuscript', - // status: [{ user: user.id, status: 'invited' }], - name: 'Reviewers', - role: 'reviewer', - members: [{ user: { id: user.id }, status: 'invited' }], - } - if (team.id) { - const newTeam = { - ...omit(team, ['object', 'id', '__typename']), - // TODO: Find a cleaner way of updating members - members: team.members.map(member => ({ - user: { - id: member.user.id, - }, - status: member.status, - })), - } - - newTeam.members.push({ user: { id: user.id }, status: 'invited' }) - // newTeam.status.push({ user: user.id, status: 'invited' }) - updateTeamMutation({ - variables: { - id: team.id, - input: newTeam, - }, - update: update(match), - }) - } else { - createTeamMutation({ - variables: { - input: teamAdd, - }, - update: update(match), - }) + if (loading) { + return <Spinner /> } + if (error) return error + + const { manuscript, teams, users } = data + const reviewersTeam = + teams.find( + team => + team.role === 'reviewer' && + team.object.objectId === manuscript.id && + team.object.objectType === 'Manuscript', + ) || {} + + const reviewers = reviewersTeam.members || [] + return ( + <Formik + displayName="reviewers" + initialValues={{ user: undefined }} + onSubmit={values => + addReviewer({ + variables: { userId: values.user.id, manuscriptId: manuscript.id }, + }) + } + > + {props => ( + <Reviewers + {...props} + manuscript={manuscript} + removeReviewer={removeReviewer} + Reviewer={ReviewerContainer} + reviewers={reviewers} + reviewerUsers={users} + history={history} + /> + )} + </Formik> + ) } -export default compose( - graphql(query, { - options: ({ match }) => ({ - variables: { - id: match.params.version, - }, - }), - }), - graphql(createTeamMutation, { name: 'createTeamMutation' }), - graphql(updateTeamMutation, { name: 'updateTeamMutation' }), - withLoader(), - withProps( - ({ - manuscript, - teams = [], - users, - match: { - params: { journal }, - }, - }) => { - const reviewersTeam = - teams.find( - team => - team.role === 'reviewer' && - team.object.objectId === manuscript.id && - team.object.objectType === 'Manuscript', - ) || {} - - return { - reviewers: reviewersTeam.members || [], - journal: { id: journal }, - reviewerUsers: users, - Reviewer: ReviewerContainer, - } - }, - ), - // withHandlers({ - // loadOptions: props => props.reviewerUsers, // loadOptions(props), - // }), - withFormik({ - mapPropsToValues: () => ({ user: '' }), - displayName: 'reviewers', - handleSubmit, - }), -)(Reviewers) +export default ReviewersPage diff --git a/app/components/component-review/src/components/assignEditors/AssignEditor.js b/app/components/component-review/src/components/assignEditors/AssignEditor.js index 51cd4ab4d846021f406c697da7a401ad354a768d..6383b954811a57823a7b8481300cbcd52c2a8596 100644 --- a/app/components/component-review/src/components/assignEditors/AssignEditor.js +++ b/app/components/component-review/src/components/assignEditors/AssignEditor.js @@ -8,7 +8,7 @@ import gql from 'graphql-tag' import { withLoader } from 'pubsweet-client' const editorOption = user => ({ - label: user.username, // TODO: name + label: user.defaultIdentity.name, value: user.id, }) @@ -35,6 +35,10 @@ const query = gql` id username admin + defaultIdentity { + id + name + } } } ` diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js index e889f256ac2e06777a3a760b81c09d791430145f..b7bcab3bd4252b71f56f752800d75ed56aff5d5e 100644 --- a/app/components/component-review/src/components/decision/DecisionReviews.js +++ b/app/components/component-review/src/components/decision/DecisionReviews.js @@ -44,12 +44,13 @@ const DecisionReviews = ({ manuscript }) => ( </SectionRow> )) ) : ( - <SectionRow> - <Action to={`/journal/versions/${manuscript.id}/reviewers`}> - Assign Reviewers - </Action> - </SectionRow> + <SectionRow>No reviews completed yet.</SectionRow> )} + <SectionRow> + <Action to={`/journal/versions/${manuscript.id}/reviewers`}> + Manage Reviewers + </Action> + </SectionRow> </Container> ) diff --git a/app/components/component-review/src/components/reviewers/ReviewerForm.js b/app/components/component-review/src/components/reviewers/ReviewerForm.js index b2df4cddfeed6af40038ff113ff255a69f8c60ac..efc35edbdb1c564b9732596ac08bdf4c0b485020 100644 --- a/app/components/component-review/src/components/reviewers/ReviewerForm.js +++ b/app/components/component-review/src/components/reviewers/ReviewerForm.js @@ -1,9 +1,11 @@ import React from 'react' -import Select from 'react-select1' import { Field } from 'formik' import { Button } from '@pubsweet/ui' import { required } from 'xpub-validators' -import 'react-select1/dist/react-select.css' +import styled from 'styled-components' +// import 'react-select1/dist/react-select.css' +import { grid } from '@pubsweet/ui-toolkit' +import { Select } from '../../../../shared' const OptionRenderer = option => ( <div> @@ -12,6 +14,11 @@ const OptionRenderer = option => ( </div> ) +const FieldAndButton = styled.div` + display: grid; + grid-template-columns: ${grid(30)} ${grid(10)}; + grid-gap: ${grid(2)}; +` const ReviewerInput = ({ field, form: { values, setFieldValue }, @@ -19,14 +26,15 @@ const ReviewerInput = ({ replace, reviewerUsers, }) => ( - <Select.Creatable + <Select {...field} - labelKey="username" onChange={user => { setFieldValue('user', user) }} optionRenderer={OptionRenderer} options={reviewerUsers} + getOptionValue={option => option.id} + getOptionLabel={option => option.defaultIdentity?.name} promptTextCreator={label => `Add ${label}?`} valueKey="id" /> @@ -40,15 +48,17 @@ const ReviewerForm = ({ reviewerUsers, }) => ( <form onSubmit={handleSubmit}> - <Field - component={ReviewerInput} - name="user" - reviewerUsers={reviewerUsers} - validate={required} - /> - <Button disabled={!isValid} primary type="submit"> - Invite reviewer - </Button> + <FieldAndButton> + <Field + component={ReviewerInput} + name="user" + reviewerUsers={reviewerUsers} + validate={required} + /> + <Button disabled={!isValid} primary type="submit"> + Invite reviewer + </Button> + </FieldAndButton> </form> ) diff --git a/app/components/component-review/src/components/reviewers/Reviewers.js b/app/components/component-review/src/components/reviewers/Reviewers.js index 2cdf32c8c50f3683e8868ab50414cda853cd712b..51b881c54bb138886da52de54abddce6238ae193 100644 --- a/app/components/component-review/src/components/reviewers/Reviewers.js +++ b/app/components/component-review/src/components/reviewers/Reviewers.js @@ -1,20 +1,31 @@ import React from 'react' import styled from 'styled-components' -import { Link } from '@pubsweet/ui' +import { Link, Action, Button } from '@pubsweet/ui' +import { grid } from '@pubsweet/ui-toolkit' import ReviewerForm from './ReviewerForm' -import { Container, PaddedContent } from '../../../../shared' +import { + Container, + SectionRow, + SectionContent, + SectionHeader, + Title, + Heading, + HeadingWithAction, + StatusBadge, +} from '../../../../shared' // TODO: Make this a proper shared component? import { UserAvatar } from '../../../../component-avatar/src' -const Form = styled.div`` const ReviewersList = styled.div` - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr)); + grid-gap: ${grid(2)}; ` +const Reviewer = styled.div`` + const Reviewers = ({ - Reviewer, journal, isValid, loadOptions, @@ -23,11 +34,30 @@ const Reviewers = ({ reviewerUsers, manuscript, handleSubmit, + removeReviewer, teams, + history, }) => ( <Container> - <PaddedContent> - <Form> + <HeadingWithAction> + <Heading>Reviewers</Heading> + <Button + onClick={() => + history.push( + `/journal/versions/${manuscript.id}/decisions/${manuscript.id}`, + ) + } + primary + > + Back to control panel + </Button> + </HeadingWithAction> + <SectionContent> + <SectionHeader> + <Title>Invite reviewers</Title> + </SectionHeader> + + <SectionRow> <ReviewerForm handleSubmit={handleSubmit} isValid={isValid} @@ -35,23 +65,40 @@ const Reviewers = ({ loadOptions={loadOptions} reviewerUsers={reviewerUsers} /> - <Link - to={`/journal/versions/${manuscript.id}/decisions/${manuscript.id}`} - > - Back to Control Panel - </Link> - </Form> - </PaddedContent> - <PaddedContent> - {reviewers && ( - <ReviewersList> - {reviewers.map(reviewer => ( - <UserAvatar key={reviewer.id} user={reviewer.user} /> - // <Reviewer journal={journal} key={reviewer.id} username={reviewer.user.username} /> - ))} - </ReviewersList> - )} - </PaddedContent> + </SectionRow> + </SectionContent> + <SectionContent> + <SectionHeader> + <Title>Reviewer status</Title> + </SectionHeader> + <SectionRow> + {reviewers && ( + <ReviewersList> + {reviewers.map(reviewer => ( + <Reviewer> + <StatusBadge minimal status={reviewer.status} /> + <UserAvatar key={reviewer.id} user={reviewer.user} /> + {reviewer.user.defaultIdentity.name} + <div> + <Action + onClick={() => + removeReviewer({ + variables: { + userId: reviewer.user.id, + manuscriptId: manuscript.id, + }, + }) + } + > + Delete + </Action> + </div> + </Reviewer> + ))} + </ReviewersList> + )} + </SectionRow> + </SectionContent> </Container> )