Skip to content

Commit 7dc7ee7

Browse files
committed
merge dev
2 parents dc0f42b + b4443ff commit 7dc7ee7

23 files changed

+701
-474
lines changed

src/components/Grid/GridView.jsx

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,17 @@ const GridView = props => {
3535
<LoadingIndicator />
3636
) : (
3737
<div className="container">
38-
{resultSet.length ? (
39-
<div className="flex-area">
40-
<div className="flex-data">
41-
<ListHeader {...headerProps} />
42-
{resultSet.map(renderItem)}
43-
</div>
44-
<PaginationBar {...paginationProps} />
45-
</div>
46-
) : (
47-
<div className="flex-area">
48-
{/* TODO replace this with proper style */}
49-
<div style={{textAlign: 'center'}}><br/><br/><h3> No results </h3><br/><br/></div>
38+
<div className="flex-area">
39+
<div className="flex-data">
40+
<ListHeader {...headerProps} />
41+
{resultSet.length ? (
42+
resultSet.map(renderItem)
43+
) : (
44+
<div style={{textAlign: 'center'}}><br/><br/><h3> No results </h3><br/><br/></div>
45+
)}
5046
</div>
51-
)}
47+
<PaginationBar {...paginationProps} />
48+
</div>
5249
</div>
5350
)
5451
)
@@ -77,6 +74,15 @@ const GridView = props => {
7774
>
7875
{[...resultSet, ...placeholders].map(renderItem)}
7976
</InfiniteScroll>
77+
{totalCount === 0 && <section className="content gridview-content">
78+
<div key="end" className="gridview-no-project">
79+
No results found based on current search criteria. <br /> Please modify your search criteria and/or search across all projects by selecting the "
80+
<a href="javascript:" onClick={() => { applyFilters({status: null }) }} className="tc-btn-all-projects" >
81+
All Projects
82+
</a>
83+
" filter.
84+
</div>
85+
</section>}
8086
</div>
8187
</div>
8288
</div>
@@ -94,20 +100,6 @@ const GridView = props => {
94100
)
95101
}
96102

97-
if (totalCount === 0) {
98-
return (
99-
<section className="content gridview-content">
100-
<div key="end" className="gridview-no-project">
101-
No results found based on current search criteria. <br /> Please modify your search criteria and/or search across all projects by selecting the "
102-
<a href="javascript:" onClick={() => { applyFilters({status: null }) }} className="tc-btn-all-projects" >
103-
All Projects
104-
</a>
105-
" filter.
106-
</div>
107-
</section>
108-
)
109-
}
110-
111103
return (
112104
<section className="content gridview-content">
113105
{infiniteScroll ? renderGridWithInfiniteScroll() : renderGridWithPagination()}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import {findIndex} from 'lodash'
4+
import Select from '../Select/Select'
5+
import './AutocompleteInput.scss'
6+
import {loadMemberSuggestions} from '../../api/projectMembers'
7+
import {AUTOCOMPLETE_TRIGGER_LENGTH} from '../../config/constants'
8+
9+
/**
10+
* Render a searchable dropdown for selecting users that can be invited
11+
*/
12+
class AutocompleteInput extends React.Component {
13+
constructor(props) {
14+
super(props)
15+
16+
this.onChange = this.onChange.bind(this)
17+
this.asyncOptions = this.asyncOptions.bind(this)
18+
19+
this.debounceTimer = null
20+
}
21+
22+
// cannot use debounce method from lodash, because we have to return Promise
23+
loadMemberSuggestionsDebounced(value) {
24+
return new Promise((resolve) => {
25+
if (this.debounceTimer) {
26+
clearTimeout(this.debounceTimer)
27+
}
28+
29+
this.debounceTimer = setTimeout(() => {
30+
resolve(loadMemberSuggestions(value))
31+
}, 500)
32+
})
33+
}
34+
35+
onChange(inputValue, selectedOptions = []) {
36+
const { onUpdate } = this.props
37+
38+
if (onUpdate) {
39+
onUpdate(selectedOptions)
40+
}
41+
}
42+
43+
asyncOptions(input) {
44+
const { allMembers } = this.props
45+
46+
const value = typeof input === 'string' ? input : ''
47+
const createOption = {
48+
handle: value,
49+
// Decide if it's email
50+
isEmail: (/(.+)@(.+){2,}\.(.+){2,}/).test(value),
51+
}
52+
if (value.length >= AUTOCOMPLETE_TRIGGER_LENGTH) {
53+
return this.loadMemberSuggestionsDebounced(value).then(r => {
54+
// Remove current members from suggestions
55+
const suggestions = r.filter(suggestion => (
56+
findIndex(allMembers, (member) => member.handle === suggestion.handle) === -1 &&
57+
// Remove current value from list to add it manually on top
58+
suggestion.handle !== value
59+
))
60+
// Only allow creation if it is not already exists in members
61+
const shouldIncludeCreateOption = findIndex(allMembers, (member) => member.handle === value) === -1
62+
63+
return Promise.resolve({options: shouldIncludeCreateOption?[createOption, ...suggestions]: suggestions})
64+
}).catch( () => {
65+
return Promise.resolve({options: [createOption] })
66+
})
67+
}
68+
return Promise.resolve({options: value.length > 0 ? [createOption] : []})
69+
}
70+
71+
render() {
72+
const {
73+
placeholder,
74+
selectedMembers,
75+
disabled,
76+
} = this.props
77+
78+
return (
79+
<div className="autocomplete-wrapper">
80+
<Select
81+
multi
82+
placeholder={placeholder}
83+
value={selectedMembers}
84+
onChange={this.onChange}
85+
asyncOptions={this.asyncOptions}
86+
valueKey="handle"
87+
labelKey="handle"
88+
disabled={disabled}
89+
/>
90+
</div>
91+
)
92+
}
93+
}
94+
95+
AutocompleteInput.defaultProps = {
96+
placeholder: 'Enter one or more user handles',
97+
selectedMembers: [],
98+
allMembers: [],
99+
disabled: false
100+
}
101+
102+
AutocompleteInput.propTypes = {
103+
/**
104+
* Callback fired when selected members are updated
105+
*/
106+
onUpdate: PropTypes.func,
107+
108+
/**
109+
* The current logged in user in the app.
110+
* Used to determinate "You" label and access
111+
*/
112+
currentUser: PropTypes.object,
113+
114+
/**
115+
* Placeholder to show when the input is empty
116+
*/
117+
placeholder: PropTypes.string,
118+
119+
/**
120+
* List of members that are currently selected
121+
*/
122+
selectedMembers: PropTypes.arrayOf(PropTypes.object),
123+
124+
/**
125+
* The flag if component is disabled
126+
*/
127+
disabled: PropTypes.bool,
128+
129+
/**
130+
* List of both current and invited members of project
131+
*/
132+
allMembers: PropTypes.arrayOf(PropTypes.object)
133+
}
134+
135+
136+
export default AutocompleteInput
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
@import "~tc-ui/src/styles/tc-includes";
2+
:global {
3+
.management-dialog-overlay
4+
.project-dialog-conatiner
5+
.project-dialog
6+
.input-container
7+
.autocomplete-wrapper {
8+
width: 100%;
9+
padding: 0 $base-unit*2 ;
10+
11+
input {
12+
margin: 0;
13+
}
14+
15+
.Select > input[type="hidden"] {
16+
display: none;
17+
}
18+
19+
.Select-control {
20+
max-height: $base-unit*8;
21+
padding: $base-unit 0;
22+
23+
.Select-placeholder, .Select-input {
24+
@include roboto;
25+
}
26+
27+
.Select-placeholder {
28+
line-height: $base-unit*8;
29+
}
30+
31+
.Select-arrow-zone {
32+
display: none;
33+
}
34+
35+
.Select-loading-zone {
36+
padding-right: $base-unit;
37+
}
38+
39+
}
40+
}
41+
}
Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,97 @@
11
import React from 'react'
22
import Modal from 'react-modal'
33
import PT from 'prop-types'
4+
import SelectDropdown from '../SelectDropdown/SelectDropdown'
45

5-
function Dialog({onCancel, onConfirm, title, content, buttonColor, buttonText, disabled}) {
6-
return (
7-
<Modal
8-
isOpen
9-
className="management-dialog"
10-
overlayClassName="management-dialog-overlay"
11-
onRequestClose={onCancel}
12-
shouldCloseOnOverlayClick={!disabled}
13-
shouldCloseOnEsc={!disabled}
14-
contentLabel=""
15-
>
16-
<div className="management-dialog-container">
17-
<div className="dialog-title">{title}</div>
18-
<div className="dialog-content" dangerouslySetInnerHTML={{__html: content}}/>
19-
<div className="dialog-actions">
20-
<button
21-
onClick={onCancel}
22-
className="tc-btn tc-btn-default"
23-
disabled={disabled}
24-
>
6+
7+
class Dialog extends React.Component {
8+
9+
constructor(props) {
10+
super(props)
11+
this.state = {
12+
role: 'manager'
13+
}
14+
15+
this.roles = [{
16+
title: 'Manager',
17+
value: 'manager',
18+
}, {
19+
title: 'Observer',
20+
value: 'observer',
21+
}, {
22+
title: 'Account Manager',
23+
value: 'account_manager',
24+
}]
25+
26+
this.handleRoles = this.handleRoles.bind(this)
27+
this.onConfirm = this.onConfirm.bind(this)
28+
}
29+
30+
handleRoles(option) {
31+
this.setState({
32+
role: option.value
33+
})
34+
}
35+
36+
onConfirm() {
37+
if(this.props.showRoleSelector) {
38+
this.props.onConfirm(this.state.role)
39+
} else {
40+
this.props.onConfirm()
41+
}
42+
43+
}
44+
45+
render()
46+
{
47+
const {onCancel, title, content, buttonColor, buttonText, disabled, showRoleSelector} = this.props
48+
49+
return (
50+
<Modal
51+
isOpen
52+
className="management-dialog"
53+
overlayClassName="management-dialog-overlay"
54+
onRequestClose={onCancel}
55+
shouldCloseOnOverlayClick={!disabled}
56+
shouldCloseOnEsc={!disabled}
57+
contentLabel=""
58+
>
59+
<div className="management-dialog-container">
60+
<div className="dialog-title">{title}</div>
61+
<div className="dialog-content" dangerouslySetInnerHTML={{__html: content}}/>
62+
{showRoleSelector && <Formsy.Form className="input-container">
63+
<SelectDropdown
64+
name="role"
65+
value={this.state.role}
66+
theme="role-drop-down default"
67+
options={this.roles}
68+
onSelect={this.handleRoles}
69+
/>
70+
</Formsy.Form>}
71+
<div className="dialog-actions">
72+
<button
73+
onClick={onCancel}
74+
className="tc-btn tc-btn-default"
75+
disabled={disabled}
76+
>
2577
Cancel
26-
</button>
27-
<button
28-
onClick={onConfirm}
29-
className={'tc-btn tc-btn-primary tc-btn-md ' + (buttonColor !== 'blue' ? 'btn-red' : '') }
30-
disabled={disabled}
31-
>
32-
{buttonText}
33-
</button>
78+
</button>
79+
<button
80+
onClick={this.onConfirm}
81+
className={'tc-btn tc-btn-primary tc-btn-md ' + (buttonColor !== 'blue' ? 'btn-red' : '')}
82+
disabled={disabled}
83+
>
84+
{buttonText}
85+
</button>
86+
</div>
3487
</div>
35-
</div>
36-
</Modal>
37-
)
88+
</Modal>
89+
)
90+
}
91+
}
92+
93+
Dialog.propTypes = {
94+
showRoleSelector: false
3895
}
3996

4097
Dialog.propTypes = {
@@ -44,6 +101,7 @@ Dialog.propTypes = {
44101
content: PT.string,
45102
buttonColor: PT.string,
46103
buttonText: PT.string,
104+
showRoleSelector: PT.bool,
47105
}
48106

49107
export default Dialog

0 commit comments

Comments
 (0)