Refactor member list to be searchable (#27)

Trigger!

* Added members API endpoint.

* Stubbed out member table.

Search is broken.

* Better, faster member search!

* Add add member button.
This commit is contained in:
Drew Larson
2017-01-24 19:50:23 -06:00
parent 65e71becf4
commit 560141298a
12 changed files with 282 additions and 77 deletions
@@ -1,13 +1,16 @@
import ContentAdd from 'material-ui/svg-icons/content/add';
import fetch from 'isomorphic-fetch';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import moment from 'moment';
import { polyFill } from 'es6-promise';
import RaisedButton from 'material-ui/RaisedButton';
import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import Member from './Member';
import Purpose from './Purpose';
import SignedInList from './SignedInList';
export default class SignIn extends React.Component {
constructor(props) {
super(props);
@@ -132,6 +135,7 @@ export default class SignIn extends React.Component {
searchText={this.state.searchText}
/>
</div>
<div className="mdl-cell mdl-cell--4-col">
<Purpose
handleChange={this.handlePurposeChoice}
@@ -139,7 +143,6 @@ export default class SignIn extends React.Component {
/>
</div>
</div>
<div className="mdl-grid">
<div className="mdl-cell mdl-cell--2-col mdl-cell--10-offset">
<RaisedButton onClick={this.signIn} label="Sign-in" />
@@ -147,6 +150,9 @@ export default class SignIn extends React.Component {
</div>
<div className="mdl-grid">
<SignedInList members={this.state.signedIn} />
<FloatingActionButton href="/members/new/">
<ContentAdd />
</FloatingActionButton>
</div>
</div>
);
@@ -0,0 +1,155 @@
import React from 'react';
import { polyFill } from 'es6-promise';
import fetch from 'isomorphic-fetch';
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
import FlatButton from 'material-ui/FlatButton';
import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {
return response.json();
}
export default class MemberTable extends React.Component {
constructor(props) {
super(props);
this.state = {
members: [],
searchText: '',
filteredMembers: [],
error: undefined,
};
this.handleUpdate = this.handleUpdate.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.clearSearch = this.clearSearch.bind(this);
}
componentDidMount() {
fetch('/api/v1/members/')
.then(checkStatus)
.then(parseJSON)
.then((data) => {
this.setState({ members: data });
console.log('request succeeded with JSON response', data);
})
.catch((error) => {
console.log('request failed', error);
});
}
handleUpdate(event, value) {
this.setState({ ...this.state, searchText: value });
}
clearSearch() {
this.setState({
...this.state,
searchText: '',
});
}
handleSearch() {
const value = this.state.searchText.trim();
const self = this;
fetch(`/members/search/${value}/`)
.then((response) => {
if (response.status === 200) {
return response.json();
}
throw new Error('Bad response from server');
})
.then((data) => {
if (data.results.length > 0) {
self.setState({
...this.state,
error: '',
filteredMembers: this.state.members.filter((member) => {
const ids = data.results.map(m => m.id);
console.log(ids);
if (ids.indexOf(member.id) !== -1) {
return member;
}
}),
});
} else {
self.setState({ ...this.state, error: 'Member not found.' });
}
});
}
render() {
const memberRows = this.state.members.map(member => (
<TableRow selectable={false} key={member.id}>
<TableRowColumn>{member.first_name}</TableRowColumn>
<TableRowColumn>{member.last_name}</TableRowColumn>
<TableRowColumn>{member.email}</TableRowColumn>
<TableRowColumn><FlatButton label="Edit" href={`/members/edit/${member.id}`} primary /></TableRowColumn>
</TableRow>
));
const filteredMemberRows = this.state.filteredMembers.map(member => (
<TableRow selectable={false} key={member.id}>
<TableRowColumn>{member.first_name}</TableRowColumn>
<TableRowColumn>{member.last_name}</TableRowColumn>
<TableRowColumn>{member.email}</TableRowColumn>
<TableRowColumn><FlatButton label="Edit" href={`/members/edit/${member.id}`} primary /></TableRowColumn>
</TableRow>
));
return (
<div className="mdl-grid">
<div className="mdl-cell mdl-cell--12-col">
<h3>Members</h3>
<Toolbar>
<ToolbarGroup>
<TextField
hintText="ma@example.com OR name"
floatingLabelText="Search for member"
onChange={this.handleUpdate}
value={this.state.searchText}
/>
<RaisedButton label="Search" primary onClick={this.handleSearch} />
<RaisedButton label="Clear" onClick={this.clearSearch} secondary />
</ToolbarGroup>
</Toolbar>
<Table selectable={false}>
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
<TableRow>
<TableHeaderColumn>First name</TableHeaderColumn>
<TableHeaderColumn>Last Name</TableHeaderColumn>
<TableHeaderColumn>Email</TableHeaderColumn>
<TableHeaderColumn />
</TableRow>
</TableHeader>
<TableBody displayRowCheckbox={false}>
{filteredMemberRows.length ?
filteredMemberRows : undefined
}
{memberRows.length && !this.state.searchText ?
memberRows :
<TableRow>
<TableRowColumn>{'Members loading.'}</TableRowColumn>
</TableRow>
}
</TableBody>
</Table>
</div>
</div>
);
}
}
@@ -0,0 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import injectTapEventPlugin from 'react-tap-event-plugin';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import MemberTable from './components/MemberTable';
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<div>
<h1>Members</h1>
<MemberTable />
</div>
</MuiThemeProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));