diff --git a/package.json b/package.json index 5215741df..78efafa3f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "nodemailer": "~2.6.4", "pg-promise": "^5.3.5", "react": "^15.4.2", + "react-bootstrap": "^0.31.0", "react-chartjs-2": "^2.0.5", "react-dom": "^15.4.2", "react-rangeslider": "^2.0.1", diff --git a/src/client/app/components/ExportComponent.jsx b/src/client/app/components/ExportComponent.jsx new file mode 100644 index 000000000..8029a6834 --- /dev/null +++ b/src/client/app/components/ExportComponent.jsx @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from 'react'; +import { Button } from 'react-bootstrap'; +import moment from 'moment'; +import graphExport from '../services/exportData'; + +const ExportComponent = props => { + /** + * Called when Export button is clicked. + * Passes an object containing the selected meter data to a function for export. + */ + const exportReading = () => { + const compressedData = props.exportVals.datasets; + let time = compressedData[0].exportVals[0].x; + const chart = compressedData[0].currentChart; + time = moment(time).format('ddddMMMDDYYYY'); + const name = `oedExport${time}${chart}.csv`; + graphExport(compressedData, name); + }; + return ( +
+ +
+ ); +}; +export default ExportComponent; diff --git a/src/client/app/components/HomeComponent.jsx b/src/client/app/components/HomeComponent.jsx index c53f049df..2b67fe976 100644 --- a/src/client/app/components/HomeComponent.jsx +++ b/src/client/app/components/HomeComponent.jsx @@ -6,6 +6,7 @@ import React from 'react'; import HeaderComponent from './HeaderComponent'; import DashboardContainer from '../containers/DashboardContainer'; + /** * Top-level React component that controls the home page * @return JSX to create the home page diff --git a/src/client/app/components/UIOptionsComponent.jsx b/src/client/app/components/UIOptionsComponent.jsx index f052f0306..cec9dce2c 100644 --- a/src/client/app/components/UIOptionsComponent.jsx +++ b/src/client/app/components/UIOptionsComponent.jsx @@ -7,6 +7,8 @@ import Slider from 'react-rangeslider'; import moment from 'moment'; import 'react-rangeslider/lib/index.css'; import { chartTypes } from '../reducers/graph'; +import ExportContainer from '../containers/ExportContainer'; + export default class UIOptionsComponent extends React.Component { /** @@ -104,6 +106,9 @@ export default class UIOptionsComponent extends React.Component {

Bar chart interval (days):

+
+ +
); diff --git a/src/client/app/containers/ExportContainer.js b/src/client/app/containers/ExportContainer.js new file mode 100644 index 000000000..af5508a42 --- /dev/null +++ b/src/client/app/containers/ExportContainer.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { connect } from 'react-redux'; +import ExportComponent from '../components/ExportComponent'; +import { chartTypes } from '../reducers/graph'; + +/** + * @param {State} state + * @return {{meterInfo: *, selectedMeters: Array}} + */ +function mapStateToProps(state) { + const timeInterval = state.graph.timeInterval; + const data = { datasets: [] }; + let readingsData; + const chart = state.graph.chartToRender; + const barDuration = state.graph.barDuration; + + for (const meterID of state.graph.selectedMeters) { + if (chart === chartTypes.line) { + readingsData = state.readings.line.byMeterID[meterID][timeInterval]; + } else if (chart === chartTypes.bar) { readingsData = state.readings.bar.byMeterID[meterID][timeInterval][barDuration]; } + if (readingsData !== undefined && !readingsData.isFetching && chart === chartTypes.line) { + data.datasets.push({ + label: state.meters.byMeterID[meterID].name, + id: state.meters.byMeterID[meterID].id, + timestamp: state.readings.line.byMeterID[meterID][timeInterval].start_timestamp, + currentChart: chart, + exportVals: state.readings.line.byMeterID[meterID][timeInterval].readings.map(arr => ({ x: arr[0], y: arr[1] })) + }); + } else if (readingsData !== undefined && !readingsData.isFetching && chart === chartTypes.bar) { + data.datasets.push({ + label: state.meters.byMeterID[meterID].name, + id: state.meters.byMeterID[meterID].id, + timestamp: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].timestamp, + currentChart: chart, + exportVals: state.readings.bar.byMeterID[meterID][timeInterval][barDuration].readings.map(arr => ({ x: arr[0], y: arr[1] })) + }); + } + } + return { + selectedMeters: state.graph.selectedMeters, + exportVals: data + }; +} + +/** + * Connects changes to the Redux store to UIOptionsComponent via mapStateToProps + */ +export default connect(mapStateToProps)(ExportComponent); diff --git a/src/client/app/services/exportData.js b/src/client/app/services/exportData.js new file mode 100644 index 000000000..6a0cba5b0 --- /dev/null +++ b/src/client/app/services/exportData.js @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import moment from 'moment'; + + +/** + * Function to converts the compressed meter data into a CSV formatted string. + * @param items The compressed meter data. + * @returns output A string containing the CSV formatted compressed meter data. + */ + +function convertToCSV(items) { + let csvOutput = 'Label,Readings,Start Timestamp\n'; + items.forEach(set => { + const data = set.exportVals; + const label = set.label; + data.forEach(reading => { + const info = reading.y; + const startTimeStamp = moment(reading.x).format('dddd MMM DD YYYY hh:mm a'); + csvOutput += `${label},${info} kwh, ${startTimeStamp}\n`; // this assumes that meter readings are in kwh + }); + }); + return csvOutput; +} +/** + * Function to download the formatted CSV file to the users computer. + * @param inputCSV A String containing the formatted CSV data. + * @param fileName A string representing the name of the file. + */ +function downloadCSV(inputCSV, fileName) { + const element = document.createElement('a'); + element.setAttribute('href', `data:text/csv;charset=utf-8,${encodeURIComponent(inputCSV)}`); + element.setAttribute('download', fileName); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +/** + * Function to export compressed data from the graph currently displaying. May be used for routing if more export options are added + * @param dataSets An Object. The compressed data from each meter currently selected in the graph. + * @param name the name of the file. + */ +export default function graphExport(dataSets, name) { + const dataToExport = convertToCSV(dataSets); + downloadCSV(dataToExport, name); +}