Skip to content
Snippets Groups Projects
Commit e7d98386 authored by Bogdan Cochior's avatar Bogdan Cochior
Browse files

feat(review): add form to submit report

parent 6422be63
No related branches found
No related tags found
1 merge request!8Sprint #10
......@@ -17,7 +17,7 @@ const TR = ({
{isSubmitting && <AuthorStatus>SA</AuthorStatus>}
{isCorresponding && !isSubmitting && <AuthorStatus>CA</AuthorStatus>}
</td>
<td>{email || 'N/A'}</td>
<td>{email || ''}</td>
<td>{affiliation}</td>
</Row>
)
......@@ -27,7 +27,7 @@ const Authors = ({ authors }) => (
<thead>
<tr>
<td colSpan="2">Full Name</td>
<td>Email</td>
<td>{authors[0].email ? 'Email' : ''}</td>
<td>Affiliation</td>
</tr>
</thead>
......
import React, { Fragment } from 'react'
import { connect } from 'react-redux'
import { required } from 'xpub-validators'
import styled, { css } from 'styled-components'
import { compose, withHandlers, withProps } from 'recompose'
import { th, Menu, ValidatedField, Icon, Button, Spinner } from '@pubsweet/ui'
import {
reduxForm,
isSubmitting,
change as changeForm,
getFormValues,
} from 'redux-form'
import AutosaveIndicator from 'pubsweet-component-wizard/src/components/AutosaveIndicator'
import {
autosaveRequest,
autosaveSuccess,
} from 'pubsweet-component-wizard/src/redux/autosave'
import { parseReviewResponseToForm, parseReviewRequest } from './utils'
const guidelinesLink =
'https://about.hindawi.com/authors/peer-review-at-hindawi/'
const options = [
{
value: 'publish',
label: 'Publish unaltered',
},
{
value: 'major',
label: 'Consider after major revision',
},
{
value: 'minor',
label: 'Consider after major revision',
},
{
value: 'reject',
label: 'Reject',
},
]
const review = {
id: 'revuewiuuuid',
userId: 'uuuuuuid',
recommendation: 'publish',
recommendationType: 'review',
comments: [
{
content: 'Here is public text',
public: true,
files: [],
},
{
content: 'Here is PRIVATE text',
public: false,
files: [],
},
],
}
const ReviewerReportForm = ({
isSubmitting,
changeField,
handleSubmit,
formValues = {},
initialValues,
}) => (
<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={options}
placeholder="Select"
/>
)}
name="recommendation"
validate={[required]}
/>
</Row>
<Spacing />
<Row>
<Label>
Report <ActionText left={12}>Upload file</ActionText>
</Label>
</Row>
<Row>
<FullWidth>
<ValidatedField
component={input => (
<Textarea
{...input}
hasError={input.validationStatus === 'error'}
onChange={e => changeField('public', e.target.value)}
rows={6}
/>
)}
name="public"
validate={[required]}
/>
</FullWidth>
</Row>
{formValues.hasConfidential ? (
<Fragment>
<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={input => (
<Textarea
{...input}
hasError={input.validationStatus === 'error'}
onChange={e => changeField('confidential', e.target.value)}
rows={6}
/>
)}
name="confidential"
validate={[required]}
/>
</FullWidth>
</Row>
</Fragment>
) : (
<Row>
<ActionText onClick={() => changeField('hasConfidential', true)}>
Add confidential note for the Editorial Team
</ActionText>
</Row>
)}
<Spacing />
<Row>
{isSubmitting ? (
<Spinner size={4} />
) : (
<ActionButton onClick={handleSubmit}> Submit report </ActionButton>
)}
<AutosaveIndicator formName="reviewerReport" />
</Row>
</Root>
)
// To be removed
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
export default compose(
connect(
state => ({
formValues: getFormValues('reviewerReport')(state),
isSubmitting: isSubmitting('reviewerReport')(state),
}),
{ changeForm, getFormValues },
),
withProps(() => ({
initialValues: parseReviewResponseToForm({}),
})),
withHandlers({
changeField: ({ changeForm }) => (field, value) => {
changeForm('reviewerReport', field, value)
},
}),
reduxForm({
form: 'reviewerReport',
enableReinitialize: true,
forceUnregisterOnUnmount: true,
onChange: (values, dispatch) => {
dispatch(autosaveRequest())
sleep(1000).then(() => dispatch(autosaveSuccess(new Date())))
},
onSubmit: (values, dispatch, { isSubmitting }) =>
sleep(1000).then(() => {
// TODO: link to backend
const review = parseReviewRequest(values)
window.alert(`You submitted:\n\n${JSON.stringify(review, null, 2)}`)
}),
}),
)(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};
text-decoration: underline;
cursor: pointer;
margin-left: ${({ left }) => left || 0}px;
`
const ActionTextIcon = styled(ActionText)`
display: flex;
align-items: center;
`
const ActionLink = styled.a`
${defaultText};
`
const Textarea = styled.textarea`
width: 100%;
padding: calc(${th('subGridUnit')}*2);
font-size: ${th('fontSizeBaseSmall')};
font-family: ${th('fontWriting')};
border-color: ${({ hasError }) =>
hasError ? th('colorError') : th('colorPrimary')};
`
const Spacing = styled.div`
margin-top: ${th('gridUnit')};
flex: 1;
`
const FullWidth = styled.div`
flex: 1;
> div {
flex: 1;
}
`
const Row = styled.div`
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
box-sizing: border-box;
flex-wrap: wrap;
justify-content: space-between;
`
const ActionButton = styled(Button)`
${defaultText};
align-items: center;
background-color: ${th('colorPrimary')};
color: ${th('colorTextReverse')};
display: flex;
padding: 4px 8px;
text-align: center;
height: calc(${th('subGridUnit')}*5);
text-transform: uppercase;
`
// #endregion
......@@ -5,6 +5,7 @@ import styled from 'styled-components'
import { compose, withHandlers, lifecycle } from 'recompose'
import { ReviewerBreakdown } from 'pubsweet-components-faraday/src/components/Invitations'
import ReviewersDetailsList from 'pubsweet-components-faraday/src/components/Reviewers/ReviewersDetailsList'
import ReviewerReportForm from 'pubsweet-component-manuscript/src/components/ReviewerReportForm'
import {
selectReviewers,
selectFetchingReviewers,
......@@ -54,9 +55,9 @@ const ReviewsAndReports = ({
</Root>
)}
{isReviewer && (
<Root>
<Root id="review-report">
<Expandable label="Your Report" startExpanded>
<div>Form here, to be implemented</div>
<ReviewerReportForm />
</Expandable>
</Root>
)}
......
import moment from 'moment'
import { get, find, capitalize } from 'lodash'
import { get, find, capitalize, omit } from 'lodash'
export const parseTitle = version => {
const title = get(version, 'metadata.title')
......@@ -80,3 +80,38 @@ export const redirectToError = redirectFn => err => {
redirectFn('/error-page', 'Oops! Something went wrong.')
}
}
export const parseReviewResponseToForm = (review = {}) => {
const comments = review.comments || []
const publicComment = comments.find(c => c.public)
const privateComment = comments.find(c => !c.public)
return {
...review,
public: get(publicComment, 'content'),
files: get(publicComment, 'files'),
confidential: get(privateComment, 'content'),
hasConfidential: !!get(privateComment, 'content'),
}
}
export const parseReviewRequest = (review = {}) => {
const comments = [
{
public: true,
content: review.public,
files: review.files || [],
},
]
if (review.hasConfidential) {
comments.push({
public: false,
content: review.confidential,
files: [],
})
}
return {
...omit(review, ['public', 'confidential', 'hasConfidential', 'files']),
comments,
}
}
......@@ -43,7 +43,7 @@ export default (state = initialState, action) => {
case 'UPDATE_FRAGMENT_SUCCESS':
return {
...initialState,
lastUpdate: action.receivedAt,
lastUpdate: action.receivedAt || action.lastUpdate,
}
default:
return state
......
......@@ -58,6 +58,9 @@ export const createDraftSubmission = history => (dispatch, getState) => {
version: 1,
}),
).then(({ fragment }) => {
if (!fragment.id) {
throw new Error('Failed to create a project')
}
const route = `/projects/${collection.id}/versions/${fragment.id}/submit`
if (!currentUser.admin) {
addSubmittingAuthor(currentUser, collection.id)
......
......@@ -2,3 +2,4 @@ export { default as authors } from './authors'
export { default as editors } from './editors'
export { default as files } from './files'
export { default as reviewers } from './reviewers'
export { default as recommendations } from './recommendations'
import {
get as apiGet,
create,
remove,
update,
} from 'pubsweet-client/src/helpers/api'
const REQUEST = 'recommendations/REQUEST'
const ERROR = 'recommendations/ERROR'
const GET_RECOMMENDATIONS_SUCCESS = 'recommendations/GET_SUCCESS'
const GET_RECOMMENDATION_SUCCESS = 'recommendations/GET_ITEM_SUCCESS'
const UPDATE_RECOMMENDATION_SUCCESS = 'recommendations/UPDATE_SUCCESS'
export const recommendationsRequest = () => ({
type: REQUEST,
})
export const recommendationsError = error => ({
type: ERROR,
error,
})
export const getRecommendationsSuccess = recommendations => ({
type: GET_RECOMMENDATIONS_SUCCESS,
payload: { recommendations },
})
export const getRecommendationSuccess = recommendation => ({
type: GET_RECOMMENDATION_SUCCESS,
payload: { recommendation },
})
export const updateRecommendationSuccess = recommendation => ({
type: UPDATE_RECOMMENDATION_SUCCESS,
payload: { recommendation },
})
// Actions
// State
const initialState = {
fetching: false,
error: null,
recommendations: [],
recommendation: {},
}
export default (state = initialState, action = {}) => {
switch (action.type) {
case REQUEST:
return {
...state,
fetching: true,
recommendations: [],
recommendation: {},
}
case ERROR:
return {
...state,
fetching: false,
error: action.error,
}
case GET_RECOMMENDATIONS_SUCCESS:
return {
...state,
fetching: false,
error: null,
recommendations: action.payload.recommendations,
}
case GET_RECOMMENDATION_SUCCESS:
return {
...state,
fetching: false,
error: null,
recommendation: action.payload.recommendation,
}
case UPDATE_RECOMMENDATION_SUCCESS:
return {
...state,
fetching: false,
error: null,
recommendation: action.payload.recommendation,
}
default:
return state
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment