mirror of
https://github.com/fspc/workstand.git
synced 2025-02-23 17:23:24 -05:00
Load data with redux
This commit is contained in:
parent
e6b189edd8
commit
8f9d1c3663
7
bikeshop_project/assets/js/bikes/actions.js
Normal file
7
bikeshop_project/assets/js/bikes/actions.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createAction } from 'redux-actions';
|
||||||
|
|
||||||
|
export const fetchBikes = createAction('fetch bikes');
|
||||||
|
export const setBikes = createAction('set bikes');
|
||||||
|
export const setBikesIsFetching = createAction('set bikes.isFetching');
|
||||||
|
export const setBikesFetched = createAction('set bikes.fetched');
|
||||||
|
export const setBike = createAction('set bike');
|
@ -1,8 +1,12 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
|
import { Field, reduxForm } from 'redux-form';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import Checkbox from 'material-ui/Checkbox';
|
import Checkbox from 'material-ui/Checkbox';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import FlatButton from 'material-ui/FlatButton';
|
import FlatButton from 'material-ui/FlatButton';
|
||||||
|
import MenuItem from 'material-ui/MenuItem';
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
|
import SelectField from 'material-ui/SelectField';
|
||||||
import TextField from 'material-ui/TextField';
|
import TextField from 'material-ui/TextField';
|
||||||
import fetch from 'isomorphic-fetch';
|
import fetch from 'isomorphic-fetch';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
@ -21,12 +25,68 @@ const styles = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class BikeForm extends React.Component {
|
const sources = ['COS_BIKE_DIVERSION_PILOT', 'UOFS', 'DROP_OFF'];
|
||||||
static propTypes = {
|
|
||||||
bike: PropTypes.object,
|
const friendly = (s) => {
|
||||||
editing: PropTypes.bool,
|
switch (s) {
|
||||||
handleClose: PropTypes.func,
|
case 'COS_BIKE_DIVERSION_PILOT':
|
||||||
|
return 'City of Saskatoon Bike Diversion Pilot';
|
||||||
|
case 'UOFS':
|
||||||
|
return 'University of Saskatchewan';
|
||||||
|
case 'DROP_OFF':
|
||||||
|
return 'Drop Off';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sourceMenuItems = sources.map(s =>
|
||||||
|
<MenuItem key={s} value={s} primaryText={friendly(s)} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTextField = ({ input, meta: { touched, error }, ...custom }) => (
|
||||||
|
<TextField
|
||||||
|
errorText={touched && error}
|
||||||
|
{...input}
|
||||||
|
{...custom}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderCheckbox = ({ input, meta, label, ...custom }) => (
|
||||||
|
<Checkbox
|
||||||
|
label={label}
|
||||||
|
checked={!!input.value}
|
||||||
|
onCheck={input.onChange}
|
||||||
|
{...custom}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderSelectField = ({ input, label, meta: { touched, error }, children, ...custom }) => (
|
||||||
|
<SelectField
|
||||||
|
errorText={touched && error}
|
||||||
|
{...input}
|
||||||
|
onChange={(event, index, value) => input.onChange(value)}
|
||||||
|
children={children}
|
||||||
|
{...custom}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const validate = (values) => {
|
||||||
|
console.log(values);
|
||||||
|
const errors = {};
|
||||||
|
const requiredFields = ['make', 'colour', 'size', 'serial_number', 'donation_source'];
|
||||||
|
|
||||||
|
requiredFields.forEach((field) => {
|
||||||
|
if (!values[field]) {
|
||||||
|
errors[field] = 'Required';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = data => false;
|
||||||
|
|
||||||
|
class BikeForm extends React.Component {
|
||||||
constructor({ bike, editing = false }) {
|
constructor({ bike, editing = false }) {
|
||||||
super();
|
super();
|
||||||
if (editing) {
|
if (editing) {
|
||||||
@ -40,19 +100,19 @@ class BikeForm extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange = (event, value) => {
|
handleChange(event, value) {
|
||||||
this.setState({ bike: { ...this.state.bike, [event.target.name]: value } });
|
this.setState({ bike: { ...this.state.bike, [event.target.name]: value } });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSizeChange = (event, index, value) => {
|
handleSizeChange(event, index, value) {
|
||||||
this.setState({ bike: { ...this.state.bike, size: value } });
|
this.setState({ bike: { ...this.state.bike, size: value } });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSourceChange = (event, index, value) => {
|
handleSourceChange(event, index, value) {
|
||||||
this.setState({ bike: { ...this.state.bike, source: value } });
|
this.setState({ bike: { ...this.state.bike, source: value } });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCpicCheck = () => {
|
handleCpicCheck() {
|
||||||
const id = this.state.bike.id;
|
const id = this.state.bike.id;
|
||||||
const serialNumber = this.state.bike.serial_number;
|
const serialNumber = this.state.bike.serial_number;
|
||||||
const data = JSON.stringify({ serial_number: serialNumber });
|
const data = JSON.stringify({ serial_number: serialNumber });
|
||||||
@ -107,6 +167,7 @@ class BikeForm extends React.Component {
|
|||||||
cpic_searched_at,
|
cpic_searched_at,
|
||||||
created_at,
|
created_at,
|
||||||
stolen,
|
stolen,
|
||||||
|
checked,
|
||||||
} = this.props.bike;
|
} = this.props.bike;
|
||||||
const editing = this.props.editing;
|
const editing = this.props.editing;
|
||||||
const createdAtFormatted = (moment(created_at).isValid()) ? moment(created_at).tz(timezone).fromNow() : '';
|
const createdAtFormatted = (moment(created_at).isValid()) ? moment(created_at).tz(timezone).fromNow() : '';
|
||||||
@ -116,147 +177,163 @@ class BikeForm extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mdl-grid">
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="mdl-cell mdl-cell--3-col">
|
<div className="mdl-grid">
|
||||||
<TextField
|
<div className="mdl-cell mdl-cell--3-col">
|
||||||
name="make"
|
<Field
|
||||||
floatingLabelText="Make"
|
name="make"
|
||||||
hintText="Norco"
|
component={renderTextField}
|
||||||
value={this.state.bike.make || undefined}
|
floatingLabelText="Make"
|
||||||
onChange={this.handleChange}
|
hintText="Norco"
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mdl-cell mdl-cell--3-col">
|
|
||||||
<TextField
|
|
||||||
name="price"
|
|
||||||
floatingLabelText="Price"
|
|
||||||
hintText="35.60"
|
|
||||||
value={this.state.bike.price || undefined}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mdl-cell mdl-cell--3-col">
|
|
||||||
<TextField
|
|
||||||
name="colour"
|
|
||||||
floatingLabelText="Colour"
|
|
||||||
hintText="orange"
|
|
||||||
value={this.state.bike.colour || undefined}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mdl-cell mdl-cell--3-col">
|
|
||||||
<Size
|
|
||||||
onChange={this.handleSizeChange}
|
|
||||||
size={this.state.bike.size}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mdl-grid">
|
|
||||||
<div className="mdl-cell mdl-cell--6-col">
|
|
||||||
<TextField
|
|
||||||
name="serial_number"
|
|
||||||
floatingLabelText="Serial number"
|
|
||||||
hintText="ab90cd23"
|
|
||||||
value={this.state.bike.serial_number || undefined}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{editing &&
|
|
||||||
<div className="mdl-cell mdl-cell--6-col">
|
|
||||||
<TextField
|
|
||||||
floatingLabelText="Created at"
|
|
||||||
value={createdAtFormatted}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div className="mdl-cell mdl-cell--3-col">
|
||||||
</div>
|
<Field
|
||||||
{editing &&
|
name="price"
|
||||||
|
component={renderTextField}
|
||||||
|
floatingLabelText="Price"
|
||||||
|
hintText="35.60"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mdl-cell mdl-cell--3-col">
|
||||||
|
<Field
|
||||||
|
name="colour"
|
||||||
|
component={renderTextField}
|
||||||
|
floatingLabelText="Colour"
|
||||||
|
hintText="orange"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mdl-cell mdl-cell--3-col">
|
||||||
|
<Size />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="mdl-grid">
|
<div className="mdl-grid">
|
||||||
<div className="mdl-cell mdl-cell--6-col">
|
<div className="mdl-cell mdl-cell--6-col">
|
||||||
<TextField
|
<Field
|
||||||
floatingLabelText="Claimed on"
|
name="serial_number"
|
||||||
value={claimedAtFormatted}
|
component={renderTextField}
|
||||||
|
floatingLabelText="Serial number"
|
||||||
|
hintText="ab90cd23"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mdl-cell mdl-cell--6-col">
|
{editing &&
|
||||||
<TextField
|
<div className="mdl-cell mdl-cell--6-col">
|
||||||
floatingLabelText="Claimed by"
|
<Field
|
||||||
value={claimed_by || undefined}
|
name="created_at"
|
||||||
fullWidth
|
component={renderTextField}
|
||||||
disabled
|
floatingLabelText="Created at"
|
||||||
readOnly
|
value={createdAtFormatted}
|
||||||
/>
|
fullWidth
|
||||||
</div>
|
readOnly
|
||||||
</div>
|
disabled
|
||||||
}
|
/>
|
||||||
|
</div>
|
||||||
{editing &&
|
}
|
||||||
<div className="content-grid mdl-grid" style={styles.bottom}>
|
|
||||||
<div className="mdl-cell mdl-cell--6-col">
|
|
||||||
<TextField floatingLabelText="CPIC searched" value={cpicSearchedAtFormatted} disabled />
|
|
||||||
</div>
|
|
||||||
<div className="mdl-cell mdl-cell--4-col">
|
|
||||||
<Checkbox
|
|
||||||
name="stolen"
|
|
||||||
label="Stolen"
|
|
||||||
labelPosition="left"
|
|
||||||
style={styles.checkbox}
|
|
||||||
checked={stolen}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mdl-cell mdl-cell--2-col">
|
|
||||||
<FlatButton label="Check" onTouchTap={this.handleCpicCheck} disabled={moment(cpic_searched_at).isValid()} primary />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div className="mdl-grid" style={styles.bottom}>
|
|
||||||
<div className="mdl-cell mdl-cell--8-col">
|
|
||||||
<Source
|
|
||||||
source={this.state.bike.source}
|
|
||||||
onChange={this.handleSourceChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{editing &&
|
{editing &&
|
||||||
<div className="mdl-cell mdl-cell--4-col">
|
<div className="mdl-grid">
|
||||||
<div style={styles.block}>
|
<div className="mdl-cell mdl-cell--6-col">
|
||||||
<Checkbox
|
<Field
|
||||||
checked={this.state.bike.stripped}
|
name="claimed_at"
|
||||||
name="stripped"
|
component={renderTextField}
|
||||||
label="Stripped"
|
floatingLabelText="Claimed on"
|
||||||
labelPosition="left"
|
value={claimedAtFormatted}
|
||||||
style={styles.checkbox}
|
fullWidth
|
||||||
onCheck={this.handleChange}
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mdl-cell mdl-cell--6-col">
|
||||||
|
<Field
|
||||||
|
name="claimed_by"
|
||||||
|
component={renderTextField}
|
||||||
|
floatingLabelText="Claimed by"
|
||||||
|
fullWidth
|
||||||
|
disabled
|
||||||
|
readOnly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
<div className="mdl-grid">
|
{editing &&
|
||||||
<div style={{ textAlign: 'right' }} className="mdl-cell mdl-cell--12-col">
|
<div className="content-grid mdl-grid" style={styles.bottom}>
|
||||||
<RaisedButton label="Cancel" onTouchTap={this.props.handleClose} secondary />
|
<div className="mdl-cell mdl-cell--6-col">
|
||||||
<RaisedButton label="Save" onTouchTap={this.handleSave} default />
|
<TextField floatingLabelText="CPIC searched" value={cpicSearchedAtFormatted} disabled />
|
||||||
|
</div>
|
||||||
|
<div className="mdl-cell mdl-cell--4-col">
|
||||||
|
<Field
|
||||||
|
name="stolen"
|
||||||
|
component={renderCheckbox}
|
||||||
|
label="Stolen"
|
||||||
|
labelPosition="left"
|
||||||
|
style={styles.checkbox}
|
||||||
|
checked={stolen}
|
||||||
|
disabled
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mdl-cell mdl-cell--2-col">
|
||||||
|
<FlatButton label="Check" onTouchTap={this.handleCpicCheck} disabled={moment(cpic_searched_at).isValid()} primary />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div className="mdl-grid" style={styles.bottom}>
|
||||||
|
<div className="mdl-cell mdl-cell--8-col">
|
||||||
|
<Field
|
||||||
|
name="donation_source"
|
||||||
|
component={renderSelectField}
|
||||||
|
floatingLabelText="Donation source"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{sourceMenuItems}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
{editing &&
|
||||||
|
<div className="mdl-cell mdl-cell--4-col">
|
||||||
|
<div style={styles.block}>
|
||||||
|
<Field
|
||||||
|
checked={checked}
|
||||||
|
name="stripped"
|
||||||
|
label="Stripped"
|
||||||
|
labelPosition="left"
|
||||||
|
style={styles.checkbox}
|
||||||
|
component={renderCheckbox}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="mdl-grid">
|
||||||
|
<div style={{ textAlign: 'right' }} className="mdl-cell mdl-cell--12-col">
|
||||||
|
<RaisedButton style={{ marginRight: '8px' }} label="Cancel" onTouchTap={this.props.handleClose} secondary />
|
||||||
|
<RaisedButton type="submit" label="Save" default disabled={this.props.pristine || this.props.submitting || this.props.invalid} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BikeForm = reduxForm({
|
||||||
|
form: 'BikeForm', // a unique identifier for this form
|
||||||
|
validate,
|
||||||
|
})(BikeForm);
|
||||||
|
|
||||||
|
BikeForm = connect(
|
||||||
|
state => ({
|
||||||
|
initialValues: state.bike, // pull initial values from account reducer
|
||||||
|
}),
|
||||||
|
)(BikeForm);
|
||||||
|
|
||||||
BikeForm.propTypes = {
|
BikeForm.propTypes = {
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
handleClose: PropTypes.func,
|
handleClose: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BikeForm;
|
export default BikeForm;
|
||||||
|
|
||||||
|
@ -58,8 +58,10 @@ class BikeModal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BikeModal.propTypes = {
|
BikeModal.propTypes = {
|
||||||
open: PropTypes.bool,
|
open: PropTypes.bool,
|
||||||
bike: PropTypes.object,
|
bike: PropTypes.object,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default BikeModal;
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
|
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
import fetch from 'isomorphic-fetch';
|
|
||||||
import FlatButton from 'material-ui/FlatButton';
|
import FlatButton from 'material-ui/FlatButton';
|
||||||
import FloatingActionButton from 'material-ui/FloatingActionButton';
|
import FloatingActionButton from 'material-ui/FloatingActionButton';
|
||||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { friendlySize } from '../Size';
|
import { friendlySize } from '../Size';
|
||||||
import BikeModal from '../BikeModal';
|
import BikeModal from '../BikeModal';
|
||||||
|
import { fetchBikes, setBike } from '../../actions';
|
||||||
|
|
||||||
function checkStatus(response) {
|
const renderBikes = (bikes) => {
|
||||||
if (response.status >= 200 && response.status < 300) {
|
console.log(bikes);
|
||||||
return response;
|
const bikeRows = bikes.map(bike => (
|
||||||
}
|
<TableRow selectable={false} key={bike.id}>
|
||||||
const error = new Error(response.statusText);
|
<TableRowColumn>{friendlySize(bike.size)}</TableRowColumn>
|
||||||
error.response = response;
|
<TableRowColumn>{bike.colour}</TableRowColumn>
|
||||||
throw error;
|
<TableRowColumn>{bike.make}</TableRowColumn>
|
||||||
|
<TableRowColumn>{bike.serial_number}</TableRowColumn>
|
||||||
|
<TableRowColumn>{bike.state}</TableRowColumn>
|
||||||
|
<TableRowColumn>{bike.claimed_by}</TableRowColumn>
|
||||||
|
<TableRowColumn><FlatButton label="Edit" primary /></TableRowColumn>
|
||||||
|
</TableRow>
|
||||||
|
));
|
||||||
|
return bikeRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseJSON(response) {
|
class BikeTableComponent extends React.Component {
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class BikeTable extends React.Component {
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@ -38,10 +41,9 @@ export default class BikeTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getBikes();
|
this.props.fetchBikes();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBikes = () => {
|
|
||||||
handleEditBike(bike) {
|
handleEditBike(bike) {
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
@ -65,54 +67,57 @@ export default class BikeTable extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const bikeRows = this.state.bikes.map(bike => (
|
if (this.props.bikes.fetched) {
|
||||||
<TableRow selectable={false} key={bike.id}>
|
const bikeRows = renderBikes(Object.values(this.props.bikes.entities['bikes'] || []));
|
||||||
<TableRowColumn>{friendlySize(bike.size)}</TableRowColumn>
|
return (
|
||||||
<TableRowColumn>{bike.colour}</TableRowColumn>
|
<div className="mdl-grid">
|
||||||
<TableRowColumn>{bike.make}</TableRowColumn>
|
<div className="mdl-cell mdl-cell--12-col">
|
||||||
<TableRowColumn>{bike.serial_number}</TableRowColumn>
|
<h3>Bikes</h3>
|
||||||
<TableRowColumn>{bike.state}</TableRowColumn>
|
<FloatingActionButton onTouchTap={this.handleAddBike}>
|
||||||
<TableRowColumn>{bike.claimed_by}</TableRowColumn>
|
<ContentAdd />
|
||||||
<TableRowColumn><FlatButton label="Edit" primary onTouchTap={this.handleEditBike.bind(null, bike)} /></TableRowColumn>
|
</FloatingActionButton>
|
||||||
</TableRow>
|
<Table selectable={false}>
|
||||||
));
|
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
|
||||||
|
<TableRow>
|
||||||
return (
|
<TableHeaderColumn>Size</TableHeaderColumn>
|
||||||
<div className="mdl-grid">
|
<TableHeaderColumn>Colour</TableHeaderColumn>
|
||||||
<div className="mdl-cell mdl-cell--12-col">
|
<TableHeaderColumn>Make</TableHeaderColumn>
|
||||||
<h3>Bikes</h3>
|
<TableHeaderColumn>Serial number</TableHeaderColumn>
|
||||||
<FloatingActionButton onTouchTap={this.handleAddBike}>
|
<TableHeaderColumn>State</TableHeaderColumn>
|
||||||
<ContentAdd />
|
<TableHeaderColumn>Claimed by</TableHeaderColumn>
|
||||||
</FloatingActionButton>
|
<TableRowColumn />
|
||||||
<Table selectable={false}>
|
</TableRow>
|
||||||
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
|
</TableHeader>
|
||||||
<TableRow>
|
<TableBody displayRowCheckbox={false}>
|
||||||
<TableHeaderColumn>Size</TableHeaderColumn>
|
{bikeRows.length ?
|
||||||
<TableHeaderColumn>Colour</TableHeaderColumn>
|
bikeRows :
|
||||||
<TableHeaderColumn>Make</TableHeaderColumn>
|
<TableRow >
|
||||||
<TableHeaderColumn>Serial number</TableHeaderColumn>
|
<TableRowColumn key="none">{'No bikes found.'}</TableRowColumn>
|
||||||
<TableHeaderColumn>State</TableHeaderColumn>
|
</TableRow>
|
||||||
<TableHeaderColumn>Claimed by</TableHeaderColumn>
|
}
|
||||||
<TableRowColumn />
|
</TableBody>
|
||||||
</TableRow>
|
</Table>
|
||||||
</TableHeader>
|
</div>
|
||||||
<TableBody displayRowCheckbox={false}>
|
|
||||||
{bikeRows.length ?
|
|
||||||
bikeRows :
|
|
||||||
<TableRow >
|
|
||||||
<TableRowColumn key="none">{'No bikes found.'}</TableRowColumn>
|
|
||||||
</TableRow>
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<BikeModal
|
|
||||||
bike={this.state.bikeModal.bike}
|
|
||||||
open={this.state.bikeModal.open}
|
|
||||||
editing={this.state.bikeModal.editing}
|
|
||||||
getBikes={this.getBikes}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
bikes: state.bikes.bikes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
fetchBikes: () => {
|
||||||
|
dispatch(fetchBikes());
|
||||||
|
},
|
||||||
|
setBike: (id) => {
|
||||||
|
dispatch(setBike(id));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const BikeTable = connect(mapStateToProps, mapDispatchToProps)(BikeTableComponent);
|
||||||
|
export default BikeTable;
|
@ -1,6 +1,7 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import SelectField from 'material-ui/SelectField';
|
import { Field } from 'redux-form';
|
||||||
import MenuItem from 'material-ui/MenuItem';
|
import MenuItem from 'material-ui/MenuItem';
|
||||||
|
import SelectField from 'material-ui/SelectField';
|
||||||
|
|
||||||
const sizes = ['C', 'S', 'M', 'L', 'XL'];
|
const sizes = ['C', 'S', 'M', 'L', 'XL'];
|
||||||
|
|
||||||
@ -23,26 +24,33 @@ export const friendlySize = (size) => {
|
|||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
float: 'left',
|
float: 'left',
|
||||||
}
|
};
|
||||||
|
|
||||||
const Size = ({ size, onChange }) => {
|
const renderSelectField = ({ input, label, meta: { touched, error }, children, ...custom }) => (
|
||||||
|
<SelectField
|
||||||
|
errorText={touched && error}
|
||||||
|
{...input}
|
||||||
|
onChange={(event, index, value) => input.onChange(value)}
|
||||||
|
children={children}
|
||||||
|
{...custom}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Size = () => {
|
||||||
const items = sizes.map(s =>
|
const items = sizes.map(s =>
|
||||||
<MenuItem key={s} name="size" value={s} primaryText={friendlySize(s)} />,
|
<MenuItem key={s} name="size" value={s} primaryText={friendlySize(s)} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles}>
|
<Field
|
||||||
<SelectField
|
name="size"
|
||||||
floatingLabelText="Size"
|
component={renderSelectField}
|
||||||
name="size"
|
floatingLabelText="Size"
|
||||||
value={size}
|
fullWidth
|
||||||
onChange={onChange}
|
>
|
||||||
autoWidth
|
<MenuItem value={null} primaryText="" />
|
||||||
>
|
{items}
|
||||||
<MenuItem value={null} primaryText="" />
|
</Field>
|
||||||
{items}
|
|
||||||
</SelectField>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ const Source = ({ source, onChange }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SelectField
|
<SelectField
|
||||||
|
name="donation_source"
|
||||||
floatingLabelText="Donation source"
|
floatingLabelText="Donation source"
|
||||||
value={source}
|
value={source}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -1,23 +1,46 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
import getMuiTheme from 'material-ui/styles/getMuiTheme';
|
import getMuiTheme from 'material-ui/styles/getMuiTheme';
|
||||||
|
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
|
||||||
|
import { reducer as formReducer } from 'redux-form';
|
||||||
|
import bikesReducer from './reducers';
|
||||||
|
import createSagaMiddleware from 'redux-saga'
|
||||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||||
import BikeTable from './components/BikeTable';
|
import BikeTable from './components/BikeTable';
|
||||||
|
import watchFetchBikes from './sagas';
|
||||||
|
|
||||||
|
|
||||||
// Needed for onTouchTap
|
// Needed for onTouchTap
|
||||||
// http://stackoverflow.com/a/34015469/988941
|
// http://stackoverflow.com/a/34015469/988941
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
|
|
||||||
|
const reducers = {
|
||||||
|
// ... your other reducers here ...
|
||||||
|
form: formReducer, // <---- Mounted at 'form'
|
||||||
|
bikes: bikesReducer,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
||||||
|
const combinedReducers = combineReducers(reducers);
|
||||||
|
|
||||||
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
|
const store = createStore(combinedReducers, composeEnhancers(applyMiddleware(sagaMiddleware)));
|
||||||
|
|
||||||
|
sagaMiddleware.run(watchFetchBikes);
|
||||||
|
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MuiThemeProvider muiTheme={getMuiTheme()}>
|
<MuiThemeProvider muiTheme={getMuiTheme()}>
|
||||||
<BikeTable/>
|
<BikeTable />
|
||||||
</MuiThemeProvider>
|
</MuiThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'));
|
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
|
||||||
|
27
bikeshop_project/assets/js/bikes/reducers.js
Normal file
27
bikeshop_project/assets/js/bikes/reducers.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { setBike, setBikes, setBikesIsFetching, setBikesFetched } from './actions';
|
||||||
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
|
export default handleActions({
|
||||||
|
[setBikes]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
bikes: action.payload,
|
||||||
|
}),
|
||||||
|
[setBikesIsFetching]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
bikes: {
|
||||||
|
...state.bikes,
|
||||||
|
isFetching: action.payload,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[setBikesFetched]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
bikes: {
|
||||||
|
...state.bikes,
|
||||||
|
fetched: action.payload
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[setBike]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
...action.payload,
|
||||||
|
}),
|
||||||
|
}, { bikes: [], bike: undefined });
|
27
bikeshop_project/assets/js/bikes/sagas.js
Normal file
27
bikeshop_project/assets/js/bikes/sagas.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||||
|
import { fetchBikes as fetchBikesAction, setBikes, setBikesIsFetching, setBikesFetched } from './actions';
|
||||||
|
import { normalize } from 'normalizr';
|
||||||
|
import * as schema from './schema';
|
||||||
|
import Api from './services';
|
||||||
|
|
||||||
|
|
||||||
|
// worker Saga: will be fired on USER_FETCH_REQUESTED actions
|
||||||
|
function* fetchBikes(action) {
|
||||||
|
try {
|
||||||
|
yield put({ type: setBikesIsFetching.toString(), payload: true });
|
||||||
|
const bikes = yield call(Api.fetchBikes);
|
||||||
|
yield put({ type: setBikes.toString(), payload: normalize(bikes, schema.bikes) });
|
||||||
|
yield put({ type: setBikesFetched, payload: true });
|
||||||
|
} catch (e) {
|
||||||
|
yield put({ type: 'BIKES_FETCH_FAILED', message: e.message });
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
yield put({ type: setBikesIsFetching.toString(), payload: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* watchFetchBikes() {
|
||||||
|
yield takeEvery(fetchBikesAction.toString(), fetchBikes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default watchFetchBikes;
|
4
bikeshop_project/assets/js/bikes/schema/index.js
Normal file
4
bikeshop_project/assets/js/bikes/schema/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { normalize, schema } from 'normalizr';
|
||||||
|
|
||||||
|
const bike = new schema.Entity('bikes');
|
||||||
|
export const bikes = new schema.Array(bike);
|
28
bikeshop_project/assets/js/bikes/services/index.js
Normal file
28
bikeshop_project/assets/js/bikes/services/index.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import fetch from 'isomorphic-fetch';
|
||||||
|
|
||||||
|
const checkStatus = (response) => {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
const error = new Error(response.statusText);
|
||||||
|
error.response = response;
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseJson = response => response.json();
|
||||||
|
|
||||||
|
const Api = {
|
||||||
|
fetchBikes() {
|
||||||
|
return fetch('/api/v1/bikes/', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
.then(checkStatus)
|
||||||
|
.then(parseJson)
|
||||||
|
.then(data => data)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log('request failed', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Api;
|
@ -18,10 +18,16 @@
|
|||||||
"material-ui": "^0.18.1",
|
"material-ui": "^0.18.1",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.13.0",
|
||||||
"node-sass": "^3.4.2",
|
"node-sass": "^3.4.2",
|
||||||
|
"normalizr": "^3.2.2",
|
||||||
"react": "^15.4.1",
|
"react": "^15.4.1",
|
||||||
"react-addons-css-transition-group": "^15.4.2",
|
"react-addons-css-transition-group": "^15.4.2",
|
||||||
"react-dom": "^15.4.1",
|
"react-dom": "^15.4.1",
|
||||||
|
"react-redux": "^5.0.2",
|
||||||
"react-tap-event-plugin": "^2.0.1",
|
"react-tap-event-plugin": "^2.0.1",
|
||||||
|
"redux": "^3.6.0",
|
||||||
|
"redux-actions": "^1.2.2",
|
||||||
|
"redux-form": "^6.5.0",
|
||||||
|
"redux-saga": "^0.14.3",
|
||||||
"webpack": "1.13.1",
|
"webpack": "1.13.1",
|
||||||
"webpack-bundle-tracker": "0.0.93"
|
"webpack-bundle-tracker": "0.0.93"
|
||||||
},
|
},
|
||||||
|
@ -7,8 +7,7 @@ const config = require('./webpack.base.config.js');
|
|||||||
// Use webpack dev server
|
// Use webpack dev server
|
||||||
config.entry = {
|
config.entry = {
|
||||||
webpack: [
|
webpack: [
|
||||||
'webpack-dev-server/client?http://localhost:3000',
|
'webpack-dev-server/client?http://workstand.docker:3000',
|
||||||
'webpack/hot/only-dev-server',
|
|
||||||
],
|
],
|
||||||
signin: './assets/js/index',
|
signin: './assets/js/index',
|
||||||
members: './assets/js/members/index',
|
members: './assets/js/members/index',
|
||||||
@ -16,7 +15,7 @@ config.entry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// override django's STATIC_URL for webpack bundles
|
// override django's STATIC_URL for webpack bundles
|
||||||
config.output.publicPath = 'http://localhost:3000/assets/bundles/';
|
config.output.publicPath = 'http://workstand.docker:3000/assets/bundles/';
|
||||||
|
|
||||||
config.devtool = 'eval-source-map';
|
config.devtool = 'eval-source-map';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user