Skip to content

Instantly share code, notes, and snippets.

@nwwells
Created October 27, 2015 19:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nwwells/570b0a9bb26fe5f8f0c4 to your computer and use it in GitHub Desktop.
Save nwwells/570b0a9bb26fe5f8f0c4 to your computer and use it in GitHub Desktop.
Nathan's work at Symbiont.io
From 8522592042377b46150041e1f723071a9b9016a8 Mon Sep 17 00:00:00 2001
From: Nathan Wells <nwwells@gmail.com>
Date: Tue, 27 Oct 2015 12:06:34 -0400
Subject: [PATCH] renaming Content to content
---
components/record/index.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/components/record/index.js b/components/record/index.js
index 6a06933..8748703 100644
--- a/components/record/index.js
+++ b/components/record/index.js
@@ -60,11 +60,11 @@ export default React.createClass({
const { data, loading, modifying, editButtons, viewButtons, canEdit, id } = this.props;
const fields = this.props.fields.map(addDefaultFieldProperties);
- let Content;
+ let content;
const isReadonly = !canEdit && modifying[id];
if (isReadonly) {
- Content = (
+ content = (
<div>
<Table striped bordered hover>
{
@@ -89,7 +89,7 @@ export default React.createClass({
//(<span>{modifying[field.get]}</span>) :
);
} else {
- Content = (
+ content = (
<form className='form-horizontal'>
{
fields.map(field => {
@@ -115,7 +115,7 @@ export default React.createClass({
}
return (
- <div>{Content}</div>
+ <div>{content}</div>
);
},
--
2.2.1
From 4d6763bc0f79b98890c58f0d272aaf7f25d8d28a Mon Sep 17 00:00:00 2001
From: Nathan Wells <nwwells@gmail.com>
Date: Tue, 27 Oct 2015 15:14:16 -0400
Subject: [PATCH] Implement Loan Details with good abstrations
---
actions/loans.js | 53 +++++++++++++++++++++++++++--------
components/record/index.js | 5 +---
components/simple-table.js | 8 ++++--
components/tableditable/index.js | 47 ++++++++++++++++++++++++-------
containers/loans.js | 60 +++++++++++++++++++++++++++++-----------
utils.js | 8 +++++-
6 files changed, 135 insertions(+), 46 deletions(-)
diff --git a/actions/loans.js b/actions/loans.js
index e5daded..0821a8f 100644
--- a/actions/loans.js
+++ b/actions/loans.js
@@ -2,7 +2,7 @@ import { get } from 'lodash';
import { getApi } from '../api';
-import { hydrate } from '../utils';
+import { hydrate, resolveProps } from '../utils';
import {
CANCEL_LOAN_ACTION,
@@ -10,23 +10,52 @@ import {
FETCH_LOANS_ASYNC_STATES,
EDIT_LOAN_ASYNC_STATES,
ADD_LOAN_ASYNC_STATES,
- SAVE_LOAN_ASYNC_STATES
+ SAVE_LOAN_ASYNC_STATES,
} from '../constants/actions';
-const organizationTypes = ['agent', 'borrower', 'issuer', 'lender'];
+const loanOrganizationTypes = ['agent', 'borrower', 'issuer', 'lender'];
+const assignmentOrganizationTypes = ['assigner', 'borrower', 'lender'];
+const lenderOrganizationTypes = ['lender_id'];
const getOrganizations = async () => await getApi().get(`/organizations`).then(res => res.data);
+const getAssignments = async baseUrl => await getApi().get(`${baseUrl}/assignments`).then(res => res.data);
+const getLenders = async baseUrl => await getApi().get(`${baseUrl}/lenders`).then(res => res.data);
+
+const getFullyHydratedLoan = async loanId => {
+ const url = `/loans/${loanId}`;
+ const {loan, organizations, assignments, lenders} = await resolveProps({
+ loan: getApi().get(url).then(res => res.data),
+ organizations: getOrganizations(),
+ assignments: getAssignments(url),
+ lenders: getLenders(url),
+ });
+ const hydratedLoan = hydrate({
+ itemOrCollection: loan,
+ propertiesToHydrate: loanOrganizationTypes,
+ mapping: organizations,
+ });
+ const hydratedAssignments = hydrate({
+ itemOrCollection: assignments,
+ propertiesToHydrate: assignmentOrganizationTypes,
+ mapping: organizations,
+ });
+ const hydratedLenders = hydrate({
+ itemOrCollection: lenders,
+ propertiesToHydrate: lenderOrganizationTypes,
+ mapping: organizations,
+ });
+ return { ...hydratedLoan, assignments: hydratedAssignments, lenders: hydratedLenders };
+};
-const getFullyHydratedLoanOrLoans = async loanId => {
- const urlSuffix = loanId ? `/${loanId}` : '';
- const url = `/loans${urlSuffix}`;
- const [loanOrLoans, organizations] = await Promise.all([
+const getFullyHydratedLoans = async () => {
+ const url = `/loans`;
+ const [loans, organizations] = await Promise.all([
getApi().get(url).then(res => res.data),
getOrganizations(),
]);
return hydrate({
- itemOrCollection: loanOrLoans,
- propertiesToHydrate: organizationTypes,
+ itemOrCollection: loans,
+ propertiesToHydrate: loanOrganizationTypes,
mapping: organizations,
});
};
@@ -41,14 +70,14 @@ const groupOrganizationsByType = async () => {
export const getLoans = () => ({
types: FETCH_LOANS_ASYNC_STATES,
payload: {
- records: getFullyHydratedLoanOrLoans(),
+ records: getFullyHydratedLoans(),
},
});
export const editLoan = ({ loan_id }) => ({
types: EDIT_LOAN_ASYNC_STATES,
payload: {
- record: getFullyHydratedLoanOrLoans(loan_id),
+ record: getFullyHydratedLoan(loan_id),
id: loan_id,
},
});
@@ -82,7 +111,7 @@ export const saveLoan = record => (dispach, getState) => {
dispach({
types: SAVE_LOAN_ASYNC_STATES,
payload: {
- records: getApi().post('/loans', payload).then(() => getFullyHydratedLoanOrLoans()),
+ records: getApi().post('/loans', payload).then(getFullyHydratedLoans),
},
});
};
diff --git a/components/record/index.js b/components/record/index.js
index 8748703..66dbeac 100644
--- a/components/record/index.js
+++ b/components/record/index.js
@@ -1,5 +1,6 @@
import React from 'react';
import { get, map, camelCase } from 'lodash';
+import { labelize } from '../../utils';
import { Table, ButtonToolbar, Button, Input as BootstrapInput } from 'react-bootstrap';
@@ -13,10 +14,6 @@ import {
const { PropTypes } = React;
-const labelize = propName => {
- return propName[0].toUpperCase() + camelCase(propName.slice(1)).replace(/[A-Z]/g, ' $&');
-};
-
const addDefaultFieldProperties = field => {
const obj = typeof field === 'string' ? {} : {...field};
const property = obj.property || field;
diff --git a/components/simple-table.js b/components/simple-table.js
index e41da3a..e5a6cd8 100644
--- a/components/simple-table.js
+++ b/components/simple-table.js
@@ -1,12 +1,14 @@
import React from 'react';
+import { get } from 'lodash';
import { Table } from 'react-bootstrap';
+import { labelize } from '../utils';
export default React.createClass({
render() {
const { records } = this.props;
const fields = this.props.fields.map(field => typeof field === 'object' ? field : {
- label: field[0].toUpperCase() + field.slice(1).replace(/[-_]./g, m => m[1].toUpperCase()),
- getter(record) { return record[field]; },
+ label: labelize(field),
+ render(record) { return get(record, field); },
});
let content;
if (!records) {
@@ -29,7 +31,7 @@ export default React.createClass({
return (
<tr>
{
- fields.map(field => <td>{field.getter(portfolioItem)}</td>)
+ fields.map(field => <td>{field.render(portfolioItem)}</td>)
}
</tr>
);
diff --git a/components/tableditable/index.js b/components/tableditable/index.js
index f6858ca..1188de2 100644
--- a/components/tableditable/index.js
+++ b/components/tableditable/index.js
@@ -2,6 +2,7 @@ import React from 'react';
import { get, map, camelCase } from 'lodash';
import { Table, Button, Glyphicon, Modal } from 'react-bootstrap';
import { types } from 'zan';
+import { labelize } from '../../utils';
const { string, arrayOf, oneOfType, shape, objectOf, object, func, oneOf, number } = types;
@@ -27,8 +28,12 @@ export {
Null,
};
-const labelize = propName => {
- return propName[0].toUpperCase() + camelCase(propName.slice(1)).replace(/[A-Z]/g, ' $&');
+const convertButtonMapToArray = (value, key) => {
+ return {
+ iconName: null,
+ label: labelize(key),
+ callback: value,
+ };
};
const addDefaultFieldProperties = field => {
@@ -112,11 +117,19 @@ export default React.createClass({
this.props.actions.save(record);
},
- render() {
-
+ createDispatcher(record, callback) {
+ return e => {
+ e.preventDefault();
+ callback(record);
+ };
+ },
+ render() {
const { data, loading, modifying, actionsLabel } = this.props;
const fields = this.props.fields.map(addDefaultFieldProperties);
+ const tableButtons = Array.isArray(this.props.tableButtons) ?
+ this.props.tableButtons :
+ map(this.props.tableButtons, convertButtonMapToArray);
let content;
@@ -152,12 +165,26 @@ export default React.createClass({
{
record.pending && <div style={cellOverlay}>{record.pending}</div>
}
- <Button onClick={e => this.editAction(e, record)}>
- <Glyphicon glyph='pencil' /> Edit
- </Button>
- <Button onClick={e => this.deleteAction(e, record)}>
- <Glyphicon glyph='trash' /> Delete
- </Button>
+ {
+ tableButtons.map((tableButton, index) => {
+ let iconContent;
+ if (tableButton.iconName) {
+ iconContent = (
+ <span>
+ <Glyphicon glyph={tableButton.iconName} />{' '}
+ </span>
+ );
+ } else {
+ iconContent = null;
+ }
+ return (
+ <Button key={index} onClick={this.createDispatcher(record, tableButton.callback)}>
+ {iconContent}
+ {tableButton.label}
+ </Button>
+ );
+ })
+ }
</th>
{
fields.map((field, index) => {
diff --git a/containers/loans.js b/containers/loans.js
index e1f0575..339cb82 100644
--- a/containers/loans.js
+++ b/containers/loans.js
@@ -9,6 +9,7 @@ import { numberWithCommas } from '../utils';
import Tableditable, { Input, Checkbox, Select, Datepicker, Null } from '../components/tableditable';
import Record from '../components/record';
+import SimpleTable from '../components/simple-table';
import { addLoan, editLoan, deleteLoan, cancelAction, saveLoan } from '../actions/loans';
@@ -18,7 +19,6 @@ const Component = React.createClass({
const { dispatch, records, creatingOrEditing } = this.props;
const agents = get(this, 'props.agents', []).map(org => ({ label: org.display_name, value: org.id }));
- const lenders = get(this, 'props.lenders', []).map(org => ({ label: org.display_name, value: org.id }));
const borrowers = get(this, 'props.borrowers', []).map(org => ({ label: org.display_name, value: org.id }));
const issuers = get(this, 'props.issuers', []).map(org => ({ label: org.display_name, value: org.id }));
@@ -30,11 +30,9 @@ const Component = React.createClass({
const fields = [
{ property: 'loan_id', label: 'ID', Component: Null },
- //{ property: 'asset_type', label: 'Type', Component: Select, options: [{label: 'Loan', value: 'loan'}] },
{ property: 'agent.display_name', label: 'Agent', Component: Null },
{ get: 'borrower.display_name', set: 'borrower', label: 'Borrower', Component: Select, options: borrowers },
{ property: 'issuer.display_name', label: 'Issuer', Component: Null },
- //{ property: 'lender.display_name', label: 'Lender', Component: Null },
{ get: 'quantity', set: 'loan_quantity', label: 'Quantity', render: record => numberWithCommas(record.quantity) },
{ set: 'loan_currency', get: 'loan_currency_code', label: 'Currency', Component: Select, options: currencies },
{ property: 'maturity', label: 'Maturity', Component: Datepicker, render: renderDate('maturity') },
@@ -42,7 +40,9 @@ const Component = React.createClass({
{ property: 'payment_frequency', Component: Select, options: frequencies, render: renderFrequency },
];
-
+ const tableButtons = [
+ { label: 'View Details', iconName: 'th-list' , callback: record => dispatch(editLoan(record)) },
+ ];
const Viewer = creatingOrEditing.record ? Record : Tableditable;
@@ -86,19 +86,47 @@ const Component = React.createClass({
},
};
+ const assignmentFields = [
+ 'assignment_id',
+ 'assigner.display_name',
+ 'borrower.display_name',
+ 'borrower_response',
+ 'lender.display_name',
+ 'lender_response',
+ 'lender_quantity',
+ 'status',
+ ];
+
+ const lenderFields = [
+ 'lender_id.display_name',
+ 'quantity',
+ ];
- return <Viewer
- canEdit={false}
- id="loan_id"
- fields={fields}
- actions={actions}
- viewButtons={viewButtons}
- editButtons={editButtons}
- data={records}
- loading="Loading..."
- modifying={ get(creatingOrEditing, 'record') }
- modalTitle={ get(creatingOrEditing, 'record.id') ? 'Editing ' + creatingOrEditing.loan.display_name : 'Adding new user'}
- />;
+ const assignments = get(creatingOrEditing, 'record.assignments');
+ const assignmentTable = assignments ? <SimpleTable records={assignments} fields={assignmentFields} /> : null;
+
+ const lenders = get(creatingOrEditing, 'record.lenders');
+ const lenderTable = lenders ? <SimpleTable records={lenders} fields={lenderFields} /> : null;
+
+ return <div>
+ <Viewer
+ canEdit={false}
+ id="loan_id"
+ fields={fields}
+ actions={actions}
+ viewButtons={viewButtons}
+ editButtons={editButtons}
+ tableButtons={tableButtons}
+ data={records}
+ loading="Loading..."
+ modifying={ get(creatingOrEditing, 'record') }
+ modalTitle={ get(creatingOrEditing, 'record.id') ? 'Editing ' + creatingOrEditing.loan.display_name : 'Adding new user'}
+ />
+ <h3>Assignments</h3>
+ { assignmentTable }
+ <h3>Lenders</h3>
+ { lenderTable }
+ </div>;
},
});
diff --git a/utils.js b/utils.js
index d83a29b..e66ba43 100644
--- a/utils.js
+++ b/utils.js
@@ -1,4 +1,4 @@
-import { find, map, indexBy } from 'lodash';
+import { find, map, indexBy, camelCase } from 'lodash';
export const findIndex = (collection, predicate) => {
const found = find(collection, predicate);
@@ -30,3 +30,9 @@ export const hydrate = ({ itemOrCollection, propertiesToHydrate, mapping, id = '
});
return isCollection ? hydrated : hydrated[0];
};
+
+
+export const labelize = path => {
+ const propName = path.split('.')[0];
+ return propName[0].toUpperCase() + camelCase(propName.slice(1)).replace(/[A-Z]/g, ' $&');
+};
--
2.2.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment