Skip to content

PM-875 - move projects to new container, add filters #1613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/actions/projects.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import _ from 'lodash'

import {
PROJECT_TYPE_TAAS,
PROJECTS_PAGE_SIZE,
LOAD_PROJECTS_PENDING,
LOAD_PROJECTS_SUCCESS,
UNLOAD_PROJECTS_SUCCESS,
LOAD_PROJECTS_FAILURE,
LOAD_PROJECT_BILLING_ACCOUNT,
LOAD_CHALLENGE_MEMBERS_SUCCESS,
LOAD_PROJECT_DETAILS,
Expand All @@ -21,8 +29,96 @@ import {
getProjectTypes,
createProjectApi,
fetchBillingAccounts,
fetchMemberProjects,
updateProjectApi
} from '../services/projects'
import { checkAdmin } from '../util/tc'

function _loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
return (dispatch, getState) => {
dispatch({
type: LOAD_PROJECTS_PENDING
})

const filters = {
sort: 'lastActivityAt desc',
perPage: PROJECTS_PAGE_SIZE,
...paramFilters
}

if (!_.isEmpty(projectNameOrIdFilter)) {
if (!isNaN(projectNameOrIdFilter)) { // if it is number
filters['id'] = parseInt(projectNameOrIdFilter, 10)
} else { // text search
filters['keyword'] = decodeURIComponent(projectNameOrIdFilter)
}
}

if (!checkAdmin(getState().auth.token)) {
filters['memberOnly'] = true
}

// eslint-disable-next-line no-debugger
const state = getState().projects
fetchMemberProjects(filters).then(({ projects, pagination }) => dispatch({
filters,
type: LOAD_PROJECTS_SUCCESS,
projects: _.uniqBy((filters.page ? state.projects || [] : []).concat(projects), 'id'),
total: pagination.xTotal,
page: pagination.xPage
})).catch(() => dispatch({
type: LOAD_PROJECTS_FAILURE
}))
}
}

export function loadProjects (projectNameOrIdFilter = '', paramFilters = {}) {
return async (dispatch, getState) => {
const _filters = _.assign({}, paramFilters)
if (_.isEmpty(_filters) || !_filters.type) {
let projectTypes = getState().projects.projectTypes

if (!projectTypes.length) {
dispatch({
type: LOAD_PROJECTS_PENDING
})
await loadProjectTypes()(dispatch)
projectTypes = getState().projects.projectTypes
}

_.assign(_filters, {
type: projectTypes.filter(d => d.key !== PROJECT_TYPE_TAAS).map(d => d.key)
})
}

return _loadProjects(projectNameOrIdFilter, _filters)(dispatch, getState)
}
}

/**
* Load more projects for the authenticated user
*/
export function loadMoreProjects () {
return (dispatch, getState) => {
const { projectFilters, projectsPage } = getState().projects

loadProjects('', _.assign({}, projectFilters, {
perPage: PROJECTS_PAGE_SIZE,
page: projectsPage + 1
}))(dispatch, getState)
}
}

/**
* Unloads projects of the authenticated user
*/
export function unloadProjects () {
return (dispatch) => {
dispatch({
type: UNLOAD_PROJECTS_SUCCESS
})
}
}

/**
* Loads project details
Expand Down
21 changes: 1 addition & 20 deletions src/actions/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
})

const filters = {
status: 'active',
sort: 'lastActivityAt desc',
perPage: PROJECTS_PAGE_SIZE,
...paramFilters
Expand Down Expand Up @@ -66,26 +67,6 @@ export function loadProjects (filterProjectName = '', paramFilters = {}) {
}
}

/**
* Load more projects for the authenticated user
*/
export function loadMoreProjects (filterProjectName = '', paramFilters = {}) {
return (dispatch, getState) => {
const state = getState().sidebar

loadProjects(filterProjectName, _.assignIn({}, paramFilters, {
perPage: PROJECTS_PAGE_SIZE,
page: state.page + 1
}))(dispatch, getState)
}
}

export function loadTaasProjects (filterProjectName = '', paramFilters = {}) {
return loadProjects(filterProjectName, Object.assign({
type: 'talent-as-a-service'
}, paramFilters))
}

/**
* Unloads projects of the authenticated user
*/
Expand Down
6 changes: 2 additions & 4 deletions src/components/ProjectCard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import { PROJECT_STATUSES } from '../../config/constants'

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

const ProjectCard = ({ projectName, projectStatus, projectId, selected, setActiveProject }) => {
const ProjectCard = ({ projectName, projectStatus, projectId, selected }) => {
return (
<div className={styles.container}>
<Link
to={`/projects/${projectId}/challenges`}
className={cn(styles.projectName, { [styles.selected]: selected })}
onClick={() => setActiveProject(parseInt(projectId))}
>
<div className={styles.name}>
<span>{projectName}</span>
Expand All @@ -29,8 +28,7 @@ ProjectCard.propTypes = {
projectStatus: PT.string.isRequired,
projectId: PT.number.isRequired,
projectName: PT.string.isRequired,
selected: PT.bool.isRequired,
setActiveProject: PT.func
selected: PT.bool
}

export default ProjectCard
2 changes: 2 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,5 @@ export const ATTACHMENT_TYPE_LINK = 'link'
*/
export const PROJECT_ASSETS_SHARED_WITH_ALL_MEMBERS = 'All Project Members'
export const PROJECT_ASSETS_SHARED_WITH_ADMIN = 'Only Admins'

export const PROJECT_TYPE_TAAS = 'talent-as-a-service'
45 changes: 1 addition & 44 deletions src/containers/Challenges/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import React, { Component, Fragment } from 'react'
// import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import ChallengesComponent from '../../components/ChallengesComponent'
import ProjectCard from '../../components/ProjectCard'
// import Loader from '../../components/Loader'
import {
loadChallengesByPage,
Expand All @@ -18,15 +16,11 @@ import {
} from '../../actions/challenges'
import { loadProject, updateProject } from '../../actions/projects'
import {
loadMoreProjects,
loadProjects,
setActiveProject,
resetSidebarActiveParams
} from '../../actions/sidebar'
import styles from './Challenges.module.scss'
import { checkAdmin, checkAdminOrCopilot } from '../../util/tc'
import { PrimaryButton } from '../../components/Buttons'
import InfiniteLoadTrigger from '../../components/InfiniteLoadTrigger'
import { checkAdmin } from '../../util/tc'

class Challenges extends Component {
constructor (props) {
Expand Down Expand Up @@ -145,46 +139,11 @@ class Challenges extends Component {
metadata
} = this.props
const { challengeTypes = [] } = metadata
const projectInfo = _.find(projects, { id: activeProjectId }) || {}
const projectComponents =
!dashboard &&
projects.map((p) => (
<li key={p.id}>
<ProjectCard
projectStatus={p.status}
projectName={p.name}
projectId={p.id}
selected={activeProjectId === `${p.id}`}
setActiveProject={setActiveProject}
/>
</li>
))
return (
<Fragment>
{!dashboard &&
(!!projectComponents.length ||
(activeProjectId === -1 && !selfService)) ? (
<div className={!dashboard && styles.projectSearch}>
{activeProjectId === -1 && !selfService && (
<div className={styles.buttonNewProjectWrapper}>
<div>No project selected. Select one below</div>
{checkAdminOrCopilot(auth.token) && (
<Link className={styles.buttonNewProject} to={`/projects/new`}>
<PrimaryButton text={'Create Project'} type={'info'} />
</Link>
)}
</div>
)}
<ul>{projectComponents}</ul>
{projects && !!projects.length && (
<InfiniteLoadTrigger onLoadMore={this.props.loadMoreProjects} />
)}
</div>
) : null}
{(dashboard || activeProjectId !== -1 || selfService) && (
<ChallengesComponent
activeProject={{
...projectInfo,
...(reduxProjectInfo && reduxProjectInfo.id === activeProjectId
? reduxProjectInfo
: {})
Expand Down Expand Up @@ -270,7 +229,6 @@ Challenges.propTypes = {
dashboard: PropTypes.bool,
auth: PropTypes.object.isRequired,
loadChallengeTypes: PropTypes.func,
loadMoreProjects: PropTypes.func,
metadata: PropTypes.shape({
challengeTypes: PropTypes.array
})
Expand Down Expand Up @@ -298,7 +256,6 @@ const mapStateToProps = ({ challenges, sidebar, projects, auth }) => ({
const mapDispatchToProps = {
loadChallengesByPage,
resetSidebarActiveParams,
loadMoreProjects,
loadProject,
loadProjects,
updateProject,
Expand Down
132 changes: 132 additions & 0 deletions src/containers/Projects/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useEffect, useMemo, useState } from 'react'
import cn from 'classnames'
import { DebounceInput } from 'react-debounce-input'
import { withRouter, Link } from 'react-router-dom'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Loader from '../../components/Loader'
import { checkAdminOrCopilot } from '../../util/tc'
import { PrimaryButton } from '../../components/Buttons'
import Select from '../../components/Select'
import ProjectCard from '../../components/ProjectCard'
import InfiniteLoadTrigger from '../../components/InfiniteLoadTrigger'
import { loadProjects, loadMoreProjects, unloadProjects } from '../../actions/projects'
import { PROJECT_STATUSES } from '../../config/constants'

import styles from './styles.module.scss'

const Projects = ({ projects, auth, isLoading, projectsCount, loadProjects, loadMoreProjects, unloadProjects }) => {
const [search, setSearch] = useState()
const [projectStatus, setProjectStatus] = useState('')
const selectedStatus = useMemo(() => PROJECT_STATUSES.find(s => s.value === projectStatus))

useEffect(() => {
loadProjects(search, projectStatus ? { status: projectStatus } : {})
}, [search, projectStatus])

// unload projects on dismount
useEffect(() => () => unloadProjects, [])

if (isLoading && projects.length === 0) {
return (
<div className={styles.container}>
<Loader />
</div>
)
}

return (
<div className={styles.container}>
<div className={styles.headerLine}>
<h2>Projects</h2>
{checkAdminOrCopilot(auth.token) && (
<Link className={styles.buttonNewProject} to={`/projects/new`}>
<PrimaryButton text={'New Project'} type={'info'} />
</Link>
)}
</div>
<div className={styles.searchWrapper}>
<div className={styles['col-6']}>
<div className={cn(styles.field, styles.input1)}>
<label>Search :</label>
</div>
<div className={styles.searchInputWrapper}>
<DebounceInput
className={styles.searchInput}
minLength={2}
debounceTimeout={300}
placeholder='Keyword'
onChange={e => setSearch(e.target.value)}
value={search}
/>
</div>
</div>
<div className={styles['col-6']}>
<div className={cn(styles.field, styles.input1)}>
<label>Project Status:</label>
</div>
<div className={styles.searchInputWrapper}>
<Select
name='projectStatus'
options={PROJECT_STATUSES}
placeholder='All'
value={selectedStatus}
onChange={e => setProjectStatus(e ? e.value : '')}
isClearable
/>
</div>
</div>
</div>
{projects.length > 0 ? (
<>
<ul>
{projects.map(p => (
<li key={p.id}>
<ProjectCard
projectStatus={p.status}
projectName={p.name}
projectId={p.id}
/>
</li>
))}
</ul>
{projects && projects.length < projectsCount - 1 && (
// fix
<InfiniteLoadTrigger onLoadMore={loadMoreProjects} />
)}
</>
) : (
<span>No projects available yet</span>
)}
</div>
)
}

Projects.propTypes = {
projectsCount: PropTypes.number.isRequired,
projects: PropTypes.array,
auth: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
unloadProjects: PropTypes.func.isRequired,
loadProjects: PropTypes.func.isRequired,
loadMoreProjects: PropTypes.func.isRequired
}

const mapStateToProps = ({ projects, auth }) => {
return {
projectsCount: projects.projectsCount,
projects: projects.projects,
isLoading: projects.isLoading,
auth
}
}

const mapDispatchToProps = {
unloadProjects: unloadProjects,
loadProjects: loadProjects,
loadMoreProjects: loadMoreProjects
}

export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Projects)
)
Loading