Skip to content

Commit 86e5ca3

Browse files
committed
PM-802 - show all projects
1 parent a745f7b commit 86e5ca3

File tree

14 files changed

+100
-28
lines changed

14 files changed

+100
-28
lines changed

src/actions/sidebar.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export function loadProjects (filterProjectName = '', myProjects = true, paramFi
3636
})
3737

3838
const filters = {
39-
status: 'active',
4039
sort: 'lastActivityAt desc',
4140
perPage: PROJECTS_PAGE_SIZE,
4241
...paramFilters
@@ -54,10 +53,13 @@ export function loadProjects (filterProjectName = '', myProjects = true, paramFi
5453
}
5554

5655
const state = getState().sidebar
57-
fetchMemberProjects(filters).then(projects => dispatch({
56+
// eslint-disable-next-line no-sequences
57+
fetchMemberProjects(filters).then(({ projects, pagination }) => (console.log('here', pagination), dispatch({
5858
type: LOAD_PROJECTS_SUCCESS,
59-
projects: _.uniqBy((state.projects || []).concat(projects), 'id')
60-
})).catch(() => dispatch({
59+
projects: _.uniqBy((state.projects || []).concat(projects), 'id'),
60+
total: pagination.xTotal,
61+
page: pagination.xPage
62+
}))).catch(() => dispatch({
6163
type: LOAD_PROJECTS_FAILURE
6264
}))
6365
}
@@ -69,15 +71,20 @@ export function loadProjects (filterProjectName = '', myProjects = true, paramFi
6971
export function loadMoreProjects (filterProjectName = '', myProjects = true, paramFilters = {}) {
7072
return (dispatch, getState) => {
7173
const state = getState().sidebar
72-
const projects = state.projects || []
7374

7475
loadProjects(filterProjectName, myProjects, _.assignIn({}, paramFilters, {
7576
perPage: PROJECTS_PAGE_SIZE,
76-
page: Math.ceil(projects.length / PROJECTS_PAGE_SIZE) + 1
77+
page: state.page + 1
7778
}))(dispatch, getState)
7879
}
7980
}
8081

82+
export function loadTaasProjects (filterProjectName = '', myProjects = true, paramFilters = {}) {
83+
return loadProjects(filterProjectName, myProjects, Object.assign({
84+
type: 'talent-as-a-service'
85+
}, paramFilters))
86+
}
87+
8188
/**
8289
* Unloads projects of the authenticated user
8390
*/

src/actions/users.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function searchUserProjects (isAdmin = true, keyword) {
6666
filters['memberOnly'] = true
6767
}
6868

69-
fetchMemberProjects(filters).then(projects => dispatch({
69+
fetchMemberProjects(filters).then(({ projects }) => dispatch({
7070
type: SEARCH_USER_PROJECTS_SUCCESS,
7171
projects
7272
})).catch(() => dispatch({
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.loader {
2+
width: 100%;
3+
display: flex;
4+
justify-content: center;
5+
margin-bottom: 20px;
6+
7+
> * {
8+
width: auto;
9+
min-width: 130px;
10+
}
11+
}

src/components/InfiniteLoadTrigger/index.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React, { useEffect, useRef, useCallback } from 'react'
22
import PropTypes from 'prop-types'
33

4-
const InfiniteScrollTrigger = ({ onLoadMore, rootMargin = '100px', threshold = 0.1 }) => {
4+
import styles from './InfiniteLoadTrigger.module.scss'
5+
import { OutlineButton } from '../Buttons'
6+
7+
const InfiniteLoadTrigger = ({ onLoadMore, rootMargin = '100px', threshold = 0.1 }) => {
58
const triggerRef = useRef(null)
69

710
const observerCallback = useCallback(
@@ -33,13 +36,17 @@ const InfiniteScrollTrigger = ({ onLoadMore, rootMargin = '100px', threshold = 0
3336
}
3437
}, [observerCallback, rootMargin, threshold])
3538

36-
return <div ref={triggerRef} style={{ height: '1px', width: '100%' }} />
39+
return (
40+
<div ref={triggerRef} className={styles.loader}>
41+
<OutlineButton type='info' text='Load More' onClick={() => onLoadMore()} />
42+
</div>
43+
)
3744
}
3845

39-
InfiniteScrollTrigger.propTypes = {
46+
InfiniteLoadTrigger.propTypes = {
4047
onLoadMore: PropTypes.func.isRequired,
4148
rootMargin: PropTypes.string,
4249
threshold: PropTypes.number
4350
}
4451

45-
export default InfiniteScrollTrigger
52+
export default InfiniteLoadTrigger

src/components/ProjectCard/ProjectCard.module.scss

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@
4444
overflow: hidden;
4545
white-space: nowrap;
4646
text-overflow: ellipsis;
47-
//display: flex;
48-
//justify-content: space-between;
49-
//align-items: center;
47+
display: flex;
48+
justify-content: space-between;
49+
align-items: center;
50+
51+
.status {
52+
opacity: 0.7;
53+
}
5054
}
5155

5256
.icon {

src/components/ProjectCard/index.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,31 @@ import React from 'react'
22
import PT from 'prop-types'
33
import { Link } from 'react-router-dom'
44
import cn from 'classnames'
5+
import { find } from 'lodash'
6+
7+
import { PROJECT_STATUS } from '../../config/constants'
58

69
import styles from './ProjectCard.module.scss'
710

8-
const ProjectCard = ({ projectName, projectId, selected, setActiveProject }) => {
11+
const ProjectCard = ({ projectName, projectStatus, projectId, selected, setActiveProject }) => {
912
return (
1013
<div className={styles.container}>
1114
<Link
1215
to={`/projects/${projectId}/challenges`}
1316
className={cn(styles.projectName, { [styles.selected]: selected })}
1417
onClick={() => setActiveProject(parseInt(projectId))}
1518
>
16-
<div className={styles.name}>{projectName}</div>
19+
<div className={styles.name}>
20+
<span>{projectName}</span>
21+
<span className={styles.status}>{find(PROJECT_STATUS, { value: projectStatus }).label}</span>
22+
</div>
1723
</Link>
1824
</div>
1925
)
2026
}
2127

2228
ProjectCard.propTypes = {
29+
projectStatus: PT.string.isRequired,
2330
projectId: PT.number.isRequired,
2431
projectName: PT.string.isRequired,
2532
selected: PT.bool.isRequired,

src/components/ProjectForm/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const ProjectForm = ({
1515
setActiveProject,
1616
history,
1717
isEdit,
18+
canManage,
1819
projectDetail
1920
}) => {
2021
const [isSaving, setIsSaving] = useState(false)
@@ -50,7 +51,7 @@ const ProjectForm = ({
5051
name: data.projectName,
5152
description: data.description,
5253
type: data.projectType.value,
53-
status: (data.status || {}).value,
54+
status: canManage ? (data.status || {}).value : undefined,
5455
groups: data.groups,
5556
terms: data.terms ? [data.terms] : []
5657
}
@@ -112,7 +113,7 @@ const ProjectForm = ({
112113
)}
113114
</div>
114115
</div>
115-
{isEdit && (
116+
{isEdit && canManage && (
116117
<div className={cn(styles.row)}>
117118
<div className={cn(styles.formLabel, styles.field)}>
118119
<label label htmlFor='status'>

src/config/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ export const PROJECT_ROLES = {
247247
COPILOT: 'copilot'
248248
}
249249

250+
export const ALLOWED_ACCEPT_PROJECT_ROLES = [
251+
'administrator',
252+
PROJECT_ROLES.MANAGER
253+
]
254+
250255
export const ALLOWED_DOWNLOAD_SUBMISSIONS_ROLES = [
251256
'administrator',
252257
PROJECT_ROLES.MANAGER,

src/containers/Challenges/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import styles from './Challenges.module.scss'
2727
import { checkAdmin, checkAdminOrCopilot } from '../../util/tc'
2828
import { PrimaryButton } from '../../components/Buttons'
29-
import InfiniteScrollTrigger from '../../components/InfiniteLoadTrigger'
29+
import InfiniteLoadTrigger from '../../components/InfiniteLoadTrigger'
3030

3131
class Challenges extends Component {
3232
constructor (props) {
@@ -151,6 +151,7 @@ class Challenges extends Component {
151151
projects.map((p) => (
152152
<li key={p.id}>
153153
<ProjectCard
154+
projectStatus={p.status}
154155
projectName={p.name}
155156
projectId={p.id}
156157
selected={activeProjectId === `${p.id}`}
@@ -176,7 +177,7 @@ class Challenges extends Component {
176177
)}
177178
<ul>{projectComponents}</ul>
178179
{projects && !!projects.length && (
179-
<InfiniteScrollTrigger onLoadMore={this.props.loadMoreProjects} />
180+
<InfiniteLoadTrigger onLoadMore={this.props.loadMoreProjects} />
180181
)}
181182
</div>
182183
) : null}

src/containers/Tab/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { connect } from 'react-redux'
55
import Tab from '../../components/Tab'
66
import {
77
loadProjects,
8+
loadTaasProjects,
89
setActiveProject,
910
resetSidebarActiveParams,
1011
unloadProjects
@@ -96,7 +97,7 @@ class TabContainer extends Component {
9697
const { history } = props
9798

9899
if (history.location.pathname === '/taas') {
99-
this.props.loadProjects('', false, { type: 'talent-as-a-service', status: undefined })
100+
this.props.loadTaasProjects()
100101
} else {
101102
this.props.loadProjects()
102103
}
@@ -138,6 +139,7 @@ TabContainer.propTypes = {
138139
isLoading: PropTypes.bool,
139140
isLoadProjectsSuccess: PropTypes.bool,
140141
loadProjects: PropTypes.func,
142+
loadTaasProjects: PropTypes.func,
141143
unloadProjects: PropTypes.func,
142144
activeProjectId: PropTypes.number,
143145
history: PropTypes.any.isRequired,
@@ -153,6 +155,7 @@ const mapStateToProps = ({ sidebar }) => ({
153155

154156
const mapDispatchToProps = {
155157
loadProjects,
158+
loadTaasProjects,
156159
unloadProjects,
157160
setActiveProject,
158161
resetSidebarActiveParams

src/reducers/sidebar.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const initialState = {
1717
isLoading: false,
1818
projects: [],
1919
taasProjects: [],
20+
total: 0,
21+
page: 0,
2022
isLoadProjectsSuccess: false
2123
}
2224

@@ -31,12 +33,14 @@ export default function (state = initialState, action) {
3133
taasProjects: _.filter(action.projects, {
3234
type: 'talent-as-a-service'
3335
}),
36+
total: action.total,
37+
page: action.page,
3438
isLoading: false,
3539
isLoggedIn: true,
3640
isLoadProjectsSuccess: true
3741
}
3842
case UNLOAD_PROJECTS_SUCCESS:
39-
return { ...state, projects: [], isLoading: false, isLoggedIn: true, isLoadProjectsSuccess: false }
43+
return { ...state, total: 0, page: 0, projects: [], isLoading: false, isLoggedIn: true, isLoadProjectsSuccess: false }
4044
case LOAD_PROJECTS_PENDING:
4145
return { ...state, isLoading: true }
4246
case LOAD_PROJECTS_FAILURE: {

src/services/projects.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
PHASE_PRODUCT_CHALLENGE_ID_FIELD,
1010
PHASE_PRODUCT_TEMPLATE_ID
1111
} from '../config/constants'
12+
import { paginationHeaders } from '../util/pagination'
13+
1214
const { PROJECT_API_URL } = process.env
1315

1416
/**
@@ -39,13 +41,21 @@ export async function fetchBillingAccount (projectId) {
3941
* Api request for fetching member's projects
4042
* @returns {Promise<*>}
4143
*/
42-
export async function fetchMemberProjects (filters) {
44+
export function fetchMemberProjects (filters) {
4345
const params = {
4446
...filters
4547
}
4648

47-
const response = await axiosInstance.get(`${PROJECT_API_URL}?${queryString.stringify(params)}`)
48-
return _.get(response, 'data')
49+
for (let param in params) {
50+
if (params[param] && Array.isArray(params[param])) {
51+
params[`${param}[$in]`] = params[param]
52+
params[param] = undefined
53+
}
54+
}
55+
56+
return axiosInstance.get(`${PROJECT_API_URL}?${queryString.stringify(params)}`).then(response => {
57+
return { projects: _.get(response, 'data'), pagination: paginationHeaders(response) }
58+
})
4959
}
5060

5161
/**

src/util/pagination.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { get, pick, camelCase } from 'lodash'
2+
3+
export function paginationHeaders (response) {
4+
const headers = pick(get(response, 'headers'), 'x-page', 'x-per-page', 'x-total', 'x-total-pages')
5+
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [camelCase(key), +value]))
6+
}

src/util/tc.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
SUBMITTER_ROLE_UUID,
1111
READ_ONLY_ROLES,
1212
ALLOWED_DOWNLOAD_SUBMISSIONS_ROLES,
13+
ALLOWED_ACCEPT_PROJECT_ROLES,
1314
ALLOWED_EDIT_RESOURCE_ROLES
1415
} from '../config/constants'
1516
import _ from 'lodash'
@@ -194,9 +195,13 @@ export const checkEditResourceRoles = resourceRoles => {
194195
* Checks if token has any of the admin roles
195196
* @param token
196197
*/
197-
export const checkAdmin = token => {
198-
const roles = _.get(decodeToken(token), 'roles')
199-
return roles.some(val => ADMIN_ROLES.indexOf(val.toLowerCase()) > -1)
198+
export const checkAdmin = (token, project) => {
199+
const tokenData = decodeToken(token)
200+
const roles = _.get(tokenData, 'roles')
201+
const isAdmin = roles.some(val => ADMIN_ROLES.indexOf(val.toLowerCase()) > -1)
202+
const canManageProject = !project || _.isEmpty(project) || ALLOWED_ACCEPT_PROJECT_ROLES.includes(_.get(_.find(project.members, { userId: tokenData.userId }), 'role'))
203+
204+
return isAdmin && canManageProject
200205
}
201206

202207
/**
@@ -208,6 +213,7 @@ export const checkCopilot = (token, project) => {
208213
const roles = _.get(tokenData, 'roles')
209214
const isCopilot = roles.some(val => COPILOT_ROLES.indexOf(val.toLowerCase()) > -1)
210215
const canManageProject = !project || _.isEmpty(project) || ALLOWED_EDIT_RESOURCE_ROLES.includes(_.get(_.find(project.members, { userId: tokenData.userId }), 'role'))
216+
211217
return isCopilot && canManageProject
212218
}
213219

0 commit comments

Comments
 (0)