Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ReviewerReportForm.js 6.87 KiB
import React, { Fragment } from 'react'
import { connect } from 'react-redux'
import { isEmpty, merge } from 'lodash'
import { th } from '@pubsweet/ui-toolkit'
import { withJournal } from 'xpub-journal'
import { required } from 'xpub-validators'
import styled, { css } from 'styled-components'
import { compose, withHandlers, withProps } from 'recompose'
import { Menu, Icon, Button, ErrorText, ValidatedField } from '@pubsweet/ui'
import {
  reduxForm,
  isSubmitting,
  getFormValues,
  change as changeForm,
} from 'redux-form'

import {
  uploadFile,
  deleteFile,
  getSignedUrl,
  getRequestStatus,
} from 'pubsweet-components-faraday/src/redux/files'

import {
  withModal,
  ConfirmationModal,
} from 'pubsweet-component-modal/src/components'

import {
  createRecommendation,
  updateRecommendation,
} from 'pubsweet-components-faraday/src/redux/recommendations'

import {
  onReviewSubmit,
  onReviewChange,
  parseReviewResponseToForm,
} from './utils'

const guidelinesLink =
  'https://about.hindawi.com/authors/peer-review-at-hindawi/'

const TextAreaField = input => <Textarea {...input} height={70} rows={6} />

const ReviewerReportForm = ({
  addFile,
  fileError,
  removeFile,
  changeField,
  isSubmitting,
  handleSubmit,
  fileFetching,
  review = {},
  formValues = {},
  journal: { recommendations },
}) => (
  <Root>
    <Row>
      <Label>Recommendation*</Label>
      <ActionLink href={guidelinesLink} target="_blank">
        Hindawi Reviewer Guidelines
      </ActionLink>
    </Row>
    <Row>
      <ValidatedField
        component={input => (
          <Menu
            {...input}
            inline
            onChange={v => changeField('recommendation', v)}
            options={recommendations}
            placeholder="Select"
          />
        )}
        name="recommendation"
        validate={[required]}
      />
    </Row>

    <Spacing />

    <Row>
      <FullWidth className="full-width">
        <ValidatedField
          component={TextAreaField}
          name="public"
          validate={isEmpty(formValues.files) ? [required] : []}
        />
      </FullWidth>
    </Row>

    {formValues.hasConfidential ? (
      <Fragment>
        <Spacing />
        <Row>
          <Label>
            Note for the editorial team <i>Not shared with the author</i>
          </Label>
          <ActionTextIcon onClick={() => changeField('hasConfidential', false)}>
            <Icon primary size={3}>
              x
            </Icon>
            Remove
          </ActionTextIcon>
        </Row>
        <Row>
          <FullWidth>
            <ValidatedField
              component={TextAreaField}
              name="confidential"
              validate={[required]}
            />
          </FullWidth>
        </Row>
      </Fragment>
    ) : (
      <Row>
        <ActionText onClick={() => changeField('hasConfidential', true)}>
          Add confidential note for the Editorial Team
        </ActionText>
      </Row>
    )}

    <Spacing />
    {fileError && (
      <Row>
        <ErrorText>{fileError}</ErrorText>
      </Row>
    )}
    <Row>
      <ActionButton onClick={handleSubmit}>Submit report</ActionButton>
    </Row>
  </Root>
)

const ModalWrapper = compose(
  connect(state => ({
    fetching: false,
  })),
)(({ fetching, ...rest }) => (
  <ConfirmationModal {...rest} isFetching={fetching} />
))

export default compose(
  withJournal,
  connect(
    state => ({
      fileFetching: getRequestStatus(state),
      formValues: getFormValues('reviewerReport')(state),
      isSubmitting: isSubmitting('reviewerReport')(state),
    }),
    {
      uploadFile,
      deleteFile,
      changeForm,
      getSignedUrl,
      getFormValues,
      createRecommendation,
      updateRecommendation,
    },
  ),
  withProps(({ review = {}, formValues = {} }) => ({
    initialValues: merge(parseReviewResponseToForm(review), formValues),
  })),
  withModal(props => ({
    modalComponent: ModalWrapper,
  })),
  withHandlers({
    changeField: ({ changeForm }) => (field, value) => {
      changeForm('reviewerReport', field, value)
    },
    addFile: ({ formValues = {}, uploadFile, changeForm, version }) => file => {
      uploadFile(file, 'review', version)
        .then(file => {
          const files = formValues.files || []
          const newFiles = [...files, file]
          changeForm('reviewerReport', 'files', newFiles)
        })
        .catch(e => console.error(`Couldn't upload file.`, e))
    },
    removeFile: ({
      formValues: { files = [] },
      changeForm,
      deleteFile,
    }) => id => e => {
      deleteFile(id)
        .then(r => {
          const newFiles = files.filter(f => f.id !== id)
          changeForm('reviewerReport', 'files', newFiles)
        })
        .catch(e => console.error(`Couldn't delete the file.`, e))

      const newFiles = files.filter(f => f.id !== id)
      changeForm('reviewerReport', 'files', newFiles)
    },
  }),
  reduxForm({
    form: 'reviewerReport',
    onChange: onReviewChange,
    onSubmit: onReviewSubmit,
    enableReinitialize: false,
    keepDirtyOnReinitialize: true,
  }),
)(ReviewerReportForm)

// #region styled-components

const defaultText = css`
  color: ${th('colorPrimary')};
  font-family: ${th('fontReading')};
  font-size: ${th('fontSizeBaseSmall')};
`
const Root = styled.div`
  display: flex;
  flex-direction: column;
  margin: auto;
  [role='listbox'] {
    min-width: 280px;
  }
`

const Label = styled.div`
  ${defaultText};
  text-transform: uppercase;
  i {
    text-transform: none;
    margin-left: ${th('gridUnit')};
  }
`

const ActionText = styled.span`
  ${defaultText};
  cursor: pointer;
  margin-left: ${({ left }) => left || 0}px;
  text-decoration: underline;
`

const ActionTextIcon = styled(ActionText)`
  align-items: center;
  display: flex;
`
const ActionLink = styled.a`
  ${defaultText};
`

const Textarea = styled.textarea`
  border-color: ${({ hasError }) =>
    hasError ? th('colorError') : th('colorPrimary')};
  font-size: ${th('fontSizeBaseSmall')};
  font-family: ${th('fontWriting')};
  padding: calc(${th('subGridUnit')} * 2);
  transition: all 300ms linear;
  width: 100%;

  &:read-only {
    background-color: ${th('colorBackgroundHue')};
  }
`

const Spacing = styled.div`
  flex: 1;
  margin-top: calc(${th('gridUnit')} / 2);
`

const FullWidth = styled.div`
  flex: 1;
  > div {
    flex: 1;
  }
`

const Row = styled.div`
  ${defaultText};
  align-items: center;
  box-sizing: border-box;
  display: flex;
  flex-direction: row;
  flex: 1;
  flex-wrap: wrap;
  justify-content: ${({ left }) => (left ? 'left' : 'space-between')};

  div[role='alert'] {
    margin-top: 0;
  }
`

const ActionButton = styled(Button)`
  ${defaultText};
  align-items: center;
  background-color: ${th('colorPrimary')};
  color: ${th('colorTextReverse')};
  height: calc(${th('subGridUnit')} * 5);
  display: flex;
  padding: calc(${th('subGridUnit')} / 2) calc(${th('subGridUnit')});
  text-align: center;
  text-transform: uppercase;
`
// #endregion