View all groups
@@ -416,6 +438,8 @@ BGroup.propTypes = {
deleteBuildingFromBGroup: PropTypes.func,
user: userPropType,
projects: completeProjectPropTypes,
+ report: reportsPropTypes,
+ loadCustomBpImpactReport: PropTypes.func,
loadProjects: PropTypes.func,
displayNavBar: PropTypes.bool,
displayProjectOverview: PropTypes.bool,
@@ -428,6 +452,7 @@ BGroup.propTypes = {
toggleAnalysis: PropTypes.bool,
toggleProjects: PropTypes.bool,
toggleImpact: PropTypes.bool,
+ onBuildingFilter: PropTypes.func,
};
BGroup.defaultProps = {
@@ -442,12 +467,14 @@ BGroup.defaultProps = {
toggleAnalysis: true,
toggleProjects: true,
toggleImpact: true,
+ onBuildingFilter: () => { },
};
const mapStateToProps = state => (
{
bGroup: state.bGroup,
projects: state.projectList,
+ report: state.report,
}
);
@@ -459,6 +486,7 @@ const mapDispatchToProps = dispatch => (
deleteBuildingFromBGroup,
deleteBGroup,
loadProjects,
+ loadCustomBpImpactReport,
}, dispatch)
);
diff --git a/src/containers/BGroup/BGroupBuildingTable.js b/src/containers/BGroup/BGroupBuildingTable.js
index 0dec97fb0a85e9c14368eba476b193323680970a..ab26d832f8e74733c3ce2054e5ec2ec0a89bcacf 100644
--- a/src/containers/BGroup/BGroupBuildingTable.js
+++ b/src/containers/BGroup/BGroupBuildingTable.js
@@ -29,6 +29,10 @@ export default class BGroupBuildingTable extends Component {
completedDeemedToggle: {},
};
+ componentWillUnmount() {
+ clearTimeout(this.updateNumRows);
+ }
+
handleAddBuilding = (item) => {
this.props.addBuildingToBGroup(
this.props.bGroupId,
@@ -667,6 +671,7 @@ export default class BGroupBuildingTable extends Component {
},
250,
);
+ this.props.onBuildingFilter(filter, this.reactTable.state);
}}
noDataText={this.props.bGroup.bGroupDetailLoading ? 'Loading...' : 'No data'}
SubComponent={row => {
@@ -750,4 +755,5 @@ BGroupBuildingTable.propTypes = {
toggleAnalysis: PropTypes.bool,
toggleProjects: PropTypes.bool,
toggleImpact: PropTypes.bool,
+ onBuildingFilter: PropTypes.func,
};
diff --git a/src/containers/BGroup/BGroupProjectOverview.js b/src/containers/BGroup/BGroupProjectOverview.js
index eccb35e93e675df3d1bcf7d6a1e9c003b864f257..cb9e4933f7bfa28a64d15d7a9aa322ceed3601d1 100644
--- a/src/containers/BGroup/BGroupProjectOverview.js
+++ b/src/containers/BGroup/BGroupProjectOverview.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import userPropTypes from '../User/propTypes';
import completeProjectPropTypes from '../Project/propTypes';
import Loading from '../../components/Loading';
+import BpImpact from '../../containers/Reports/components/BpImpact/BpImpact';
export default class BGroupProjectOverview extends Component {
state = { };
@@ -12,21 +13,31 @@ export default class BGroupProjectOverview extends Component {
user, projects,
projectStatusBreakdown,
projectTypeBreakdown,
+ impactSummary,
+ impactSummaryLoading,
} = this.props;
- if (projects.loading) {
+ if (projects.loading || impactSummaryLoading) {
return
;
}
- if (
- !user.permissions['view::bgroupProjectsSummary'] ||
- Object.keys(projectTypeBreakdown).length === 0
- ) {
- return null;
+ if (!user.permissions['view::bgroupProjectsSummary']) {
+ return
Incorrect permissions
;
+ }
+ if (Object.keys(projectTypeBreakdown).length === 0) {
+ return
Insufficient project data
;
}
let numProjects = 0;
const stageOrder = ['Engaged', 'Out to Bid', 'In Construction', 'Complete', 'HPD Pipeline'];
return (
+
+
Project Impact Summary
+
+
+
+
Project Type Summary
@@ -92,5 +103,7 @@ BGroupProjectOverview.propTypes = {
projects: completeProjectPropTypes,
projectTypeBreakdown: PropTypes.object, // eslint-disable-line
projectStatusBreakdown: PropTypes.object, // eslint-disable-line
+ impactSummary: PropTypes.array, // eslint-disable-line
+ impactSummaryLoading: PropTypes.bool,
};
diff --git a/src/containers/Reports/actions.js b/src/containers/Reports/actions.js
index e9ad84e8a2197088748da61f39da8ef8814b4f0c..8941c6052ec5a7f5d7fb8f40264bf99b5ac41757 100644
--- a/src/containers/Reports/actions.js
+++ b/src/containers/Reports/actions.js
@@ -3,6 +3,12 @@ import {
LOAD_LIGHTING_REPORT,
LOAD_LIGHTING_REPORT_SUCCESS,
LOAD_LIGHTING_REPORT_ERROR,
+ LOAD_BP_IMPACT_REPORT,
+ LOAD_BP_IMPACT_REPORT_SUCCESS,
+ LOAD_BP_IMPACT_REPORT_ERROR,
+ LOAD_CUSTOM_BP_IMPACT_REPORT,
+ LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS,
+ LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR,
} from './constants';
/**
@@ -32,3 +38,57 @@ export function lightingReportsLoadingError(error) {
error,
};
}
+
+/**
+ * Load bp impact reports
+ *
+ * @returns {object} An action object with a type of LOAD_BUILDING_BP_IMPACT_REPORT
+ */
+
+export function loadBpImpactReports(limit = -1) {
+ return {
+ type: LOAD_BP_IMPACT_REPORT,
+ limit,
+ };
+}
+
+export function bpImpactReportsLoaded(bpImpactReports) {
+ return {
+ type: LOAD_BP_IMPACT_REPORT_SUCCESS,
+ payload: bpImpactReports.data,
+ };
+}
+
+export function bpImpactReportsLoadingError(error) {
+ return {
+ type: LOAD_BP_IMPACT_REPORT_ERROR,
+ error,
+ };
+}
+
+/**
+ * Load custom bp impact reports
+ *
+ * @returns {object} An action object with a type of LOAD_CUSTOM_BP_IMPACT_REPORT
+ */
+
+export function loadCustomBpImpactReport(buildingIds) {
+ return {
+ type: LOAD_CUSTOM_BP_IMPACT_REPORT,
+ buildingIds,
+ };
+}
+
+export function customBpImpactReportsLoaded(bpImpactReport) {
+ return {
+ type: LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS,
+ payload: bpImpactReport.data,
+ };
+}
+
+export function customBpImpactReportsLoadingError(error) {
+ return {
+ type: LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR,
+ error,
+ };
+}
diff --git a/src/containers/Reports/components/BpImpact/BpImpact.js b/src/containers/Reports/components/BpImpact/BpImpact.js
new file mode 100644
index 0000000000000000000000000000000000000000..45d60702b2f6868e2e68b894b74cf65eddc49425
--- /dev/null
+++ b/src/containers/Reports/components/BpImpact/BpImpact.js
@@ -0,0 +1,81 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import ReactTooltip from 'react-tooltip';
+
+import { bpImpactReportPropTypes } from '../../propTypes';
+import Loading from '../../../../components/Loading';
+
+
+export default class BpImpact extends Component {
+
+ state = {}
+
+ render() {
+ if (this.props.loading) {
+ return ;
+ }
+ const bpImpactReports = this.props.bpImpactReports;
+ if (!this.props.bpImpactReports) {
+ return null;
+ }
+ const dateCompareString = `
+ The blue number represents the difference between the last two generated reports
+
+ The last reports were generated on
+ ${bpImpactReports[0].created} and ${bpImpactReports.length > 1 ? bpImpactReports[1].created : 'n/a'}
+ `;
+ return (
+
+
+
+
+ {
+ bpImpactReports[0].report.map((val, index) => {
+ let change = 0;
+ if (bpImpactReports.length > 1) {
+ const previousReport = bpImpactReports[1];
+ change = val.value - previousReport.report[index].value;
+ }
+ let changeString = '';
+ if (change > 0) {
+ changeString = ` +${change}`;
+ }
+ if (change < 0) {
+ changeString = ` ${change}`;
+ }
+ const changeEl = (
+
+ {changeString}
+
+ );
+ let trStyling = {};
+ if (val.col === 'Total Buildings') {
+ trStyling = {
+ backgroundColor: 'rgba(176, 167, 167, 0.41)',
+ pointerEvents: 'none',
+ };
+ }
+ return (
+
+ {val.col}
+ {val.value}{changeEl}
+
+ );
+ })
+ }
+
+
+
+ );
+
+ }
+}
+
+BpImpact.propTypes = {
+ bpImpactReports: bpImpactReportPropTypes,
+ loading: PropTypes.bool,
+};
diff --git a/src/containers/Reports/components/BpImpact/BpImpactWrapper.js b/src/containers/Reports/components/BpImpact/BpImpactWrapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb48e2d469dfa93a1bddaa1c9cdec9d9e47f15f6
--- /dev/null
+++ b/src/containers/Reports/components/BpImpact/BpImpactWrapper.js
@@ -0,0 +1,122 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import ReactTooltip from 'react-tooltip';
+import { Icon } from 'react-fa';
+
+import { bpImpactReportPropTypes } from '../../propTypes';
+import { BGroup } from '../../../../containers/BGroup';
+import userPropType from '../../../../containers/User/propTypes';
+import BpImpact from './BpImpact';
+
+
+export default class BpImpactWrapper extends Component {
+
+ state = {
+ filterReport: null,
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (
+ nextProps.customBpImpactReport !== this.props.customBpImpactReport
+ ) {
+ this.setState({ filterReport: [nextProps.customBpImpactReport[0]] });
+ }
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.updateFilterReportTimeout);
+ }
+
+ updateFilterReport = (filter, tableState) => {
+ const buildingIds = tableState.sortedData.map(val => (
+ val._original.building_id // eslint-disable-line
+ ));
+ clearTimeout(this.updateFilterReportTimeout);
+ this.updateFilterReportTimeout = setTimeout(
+ () => {
+ this.props.loadCustomBpImpactReport(buildingIds);
+ },
+ 250,
+ );
+ }
+
+ render() {
+ if (this.props.bpImpactReports !== null && this.props.bpImpactReports.length < 1) {
+ return No report
;
+ }
+ if (!this.props.detail) {
+ return (
+
+ );
+ }
+ const filterDescriptionString = `
+ Filter buildings below to see a new report generated
+
+ for the subset of buildings that pass the filter
+ `;
+ return (
+
+
+
+
+
Overall Report
+
+
+
+
+
+
+
+
BlocPower Buildings
+
+
+
+
+ );
+ }
+}
+
+BpImpactWrapper.propTypes = {
+ bpImpactReports: bpImpactReportPropTypes,
+ loadCustomBpImpactReport: PropTypes.func,
+ customBpImpactReport: PropTypes.array, // eslint-disable-line
+ loadingCustomBpImpact: PropTypes.bool,
+ detail: PropTypes.bool,
+ loading: PropTypes.bool,
+ user: userPropType,
+};
diff --git a/src/containers/Reports/components/Lighting/index.js b/src/containers/Reports/components/Lighting/index.js
index af7c884034fcfdca994a4b80fd406af53bf904cb..aa378975d67e57b0abe3389e76cc33733da44928 100644
--- a/src/containers/Reports/components/Lighting/index.js
+++ b/src/containers/Reports/components/Lighting/index.js
@@ -1,14 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Icon } from 'react-fa';
import ReactTooltip from 'react-tooltip';
+import { Icon } from 'react-fa';
import { lightingReportPropTypes } from '../../propTypes';
+import Loading from '../../../../components/Loading';
export default function Lighting({
lightingReports,
detail,
+ loading,
}) {
+ if (loading) {
+ return ;
+ }
const dateCompareString = `
The blue number represents the difference between the last two generated reports
@@ -76,4 +81,5 @@ export default function Lighting({
Lighting.propTypes = {
lightingReports: lightingReportPropTypes,
detail: PropTypes.bool,
+ loading: PropTypes.bool,
};
diff --git a/src/containers/Reports/constants.js b/src/containers/Reports/constants.js
index 470dc388217739b0d78224e975059ea9c87c9228..1ba142cbde158c06149636f516be5b370175322b 100644
--- a/src/containers/Reports/constants.js
+++ b/src/containers/Reports/constants.js
@@ -1,3 +1,10 @@
export const LOAD_LIGHTING_REPORT = 'LOAD_LIGHTING_REPORT';
export const LOAD_LIGHTING_REPORT_SUCCESS = 'LOAD_LIGHTING_REPORT_SUCCESS';
export const LOAD_LIGHTING_REPORT_ERROR = 'LOAD_LIGHTING_REPORT_ERROR';
+export const LOAD_BP_IMPACT_REPORT = 'LOAD_BP_IMPACT_REPORT';
+export const LOAD_BP_IMPACT_REPORT_SUCCESS = 'LOAD_BP_IMPACT_REPORT_SUCCESS';
+export const LOAD_BP_IMPACT_REPORT_ERROR = 'LOAD_BP_IMPACT_REPORT_ERROR';
+// A bp impact report for a subset of buildings
+export const LOAD_CUSTOM_BP_IMPACT_REPORT = 'LOAD_CUSTOM_BP_IMPACT_REPORT';
+export const LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS = 'LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS';
+export const LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR = 'LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR';
diff --git a/src/containers/Reports/index.js b/src/containers/Reports/index.js
index bf44b61c37c737ef3e574ad618025f4196714bf1..d45cd6f994aad9de82bdf93dd1919391a97ad8bd 100644
--- a/src/containers/Reports/index.js
+++ b/src/containers/Reports/index.js
@@ -5,13 +5,15 @@ import { connect } from 'react-redux';
import { Link } from 'react-router';
import { bindActionCreators } from 'redux';
import { Nav, NavItem, NavLink } from 'reactstrap';
-import { Icon } from 'react-fa';
import ReactTooltip from 'react-tooltip';
import {
loadLightingReports,
+ loadBpImpactReports,
+ loadCustomBpImpactReport,
} from './actions';
-import lightingReportsPropTypes from './propTypes';
+import reportsPropTypes from './propTypes';
+import BpImpactWrapper from './components/BpImpact/BpImpactWrapper';
import Lighting from './components/Lighting';
import userPropType from '../../containers/User/propTypes';
@@ -20,7 +22,7 @@ class Reports extends Component {
super(props);
this.state = {
display: true,
- reportTab: 'lighting',
+ reportTab: 'bpimpact',
mode: this.props.mode,
};
}
@@ -33,55 +35,71 @@ class Reports extends Component {
this.props.loadLightingReports(2);
}
}
- }
- componentWillReceiveProps(nextProps) {
- if (
- !this.props.user.permissions['read::KissflowLighting'] &&
- nextProps.user.permissions['read::KissflowLighting']
- ) {
- if (this.state.mode === 'summary') {
- this.props.loadLightingReports(2, true);
- } else {
- this.props.loadLightingReports(2);
- }
+ if (this.props.user.permissions['read::BpImpact']) {
+ this.props.loadBpImpactReports(2);
}
}
render() {
- let content = (
-
-
-
- );
- if (
- this.props.report.lightingReports.length !== 0 &&
- !this.props.report.loadingLighting &&
- !this.props.report.error
- ) {
- switch (this.state.reportTab) {
- case 'lighting':
- content = (
-
- );
- break;
- default:
- content = 'There was an error';
- }
+ let content = null;
+ let loading = false;
+ switch (this.state.reportTab) {
+ case 'bpimpact':
+ loading = (
+ this.props.report.bpImpactReports === null ||
+ this.props.report.loadingBpImpact
+ );
+ content = (
+
+ );
+ break;
+ case 'lighting':
+ loading = (
+ this.props.report.lightingReports === null ||
+ this.props.report.loadingLighting
+ );
+ content = (
+
+ );
+ break;
+ default:
+ content = 'There was an error';
}
if (this.props.report.error) {
content = 'There was an error';
}
- if (!this.props.user.permissions['read::KissflowLighting']) {
+ if (
+ (this.state.reportTab === 'lighting' && !this.props.user.permissions['read::KissflowLighting']) ||
+ (this.state.reportTab === 'bpimpact' && !this.props.user.permissions['read::BpImpact'])
+ ) {
content = 'You do not have the permission to view this report';
}
+
return (
// create a simple report that is engaged, out to bid, complete, total active, total inactive
-
+
+
+ this.setState({ reportTab: 'bpimpact' })}
+ >
+ BP Impact
+
+
(
+ `${acc}building_id=${val}&`
+ ), '');
+ const res = yield call(
+ request,
+ `${bpImpactReportURL}?generate=true&${filterString}`, {
+ method: 'GET',
+ headers: getHeaders(),
+ }
+ );
+
+ if (!res.err) {
+ yield put(customBpImpactReportsLoaded(res));
+ } else {
+ yield put(customBpImpactReportsLoadingError(res.err));
+ }
+}
+
/**
- * Watches for LOAD_LIGHTING_REPORT and LOAD_CONTACTS
+ * Watches for LOAD_LIGHTING_REPORT and LOAD_BP_IMPACT
* actions and calls the appropriate handler function.
*/
function* reportWatcher() {
yield takeLatest(LOAD_LIGHTING_REPORT, getLightingReports);
+ yield takeLatest(LOAD_BP_IMPACT_REPORT, getBpImpactReports);
+ yield takeLatest(LOAD_CUSTOM_BP_IMPACT_REPORT, getCustomBpImpactReport);
}
export default reportWatcher;
diff --git a/src/utils/restServices.js b/src/utils/restServices.js
index 95747f903c7b9ba991abcdef223cd94f183f2308..fc7e675b2f244e46e9f784883328766be6bbda96 100644
--- a/src/utils/restServices.js
+++ b/src/utils/restServices.js
@@ -39,6 +39,7 @@ export const projectDocumentURL = `${projectService}/project/document/`;
export const sfBuildingImpactURL = `${projectService}/sfbuildingimpact/`;
export const lightingReportURL = `${reportService}/kissflowlighting/`;
+export const bpImpactReportURL = `${reportService}/bpimpact/`;
export const gatewayURL = `${iotService}/gateway/`;
export const sensewareNodeURL = `${iotService}/sensewarenode/`;