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

feat(details): wip: reviewers reports

parent ba5d46f6
No related branches found
No related tags found
1 merge request!8Sprint #10
import React from 'react' import React from 'react'
import { th } from '@pubsweet/ui' import { th } from '@pubsweet/ui'
import { connect } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { compose, withHandlers, lifecycle } from 'recompose'
import { ReviewerBreakdown } from 'pubsweet-components-faraday/src/components/Invitations'
import { ReviewersList } from 'pubsweet-components-faraday/src/components/Reviewers'
import {
selectReviewers,
selectFetchingReviewers,
getCollectionReviewers,
} from 'pubsweet-components-faraday/src/redux/reviewers'
import Tabs from '../molecules/Tabs' import Tabs from '../molecules/Tabs'
import Expandable from '../molecules/Expandable' import Expandable from '../molecules/Expandable'
const tabSections = [ const getTabSections = (collectionId, reviewers) => [
{ {
key: 1, key: 1,
label: 'Reviewers Details', label: 'Reviewers Details',
content: <div>Reviewers Details Content</div>, content: (
<ReviewersList collectionId={collectionId} reviewers={reviewers} />
),
}, },
{ {
key: 2, key: 2,
...@@ -18,15 +30,38 @@ const tabSections = [ ...@@ -18,15 +30,38 @@ const tabSections = [
}, },
] ]
const ReviewsAndReports = () => ( const ReviewsAndReports = ({ project, reviewers = [] }) => (
<Root> <Root>
<Expandable label="Reviewers & Reports" startExpanded> <Expandable
<Tabs activeKey={1} sections={tabSections} /> label="Reviewers & Reports"
rightHTML={<ReviewerBreakdown values={project.invitations || []} />}
startExpanded
>
<Tabs activeKey={1} sections={getTabSections(project.id, reviewers)} />
</Expandable> </Expandable>
</Root> </Root>
) )
export default ReviewsAndReports export default compose(
connect(
state => ({
reviewers: selectReviewers(state),
fetchingReviewers: selectFetchingReviewers(state),
}),
{ getCollectionReviewers },
),
withHandlers({
getReviewers: ({ project, setReviewers, getCollectionReviewers }) => () => {
getCollectionReviewers(project.id)
},
}),
lifecycle({
componentDidMount() {
const { getReviewers } = this.props
getReviewers()
},
}),
)(ReviewsAndReports)
// #region styled-components // #region styled-components
......
import React from 'react' import React from 'react'
import styled, { css } from 'styled-components' import styled from 'styled-components'
import { th, Icon } from '@pubsweet/ui' import { th, Icon } from '@pubsweet/ui'
import { compose, withState, withHandlers } from 'recompose' import { compose, withState, withHandlers } from 'recompose'
const Expandable = ({ expanded, label, children, toggle }) => ( const Expandable = ({ expanded, label, children, toggle, rightHTML }) => (
<Root expanded={expanded}> <Root expanded={expanded}>
<Header expanded={expanded} onClick={toggle}> <Header expanded={expanded} onClick={toggle}>
<Chevron expanded={expanded}> <LeftDetails>
<Icon primary size={3}> <Chevron expanded={expanded}>
chevron_up <Icon primary size={3}>
</Icon> chevron_up
</Chevron> </Icon>
<SectionLabel>{label}</SectionLabel> </Chevron>
<SectionLabel>{label}</SectionLabel>
</LeftDetails>
{rightHTML && <RightDetails>{rightHTML}</RightDetails>}
</Header> </Header>
{expanded && <ChildrenContainer>{children}</ChildrenContainer>} {expanded && <ChildrenContainer>{children}</ChildrenContainer>}
</Root> </Root>
...@@ -51,18 +54,13 @@ const ChildrenContainer = styled.div` ...@@ -51,18 +54,13 @@ const ChildrenContainer = styled.div`
const Header = styled.div` const Header = styled.div`
align-items: center; align-items: center;
border-width: 0 0 ${th('borderWidth')} 0;
border-style: ${th('borderStyle')};
border-color: ${th('colorBorder')};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
border-width: 0 0 ${th('borderWidth')} 0; margin-bottom: calc(${th('subGridUnit')} * 3);
border-style: ${th('borderStyle')};
border-color: transparent;
${({ expanded }) =>
expanded &&
css`
border-color: ${th('colorBorder')};
margin-bottom: calc(${th('subGridUnit')} * 3);
`};
` `
const Root = styled.div` const Root = styled.div`
...@@ -71,4 +69,20 @@ const Root = styled.div` ...@@ -71,4 +69,20 @@ const Root = styled.div`
flex-direction: column; flex-direction: column;
transition: all 0.3s; transition: all 0.3s;
` `
const LeftDetails = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
flex: ${({ flex }) => flex || 1};
`
const RightDetails = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
flex: ${({ flex }) => flex || 1};
`
// #endregion // #endregion
...@@ -49,14 +49,14 @@ const DashboardCard = ({ ...@@ -49,14 +49,14 @@ const DashboardCard = ({
<Card id={customId}> <Card id={customId}>
<ListView> <ListView>
<Top> <Top>
<LeftDetails flex="5"> <LeftDetails flex={6}>
<ManuscriptId>{`ID ${customId}`}</ManuscriptId> <ManuscriptId>{`ID ${customId}`}</ManuscriptId>
<Title <Title
title={title} title={title}
dangerouslySetInnerHTML={{ __html: title }} // eslint-disable-line dangerouslySetInnerHTML={{ __html: title }} // eslint-disable-line
/> />
</LeftDetails> </LeftDetails>
<RightDetails flex="2"> <RightDetails flex={2}>
<ZipFiles <ZipFiles
archiveName={`ID-${project.customId}`} archiveName={`ID-${project.customId}`}
disabled={!hasFiles} disabled={!hasFiles}
...@@ -81,11 +81,11 @@ const DashboardCard = ({ ...@@ -81,11 +81,11 @@ const DashboardCard = ({
</RightDetails> </RightDetails>
</Top> </Top>
<Bottom> <Bottom>
<LeftDetails flex="3"> <LeftDetails flex={3}>
<Status>{mapStatusToLabel(project)}</Status> <Status>{mapStatusToLabel(project)}</Status>
<DateField>{submitted || ''}</DateField> <DateField>{submitted || ''}</DateField>
</LeftDetails> </LeftDetails>
<RightDetails flex="4"> <RightDetails flex={4}>
<ManuscriptType title={manuscriptMeta}> <ManuscriptType title={manuscriptMeta}>
{manuscriptMeta} {manuscriptMeta}
</ManuscriptType> </ManuscriptType>
...@@ -257,6 +257,7 @@ const ManuscriptId = styled.div` ...@@ -257,6 +257,7 @@ const ManuscriptId = styled.div`
text-align: left; text-align: left;
text-transform: uppercase; text-transform: uppercase;
white-space: nowrap; white-space: nowrap;
flex: 1;
` `
const Details = styled.div` const Details = styled.div`
...@@ -331,7 +332,7 @@ const Title = styled.div` ...@@ -331,7 +332,7 @@ const Title = styled.div`
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
flex: 1; flex: 10;
` `
const Status = styled.div` const Status = styled.div`
......
...@@ -147,11 +147,12 @@ const Root = styled.div` ...@@ -147,11 +147,12 @@ const Root = styled.div`
margin-left: ${th('gridUnit')}; margin-left: ${th('gridUnit')};
` `
const HEName = styled.div`` const HEName = styled.div`
text-decoration: underline;
`
const HEActions = styled.div` const HEActions = styled.div`
${defaultText}; ${defaultText};
text-transform: uppercase;
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
...@@ -173,5 +174,6 @@ const AssignButton = styled(Button)` ...@@ -173,5 +174,6 @@ const AssignButton = styled(Button)`
background-color: ${th('colorPrimary')}; background-color: ${th('colorPrimary')};
height: calc(${th('subGridUnit')}*5); height: calc(${th('subGridUnit')}*5);
text-align: center; text-align: center;
text-transform: uppercase;
` `
// #endregion // #endregion
...@@ -24,7 +24,10 @@ const reviewerReduce = (acc, r) => ({ ...@@ -24,7 +24,10 @@ const reviewerReduce = (acc, r) => ({
}) })
const invitationReduce = (acc, i) => { const invitationReduce = (acc, i) => {
const key = i.isAccepted ? 'accepted' : 'declined' let key = 'pending'
if (i.hasAnswer) {
key = i.isAccepted ? 'accepted' : 'declined'
}
return { return {
...acc, ...acc,
[key]: acc[key] + 1, [key]: acc[key] + 1,
......
import React from 'react'
import moment from 'moment'
import { pick } from 'lodash'
import { connect } from 'react-redux'
import { th, Icon } from '@pubsweet/ui'
import styled, { withTheme } from 'styled-components'
import { compose, withHandlers, withProps } from 'recompose'
import { revokeReviewer, inviteReviewer } from '../../redux/reviewers'
const ResendRevoke = withTheme(
({ theme, showConfirmResend, showConfirmRevoke, status }) => (
<ActionButtons>
<div onClick={showConfirmResend}>
<Icon color={theme.colorPrimary}>refresh-cw</Icon>
</div>
{status === 'pending' && (
<div onClick={showConfirmRevoke}>
<Icon color={theme.colorPrimary}>x-circle</Icon>
</div>
)}
</ActionButtons>
),
)
const ReviewersList = ({
renderAcceptedLabel,
reviewers,
showConfirmResend,
showConfirmRevoke,
renderTimestamp,
}) =>
reviewers.length > 0 && (
<Root>
<ScrollContainer>
{reviewers.map((r, index) => (
<ReviewerItem key={r.invitationId}>
<Column flex={3}>
<div>
<ReviewerName>{r.name}</ReviewerName>
{r.status === 'accepted' && (
<AcceptedReviewer>
{renderAcceptedLabel(index)}
</AcceptedReviewer>
)}
</div>
<ReviewerEmail>{r.email}</ReviewerEmail>
</Column>
<Column>
<StatusText>{r.status}</StatusText>
<DateText>{renderTimestamp(r.timestamp)}</DateText>
</Column>
{r.status === 'pending' ? (
<ResendRevoke
showConfirmResend={showConfirmResend(r)}
showConfirmRevoke={showConfirmRevoke(r.invitationId)}
status={r.status}
/>
) : (
<Column />
)}
</ReviewerItem>
))}
</ScrollContainer>
</Root>
)
export default compose(
connect(null, { inviteReviewer, revokeReviewer }),
withProps(({ reviewers = [] }) => ({
firstAccepted: reviewers.findIndex(r => r.status === 'accepted'),
})),
withHandlers({
renderTimestamp: () => timestamp => {
const today = moment()
const stamp = moment(timestamp)
const duration = moment.duration(today.diff(stamp))
if (duration.asDays() < 1) {
return `${duration.humanize()} ago`
}
return stamp.format('DD.MM.YYYY')
},
goBackToReviewers: ({ showModal, hideModal, collectionId }) => () => {
showModal({
collectionId,
type: 'invite-reviewers',
onConfirm: () => {
hideModal()
},
})
},
renderAcceptedLabel: ({ firstAccepted }) => index =>
`Reviewer ${index - firstAccepted + 1}`,
}),
withHandlers({
showConfirmResend: ({
showModal,
goBackToReviewers,
inviteReviewer,
collectionId,
}) => reviewer => () => {
showModal({
title: 'Resend reviewer invite',
confirmText: 'Resend',
onConfirm: () => {
inviteReviewer(
pick(reviewer, ['email', 'firstName', 'lastName', 'affiliation']),
collectionId,
).then(goBackToReviewers, goBackToReviewers)
},
onCancel: goBackToReviewers,
})
},
showConfirmRevoke: ({
showModal,
hideModal,
goBackToReviewers,
revokeReviewer,
collectionId,
}) => invitationId => () => {
showModal({
title: 'Unassign Reviewer',
confirmText: 'Unassign',
onConfirm: () => {
revokeReviewer(invitationId, collectionId).then(
goBackToReviewers,
goBackToReviewers,
)
},
onCancel: goBackToReviewers,
})
},
}),
)(ReviewersList)
// #region styled-components
const ReviewerEmail = styled.span`
font-size: ${th('fontSizeBaseSmall')};
color: ${th('colorPrimary')};
font-family: ${th('fontReading')};
`
const ReviewerName = ReviewerEmail.extend`
font-size: ${th('fontSizeBase')};
text-decoration: underline;
`
const AcceptedReviewer = ReviewerEmail.extend`
font-weight: bold;
margin-left: ${th('subGridUnit')};
`
const StatusText = ReviewerEmail.extend`
text-transform: uppercase;
`
const DateText = ReviewerEmail.extend``
const Column = styled.div`
align-items: flex-start;
display: flex;
flex-direction: column;
justify-content: center;
flex: ${({ flex }) => flex || 1};
`
const ReviewerItem = styled.div`
align-items: center;
border-bottom: ${th('borderDefault')};
display: flex;
justify-content: space-between;
padding: ${th('subGridUnit')} calc(${th('subGridUnit')} * 2);
&:hover {
background-color: ${th('colorSecondary')};
}
`
const ActionButtons = styled.div`
align-items: center;
display: flex;
flex: 1;
justify-content: space-evenly;
opacity: 0;
div {
cursor: pointer;
}
${ReviewerItem}:hover & {
opacity: 1;
}
`
const ScrollContainer = styled.div`
align-self: stretch;
flex: 1;
overflow: auto;
`
const Root = styled.div`
align-items: stretch;
align-self: stretch;
background-color: ${th('backgroundColorReverse')};
border: ${th('borderDefault')};
display: flex;
flex-direction: column;
justify-content: flex-start;
height: 25vh;
`
// #endregion
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