mirror of https://github.com/fspc/workstand.git
Browse Source
Trigger! * Added members API endpoint. * Stubbed out member table. Search is broken. * Better, faster member search! * Add add member button.feature/python-error-tracking
Drew Larson
8 years ago
12 changed files with 282 additions and 77 deletions
@ -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')); |
@ -0,0 +1,13 @@ |
|||||
|
from rest_framework import serializers |
||||
|
from rest_framework.serializers import ModelSerializer |
||||
|
|
||||
|
from .models import Member |
||||
|
|
||||
|
|
||||
|
class MemberSerializer(ModelSerializer): |
||||
|
first_name = serializers.CharField(allow_blank=True, required=False) |
||||
|
last_name = serializers.CharField(allow_blank=True, required=False) |
||||
|
|
||||
|
class Meta: |
||||
|
model = Member |
||||
|
fields = ('first_name', 'last_name', 'email', 'id') |
@ -1,22 +1,7 @@ |
|||||
{% extends 'dashboard.html' %} |
{% extends 'dashboard.html' %} |
||||
|
{% load render_bundle from webpack_loader %} |
||||
|
|
||||
{% block content %} |
{% block content %} |
||||
<div class="mdl-cell mdl-cell--6-col"> |
<div id="root"></div> |
||||
<ul class="demo-list-three mdl-list"> |
{% render_bundle 'members' %} |
||||
{% for member in members %} |
|
||||
<li class="mdl-list__item mdl-list__item--three-line"> |
|
||||
<span class="mdl-list__item-primary-content"> |
|
||||
<i class="material-icons mdl-list__item-avatar">person</i> |
|
||||
<span>{{ member.full_name }}</span> |
|
||||
<span class="mdl-list__item-text-body"> |
|
||||
{{ member.email }} |
|
||||
</span> |
|
||||
</span> |
|
||||
<span class="mdl-list__item-secondary-content"> |
|
||||
<a class="mdl-list__item-secondary-action" href="{% url 'member_edit' member_id=member.id %}"><i class="material-icons">edit</i></a> |
|
||||
</span> |
|
||||
</li> |
|
||||
{% endfor %} |
|
||||
</ul> |
|
||||
</div> |
|
||||
{% endblock %} |
{% endblock %} |
Loading…
Reference in new issue