diff --git a/myems-api/excelexporters/meterbatch.py b/myems-api/excelexporters/meterbatch.py index c55d44fa..04c31f7f 100644 --- a/myems-api/excelexporters/meterbatch.py +++ b/myems-api/excelexporters/meterbatch.py @@ -179,7 +179,7 @@ def generate_excel(report, space_name, reporting_start_datetime_local, reporting ws[col + str(current_row_number)].font = data_font ws[col + str(current_row_number)].border = f_border ws[col + str(current_row_number)].alignment = c_c_alignment - ws[col + str(current_row_number)] = round(report['meters'][i]['values'][j], 2) + ws[col + str(current_row_number)] = report['meters'][i]['values'][j] current_row_number += 1 diff --git a/myems-api/reports/meterbatch.py b/myems-api/reports/meterbatch.py index 1da44924..ce3e0a6a 100644 --- a/myems-api/reports/meterbatch.py +++ b/myems-api/reports/meterbatch.py @@ -194,7 +194,7 @@ class Reporting: reporting_end_datetime_utc)) rows_meter_energy = cursor_energy_db.fetchall() for energy_category in energy_category_list: - subtotal = Decimal(0.0) + subtotal = None for row_meter_energy in rows_meter_energy: if energy_category['id'] == meter_dict[meter_id]['energy_category_id']: subtotal = row_meter_energy[0] diff --git a/web/src/components/MyEMS/Meter/MeterBatch.js b/web/src/components/MyEMS/Meter/MeterBatch.js new file mode 100644 index 00000000..c8269388 --- /dev/null +++ b/web/src/components/MyEMS/Meter/MeterBatch.js @@ -0,0 +1,335 @@ +import React, { Fragment, useEffect, useState } from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Row, + Col, + Card, + CardBody, + Button, + ButtonGroup, + Form, + FormGroup, + Input, + Label, + CustomInput, + Spinner, +} from 'reactstrap'; +import Datetime from 'react-datetime'; +import moment from 'moment'; +import loadable from '@loadable/component'; +import Cascader from 'rc-cascader'; +import { getCookieValue, createCookie } from '../../../helpers/utils'; +import withRedirect from '../../../hoc/withRedirect'; +import { withTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import ButtonIcon from '../../common/ButtonIcon'; +import { APIBaseURL } from '../../../config'; + + +const DetailedDataTable = loadable(() => import('../common/DetailedDataTable')); + + +const MeterBatch = ({ setRedirect, setRedirectUrl, t }) => { + let current_moment = moment(); + useEffect(() => { + let is_logged_in = getCookieValue('is_logged_in'); + let user_name = getCookieValue('user_name'); + let user_display_name = getCookieValue('user_display_name'); + let user_uuid = getCookieValue('user_uuid'); + let token = getCookieValue('token'); + if (is_logged_in === null || !is_logged_in) { + setRedirectUrl(`/authentication/basic/login`); + setRedirect(true); + } else { + //update expires time of cookies + createCookie('is_logged_in', true, 1000 * 60 * 60 * 8); + createCookie('user_name', user_name, 1000 * 60 * 60 * 8); + createCookie('user_display_name', user_display_name, 1000 * 60 * 60 * 8); + createCookie('user_uuid', user_uuid, 1000 * 60 * 60 * 8); + createCookie('token', token, 1000 * 60 * 60 * 8); + } + }); + // State + // Query Parameters + const [selectedSpaceName, setSelectedSpaceName] = useState(undefined); + const [selectedSpaceID, setSelectedSpaceID] = useState(undefined); + const [meterList, setMeterList] = useState([]); + const [reportingPeriodBeginsDatetime, setReportingPeriodBeginsDatetime] = useState(current_moment.clone().startOf('month')); + const [reportingPeriodEndsDatetime, setReportingPeriodEndsDatetime] = useState(current_moment); + const [cascaderOptions, setCascaderOptions] = useState(undefined); + + // buttons + const [submitButtonDisabled, setSubmitButtonDisabled] = useState(false); + const [spinnerHidden, setSpinnerHidden] = useState(true); + const [exportButtonHidden, setExportButtonHidden] = useState(true); + + //Results + const [detailedDataTableColumns, setDetailedDataTableColumns] = useState( + [{dataField: 'name', text: t('Name'), sort: true}, {dataField: 'space', text: t('Space'), sort: true}]); + const [excelBytesBase64, setExcelBytesBase64] = useState(undefined); + + useEffect(() => { + let isResponseOK = false; + fetch(APIBaseURL + '/spaces/tree', { + method: 'GET', + headers: { + "Content-type": "application/json", + "User-UUID": getCookieValue('user_uuid'), + "Token": getCookieValue('token') + }, + body: null, + + }).then(response => { + console.log(response); + if (response.ok) { + isResponseOK = true; + } + return response.json(); + }).then(json => { + console.log(json); + if (isResponseOK) { + // rename keys + json = JSON.parse(JSON.stringify([json]).split('"id":').join('"value":').split('"name":').join('"label":')); + setCascaderOptions(json); + // set the default selected space + setSelectedSpaceName([json[0]].map(o => o.label)); + setSelectedSpaceID([json[0]].map(o => o.value)); + + setSubmitButtonDisabled(false); + setSpinnerHidden(true); + } else { + toast.error(json.description); + } + }).catch(err => { + console.log(err); + }); + + }, []); + + const labelClasses = 'ls text-uppercase text-600 font-weight-semi-bold mb-0'; + + let onSpaceCascaderChange = (value, selectedOptions) => { + setSelectedSpaceName(selectedOptions.map(o => o.label).join('/')); + setSelectedSpaceID(value[value.length - 1]); + setMeterList([]); + setExportButtonHidden(true); + setSubmitButtonDisabled(false); + } + let onReportingPeriodBeginsDatetimeChange = (newDateTime) => { + setReportingPeriodBeginsDatetime(newDateTime); + } + + let onReportingPeriodEndsDatetimeChange = (newDateTime) => { + setReportingPeriodEndsDatetime(newDateTime); + } + + var getValidReportingPeriodBeginsDatetimes = function (currentDate) { + return currentDate.isBefore(moment(reportingPeriodEndsDatetime, 'MM/DD/YYYY, hh:mm:ss a')); + } + + var getValidReportingPeriodEndsDatetimes = function (currentDate) { + return currentDate.isAfter(moment(reportingPeriodBeginsDatetime, 'MM/DD/YYYY, hh:mm:ss a')); + } + + // Handler + const handleSubmit = e => { + e.preventDefault(); + console.log('handleSubmit'); + console.log(selectedSpaceID); + console.log(reportingPeriodBeginsDatetime.format('YYYY-MM-DDTHH:mm:ss')); + console.log(reportingPeriodEndsDatetime.format('YYYY-MM-DDTHH:mm:ss')); + + // disable submit button + setSubmitButtonDisabled(true); + // show spinner + setSpinnerHidden(false); + // hide export buttion + setExportButtonHidden(true) + + // Reinitialize tables + setMeterList([]); + + let isResponseOK = false; + fetch(APIBaseURL + '/reports/meterbatch?' + + 'spaceid=' + selectedSpaceID + + '&reportingperiodstartdatetime=' + reportingPeriodBeginsDatetime.format('YYYY-MM-DDTHH:mm:ss') + + '&reportingperiodenddatetime=' + reportingPeriodEndsDatetime.format('YYYY-MM-DDTHH:mm:ss'), { + method: 'GET', + headers: { + "Content-type": "application/json", + "User-UUID": getCookieValue('user_uuid'), + "Token": getCookieValue('token') + }, + body: null, + + }).then(response => { + if (response.ok) { + isResponseOK = true; + }; + return response.json(); + }).then(json => { + if (isResponseOK) { + console.log(json) + let meters = []; + if (json['meters'].length > 0) { + json['meters'].forEach((currentMeter, index) => { + let detailed_value = {}; + detailed_value['id'] = currentMeter['id']; + detailed_value['name'] = currentMeter['meter_name']; + detailed_value['space'] = currentMeter['space_name']; + detailed_value['costcenter'] = currentMeter['cost_center_name']; + currentMeter['values'].forEach((currentValue, energyCategoryIndex) => { + if (currentValue !== null) { + detailed_value['a' + energyCategoryIndex] = currentValue.toFixed(2); + } else { + detailed_value['a' + energyCategoryIndex] = null; + } + + }); + meters.push(detailed_value); + }); + }; + + setMeterList(meters); + + let detailed_column_list = []; + detailed_column_list.push({ + dataField: 'name', + text: t('Name'), + sort: true + }); + detailed_column_list.push({ + dataField: 'space', + text: t('Space'), + sort: true + }); + json['energycategories'].forEach((currentValue, index) => { + detailed_column_list.push({ + dataField: 'a' + index, + text: currentValue['name'] + ' (' + currentValue['unit_of_measure'] + ')', + sort: true + }) + }); + setDetailedDataTableColumns(detailed_column_list); + + setExcelBytesBase64(json['excel_bytes_base64']); + + // enable submit button + setSubmitButtonDisabled(false); + // hide spinner + setSpinnerHidden(true); + // show export buttion + setExportButtonHidden(false); + + } else { + toast.error(json.description) + } + }).catch(err => { + console.log(err); + }); + }; + + const handleExport = e => { + e.preventDefault(); + const mimeType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + const fileName = 'meterbatch.xlsx' + var fileUrl = "data:" + mimeType + ";base64," + excelBytesBase64; + fetch(fileUrl) + .then(response => response.blob()) + .then(blob => { + var link = window.document.createElement("a"); + link.href = window.URL.createObjectURL(blob, { type: mimeType }); + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }); + }; + + + + return ( + +
+ + {t('Meter Data')}{t('Batch Analysis')} + +
+ + +
+ + + + +
+ + + +
+ + + + + + + + + + + + + + + + +

+ + + +
+ + + +

+
+ + +

+ + +
+
+
+
+ + + +
+ ); +}; + +export default withTranslation()(withRedirect(MeterBatch)); diff --git a/web/src/layouts/MyEMSRoutes.js b/web/src/layouts/MyEMSRoutes.js index 710f3130..6f17a20b 100644 --- a/web/src/layouts/MyEMSRoutes.js +++ b/web/src/layouts/MyEMSRoutes.js @@ -125,16 +125,17 @@ import EquipmentSaving from '../components/MyEMS/Equipment/EquipmentSaving'; import EquipmentStatistics from '../components/MyEMS/Equipment/EquipmentStatistics'; import EquipmentTracking from '../components/MyEMS/Equipment/EquipmentTracking'; // Meter -import MeterEnergy from '../components/MyEMS/Meter/MeterEnergy'; +import MeterBatch from '../components/MyEMS/Meter/MeterBatch'; import MeterCost from '../components/MyEMS/Meter/MeterCost'; -import MeterTrend from '../components/MyEMS/Meter/MeterTrend'; +import MeterEnergy from '../components/MyEMS/Meter/MeterEnergy'; import MeterRealtime from '../components/MyEMS/Meter/MeterRealtime'; import MeterSubmetersBalance from '../components/MyEMS/Meter/MeterSubmetersBalance'; -import OfflineMeterEnergy from '../components/MyEMS/Meter/OfflineMeterEnergy'; -import OfflineMeterCost from '../components/MyEMS/Meter/OfflineMeterCost'; -import VirtualMeterEnergy from '../components/MyEMS/Meter/VirtualMeterEnergy'; -import VirtualMeterCost from '../components/MyEMS/Meter/VirtualMeterCost'; import MeterTracking from '../components/MyEMS/Meter/MeterTracking'; +import MeterTrend from '../components/MyEMS/Meter/MeterTrend'; +import OfflineMeterCost from '../components/MyEMS/Meter/OfflineMeterCost'; +import OfflineMeterEnergy from '../components/MyEMS/Meter/OfflineMeterEnergy'; +import VirtualMeterCost from '../components/MyEMS/Meter/VirtualMeterCost'; +import VirtualMeterEnergy from '../components/MyEMS/Meter/VirtualMeterEnergy'; // Tenant import TenantEnergyCategory from '../components/MyEMS/Tenant/TenantEnergyCategory'; import TenantEnergyItem from '../components/MyEMS/Tenant/TenantEnergyItem'; @@ -362,6 +363,7 @@ const MyEMSRoutes = () => ( {/*Meter*/} + diff --git a/web/src/routes.js b/web/src/routes.js index 05fccaba..1e3510dc 100644 --- a/web/src/routes.js +++ b/web/src/routes.js @@ -368,6 +368,7 @@ export const meterRoutes = { { to: '/meter/virtualmetercost', name: 'Virtual Meter Cost' }, { to: '/meter/offlinemeterenergy', name: 'Offline Meter Energy' }, { to: '/meter/offlinemetercost', name: 'Offline Meter Cost' }, + { to: '/meter/batch', name: 'Batch Analysis'}, { to: '/meter/tracking', name: 'Meter Tracking' }, ] };