mirror of
				https://github.com/fspc/workstand.git
				synced 2025-10-31 08:25:35 -04: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 { Field, reduxForm } from 'redux-form'; | ||||
| import { connect } from 'react-redux'; | ||||
| import Checkbox from 'material-ui/Checkbox'; | ||||
| import Cookies from 'js-cookie'; | ||||
| import FlatButton from 'material-ui/FlatButton'; | ||||
| import MenuItem from 'material-ui/MenuItem'; | ||||
| import RaisedButton from 'material-ui/RaisedButton'; | ||||
| import SelectField from 'material-ui/SelectField'; | ||||
| import TextField from 'material-ui/TextField'; | ||||
| import fetch from 'isomorphic-fetch'; | ||||
| import moment from 'moment-timezone'; | ||||
| @ -21,12 +25,68 @@ const styles = { | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| class BikeForm extends React.Component { | ||||
|   static propTypes = { | ||||
|     bike: PropTypes.object, | ||||
|     editing: PropTypes.bool, | ||||
|     handleClose: PropTypes.func, | ||||
| const sources = ['COS_BIKE_DIVERSION_PILOT', 'UOFS', 'DROP_OFF']; | ||||
| 
 | ||||
| const friendly = (s) => { | ||||
|   switch (s) { | ||||
|     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 }) { | ||||
|     super(); | ||||
|     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 } }); | ||||
|   } | ||||
| 
 | ||||
|   handleSizeChange = (event, index, value) => { | ||||
|   handleSizeChange(event, index, 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 } }); | ||||
|   } | ||||
| 
 | ||||
|   handleCpicCheck = () => { | ||||
|   handleCpicCheck() { | ||||
|     const id = this.state.bike.id; | ||||
|     const serialNumber = this.state.bike.serial_number; | ||||
|     const data = JSON.stringify({ serial_number: serialNumber }); | ||||
| @ -107,6 +167,7 @@ class BikeForm extends React.Component { | ||||
|       cpic_searched_at, | ||||
|       created_at, | ||||
|       stolen, | ||||
|       checked, | ||||
|     } = this.props.bike; | ||||
|     const editing = this.props.editing; | ||||
|     const createdAtFormatted = (moment(created_at).isValid()) ? moment(created_at).tz(timezone).fromNow() : ''; | ||||
| @ -116,147 +177,163 @@ class BikeForm extends React.Component { | ||||
| 
 | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className="mdl-grid"> | ||||
|           <div className="mdl-cell mdl-cell--3-col"> | ||||
|             <TextField | ||||
|               name="make" | ||||
|               floatingLabelText="Make" | ||||
|               hintText="Norco" | ||||
|               value={this.state.bike.make || undefined} | ||||
|               onChange={this.handleChange} | ||||
|               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} | ||||
|         <form onSubmit={handleSubmit}> | ||||
|           <div className="mdl-grid"> | ||||
|             <div className="mdl-cell mdl-cell--3-col"> | ||||
|               <Field | ||||
|                 name="make" | ||||
|                 component={renderTextField} | ||||
|                 floatingLabelText="Make" | ||||
|                 hintText="Norco" | ||||
|                 fullWidth | ||||
|                 readOnly | ||||
|                 disabled | ||||
|               /> | ||||
|             </div> | ||||
|           } | ||||
|         </div> | ||||
|         {editing && | ||||
|             <div className="mdl-cell mdl-cell--3-col"> | ||||
|               <Field | ||||
|                 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-cell mdl-cell--6-col"> | ||||
|               <TextField | ||||
|                 floatingLabelText="Claimed on" | ||||
|                 value={claimedAtFormatted} | ||||
|               <Field | ||||
|                 name="serial_number" | ||||
|                 component={renderTextField} | ||||
|                 floatingLabelText="Serial number" | ||||
|                 hintText="ab90cd23" | ||||
|                 fullWidth | ||||
|                 disabled | ||||
|               /> | ||||
|             </div> | ||||
|             <div className="mdl-cell mdl-cell--6-col"> | ||||
|               <TextField | ||||
|                 floatingLabelText="Claimed by" | ||||
|                 value={claimed_by || undefined} | ||||
|                 fullWidth | ||||
|                 disabled | ||||
|                 readOnly | ||||
|               /> | ||||
|             </div> | ||||
|           </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} | ||||
|             /> | ||||
|             {editing && | ||||
|               <div className="mdl-cell mdl-cell--6-col"> | ||||
|                 <Field | ||||
|                   name="created_at" | ||||
|                   component={renderTextField} | ||||
|                   floatingLabelText="Created at" | ||||
|                   value={createdAtFormatted} | ||||
|                   fullWidth | ||||
|                   readOnly | ||||
|                   disabled | ||||
|                 /> | ||||
|               </div> | ||||
|             } | ||||
|           </div> | ||||
|           {editing && | ||||
|             <div className="mdl-cell mdl-cell--4-col"> | ||||
|               <div style={styles.block}> | ||||
|                 <Checkbox | ||||
|                   checked={this.state.bike.stripped} | ||||
|                   name="stripped" | ||||
|                   label="Stripped" | ||||
|                   labelPosition="left" | ||||
|                   style={styles.checkbox} | ||||
|                   onCheck={this.handleChange} | ||||
|             <div className="mdl-grid"> | ||||
|               <div className="mdl-cell mdl-cell--6-col"> | ||||
|                 <Field | ||||
|                   name="claimed_at" | ||||
|                   component={renderTextField} | ||||
|                   floatingLabelText="Claimed on" | ||||
|                   value={claimedAtFormatted} | ||||
|                   fullWidth | ||||
|                   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 className="mdl-grid"> | ||||
|           <div style={{ textAlign: 'right' }} className="mdl-cell mdl-cell--12-col"> | ||||
|             <RaisedButton label="Cancel" onTouchTap={this.props.handleClose} secondary /> | ||||
|             <RaisedButton label="Save" onTouchTap={this.handleSave} default /> | ||||
| 
 | ||||
|           {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"> | ||||
|                 <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 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> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 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 = { | ||||
|   editing: PropTypes.bool, | ||||
|   handleClose: PropTypes.func, | ||||
| } | ||||
| 
 | ||||
| export default BikeForm; | ||||
| 
 | ||||
|  | ||||
| @ -58,8 +58,10 @@ class BikeModal extends React.Component { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default BikeModal.propTypes = { | ||||
| BikeModal.propTypes = { | ||||
|   open: PropTypes.bool, | ||||
|   bike: PropTypes.object, | ||||
|   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 Cookies from 'js-cookie'; | ||||
| import fetch from 'isomorphic-fetch'; | ||||
| import FlatButton from 'material-ui/FlatButton'; | ||||
| import FloatingActionButton from 'material-ui/FloatingActionButton'; | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import React from 'react'; | ||||
| import { friendlySize } from '../Size'; | ||||
| import BikeModal from '../BikeModal'; | ||||
| import { fetchBikes, setBike } from '../../actions'; | ||||
| 
 | ||||
| function checkStatus(response) { | ||||
|   if (response.status >= 200 && response.status < 300) { | ||||
|     return response; | ||||
|   } | ||||
|   const error = new Error(response.statusText); | ||||
|   error.response = response; | ||||
|   throw error; | ||||
| const renderBikes = (bikes) => { | ||||
|   console.log(bikes); | ||||
|   const bikeRows = bikes.map(bike => ( | ||||
|     <TableRow selectable={false} key={bike.id}> | ||||
|       <TableRowColumn>{friendlySize(bike.size)}</TableRowColumn> | ||||
|       <TableRowColumn>{bike.colour}</TableRowColumn> | ||||
|       <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) { | ||||
|   return response.json(); | ||||
| } | ||||
| 
 | ||||
| export default class BikeTable extends React.Component { | ||||
| class BikeTableComponent extends React.Component { | ||||
|   constructor(props) { | ||||
|     super(props); | ||||
| 
 | ||||
| @ -38,10 +41,9 @@ export default class BikeTable extends React.Component { | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.getBikes(); | ||||
|     this.props.fetchBikes(); | ||||
|   } | ||||
| 
 | ||||
|   getBikes = () => { | ||||
|   handleEditBike(bike) { | ||||
|     this.setState({ | ||||
|       ...this.state, | ||||
| @ -65,54 +67,57 @@ export default class BikeTable extends React.Component { | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const bikeRows = this.state.bikes.map(bike => ( | ||||
|       <TableRow selectable={false} key={bike.id}> | ||||
|         <TableRowColumn>{friendlySize(bike.size)}</TableRowColumn> | ||||
|         <TableRowColumn>{bike.colour}</TableRowColumn> | ||||
|         <TableRowColumn>{bike.make}</TableRowColumn> | ||||
|         <TableRowColumn>{bike.serial_number}</TableRowColumn> | ||||
|         <TableRowColumn>{bike.state}</TableRowColumn> | ||||
|         <TableRowColumn>{bike.claimed_by}</TableRowColumn> | ||||
|         <TableRowColumn><FlatButton label="Edit" primary onTouchTap={this.handleEditBike.bind(null, bike)} /></TableRowColumn> | ||||
|       </TableRow> | ||||
|       )); | ||||
| 
 | ||||
|     return ( | ||||
|       <div className="mdl-grid"> | ||||
|         <div className="mdl-cell mdl-cell--12-col"> | ||||
|           <h3>Bikes</h3> | ||||
|           <FloatingActionButton onTouchTap={this.handleAddBike}> | ||||
|             <ContentAdd /> | ||||
|           </FloatingActionButton> | ||||
|           <Table selectable={false}> | ||||
|             <TableHeader adjustForCheckbox={false} displaySelectAll={false}> | ||||
|               <TableRow> | ||||
|                 <TableHeaderColumn>Size</TableHeaderColumn> | ||||
|                 <TableHeaderColumn>Colour</TableHeaderColumn> | ||||
|                 <TableHeaderColumn>Make</TableHeaderColumn> | ||||
|                 <TableHeaderColumn>Serial number</TableHeaderColumn> | ||||
|                 <TableHeaderColumn>State</TableHeaderColumn> | ||||
|                 <TableHeaderColumn>Claimed by</TableHeaderColumn> | ||||
|                 <TableRowColumn /> | ||||
|               </TableRow> | ||||
|             </TableHeader> | ||||
|             <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} | ||||
|           /> | ||||
|     if (this.props.bikes.fetched) { | ||||
|       const bikeRows = renderBikes(Object.values(this.props.bikes.entities['bikes'] || [])); | ||||
|       return ( | ||||
|         <div className="mdl-grid"> | ||||
|           <div className="mdl-cell mdl-cell--12-col"> | ||||
|             <h3>Bikes</h3> | ||||
|             <FloatingActionButton onTouchTap={this.handleAddBike}> | ||||
|               <ContentAdd /> | ||||
|             </FloatingActionButton> | ||||
|             <Table selectable={false}> | ||||
|               <TableHeader adjustForCheckbox={false} displaySelectAll={false}> | ||||
|                 <TableRow> | ||||
|                   <TableHeaderColumn>Size</TableHeaderColumn> | ||||
|                   <TableHeaderColumn>Colour</TableHeaderColumn> | ||||
|                   <TableHeaderColumn>Make</TableHeaderColumn> | ||||
|                   <TableHeaderColumn>Serial number</TableHeaderColumn> | ||||
|                   <TableHeaderColumn>State</TableHeaderColumn> | ||||
|                   <TableHeaderColumn>Claimed by</TableHeaderColumn> | ||||
|                   <TableRowColumn /> | ||||
|                 </TableRow> | ||||
|               </TableHeader> | ||||
|               <TableBody displayRowCheckbox={false}> | ||||
|                 {bikeRows.length ? | ||||
|                   bikeRows : | ||||
|                   <TableRow > | ||||
|                     <TableRowColumn key="none">{'No bikes found.'}</TableRowColumn> | ||||
|                   </TableRow> | ||||
|                 } | ||||
|               </TableBody> | ||||
|             </Table> | ||||
|           </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 SelectField from 'material-ui/SelectField'; | ||||
| import { Field } from 'redux-form'; | ||||
| import MenuItem from 'material-ui/MenuItem'; | ||||
| import SelectField from 'material-ui/SelectField'; | ||||
| 
 | ||||
| const sizes = ['C', 'S', 'M', 'L', 'XL']; | ||||
| 
 | ||||
| @ -23,26 +24,33 @@ export const friendlySize = (size) => { | ||||
| 
 | ||||
| const styles = { | ||||
|   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 => | ||||
|     <MenuItem key={s} name="size" value={s} primaryText={friendlySize(s)} />, | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <div style={styles}> | ||||
|       <SelectField | ||||
|         floatingLabelText="Size" | ||||
|         name="size" | ||||
|         value={size} | ||||
|         onChange={onChange} | ||||
|         autoWidth | ||||
|       > | ||||
|         <MenuItem value={null} primaryText="" /> | ||||
|         {items} | ||||
|       </SelectField> | ||||
|     </div> | ||||
|     <Field | ||||
|       name="size" | ||||
|       component={renderSelectField} | ||||
|       floatingLabelText="Size" | ||||
|       fullWidth | ||||
|     > | ||||
|       <MenuItem value={null} primaryText="" /> | ||||
|       {items} | ||||
|     </Field> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -25,6 +25,7 @@ const Source = ({ source, onChange }) => { | ||||
|   return ( | ||||
|     <div> | ||||
|       <SelectField | ||||
|         name="donation_source" | ||||
|         floatingLabelText="Donation source" | ||||
|         value={source} | ||||
|         onChange={onChange} | ||||
|  | ||||
| @ -1,23 +1,46 @@ | ||||
| import React from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import { Provider } from 'react-redux' | ||||
| import injectTapEventPlugin from 'react-tap-event-plugin'; | ||||
| 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 BikeTable from './components/BikeTable'; | ||||
| import watchFetchBikes from './sagas'; | ||||
| 
 | ||||
| 
 | ||||
| // Needed for onTouchTap | ||||
| // http://stackoverflow.com/a/34015469/988941 | ||||
| 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 { | ||||
|   render() { | ||||
|     return ( | ||||
|       <MuiThemeProvider muiTheme={getMuiTheme()}> | ||||
|         <BikeTable/> | ||||
|         <BikeTable /> | ||||
|       </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", | ||||
|     "moment": "^2.13.0", | ||||
|     "node-sass": "^3.4.2", | ||||
|     "normalizr": "^3.2.2", | ||||
|     "react": "^15.4.1", | ||||
|     "react-addons-css-transition-group": "^15.4.2", | ||||
|     "react-dom": "^15.4.1", | ||||
|     "react-redux": "^5.0.2", | ||||
|     "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-bundle-tracker": "0.0.93" | ||||
|   }, | ||||
|  | ||||
| @ -7,8 +7,7 @@ const config = require('./webpack.base.config.js'); | ||||
| // Use webpack dev server
 | ||||
| config.entry = { | ||||
|   webpack: [ | ||||
|     'webpack-dev-server/client?http://localhost:3000', | ||||
|     'webpack/hot/only-dev-server', | ||||
|     'webpack-dev-server/client?http://workstand.docker:3000', | ||||
|   ], | ||||
|   signin: './assets/js/index', | ||||
|   members: './assets/js/members/index', | ||||
| @ -16,7 +15,7 @@ config.entry = { | ||||
| }; | ||||
| 
 | ||||
| // 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'; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user