From 9c5b0f45dd617024e73af6d9156ccf1b20e1209b Mon Sep 17 00:00:00 2001 From: tnoworyta Date: Tue, 16 May 2017 16:15:20 +0200 Subject: [PATCH 1/2] Customers crud --- app/resources/customer_resource.rb | 21 +++-- client/src/api/normalize.js | 12 +++ client/src/components/App.js | 3 + .../src/components/Customers/CustomerEdit.js | 48 ++++++++++ .../src/components/Customers/CustomerForm.js | 88 +++++++++++++++++++ .../src/components/Customers/CustomerList.js | 44 ++++++++++ client/src/components/Customers/index.js | 2 + client/src/components/Routes.js | 3 + 8 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 client/src/components/Customers/CustomerEdit.js create mode 100644 client/src/components/Customers/CustomerForm.js create mode 100644 client/src/components/Customers/CustomerList.js create mode 100644 client/src/components/Customers/index.js diff --git a/app/resources/customer_resource.rb b/app/resources/customer_resource.rb index 4c5077b..5783a29 100644 --- a/app/resources/customer_resource.rb +++ b/app/resources/customer_resource.rb @@ -1,12 +1,15 @@ class CustomerResource < JSONAPI::Resource attributes :company_name, - :contact_name - :contact_title - :address - :city - :region - :postal_code - :country - :phone - :fax + :contact_name, + :contact_title, + :address, + :city, + :region, + :postal_code, + :country, + :phone, + :fax, + :created_at + + paginator :paged end diff --git a/client/src/api/normalize.js b/client/src/api/normalize.js index 648c398..3d8e152 100644 --- a/client/src/api/normalize.js +++ b/client/src/api/normalize.js @@ -58,6 +58,18 @@ const serializers = { }), }, + customers: { + serializer: new Serializer('customers', { + keyForAttribute: 'camelCase', + attributes: [ + 'companyName' + ], + }), + deserializer: new Deserializer({ + keyForAttribute: 'camelCase' + }), + }, + roles: { serializer: new Serializer('roles', { keyForAttribute: 'camelCase', diff --git a/client/src/components/App.js b/client/src/components/App.js index 87a753f..d2097a1 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -38,6 +38,9 @@ export class App extends Component { Categories + + Customers + { userIsAdmin && Users diff --git a/client/src/components/Customers/CustomerEdit.js b/client/src/components/Customers/CustomerEdit.js new file mode 100644 index 0000000..3930973 --- /dev/null +++ b/client/src/components/Customers/CustomerEdit.js @@ -0,0 +1,48 @@ +import React, { Component, PropTypes } from 'react'; +import { push } from 'react-router-redux'; +import { connect } from 'react-redux'; + +import { ErrorAlert, Loading, EditHeader } from '../UI'; +import { withResource } from '../../hocs'; +import CustomerForm from './CustomerForm'; +import { getMany, fetchList } from '../../store/api'; + +export class CustomerEdit extends Component { + componentWillMount() { + const { params, fetchResource } = this.props; + if (params.id) { + fetchResource({ id: params.id }); + } + } + + render() { + const { isNew, error, loading, resource, onSubmit } = this.props; + + if (error) { + return (); + } + + if (loading) { + return (); + } + + return ( +
+ { isNew ? 'New Customer' : resource.company_name } + +
+ ); + } +} + +export const mapStateToProps = (state, props) => ({ + roles: getMany(state) +}); + +export const mapDispatchToProps = dispatch => ({ + redirectToIndex: () => dispatch(push('/customers')) +}); + +export default connect(mapStateToProps, mapDispatchToProps)( + withResource('customers')(CustomerEdit), +); diff --git a/client/src/components/Customers/CustomerForm.js b/client/src/components/Customers/CustomerForm.js new file mode 100644 index 0000000..6570bb3 --- /dev/null +++ b/client/src/components/Customers/CustomerForm.js @@ -0,0 +1,88 @@ +import React, { Component, PropTypes } from 'react'; +import { isEmpty } from 'lodash'; +import { Field, reduxForm } from 'redux-form'; +import { Button, Form } from 'reactstrap'; + +import { InputField, MultiselectField, required } from '../../forms'; + +class CustomerForm extends Component { + render() { + const { handleSubmit, pristine, reset, submitting } = this.props; + + return ( +
+
+ + + + + + + + + + + + + + +
+
+ + +
+
+ ); + } +} + +const validate = (values) => { + const errors = required(values, 'email'); + return errors; +}; + +export default reduxForm({ + enableReinitialize: true, + form: 'customer', + validate, +})(CustomerForm); diff --git a/client/src/components/Customers/CustomerList.js b/client/src/components/Customers/CustomerList.js new file mode 100644 index 0000000..ec670ef --- /dev/null +++ b/client/src/components/Customers/CustomerList.js @@ -0,0 +1,44 @@ +import React, { Component, PropTypes } from 'react'; +import { Link } from 'react-router'; +import { find, keyBy } from 'lodash'; + +import { ListTable } from '../UI'; +import { withResourceList } from '../../hocs'; + +const formatDate = date => (new Date(date)).toLocaleString(); + +export class CustomerList extends Component { + componentWillMount() { + const { resourceList } = this.props; + this.props.fetchResourceList({ sort: '-companyName', ...resourceList.params }); + } + + render() { + const columns = [ + { + attribute: 'companyName', + header: 'Company Name', + rowRender: customer => {customer.companyName}, + sortable: true, + }, + { + attribute: 'contactName', + header: 'Contact Name', + rowRender: customer => {customer.contactName}, + sortable: true, + }, + { + attribute: 'createdAt', + header: 'Created At', + rowRender: customer => formatDate(customer.confirmedAt), + sortable: true, + } + ]; + + return ( + + ); + } +} + +export default withResourceList('customers')(CustomerList); diff --git a/client/src/components/Customers/index.js b/client/src/components/Customers/index.js new file mode 100644 index 0000000..ad0a602 --- /dev/null +++ b/client/src/components/Customers/index.js @@ -0,0 +1,2 @@ +export CustomerList from './CustomerList'; +export CustomerEdit from './CustomerEdit'; diff --git a/client/src/components/Routes.js b/client/src/components/Routes.js index 9e8cfbe..0287cab 100644 --- a/client/src/components/Routes.js +++ b/client/src/components/Routes.js @@ -8,6 +8,7 @@ import Dashboard from './Dashboard'; import { PostList, PostEdit } from './Posts'; import { CategoryList, CategoryEdit } from './Categories'; import { UserList, UserEdit } from './Users'; +import { CustomerList, CustomerEdit } from './Customers'; import { Login } from './Auth'; const UserIsAuthenticated = UserAuthWrapper({ authSelector: getUser }); @@ -34,6 +35,8 @@ export class Routes extends PureComponent { + + From 1e566482880b820a2a0026afed428c034c6808b9 Mon Sep 17 00:00:00 2001 From: tnoworyta Date: Tue, 16 May 2017 16:33:58 +0200 Subject: [PATCH 2/2] Customer filters --- app/models/customer.rb | 1 + app/resources/customer_resource.rb | 2 ++ .../src/components/Customers/CustomerList.js | 17 ++++++++- .../Customers/CustomerListFilter.js | 35 +++++++++++++++++++ client/src/components/Routes.js | 1 + 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 client/src/components/Customers/CustomerListFilter.js diff --git a/app/models/customer.rb b/app/models/customer.rb index 0b52773..58ffa33 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -1,2 +1,3 @@ class Customer < ApplicationRecord + scope :company_name_contains, -> (value) { where('company_name ILIKE ?', "%#{value.join}%") } end diff --git a/app/resources/customer_resource.rb b/app/resources/customer_resource.rb index 5783a29..568651a 100644 --- a/app/resources/customer_resource.rb +++ b/app/resources/customer_resource.rb @@ -1,4 +1,5 @@ class CustomerResource < JSONAPI::Resource + extend ModelFilter attributes :company_name, :contact_name, :contact_title, @@ -12,4 +13,5 @@ class CustomerResource < JSONAPI::Resource :created_at paginator :paged + model_filters :company_name_contains end diff --git a/client/src/components/Customers/CustomerList.js b/client/src/components/Customers/CustomerList.js index ec670ef..d820fa7 100644 --- a/client/src/components/Customers/CustomerList.js +++ b/client/src/components/Customers/CustomerList.js @@ -1,9 +1,11 @@ import React, { Component, PropTypes } from 'react'; import { Link } from 'react-router'; import { find, keyBy } from 'lodash'; +import { Button } from 'reactstrap'; import { ListTable } from '../UI'; import { withResourceList } from '../../hocs'; +import CustomerListFilter from './CustomerListFilter'; const formatDate = date => (new Date(date)).toLocaleString(); @@ -14,6 +16,7 @@ export class CustomerList extends Component { } render() { + const { onFilter } = this.props; const columns = [ { attribute: 'companyName', @@ -36,9 +39,21 @@ export class CustomerList extends Component { ]; return ( - +
+ + + + + + +
); } } +export const mapStateToProps = state => ({ + filter: get(state, 'form.customerListFilter.values') || {} +}); + export default withResourceList('customers')(CustomerList); diff --git a/client/src/components/Customers/CustomerListFilter.js b/client/src/components/Customers/CustomerListFilter.js new file mode 100644 index 0000000..d21ce42 --- /dev/null +++ b/client/src/components/Customers/CustomerListFilter.js @@ -0,0 +1,35 @@ +import React, { Component, PropTypes } from 'react'; +import { isEmpty } from 'lodash'; +import { Field, reduxForm } from 'redux-form'; +import { Form, Row, Col } from 'reactstrap'; + +import { InputField, SelectField } from '../../forms'; + +class CustomerListFilter extends Component { + render() { + const { handleSubmit, onSubmit } = this.props; + + const submitOnChange = () => setTimeout(() => handleSubmit(onSubmit)(), 0); + + + return ( +
+ + + + + +
+ ); + } +} + +export default reduxForm({ + form: 'customerListFilter', + destroyOnUnmount: false, +})(CustomerListFilter); diff --git a/client/src/components/Routes.js b/client/src/components/Routes.js index 0287cab..4114d43 100644 --- a/client/src/components/Routes.js +++ b/client/src/components/Routes.js @@ -36,6 +36,7 @@ export class Routes extends PureComponent { +