diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..c67846a60bc57af0480de220d709e565245cd9e7
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+ "editor.tabSize": 2
+}
\ No newline at end of file
diff --git a/src/components/Utilities/index.js b/src/components/Utilities/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e6264e180e44a82471906253bf6cb6e9ba6f4b3c
--- /dev/null
+++ b/src/components/Utilities/index.js
@@ -0,0 +1,165 @@
+import React, { Component, PropTypes } from 'react';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import buildingDetailPropTypes from '../../containers/BuildingOverview/buildingDetailPropType';
+
+import request from '../../utils/request';
+import { getHeaders, utilityURL } from '../../utils/rest_services';
+
+import { loadBuildingDetail } from '../../containers/BuildingOverview/actions';
+import UtilityForm from '../UtilityForm';
+
+class Utilities extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ accountLines: [],
+ error: false,
+ };
+ }
+
+ componentDidMount() {
+ const { address, building_id } = this.props.buildingDetail.overview;
+
+ // TODO remove getting adress, should be done in backend
+ /* eslint-disable camelcase */
+ if (!address || this.props.buildingId !== building_id) {
+ this.props.loadBuildingDetail(this.props.buildingId);
+ } else if (address && this.props.buildingId === building_id) {
+ this.getUtilityAccounts(address);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const { address, building_id } = this.props.buildingDetail.overview;
+ const prevAddress = prevProps.buildingDetail.overview.address;
+ if (parseInt(this.props.buildingId, 10) === building_id && prevAddress !== address) {
+ this.getUtilityAccounts(address);
+ }
+ }
+
+ getUtilityAccounts = (address) => {
+ request(`${utilityURL}${this.props.buildingId}?address=${address}`, {
+ method: 'GET',
+ headers: getHeaders(),
+ }).then((res) => {
+ if (!res.err && res.data.utilities) {
+ res.data.utilities.forEach((item) => {
+ this.addAccountLine({
+ building_address: address,
+ utility: item.type,
+ account_number: item.account_number,
+ username: item.login,
+ password: item.pass,
+ access_code: item.access_code,
+ },
+ item.url_download,
+ true);
+ });
+ this.addAccountLine();
+ } else if (res.err) {
+ this.setState({ error: res.err.message });
+ }
+ });
+ }
+
+ getUtilityBill = (form) => {
+ request(utilityURL, {
+ method: 'PUT',
+ headers: getHeaders(),
+ body: JSON.stringify({
+ ...form,
+ building_id: parseInt(this.props.buildingId, 10),
+ building_address: this.props.buildingDetail.overview.address,
+ }),
+ }).then((res) => {
+ if (res.err) {
+ this.setState({ error: 'Failed to retrieve utility bill' });
+ }
+ });
+ }
+
+ createAccount = (form) => {
+ request(utilityURL, {
+ method: 'POST',
+ headers: getHeaders(),
+ body: JSON.stringify({
+ ...form,
+ building_id: parseInt(this.props.buildingId, 10),
+ building_address: this.props.buildingDetail.overview.address,
+ }),
+ }).then((res) => {
+ if (res.err) {
+ this.setState({ error: 'Failed to create account' });
+ }
+ });
+ this.addAccountLine();
+ }
+
+ deleteAccountLine = (lineItem) => {
+ const accounts = this.state.accountLines;
+ const accountToRemove = lineItem.props.form.account_number;
+
+ request(`${utilityURL}${this.props.buildingId}?account_number=${accountToRemove}`, {
+ method: 'DELETE',
+ headers: getHeaders(),
+ }).then((res) => {
+ if (!res.err) {
+ /* eslint-disable arrow-body-style */
+ // TODO remove eslint disable
+ const newAccounts = accounts.filter((item) => {
+ return item.props.form.account_number !== accountToRemove;
+ });
+ /* eslint-enable */
+ this.setState({ accountLines: newAccounts });
+ } else {
+ this.setState({ error: 'Failed to delete account' });
+ }
+ });
+ }
+
+ addAccountLine = (form, downloadURL, disabled = false) => {
+ const curr = this.state.accountLines;
+ curr.push();
+ this.setState({ accountLines: curr });
+ }
+
+ render() {
+ return (
+
+
+ {this.state.error}
+
+ {this.state.accountLines}
+
+ );
+ }
+}
+
+Utilities.propTypes = {
+ buildingId: PropTypes.string,
+ buildingDetail: buildingDetailPropTypes,
+ loadBuildingDetail: PropTypes.func,
+};
+
+function mapDispatchToProps(dispatch) {
+ return bindActionCreators({ loadBuildingDetail }, dispatch);
+}
+
+function mapStateToProps({ buildingDetail }) {
+ return { buildingDetail };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Utilities);
diff --git a/src/components/Utilities/styles.css b/src/components/Utilities/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/components/UtilityForm/index.js b/src/components/UtilityForm/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee6c64b07148bad09f9e3e8cb354e74d8cc7733e
--- /dev/null
+++ b/src/components/UtilityForm/index.js
@@ -0,0 +1,153 @@
+import React, { Component, PropTypes } from 'react';
+import './styles.css';
+
+class UtilityForm extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ disabled: this.props.disabled,
+ form: {
+ utility: this.props.form.utility,
+ account_number: this.props.form.account_number,
+ username: this.props.form.username,
+ password: this.props.form.password,
+ access_code: this.props.form.access_code,
+ },
+ downloadURL: this.props.downloadURL,
+ };
+ }
+
+ getUtilityBill = (event) => {
+ event.preventDefault();
+ this.props.getUtilityBill(this.state.form);
+ }
+
+ handleInputChange = (event) => {
+ this.setState({
+ form: {
+ ...this.state.form,
+ [event.target.name]: event.target.value,
+ },
+ });
+ }
+
+ handleCreateAccount = (event) => {
+ event.preventDefault();
+ this.setState({ disabled: true });
+ this.props.createAccount(this.state.form);
+ }
+
+ handleDeleteAccount = (event) => {
+ event.preventDefault();
+ this.props.deleteAccount(this);
+ }
+
+ render() {
+ const natGrid = this.state.form.utility === 'national_grid_gas' ? '' : 'hidden';
+ return (
+
+ );
+ }
+}
+
+UtilityForm.propTypes = {
+ disabled: PropTypes.bool,
+ form: PropTypes.shape({
+ building_address: PropTypes.string,
+ utility: PropTypes.string,
+ account_number: PropTypes.string,
+ access_code: PropTypes.string,
+ username: PropTypes.string,
+ password: PropTypes.string,
+ }),
+ downloadURL: PropTypes.string,
+ createAccount: PropTypes.func,
+ deleteAccount: PropTypes.func,
+ getUtilityBill: PropTypes.func,
+};
+
+UtilityForm.defaultProps = {
+ disable: false,
+ form: {
+ building_address: '',
+ utility: 'con_edison_electric',
+ account_number: '',
+ access_code: '',
+ username: '',
+ password: '',
+ },
+};
+
+export default UtilityForm;
diff --git a/src/components/UtilityForm/styles.css b/src/components/UtilityForm/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/containers/BuildingOverview/buildingDetailPropType.js b/src/containers/BuildingOverview/buildingDetailPropType.js
new file mode 100644
index 0000000000000000000000000000000000000000..6cdf609e2a65e0332b25b5d8886f9ee109565b77
--- /dev/null
+++ b/src/containers/BuildingOverview/buildingDetailPropType.js
@@ -0,0 +1,20 @@
+import { PropTypes } from 'react';
+
+export default PropTypes.shape({
+ overview: PropTypes.shape({
+ address: PropTypes.string,
+ bbl: PropTypes.number,
+ building_id: PropTypes.number,
+ lot_id: PropTypes.number,
+ borough: PropTypes.string,
+ zipcode: PropTypes.number,
+ loading: PropTypes.boolean,
+ error: PropTypes.boolean,
+ }),
+ hit: PropTypes.shape({
+ id: PropTypes.string,
+ status: PropTypes.string,
+ loading: PropTypes.boolean,
+ error: PropTypes.boolean,
+ }),
+});
diff --git a/src/containers/BuildingOverview/index.js b/src/containers/BuildingOverview/index.js
index c813797f51b75ef17911e9852cbb624cdcd59ce9..24e62a013bad97834df47bcb6eea584a9196f5fd 100644
--- a/src/containers/BuildingOverview/index.js
+++ b/src/containers/BuildingOverview/index.js
@@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import buildingDetailPropTypes from './buildingDetailPropType';
import {
loadBuildingDetail,
@@ -58,24 +59,7 @@ class BuildingOverview extends Component {
}
BuildingOverview.propTypes = {
- buildingDetail: PropTypes.shape({
- overview: PropTypes.shape({
- address: PropTypes.string,
- bbl: PropTypes.number,
- building_id: PropTypes.number,
- lot_id: PropTypes.number,
- borough: PropTypes.string,
- zipcode: PropTypes.number,
- loading: PropTypes.boolean,
- error: PropTypes.boolean,
- }),
- hit: PropTypes.shape({
- id: PropTypes.string,
- status: PropTypes.string,
- loading: PropTypes.boolean,
- error: PropTypes.boolean,
- }),
- }),
+ buildingDetail: buildingDetailPropTypes,
params: PropTypes.objectOf(PropTypes.string),
loadBuildingDetail: PropTypes.func,
loadHit: PropTypes.func,
diff --git a/src/routes.js b/src/routes.js
index ef2a881642aef3532cc2eafddc14f6d29ceb20d6..c64dc2a6222300e7819750bba89ad3e9e19d5082 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -9,6 +9,7 @@ import HomePage from './screens/HomePage';
import BuildingDetail from './screens/BuildingDetail';
import BuildingOverview from './containers/BuildingOverview';
+import Utilities from './components/Utilities';
import Dummy from './components/dummyComponent';
export default (
@@ -23,7 +24,7 @@ export default (
-
+
diff --git a/src/screens/BuildingDetail/index.js b/src/screens/BuildingDetail/index.js
index 620e993e60e5fec0df6333648b3b34c8be76f47d..85cfdc0daef6938caade0bc24aa1dc0eb9a67516 100644
--- a/src/screens/BuildingDetail/index.js
+++ b/src/screens/BuildingDetail/index.js
@@ -12,7 +12,9 @@ export default function BuildingDetail(props) {
- {props.children}
+ {props.children && React.cloneElement(props.children, {
+ buildingId: props.params.buildingID,
+ })}
);
diff --git a/src/utils/rest_services.js b/src/utils/rest_services.js
index 04fb1634d10ae80e1b7d7a3cd7f6aff2212b21bb..4f2a2c167ab086b5a428ecc9896e54d78bcadb97 100644
--- a/src/utils/rest_services.js
+++ b/src/utils/rest_services.js
@@ -5,5 +5,9 @@ export function getHeaders() {
});
}
-export const buildingsURL = `${process.env.REACT_APP_BUILDING_SERVICE}/building/`;
-export const turkURL = `${process.env.REACT_APP_BUILDING_SERVICE}/turkhit/`;
+const buildingService = process.env.REACT_APP_BUILDING_SERVICE;
+const utilityService = process.env.REACT_APP_UTILITY_SERVICE;
+
+export const buildingsURL = `${buildingService}/building/`;
+export const turkURL = `${buildingService}/turkhit/`;
+export const utilityURL = `${utilityService}/scraper/`;
diff --git a/typings.json b/typings.json
new file mode 100644
index 0000000000000000000000000000000000000000..520f8bbf1c19eedd4221e0a6d1b7aa7f2492cf5c
--- /dev/null
+++ b/typings.json
@@ -0,0 +1,12 @@
+{
+ "name": "buildings",
+ "dependencies": {},
+ "compilerOptions": {
+ "target": "ES6",
+ "module": "commonjs",
+ "allowSyntheticDefaultImports": true
+ },
+ "exclude": [
+ "node_modules"
+ ]
+}