Skip to content

Commit 8f46169

Browse files
authored
Merge pull request #18 from tnoworyta/tgn-north-customers
North customers crud
2 parents 5d9c681 + 1e56648 commit 8f46169

File tree

10 files changed

+266
-9
lines changed

10 files changed

+266
-9
lines changed

app/models/customer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
class Customer < ApplicationRecord
2+
scope :company_name_contains, -> (value) { where('company_name ILIKE ?', "%#{value.join}%") }
23
end

app/resources/customer_resource.rb

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
class CustomerResource < JSONAPI::Resource
2+
extend ModelFilter
23
attributes :company_name,
3-
:contact_name
4-
:contact_title
5-
:address
6-
:city
7-
:region
8-
:postal_code
9-
:country
10-
:phone
11-
:fax
4+
:contact_name,
5+
:contact_title,
6+
:address,
7+
:city,
8+
:region,
9+
:postal_code,
10+
:country,
11+
:phone,
12+
:fax,
13+
:created_at
14+
15+
paginator :paged
16+
model_filters :company_name_contains
1217
end

client/src/api/normalize.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ const serializers = {
5858
}),
5959
},
6060

61+
customers: {
62+
serializer: new Serializer('customers', {
63+
keyForAttribute: 'camelCase',
64+
attributes: [
65+
'companyName'
66+
],
67+
}),
68+
deserializer: new Deserializer({
69+
keyForAttribute: 'camelCase'
70+
}),
71+
},
72+
6173
roles: {
6274
serializer: new Serializer('roles', {
6375
keyForAttribute: 'camelCase',

client/src/components/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export class App extends Component {
3636
<NavItem>
3737
<NavLink href="/#/categories">Categories</NavLink>
3838
</NavItem>
39+
<NavItem>
40+
<NavLink href="/#/customers">Customers</NavLink>
41+
</NavItem>
3942
<NavItem>
4043
{
4144
userIsAdmin && <NavLink href="/#/users">Users</NavLink>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { push } from 'react-router-redux';
3+
import { connect } from 'react-redux';
4+
5+
import { ErrorAlert, Loading, EditHeader } from '../UI';
6+
import { withResource } from '../../hocs';
7+
import CustomerForm from './CustomerForm';
8+
import { getMany, fetchList } from '../../store/api';
9+
10+
export class CustomerEdit extends Component {
11+
componentWillMount() {
12+
const { params, fetchResource } = this.props;
13+
if (params.id) {
14+
fetchResource({ id: params.id });
15+
}
16+
}
17+
18+
render() {
19+
const { isNew, error, loading, resource, onSubmit } = this.props;
20+
21+
if (error) {
22+
return (<ErrorAlert {...error} />);
23+
}
24+
25+
if (loading) {
26+
return (<Loading />);
27+
}
28+
29+
return (
30+
<div>
31+
<EditHeader {...this.props}>{ isNew ? 'New Customer' : resource.company_name }</EditHeader>
32+
<CustomerForm initialValues={resource} onSubmit={onSubmit}></CustomerForm>
33+
</div>
34+
);
35+
}
36+
}
37+
38+
export const mapStateToProps = (state, props) => ({
39+
roles: getMany(state)
40+
});
41+
42+
export const mapDispatchToProps = dispatch => ({
43+
redirectToIndex: () => dispatch(push('/customers'))
44+
});
45+
46+
export default connect(mapStateToProps, mapDispatchToProps)(
47+
withResource('customers')(CustomerEdit),
48+
);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { isEmpty } from 'lodash';
3+
import { Field, reduxForm } from 'redux-form';
4+
import { Button, Form } from 'reactstrap';
5+
6+
import { InputField, MultiselectField, required } from '../../forms';
7+
8+
class CustomerForm extends Component {
9+
render() {
10+
const { handleSubmit, pristine, reset, submitting } = this.props;
11+
12+
return (
13+
<Form onSubmit={handleSubmit}>
14+
<div>
15+
<Field
16+
name="companyName"
17+
label="Company name"
18+
component={InputField}
19+
/>
20+
<Field
21+
name="contactName"
22+
label="Contact name"
23+
component={InputField}
24+
/>
25+
<Field
26+
name="contactTitle"
27+
label="Contact title"
28+
component={InputField}
29+
/>
30+
<Field
31+
name="address"
32+
label="Address"
33+
component={InputField}
34+
/>
35+
<Field
36+
name="city"
37+
label="City"
38+
component={InputField}
39+
/>
40+
<Field
41+
name="Region"
42+
label="Region"
43+
component={InputField}
44+
/>
45+
46+
<Field
47+
name="postalCode"
48+
label="Postal code"
49+
component={InputField}
50+
/>
51+
52+
<Field
53+
name="country"
54+
label="Country"
55+
component={InputField}
56+
/>
57+
58+
<Field
59+
name="phone"
60+
label="Phone"
61+
component={InputField}
62+
/>
63+
64+
<Field
65+
name="fax"
66+
label="Fax"
67+
component={InputField}
68+
/>
69+
</div>
70+
<div>
71+
<Button disabled={pristine || submitting} color="primary">Submit</Button>
72+
<Button disabled={pristine || submitting} onClick={reset}>Undo Changes</Button>
73+
</div>
74+
</Form>
75+
);
76+
}
77+
}
78+
79+
const validate = (values) => {
80+
const errors = required(values, 'email');
81+
return errors;
82+
};
83+
84+
export default reduxForm({
85+
enableReinitialize: true,
86+
form: 'customer',
87+
validate,
88+
})(CustomerForm);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { Link } from 'react-router';
3+
import { find, keyBy } from 'lodash';
4+
import { Button } from 'reactstrap';
5+
6+
import { ListTable } from '../UI';
7+
import { withResourceList } from '../../hocs';
8+
import CustomerListFilter from './CustomerListFilter';
9+
10+
const formatDate = date => (new Date(date)).toLocaleString();
11+
12+
export class CustomerList extends Component {
13+
componentWillMount() {
14+
const { resourceList } = this.props;
15+
this.props.fetchResourceList({ sort: '-companyName', ...resourceList.params });
16+
}
17+
18+
render() {
19+
const { onFilter } = this.props;
20+
const columns = [
21+
{
22+
attribute: 'companyName',
23+
header: 'Company Name',
24+
rowRender: customer => <Link to={`/customers/${customer.id}`}>{customer.companyName}</Link>,
25+
sortable: true,
26+
},
27+
{
28+
attribute: 'contactName',
29+
header: 'Contact Name',
30+
rowRender: customer => <Link to={`/customers/${customer.id}`}>{customer.contactName}</Link>,
31+
sortable: true,
32+
},
33+
{
34+
attribute: 'createdAt',
35+
header: 'Created At',
36+
rowRender: customer => formatDate(customer.confirmedAt),
37+
sortable: true,
38+
}
39+
];
40+
41+
return (
42+
<div>
43+
<Button tag={Link} to={'/customers/new'}>New Customer</Button>
44+
45+
<CustomerListFilter
46+
onSubmit={onFilter}>
47+
</CustomerListFilter>
48+
49+
<ListTable {...this.props} columns={columns} />
50+
</div>
51+
);
52+
}
53+
}
54+
55+
export const mapStateToProps = state => ({
56+
filter: get(state, 'form.customerListFilter.values') || {}
57+
});
58+
59+
export default withResourceList('customers')(CustomerList);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import { isEmpty } from 'lodash';
3+
import { Field, reduxForm } from 'redux-form';
4+
import { Form, Row, Col } from 'reactstrap';
5+
6+
import { InputField, SelectField } from '../../forms';
7+
8+
class CustomerListFilter extends Component {
9+
render() {
10+
const { handleSubmit, onSubmit } = this.props;
11+
12+
const submitOnChange = () => setTimeout(() => handleSubmit(onSubmit)(), 0);
13+
14+
15+
return (
16+
<Form onSubmit={handleSubmit}>
17+
<Row>
18+
<Col md={8}>
19+
<Field
20+
name="company_name_contains"
21+
label="Company Name Contains"
22+
component={InputField}
23+
onChange={submitOnChange}
24+
/>
25+
</Col>
26+
</Row>
27+
</Form>
28+
);
29+
}
30+
}
31+
32+
export default reduxForm({
33+
form: 'customerListFilter',
34+
destroyOnUnmount: false,
35+
})(CustomerListFilter);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export CustomerList from './CustomerList';
2+
export CustomerEdit from './CustomerEdit';

client/src/components/Routes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Dashboard from './Dashboard';
1010
import { PostList, PostEdit } from './Posts';
1111
import { CategoryList } from './Categories';
1212
import { UserList, UserEdit } from './Users';
13+
import { CustomerList, CustomerEdit } from './Customers';
1314
import { Login } from './Auth';
1415

1516
const UserIsAuthenticated = UserAuthWrapper({ authSelector: getUser });
@@ -36,6 +37,9 @@ export class Routes extends PureComponent {
3637
<Route path="/categories" component={CategoryList}/>
3738
<Route path="/users" component={UserIsAdmin(UserList)}/>
3839
<Route path="/users/:id" component={UserIsAdmin(UserEdit)}/>
40+
<Route path="/customers" component={UserIsAdmin(CustomerList)}/>
41+
<Route path="/customers/new" component={UserIsAdmin(CustomerEdit)}/>
42+
<Route path="/customers/:id" component={UserIsAdmin(CustomerEdit)}/>
3943
</Route>
4044
<Route path="/login" component={Login}/>
4145
</Router>

0 commit comments

Comments
 (0)