Skip to content

Commit 6ed5a2c

Browse files
author
Vikas Agarwal
committed
feat: git#669 project search improvements - allows search by id and exact phrase (to enable searching projects by title)
feat: git#669 project search improvements - moved the project search from left panel to main content area to have sufficient space to locate the project feat: git#669 project search improvements - allows searching non my projects as well to support admin users (e.g. support team) to locate other projects/challenges fix: minor style fix for the left panel
1 parent 10b0b35 commit 6ed5a2c

File tree

11 files changed

+224
-189
lines changed

11 files changed

+224
-189
lines changed

package-lock.json

Lines changed: 37 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
content="width=device-width, initial-scale=1, shrink-to-fit=no"
88
/>
99
<meta name="theme-color" content="#000000" />
10-
<title>Topcoder Challenge Creation App</title>
10+
<title>Work Manager - Topcoder</title>
1111
</head>
1212
<body>
1313
<noscript>You need to enable JavaScript to run this app.</noscript>

src/actions/sidebar.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,27 @@ export function setActiveProject (projectId) {
2121
/**
2222
* Loads projects of the authenticated user
2323
*/
24-
export function loadProjects (filterProjectName = '') {
24+
export function loadProjects (filterProjectName = '', myProjects = true) {
2525
return (dispatch) => {
2626
dispatch({
2727
type: LOAD_PROJECTS_PENDING
2828
})
2929

3030
const filters = {}
3131
if (!_.isEmpty(filterProjectName)) {
32-
filters['name'] = `*${filterProjectName}*`
32+
if (!isNaN(filterProjectName)) { // if it is number
33+
filters['id'] = parseInt(filterProjectName, 10)
34+
} else { // text search
35+
filters['keyword'] = decodeURIComponent(filterProjectName)
36+
}
3337
}
3438
filters['status'] = 'active'
35-
filters['sort'] = 'lastActivityAt'
36-
filters['memberOnly'] = 'true'
39+
filters['sort'] = 'lastActivityAt desc'
40+
// filters['perPage'] = 20
41+
// filters['page'] = 1
42+
if (myProjects) {
43+
filters['memberOnly'] = true
44+
}
3745

3846
fetchMemberProjects(filters).then(projects => dispatch({
3947
type: LOAD_PROJECTS_SUCCESS,

src/components/Sidebar/Sidebar.module.scss

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,11 @@
55
height: 100vh;
66
background-color: $dark-gray;
77
overflow: auto;
8-
9-
ul {
10-
list-style: none;
11-
width: 100%;
12-
padding: 0;
13-
margin-top: 0;
14-
15-
li {
16-
cursor: pointer;
17-
}
18-
}
19-
20-
a {
21-
text-decoration: none;
22-
}
23-
24-
.noProjects {
25-
@include roboto;
26-
font-weight: 400;
27-
padding-left: 30px;
28-
padding-right: 20px;
29-
color: $white;
30-
}
318
}
329

3310
.logo {
3411
width: calc(100% - 100px);
35-
padding: 30px 50px 42px 50px;
12+
padding: 30px 50px 42px 30px;
3613
@media screen and (max-width: 300px * 4) {
3714
width: calc(100% - 20px);
3815
padding: 30px 10px 42px 10px;
@@ -46,10 +23,8 @@
4623
font-weight: 400;
4724
line-height: 31px;
4825
color: $white;
49-
display: flex;
50-
justify-content: center;
51-
align-items: center;
52-
margin-bottom: 37px
26+
margin-bottom: 37px;
27+
padding-left: 30px;
5328
}
5429

5530
.homeLink {

src/components/Sidebar/index.js

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,71 +5,28 @@ import React from 'react'
55
import { Link } from 'react-router-dom'
66
import PropTypes from 'prop-types'
77
import cn from 'classnames'
8-
import _ from 'lodash'
9-
import ProjectCard from '../ProjectCard'
10-
import { DebounceInput } from 'react-debounce-input'
11-
import Loader from '../Loader'
128
import TopcoderLogo from '../../assets/images/topcoder-logo.png'
139
import styles from './Sidebar.module.scss'
1410

1511
const Sidebar = ({
16-
projects, isLoading, setActiveProject,
17-
projectId, resetSidebarActiveParams,
18-
updateProjectsList, searchProjectName
12+
projectId, resetSidebarActiveParams
1913
}) => {
20-
const projectComponents = projects.map(p => (
21-
<li key={p.id}>
22-
<ProjectCard
23-
projectName={p.name}
24-
projectId={p.id}
25-
selected={projectId === `${p.id}`}
26-
setActiveProject={setActiveProject}
27-
/>
28-
</li>
29-
))
30-
3114
return (
3215
<div className={styles.sidebar}>
3316
<img src={TopcoderLogo} className={styles.logo} />
3417
<div className={styles.title}>Challenge Editor</div>
35-
{
36-
!isLoading && !searchProjectName && _.get(projectComponents, 'length', 0) === 0 && (
37-
<div className={styles.noProjects}>
38-
You don't have any active projects yet!
39-
</div>
40-
)
41-
}
4218
<Link to='/'>
4319
<div className={cn(styles.homeLink, { [styles.active]: !projectId })} onClick={resetSidebarActiveParams}>
4420
Active challenges
4521
</div>
4622
</Link>
47-
<DebounceInput
48-
minLength={2}
49-
debounceTimeout={300}
50-
placeholder='Search projects'
51-
onChange={(e) => updateProjectsList(e.target.value)}
52-
value={searchProjectName}
53-
/>
54-
{
55-
isLoading ? <Loader /> : (
56-
<ul>
57-
{projectComponents}
58-
</ul>
59-
)
60-
}
6123
</div>
6224
)
6325
}
6426

6527
Sidebar.propTypes = {
66-
projects: PropTypes.arrayOf(PropTypes.shape()),
67-
isLoading: PropTypes.bool,
68-
setActiveProject: PropTypes.func,
6928
projectId: PropTypes.string,
70-
resetSidebarActiveParams: PropTypes.func,
71-
searchProjectName: PropTypes.string,
72-
updateProjectsList: PropTypes.func
29+
resetSidebarActiveParams: PropTypes.func
7330
}
7431

7532
export default Sidebar
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
@import "../../styles/includes";
2+
3+
.projectSearch {
4+
padding: 20px 20px 0px;
5+
6+
.projectSearchHeader {
7+
display: flex;
8+
9+
label {
10+
line-height: 40px;
11+
margin-right: 10px;
12+
}
13+
}
14+
15+
ul {
16+
background-color: $lighter-gray;
17+
list-style: none;
18+
width: 100%;
19+
padding: 0;
20+
margin-top: 0;
21+
border-radius: 3px;
22+
flex: 1;
23+
24+
li {
25+
cursor: pointer;
26+
}
27+
}
28+
29+
input[type="text"] {
30+
flex: 2;
31+
}
32+
33+
input[type="checkbox"] {
34+
margin-left: 20px;
35+
margin-right: 10px;
36+
height: 40px;
37+
}
38+
39+
a {
40+
color: $dark-gray;
41+
text-decoration: none;
42+
}
43+
44+
.noProjects {
45+
@include roboto;
46+
font-weight: 400;
47+
padding-left: 30px;
48+
padding-right: 20px;
49+
color: $white;
50+
}
51+
}

src/containers/Challenges/index.js

Lines changed: 102 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,39 @@
22
* Container to render Challenges page
33
*/
44
import _ from 'lodash'
5-
import React, { Component } from 'react'
5+
import React, { Component, Fragment } from 'react'
66
// import { Redirect } from 'react-router-dom'
77
import PropTypes from 'prop-types'
88
import { connect } from 'react-redux'
9+
import { DebounceInput } from 'react-debounce-input'
910
import ChallengesComponent from '../../components/ChallengesComponent'
11+
import ProjectCard from '../../components/ProjectCard'
12+
import Loader from '../../components/Loader'
1013
import { loadChallengesByPage } from '../../actions/challenges'
1114
import { loadProject } from '../../actions/projects'
12-
import { resetSidebarActiveParams } from '../../actions/sidebar'
15+
import { loadProjects, setActiveProject, resetSidebarActiveParams } from '../../actions/sidebar'
1316
import {
1417
CHALLENGE_STATUS
1518
} from '../../config/constants'
19+
import styles from './Challenges.module.scss'
1620

1721
class Challenges extends Component {
22+
constructor (props) {
23+
super(props)
24+
this.state = {
25+
searchProjectName: '',
26+
onlyMyProjects: true
27+
}
28+
29+
this.updateProjectName = this.updateProjectName.bind(this)
30+
this.toggleMyProjects = this.toggleMyProjects.bind(this)
31+
}
32+
1833
componentDidMount () {
19-
const { activeProjectId, resetSidebarActiveParams, menu, projectId } = this.props
34+
const { activeProjectId, resetSidebarActiveParams, menu, projectId, isLoading } = this.props
35+
if (activeProjectId === -1 && !isLoading) {
36+
this.props.loadProjects()
37+
}
2038
if (menu === 'NULL' && activeProjectId !== -1) {
2139
resetSidebarActiveParams()
2240
} else {
@@ -28,7 +46,9 @@ class Challenges extends Component {
2846
}
2947

3048
componentWillReceiveProps (nextProps) {
31-
this.reloadChallenges(nextProps)
49+
if (this.props.activeProjectId !== nextProps.activeProjectId) {
50+
this.reloadChallenges(nextProps)
51+
}
3252
}
3353

3454
reloadChallenges (props) {
@@ -43,6 +63,17 @@ class Challenges extends Component {
4363
}
4464
}
4565

66+
updateProjectName (val) {
67+
this.setState({ searchProjectName: val })
68+
this.props.loadProjects(val, this.state.onlyMyProjects)
69+
}
70+
71+
toggleMyProjects (evt) {
72+
this.setState({ onlyMyProjects: evt.target.checked }, () => {
73+
this.props.loadProjects(this.state.searchProjectName, this.state.onlyMyProjects)
74+
})
75+
}
76+
4677
render () {
4778
const {
4879
challenges,
@@ -56,26 +87,70 @@ class Challenges extends Component {
5687
loadChallengesByPage,
5788
page,
5889
perPage,
59-
totalChallenges
90+
totalChallenges,
91+
setActiveProject
6092
} = this.props
93+
const { searchProjectName, onlyMyProjects } = this.state
6194
const projectInfo = _.find(projects, { id: activeProjectId }) || {}
95+
const projectComponents = projects.map(p => (
96+
<li key={p.id}>
97+
<ProjectCard
98+
projectName={p.name}
99+
projectId={p.id}
100+
selected={activeProjectId === `${p.id}`}
101+
setActiveProject={setActiveProject}
102+
/>
103+
</li>
104+
))
62105
return (
63-
<ChallengesComponent
64-
activeProject={({
65-
...projectInfo,
66-
...((reduxProjectInfo && reduxProjectInfo.id === activeProjectId) ? reduxProjectInfo : {})
67-
})}
68-
warnMessage={warnMessage}
69-
challenges={challenges}
70-
isLoading={isLoading}
71-
filterChallengeName={filterChallengeName}
72-
status={status}
73-
activeProjectId={activeProjectId}
74-
loadChallengesByPage={loadChallengesByPage}
75-
page={page}
76-
perPage={perPage}
77-
totalChallenges={totalChallenges}
78-
/>
106+
<Fragment>
107+
<div className={styles.projectSearch}>
108+
<div className={styles.projectSearchHeader}>
109+
<label>Swtich Project</label>
110+
<DebounceInput
111+
minLength={2}
112+
debounceTimeout={300}
113+
placeholder='Search projects'
114+
onChange={(e) => this.updateProjectName(e.target.value)}
115+
value={searchProjectName}
116+
/>
117+
<input
118+
type='checkbox'
119+
label='My Projects'
120+
checked={onlyMyProjects}
121+
onChange={this.toggleMyProjects}
122+
/>
123+
<label>My Projects</label>
124+
</div>
125+
{
126+
activeProjectId === -1 && <div>No project selected. Select one below</div>
127+
}
128+
{
129+
isLoading ? <Loader /> : (
130+
<ul>
131+
{projectComponents}
132+
</ul>
133+
)
134+
}
135+
</div>
136+
{ activeProjectId !== -1 && <ChallengesComponent
137+
activeProject={({
138+
...projectInfo,
139+
...((reduxProjectInfo && reduxProjectInfo.id === activeProjectId) ? reduxProjectInfo : {})
140+
})}
141+
warnMessage={warnMessage}
142+
challenges={challenges}
143+
isLoading={isLoading}
144+
filterChallengeName={filterChallengeName}
145+
status={status}
146+
activeProjectId={activeProjectId}
147+
loadChallengesByPage={loadChallengesByPage}
148+
page={page}
149+
perPage={perPage}
150+
totalChallenges={totalChallenges}
151+
/>
152+
}
153+
</Fragment>
79154
)
80155
}
81156
}
@@ -96,7 +171,9 @@ Challenges.propTypes = {
96171
resetSidebarActiveParams: PropTypes.func,
97172
page: PropTypes.number.isRequired,
98173
perPage: PropTypes.number.isRequired,
99-
totalChallenges: PropTypes.number.isRequired
174+
totalChallenges: PropTypes.number.isRequired,
175+
loadProjects: PropTypes.func.isRequired,
176+
setActiveProject: PropTypes.func.isRequired
100177
}
101178

102179
const mapStateToProps = ({ challenges, sidebar, projects }) => ({
@@ -111,7 +188,9 @@ const mapStateToProps = ({ challenges, sidebar, projects }) => ({
111188
const mapDispatchToProps = {
112189
loadChallengesByPage,
113190
resetSidebarActiveParams,
114-
loadProject
191+
loadProject,
192+
loadProjects,
193+
setActiveProject
115194
}
116195

117196
export default connect(mapStateToProps, mapDispatchToProps)(Challenges)

0 commit comments

Comments
 (0)