dynamic react router
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import 'babel-polyfill';
|
||||
import 'bootstrap/dist/js/bootstrap.min';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Layout from './dashboard/layout/layout';
|
||||
import {Router} from 'react-router';
|
||||
|
||||
export default function(){
|
||||
import {ROUTES} from './dashboard/routes';
|
||||
|
||||
export default function(history){
|
||||
ReactDOM.render(
|
||||
React.createElement(Layout),
|
||||
React.createElement(Router, {routes: ROUTES, history: history}),
|
||||
document.getElementById('root')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Styles from 'config/styles';
|
||||
import Templates from 'config/templates';
|
||||
import {hashHistory} from 'react-router';
|
||||
import app from './../../app';
|
||||
|
||||
Promise.all([
|
||||
@@ -7,5 +8,5 @@ Promise.all([
|
||||
Styles.sync()
|
||||
]).then(()=>{
|
||||
jQuery('#compiling_layouts').remove();
|
||||
app();
|
||||
app(hashHistory);
|
||||
});
|
||||
|
||||
@@ -11,11 +11,6 @@ const TEMPLATE_ROUTES = Object.freeze({
|
||||
power: 'dashboard/power/power.rt'
|
||||
});
|
||||
|
||||
const COMPONENTS = {
|
||||
Power: Power,
|
||||
Energy: Energy
|
||||
};
|
||||
|
||||
var TEMPLATES = {};
|
||||
|
||||
class Templates {
|
||||
@@ -40,16 +35,14 @@ class Templates {
|
||||
url: TEMPLATE_ROUTES[view]
|
||||
}).done((template)=>{
|
||||
var code = rt.convertTemplateToReact(template, {modules: 'none', name: view}),
|
||||
context = {};
|
||||
code = code.replace('var '+view+' = ', 'context.'+view+' = ');
|
||||
eval_context = {};
|
||||
code = code.replace('var ' + view + ' = ', 'eval_context.' + view + ' = ');
|
||||
new Function('with(this){ ' + code + ' } ').call({
|
||||
Energy: Energy,
|
||||
Power: Power,
|
||||
context: context,
|
||||
eval_context: eval_context,
|
||||
'_': _,
|
||||
'React': React
|
||||
});
|
||||
TEMPLATES[view] = context[view];
|
||||
TEMPLATES[view] = eval_context[view];
|
||||
fnResolve();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {browserHistory} from 'react-router';
|
||||
import app from './../../app';
|
||||
|
||||
app();
|
||||
app(browserHistory);
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
// All react templates should be pre-compiled for development.
|
||||
// run 'gulp compile_react_templates'
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import aboutRt from './../../dashboard/about/about.rt.js';
|
||||
import houseRt from './../../dashboard/house/house.rt.js';
|
||||
import layoutRt from './../../dashboard/layout/layout.rt.js';
|
||||
import energyRt from './../../dashboard/energy/energy.rt.js';
|
||||
import energyGraphRt from './../../dashboard/energy/graph/graph.rt.js';
|
||||
import energyTableRt from './../../dashboard/energy/table/table.rt.js';
|
||||
import powerRt from './../../dashboard/power/power.rt.js';
|
||||
import powerGraphRt from './../../dashboard/power/graph/graph.rt.js';
|
||||
import powerTableRt from './../../dashboard/power/table/table.rt.js';
|
||||
|
||||
const TEMPLATES = Object.freeze({
|
||||
const TEMPLATES = {
|
||||
about: aboutRt,
|
||||
house: houseRt,
|
||||
layout: layoutRt,
|
||||
energy: energyRt,
|
||||
energy_graph: energyGraphRt,
|
||||
energy_table: energyTableRt,
|
||||
power: powerRt,
|
||||
});
|
||||
power_graph: powerGraphRt,
|
||||
power_table: powerTableRt
|
||||
};
|
||||
|
||||
class Templates {
|
||||
|
||||
|
||||
13
client/dashboard/about/about.component.js
Normal file
13
client/dashboard/about/about.component.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
class AboutComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var aboutRt = Templates.forComponent('about');
|
||||
return aboutRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AboutComponent;
|
||||
18
client/dashboard/about/about.rt
Normal file
18
client/dashboard/about/about.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<div id="about">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">About</div>
|
||||
<div class="panel-body">
|
||||
<p>This is a Spike bundle prototype using the following lirbaries:</p>
|
||||
<ul>
|
||||
<li>React</li>
|
||||
<li>React Templates</li>
|
||||
<li>React Router</li>
|
||||
<li>LokiJs - persisting API calls to indexedDb</li>
|
||||
<li>Webpack - hot mode developing and app bundling</li>
|
||||
<li>Babel - ES6 transpiler</li>
|
||||
</ul>
|
||||
<p>The demo app consists of a dataset of 10 houses and 10 years of randomly generated power consumption and production at 15 minute intervals. You can toggle between different houses and time periods to compare and contrast the dataset.</p>
|
||||
<p>Select a house below to get started.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
5
client/dashboard/about/about.rt.js
Normal file
5
client/dashboard/about/about.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'about' }, React.createElement('div', { 'className': 'panel panel-default' }, React.createElement('div', { 'className': 'panel-heading' }, 'About'), React.createElement('div', { 'className': 'panel-body' }, React.createElement('p', {}, 'This is a Spike bundle prototype using the following lirbaries:'), React.createElement('ul', {}, React.createElement('li', {}, 'React'), React.createElement('li', {}, 'React Templates'), React.createElement('li', {}, 'React Router'), React.createElement('li', {}, 'LokiJs - persisting API calls to indexedDb'), React.createElement('li', {}, 'Webpack - hot mode developing and app bundling'), React.createElement('li', {}, 'Babel - ES6 transpiler')), React.createElement('p', {}, 'The demo app consists of a dataset of 10 houses and 10 years of randomly generated power consumption and production at 15 minute intervals. You can toggle between different houses and time periods to compare and contrast the dataset.'), React.createElement('p', {}, 'Select a house below to get started.'))));
|
||||
};
|
||||
77
client/dashboard/energy/energy.component.js
Normal file
77
client/dashboard/energy/energy.component.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class EnergyComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
var energy = this;
|
||||
energy.state = {
|
||||
loading_energy_data: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var energy = this,
|
||||
house = energy.context.house;
|
||||
if (!house || energy.context.loading_energy_data) return false;
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
energy.setState({loading_energy_data: false});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var energy = this,
|
||||
house = energy.context.house;
|
||||
if (!house) return false;
|
||||
if (!prev_context.house ||
|
||||
prev_context.house.data.id != energy.context.house.data.id ||
|
||||
!house.matchesYearState(prev_props.params)) {
|
||||
energy.setState({loading_energy_data: true});
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
energy.setState({loading_energy_data: false}); // will update graph or table.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var energy = this,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
override[param] = value;
|
||||
route_helper = new RouteHelper(energy.context.house, energy.props);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
energy.context.router.push(makeRoute(house, energy.props, override));
|
||||
}
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {
|
||||
loading_energy_data: layout.state.loading_energy_data
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
var energyRt = Templates.forComponent('energy');
|
||||
return energyRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EnergyComponent.childContextTypes = {
|
||||
loading_energy_data: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
EnergyComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default EnergyComponent;
|
||||
@@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import CalendarGridChart from './../../d3/grid/calendar_grid';
|
||||
|
||||
var Energy = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var energy = this;
|
||||
return {
|
||||
graph_attr: 'production',
|
||||
loading_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
energy.graph_title = 'Daily Consumption';
|
||||
house.setEnergyData().then(()=>{
|
||||
energy.setState({loading_data: false});
|
||||
if (energy.props.view === 'graph') energy.initGraph();
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var energy = this;
|
||||
if (new_props.house !== energy.props.house){
|
||||
energy.setState({loading_data: true});
|
||||
new_props.house.setEnergyData().then(()=>{
|
||||
energy.setState({loading_data: false});
|
||||
if (energy.props.view === 'graph') energy.initGraph();
|
||||
});
|
||||
}
|
||||
if (new_props.view !== 'graph' && energy.props.view === 'graph') energy.destroyGraph();
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prev_props, _prev_state){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
if (prev_props.view !== 'graph' && energy.props.view === 'graph') energy.initGraph();
|
||||
if (prev_props.year !== energy.props.year){
|
||||
energy.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
setGraphAttr: function(event){
|
||||
var energy = this,
|
||||
graph_attr = event.target.dataset.value;
|
||||
if (graph_attr !== energy.state.graph_attr){
|
||||
energy.graph_title = 'Daily ' + event.target.innerText;
|
||||
energy.setState({
|
||||
graph_attr: graph_attr
|
||||
}, function(){
|
||||
if (energy.props.view === 'graph') energy.updateGraph();
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
initGraph: function(){
|
||||
var energy = this;
|
||||
if (energy.graph === undefined){
|
||||
energy.graph = new CalendarGridChart({
|
||||
container: '#energy_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 300,
|
||||
date_attr: 'day',
|
||||
color: '#0404B4',
|
||||
toDate: (energy_datum)=>{ return energy_datum.day_to_date; }
|
||||
});
|
||||
jQuery('#energy_graph').tooltip({
|
||||
selector: '.d3-chart-grid-unit',
|
||||
container: 'body',
|
||||
title: function(){
|
||||
var energy_datum = this.__data__,
|
||||
date_s = d3.time.format('%a %b %d, %Y')(energy_datum.day_to_date),
|
||||
range_value = `${Math.round(energy_datum.data[energy.state.graph_attr])} kWh`;
|
||||
return `${date_s}: ${range_value}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
energy.updateGraph();
|
||||
},
|
||||
|
||||
updateGraph: function(){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
energy.graph.rangeValue = (datum)=>{ return datum.data[energy.state.graph_attr]; }
|
||||
energy.graph.drawData({
|
||||
title: energy.graph_title,
|
||||
css_class: '',
|
||||
min_range: 0,
|
||||
max_range: 150,
|
||||
values: house.energy_data
|
||||
});
|
||||
},
|
||||
|
||||
destroyGraph: function(){
|
||||
var energy = this;
|
||||
document.getElementById('energy_graph').innerHTML = '';
|
||||
energy.graph = undefined;
|
||||
},
|
||||
|
||||
updateCurrentMonth: function(){
|
||||
var energy = this,
|
||||
house = energy.props.house;
|
||||
house.setEnergyData()
|
||||
.then(()=>{
|
||||
if (energy.props.view === 'graph'){
|
||||
// no update necessary since year already updated in layout.rt.
|
||||
energy.updateGraph();
|
||||
} else {
|
||||
// force update to render correct data in table.
|
||||
energy.forceUpdate();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var energyRt = Templates.forComponent('energy');
|
||||
return energyRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Energy;
|
||||
@@ -1,39 +1,23 @@
|
||||
<div id="energy_view">
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving energy data for the {this.props.house.name} household...
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_energy_data">
|
||||
Retrieving energy data...
|
||||
</div>
|
||||
<div rt-if="this.props.view === 'graph'">
|
||||
<h4>Select Data</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="graph_attr"
|
||||
data-value="consumption"
|
||||
rt-class="{active: this.state.graph_attr === 'consumption'}"
|
||||
onClick="{this.setGraphAttr}"
|
||||
onClick="{this.setAttr}"
|
||||
type="button" class="btn btn-primary">Consumption</button>
|
||||
<button
|
||||
data-param="graph_attr"
|
||||
data-value="production"
|
||||
rt-class="{active: this.state.graph_attr === 'production'}"
|
||||
onClick="{this.setGraphAttr}"
|
||||
type="button" class="btn btn-primary">Production</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table" rt-if="this.props.view === 'table'">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Day</th>
|
||||
<th>Consumption (kWh)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="energy_datum in this.props.house.energy_data" key="{energy_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{energy_datum.day_to_s}</td>
|
||||
<td>{energy_datum.consumption_to_s}</td>
|
||||
<td>{energy_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div rt-if="this.props.view === 'graph'" id="energy_graph"></div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatEnergy_datum1(energy_datum, energy_datumIndex) {
|
||||
return React.createElement('tr', { 'key': energy_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, energy_datum.day_to_s), React.createElement('td', {}, energy_datum.consumption_to_s), React.createElement('td', {}, energy_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'energy_view' }, this.state.loading_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving energy data for the ', this.props.house.name, ' household...\n ') : null, this.props.view === 'graph' ? React.createElement('div', {}, React.createElement('h4', {}, 'Select Data'), React.createElement('div', {
|
||||
return React.createElement('div', { 'id': 'energy_view' }, this.state.loading_energy_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving energy data...\n ') : null, this.props.view === 'graph' ? React.createElement('div', {}, React.createElement('h4', {}, 'Select Data'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'graph_attr',
|
||||
'data-value': 'consumption',
|
||||
'className': _.keys(_.pick({ active: this.state.graph_attr === 'consumption' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setGraphAttr,
|
||||
'onClick': this.setAttr,
|
||||
'type': 'button'
|
||||
}, 'Consumption'), React.createElement('button', {
|
||||
'data-param': 'graph_attr',
|
||||
'data-value': 'production',
|
||||
'className': _.keys(_.pick({ active: this.state.graph_attr === 'production' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setGraphAttr,
|
||||
'type': 'button'
|
||||
}, 'Production'))) : null, this.props.view === 'table' ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Day'), React.createElement('th', {}, 'Consumption (kWh)'), React.createElement('th', {}, 'Production (kWh)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.props.house.energy_data, repeatEnergy_datum1.bind(this))
|
||||
])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'energy_graph' }) : null);
|
||||
}, 'Production'))) : null, '\n ', this.props.children, '\n');
|
||||
};
|
||||
76
client/dashboard/energy/graph/graph.component.js
Normal file
76
client/dashboard/energy/graph/graph.component.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import CalendarGridChart from './../../../d3/grid/calendar_grid';
|
||||
import House from './../../../models/house';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
componentDidMount(){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house;
|
||||
if (!energy_graph.context.loading_energy_data) energy_graph.updateGraph();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house;
|
||||
if (energy_graph.context.loading_energy_data) {return false;}
|
||||
if (!prev_context.house ||
|
||||
prev_context.loading_energy_data ||
|
||||
prev_context.house.id != energy_graph.context.house.id) {
|
||||
energy_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateGraph(){
|
||||
var energy_graph = this,
|
||||
house = energy_graph.context.house,
|
||||
graph_attr = energy_graph.props.params.graph_attr;
|
||||
|
||||
if (energy_graph.graph === undefined){
|
||||
energy_graph.graph = new CalendarGridChart({
|
||||
container: '#energy_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 300,
|
||||
date_attr: 'day',
|
||||
color: '#0404B4',
|
||||
toDate: (energy_datum)=>{ return energy_datum.day_to_date; }
|
||||
});
|
||||
jQuery('#energy_graph').tooltip({
|
||||
selector: '.d3-chart-grid-unit',
|
||||
container: 'body',
|
||||
title: function(){
|
||||
var energy_datum = this.__data__,
|
||||
date_s = d3.time.format('%a %b %d, %Y')(energy_datum.day_to_date),
|
||||
range_value = `${Math.round(energy_datum.data[graph_attr])} kWh`;
|
||||
return `${date_s}: ${range_value}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
energy_graph.graph.rangeValue = (datum)=>{ return datum.data[graph_attr]; }
|
||||
energy_graph.graph.drawData({
|
||||
title: energy_graph.graph_title,
|
||||
css_class: '',
|
||||
min_range: 0,
|
||||
max_range: 150,
|
||||
values: house.energy_data
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
var energyGraphRt = Templates.forComponent('energy_graph');
|
||||
return energyGraphRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GraphComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
loading_energy_data: React.PropTypes.bool.isRequired,
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default GraphComponent;
|
||||
1
client/dashboard/energy/graph/graph.rt
Normal file
1
client/dashboard/energy/graph/graph.rt
Normal file
@@ -0,0 +1 @@
|
||||
<div id="energy_graph"></div>
|
||||
5
client/dashboard/energy/graph/graph.rt.js
Normal file
5
client/dashboard/energy/graph/graph.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'energy_graph' });
|
||||
};
|
||||
20
client/dashboard/energy/table/table.component.js
Normal file
20
client/dashboard/energy/table/table.component.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
|
||||
class TableComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var tableRt = Templates.forComponent('energy_table');
|
||||
return tableRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TableComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default TableComponent;
|
||||
18
client/dashboard/energy/table/table.rt
Normal file
18
client/dashboard/energy/table/table.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<table rt-if="this.context.house" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Day</th>
|
||||
<th>Consumption (kWh)</th>
|
||||
<th>Production (kWh)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="energy_datum in this.context.house.energy_data" key="{energy_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{energy_datum.day_to_s}</td>
|
||||
<td>{energy_datum.consumption_to_s}</td>
|
||||
<td>{energy_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
12
client/dashboard/energy/table/table.rt.js
Normal file
12
client/dashboard/energy/table/table.rt.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatEnergy_datum1(energy_datum, energy_datumIndex) {
|
||||
return React.createElement('tr', { 'key': energy_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, energy_datum.day_to_s), React.createElement('td', {}, energy_datum.consumption_to_s), React.createElement('td', {}, energy_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return this.context.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Day'), React.createElement('th', {}, 'Consumption (kWh)'), React.createElement('th', {}, 'Production (kWh)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.context.house.energy_data, repeatEnergy_datum1.bind(this))
|
||||
])) : null;
|
||||
};
|
||||
64
client/dashboard/house/house.component.js
Normal file
64
client/dashboard/house/house.component.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import {RouteHelper} from './../routes';
|
||||
import EnergyComponent from './../energy/energy.component';
|
||||
import PowerComponent from './../power/power.component';
|
||||
|
||||
class HouseComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.renders = 0;
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var house_component = this,
|
||||
house = house_component.context.house,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
update[param] = value;
|
||||
route_helper = new RouteHelper(house, house_component.props, update);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
if (house_component.renders < 10){
|
||||
house_component.context.router.push(route_helper.newRoute());
|
||||
house_component.renders += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graphSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.graphSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
tableSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.tableSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
energySelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.energySelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
powerSelected(){
|
||||
var house_component = this;
|
||||
return RouteHelper.powerSelected(house_component.props.routes);
|
||||
}
|
||||
|
||||
render() {
|
||||
var houseRt = Templates.forComponent('house');
|
||||
return houseRt.call(this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
HouseComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default HouseComponent;
|
||||
49
client/dashboard/house/house.rt
Normal file
49
client/dashboard/house/house.rt
Normal file
@@ -0,0 +1,49 @@
|
||||
<div id="house">
|
||||
<h4>Select dataset:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="energy"
|
||||
rt-class="{active: this.energySelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Daily Energy Statistics</button>
|
||||
<button
|
||||
data-param="dataset"
|
||||
data-value="power"
|
||||
rt-class="{active: this.powerSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
</div>
|
||||
|
||||
<h4>View as:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-param="view"
|
||||
data-value="graph"
|
||||
rt-class="{active: this.graphSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Graph</button>
|
||||
<button
|
||||
data-param="view"
|
||||
data-value="table"
|
||||
rt-class="{active: this.tableSelected()}"
|
||||
onClick="{this.setParam.bind(this)}"
|
||||
type="button" class="btn btn-primary">Table</button>
|
||||
</div>
|
||||
|
||||
<div rt-if="this.context.house">
|
||||
<h4>Select dates:</h4>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-repeat="year in this.context.house.years"
|
||||
data-param="year"
|
||||
data-value="{year}"
|
||||
key="data-year-{year}"
|
||||
class="btn-info btn btn-sm"
|
||||
rt-class="{active: year == this.context.house.state.year}"
|
||||
onClick="{this.setParam.bind(this)}">{year}</button>
|
||||
</div>
|
||||
</div><br/>
|
||||
|
||||
{this.props.children}
|
||||
</div>
|
||||
48
client/dashboard/house/house.rt.js
Normal file
48
client/dashboard/house/house.rt.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatYear1(year, yearIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-param': 'year',
|
||||
'data-value': year,
|
||||
'key': 'data-year-' + year,
|
||||
'className': 'btn-info btn btn-sm' + ' ' + _.keys(_.pick({ active: year == this.context.house.state.year }, _.identity)).join(' '),
|
||||
'onClick': this.setParam.bind(this)
|
||||
}, year);
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'house' }, React.createElement('h4', {}, 'Select dataset:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'dataset',
|
||||
'data-value': 'energy',
|
||||
'className': _.keys(_.pick({ active: this.energySelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Daily Energy Statistics'), React.createElement('button', {
|
||||
'data-param': 'dataset',
|
||||
'data-value': 'power',
|
||||
'className': _.keys(_.pick({ active: this.powerSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, '15-minute Power Statistics')), React.createElement('h4', {}, 'View as:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-param': 'view',
|
||||
'data-value': 'graph',
|
||||
'className': _.keys(_.pick({ active: this.graphSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Graph'), React.createElement('button', {
|
||||
'data-param': 'view',
|
||||
'data-value': 'table',
|
||||
'className': _.keys(_.pick({ active: this.tableSelected() }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setParam.bind(this),
|
||||
'type': 'button'
|
||||
}, 'Table')), this.context.house ? React.createElement('div', {}, React.createElement('h4', {}, 'Select dates:'), React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
_.map(this.context.house.years, repeatYear1.bind(this))
|
||||
])) : null, React.createElement('br', {}), '\n\n ', this.props.children, '\n');
|
||||
};
|
||||
84
client/dashboard/layout/layout.component.js
Normal file
84
client/dashboard/layout/layout.component.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import PowerDatum from './../../models/power_datum';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class LayoutComponent extends React.Component {
|
||||
|
||||
constructor(props, context){
|
||||
super(props, context);
|
||||
this.renders = 0;
|
||||
this.state = {
|
||||
houses: null,
|
||||
house: null,
|
||||
requesting_data: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var layout = this;
|
||||
House.ensureHouses().then((houses)=>{
|
||||
var house = null;
|
||||
if (layout.props.params.house_id != undefined){
|
||||
house = houses.find((h)=>{ return h.data.id == layout.props.params.house_id; });
|
||||
var route_helper = new RouteHelper(house, layout.props);
|
||||
if (route_helper.paramsHaveDateState()) route_helper.updateHouseToParams();
|
||||
}
|
||||
layout.setState({
|
||||
houses: houses,
|
||||
requesting_data: false,
|
||||
house: house });
|
||||
});
|
||||
}
|
||||
|
||||
setHouse(event){
|
||||
var layout = this,
|
||||
house_id = event.target.value,
|
||||
old_house = layout.state.house,
|
||||
house = layout.state.houses.find((house)=>{ return house.data.id == house_id });
|
||||
if (!old_house || old_house.id != house_id){
|
||||
var route_helper = new RouteHelper(house, layout.props);
|
||||
route_helper.updateHouseToParams();
|
||||
layout.setState({house: house}, ()=>{
|
||||
if (layout.renders < 10){
|
||||
layout.context.router.push(route_helper.newRoute());
|
||||
layout.renders += 1
|
||||
}
|
||||
if (old_house) old_house.closeDb();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refreshData(){
|
||||
var layout = this,
|
||||
houses = layout.state.houses,
|
||||
all = [];
|
||||
houses.forEach((house)=>{
|
||||
all.push(house.clearData());
|
||||
});
|
||||
Promise.all(all)
|
||||
.then(()=>{
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {house: layout.state.house};
|
||||
}
|
||||
|
||||
render() {
|
||||
var layoutRt = Templates.forComponent('layout');
|
||||
return layoutRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutComponent.contextTypes = {
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
LayoutComponent.childContextTypes = {
|
||||
house: React.PropTypes.instanceOf(House)
|
||||
};
|
||||
export default LayoutComponent;
|
||||
@@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import PowerDatum from './../../models/power_datum';
|
||||
|
||||
var Layout = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var layout = this;
|
||||
return {
|
||||
houses: null,
|
||||
house: null,
|
||||
view: 'graph',
|
||||
dataset: 'power',
|
||||
requesting_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var layout = this;
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
House.ensureHouses().then((houses)=>{
|
||||
layout.setState({
|
||||
houses: houses,
|
||||
house: houses[0],
|
||||
requesting_data: false,
|
||||
month: houses[0].current_month,
|
||||
year: houses[0].current_year
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setHouse: function(event){
|
||||
var layout = this,
|
||||
house_id = event.target.value,
|
||||
old_house = layout.state.house,
|
||||
house = layout.state.houses.find((house)=>{ return house.data.id == house_id });
|
||||
layout.setState({house: house}, ()=>{
|
||||
old_house.closeDb();
|
||||
});
|
||||
},
|
||||
|
||||
setView: function(event){
|
||||
var layout = this,
|
||||
view = event.target.dataset.value;
|
||||
layout.view_name = event.target.innerText;
|
||||
layout.setState({view: view});
|
||||
},
|
||||
|
||||
setDataset: function(event){
|
||||
var layout = this,
|
||||
dataset = event.target.dataset.value;
|
||||
layout.setState({dataset: dataset});
|
||||
},
|
||||
|
||||
setYear: function(event){
|
||||
var layout = this,
|
||||
year = event.target.dataset.value,
|
||||
house = layout.state.house;
|
||||
if (year != house.current_year){
|
||||
house.setYear(year);
|
||||
layout.setState({year: year});
|
||||
}
|
||||
},
|
||||
|
||||
refreshData: function(){
|
||||
var layout = this,
|
||||
houses = layout.state.houses,
|
||||
all = [];
|
||||
houses.forEach((house)=>{
|
||||
all.push(house.clearData());
|
||||
});
|
||||
Promise.all(all)
|
||||
.then(()=>{
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var layoutRt = Templates.forComponent('layout');
|
||||
return layoutRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Layout;
|
||||
@@ -1,60 +1,11 @@
|
||||
<rt-require dependency="./../energy/energy" as="Energy"/>
|
||||
<rt-require dependency="./../power/power" as="Power"/>
|
||||
<div id="layout">
|
||||
<div class="alert alert-warning" rt-if="this.state.requesting_data">Retrieving houses...</div>
|
||||
|
||||
<h4>Select household:</h4>
|
||||
<select rt-if="this.state.houses" class="form-control" onChange="{this.setHouse}">
|
||||
<select id="houses_select" rt-if="this.state.houses" class="form-control" onChange="{this.setHouse.bind(this)}" value="{this.props.params.house_id}">
|
||||
<option rt-repeat="house in this.state.houses" value="{house.data.id}" key="{house.scoped_id}">{house.data.name}</option>
|
||||
</select>
|
||||
<button rt-if="this.state.house" onClick="{this.refreshData}" class="btn btn-xs btn-default">Refresh House Data</button>
|
||||
<button rt-if="this.state.house" onClick="{this.refreshData.bind(this)}" class="btn btn-xs btn-default">Refresh House Data</button>
|
||||
|
||||
<h4>Select dataset:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-value="energy"
|
||||
rt-class="{active: this.state.dataset === 'energy'}"
|
||||
onClick="{this.setDataset}"
|
||||
type="button" class="btn btn-primary">Daily Energy Statistics</button>
|
||||
<button
|
||||
data-value="power"
|
||||
rt-class="{active: this.state.dataset === 'power'}"
|
||||
onClick="{this.setDataset}"
|
||||
type="button" class="btn btn-primary">15-minute Power Statistics</button>
|
||||
</div>
|
||||
|
||||
<h4>View as:</h4>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
data-value="graph"
|
||||
rt-class="{active: this.state.view === 'graph'}"
|
||||
onClick="{this.setView}"
|
||||
type="button" class="btn btn-primary">Graph</button>
|
||||
<button
|
||||
data-value="table"
|
||||
rt-class="{active: this.state.view === 'table'}"
|
||||
onClick="{this.setView}"
|
||||
type="button" class="btn btn-primary">Table</button>
|
||||
</div>
|
||||
|
||||
<h4>Select dates:</h4>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.state.house"
|
||||
rt-repeat="year in this.state.house.years"
|
||||
data-value="{year}"
|
||||
key="data-year-{year}"
|
||||
class="btn-info btn btn-sm"
|
||||
rt-class="{active: year == this.state.house.current_year}"
|
||||
onClick="{this.setYear}">{year}</button>
|
||||
</div><br/>
|
||||
|
||||
<Energy rt-if="this.state.house && this.state.dataset === 'energy'"
|
||||
house="{this.state.house}"
|
||||
view="{this.state.view}"
|
||||
year="{this.state.year}"></Energy>
|
||||
<Power rt-if="this.state.house && this.state.dataset === 'power'"
|
||||
house="{this.state.house}"
|
||||
view="{this.state.view}"
|
||||
year="{this.state.year}"></Power>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -1,69 +1,23 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Energy from './../energy/energy';
|
||||
import Power from './../power/power';
|
||||
function repeatHouse1(house, houseIndex) {
|
||||
return React.createElement('option', {
|
||||
'value': house.data.id,
|
||||
'key': house.scoped_id
|
||||
}, house.data.name);
|
||||
}
|
||||
function repeatYear2(year, yearIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-value': year,
|
||||
'key': 'data-year-' + year,
|
||||
'className': 'btn-info btn btn-sm' + ' ' + _.keys(_.pick({ active: year == this.state.house.current_year }, _.identity)).join(' '),
|
||||
'onClick': this.setYear
|
||||
}, year);
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'layout' }, this.state.requesting_data ? React.createElement('div', { 'className': 'alert alert-warning' }, 'Retrieving houses...') : null, React.createElement('h4', {}, 'Select household:'), this.state.houses ? React.createElement.apply(this, [
|
||||
'select',
|
||||
{
|
||||
'id': 'houses_select',
|
||||
'className': 'form-control',
|
||||
'onChange': this.setHouse
|
||||
'onChange': this.setHouse.bind(this),
|
||||
'value': this.props.params.house_id
|
||||
},
|
||||
_.map(this.state.houses, repeatHouse1.bind(this))
|
||||
]) : null, this.state.house ? React.createElement('button', {
|
||||
'onClick': this.refreshData,
|
||||
'onClick': this.refreshData.bind(this),
|
||||
'className': 'btn btn-xs btn-default'
|
||||
}, 'Refresh House Data') : null, React.createElement('h4', {}, 'Select dataset:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-value': 'energy',
|
||||
'className': _.keys(_.pick({ active: this.state.dataset === 'energy' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setDataset,
|
||||
'type': 'button'
|
||||
}, 'Daily Energy Statistics'), React.createElement('button', {
|
||||
'data-value': 'power',
|
||||
'className': _.keys(_.pick({ active: this.state.dataset === 'power' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setDataset,
|
||||
'type': 'button'
|
||||
}, '15-minute Power Statistics')), React.createElement('h4', {}, 'View as:'), React.createElement('div', {
|
||||
'className': 'btn-group',
|
||||
'role': 'group'
|
||||
}, React.createElement('button', {
|
||||
'data-value': 'graph',
|
||||
'className': _.keys(_.pick({ active: this.state.view === 'graph' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setView,
|
||||
'type': 'button'
|
||||
}, 'Graph'), React.createElement('button', {
|
||||
'data-value': 'table',
|
||||
'className': _.keys(_.pick({ active: this.state.view === 'table' }, _.identity)).join(' ') + ' ' + 'btn btn-primary',
|
||||
'onClick': this.setView,
|
||||
'type': 'button'
|
||||
}, 'Table')), React.createElement('h4', {}, 'Select dates:'), React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
this.state.house ? _.map(this.state.house.years, repeatYear2.bind(this)) : null
|
||||
]), React.createElement('br', {}), this.state.house && this.state.dataset === 'energy' ? React.createElement(Energy, {
|
||||
'house': this.state.house,
|
||||
'view': this.state.view,
|
||||
'year': this.state.year
|
||||
}) : null, this.state.house && this.state.dataset === 'power' ? React.createElement(Power, {
|
||||
'house': this.state.house,
|
||||
'view': this.state.view,
|
||||
'year': this.state.year
|
||||
}) : null);
|
||||
}, 'Refresh House Data') : null, '\n\n ', this.props.children, '\n');
|
||||
};
|
||||
91
client/dashboard/power/graph/graph.component.js
Normal file
91
client/dashboard/power/graph/graph.component.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
import SplineStackChart from './../../../d3/line/spline_stack';
|
||||
|
||||
class GraphComponent extends React.Component {
|
||||
|
||||
componentDidMount(){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
power_graph.graph_title = ' ';
|
||||
if (!power_graph.context.loading_power_data) power_graph.updateGraph();
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
if (power_graph.context.loading_power_data) {return false;}
|
||||
if (!prev_context.house ||
|
||||
prev_context.loading_power_data ||
|
||||
prev_context.house.id != power_graph.context.house.id) {
|
||||
power_graph.updateGraph();
|
||||
}
|
||||
}
|
||||
|
||||
updateGraph(){
|
||||
var power_graph = this,
|
||||
house = power_graph.context.house;
|
||||
if (power_graph.graph === undefined){
|
||||
power_graph.graph = new SplineStackChart({
|
||||
container: '#power_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 200,
|
||||
color: '#0404B4',
|
||||
time_series: true,
|
||||
domain_attr: 'x',
|
||||
range_attr: 'y',
|
||||
include_dots: true,
|
||||
titleizeDatum: (series, d)=>{
|
||||
return series.title + '<br/>' + Math.round(d.y) + ' W<br/>' + house.formatDate(d.power_graph_datum.data.time, 'MMM D [at] HH:mm');
|
||||
}
|
||||
});
|
||||
jQuery('#power_graph').tooltip({
|
||||
selector: 'circle',
|
||||
container: 'body',
|
||||
html: true,
|
||||
title: function(){
|
||||
return this.__data__.title;
|
||||
}
|
||||
});
|
||||
}
|
||||
var net_power_graph = {
|
||||
title: 'Net Power Consumption',
|
||||
values: house.power_data.map((power_graph_datum)=>{
|
||||
return {
|
||||
power_graph_datum: power_graph_datum,
|
||||
x: power_graph_datum.time_to_date,
|
||||
y: Math.max(0, power_graph_datum.data.consumption - power_graph_datum.data.production) }
|
||||
})
|
||||
},
|
||||
savings = {
|
||||
title: 'Power Production',
|
||||
values: house.power_data.map((power_graph_datum)=>{
|
||||
return {
|
||||
power_graph_datum: power_graph_datum,
|
||||
x: power_graph_datum.time_to_date,
|
||||
y: power_graph_datum.data.production }
|
||||
})
|
||||
};
|
||||
power_graph.graph.drawData({
|
||||
title: power_graph.graph_title,
|
||||
css_class: '',
|
||||
series: [net_power_graph, savings]
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
var powerGraphRt = Templates.forComponent('power_graph');
|
||||
return powerGraphRt.call(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GraphComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
loading_power_data: React.PropTypes.bool.isRequired,
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default GraphComponent;
|
||||
1
client/dashboard/power/graph/graph.rt
Normal file
1
client/dashboard/power/graph/graph.rt
Normal file
@@ -0,0 +1 @@
|
||||
<div id="power_graph"></div>
|
||||
5
client/dashboard/power/graph/graph.rt.js
Normal file
5
client/dashboard/power/graph/graph.rt.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'power_graph' });
|
||||
};
|
||||
120
client/dashboard/power/power.component.js
Normal file
120
client/dashboard/power/power.component.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
import {RouteHelper} from './../routes';
|
||||
|
||||
class PowerComponent extends React.Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
var power = this;
|
||||
power.state = {
|
||||
loading_power_data: true };
|
||||
power.updates = 0;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
power.renders = 0;
|
||||
if (!house) return false;
|
||||
power.initDateRange();
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.setState({loading_power_data: false }); });
|
||||
}
|
||||
|
||||
componentDidUpdate(prev_props, prev_state, prev_context){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
if (!house) return false;
|
||||
var route_helper = new RouteHelper(house, power.props);
|
||||
if ((!prev_context.house || prev_context.house.data.id != power.context.house.data.id) ||
|
||||
!house.matchesPowerRange(prev_props.location.query['dates[]'] || [])) {
|
||||
power.setState({loading_power_data: true});
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.initDateRange();
|
||||
power.setState({loading_power_data: false}); // will update graph or table.
|
||||
});
|
||||
} else if (!house.matchesMonthState(prev_props.params)) power.initDateRange();
|
||||
}
|
||||
|
||||
initDateRange(){
|
||||
var power = this,
|
||||
house = power.context.house;
|
||||
if (power.date_range_slider === undefined){
|
||||
power.date_range_slider = new DateRangeSlider({
|
||||
container: '#power_date_setter',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 4 * 1000){
|
||||
if (changed_date > other_date){
|
||||
return new Date(changed_date.getTime() - 3600 * 24 * 4 * 1000);
|
||||
} else {
|
||||
return new Date(changed_date.getTime() + 3600 * 24 * 4 * 1000);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.date_range_slider.onRangeUpdated = (min, max)=>{
|
||||
if (power.date_range_update) clearTimeout(power.date_range_update);
|
||||
power.date_range_update = setTimeout(()=>{
|
||||
var power_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)],
|
||||
route_helper = new RouteHelper(house, power.props, {power_range: power_range});
|
||||
|
||||
route_helper.updateHouseState();
|
||||
power.context.router.push(route_helper.newRoute());
|
||||
}, 500);
|
||||
};
|
||||
power.date_range_slider.drawData({
|
||||
abs_min: house.state.current_month_moment.toDate(),
|
||||
abs_max: house.state.end_of_current_data_moment.toDate(),
|
||||
current_min: house.toDate(house.state.power_range[0]),
|
||||
current_max: house.toDate(house.state.power_range[1])
|
||||
});
|
||||
}
|
||||
|
||||
setParam(event){
|
||||
var power = this,
|
||||
house = power.context.house,
|
||||
param = event.target.dataset.param,
|
||||
value = event.target.dataset.value,
|
||||
update = {}, route_helper;
|
||||
update[param] = value;
|
||||
route_helper = new RouteHelper(house, power.props, update);
|
||||
if (route_helper.routeUpdated()){
|
||||
route_helper.updateHouseState();
|
||||
power.context.router.push(route_helper.newRoute());
|
||||
}
|
||||
}
|
||||
|
||||
getChildContext(){
|
||||
var layout = this;
|
||||
return {
|
||||
loading_power_data: layout.state.loading_power_data
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
var powerRt = Templates.forComponent('power');
|
||||
return powerRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
PowerComponent.childContextTypes = {
|
||||
loading_power_data: React.PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
PowerComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default PowerComponent;
|
||||
@@ -1,195 +0,0 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Templates from 'config/templates';
|
||||
import House from './../../models/house';
|
||||
import SplineStackChart from './../../d3/line/spline_stack';
|
||||
import DateRangeSlider from './../../d3/sliders/date_range';
|
||||
|
||||
var Power = React.createClass({
|
||||
|
||||
getInitialState: function(){
|
||||
var power = this;
|
||||
return {
|
||||
loading_data: true
|
||||
};
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
this.setState({windowWidth: window.innerWidth});
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
// window.addEventListener('resize', this.handleResize);
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
power.graph_title = '';
|
||||
power.initDateRange();
|
||||
house.setPowerData().then(()=>{
|
||||
power.setState({loading_data: false});
|
||||
if (power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(new_props){
|
||||
var power = this;
|
||||
if (new_props.house !== power.props.house){
|
||||
// house will change.
|
||||
power.setState({loading_data: true});
|
||||
new_props.house.setPowerData().then(()=>{
|
||||
power.setState({loading_data: false});
|
||||
if (power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
});
|
||||
}
|
||||
// view will change from graph to table.
|
||||
if (new_props.view !== 'graph' && power.props.view === 'graph') power.destroyGraph();
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prev_props, _prev_state){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
// view has changed from graph to table.
|
||||
if (prev_props.view !== 'graph' && power.props.view === 'graph'){
|
||||
power.initGraph();
|
||||
}
|
||||
if (prev_props.house !== house) power.initDateRange();
|
||||
var need_update = false;
|
||||
if (prev_props.year !== power.props.year){
|
||||
power.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
initGraph: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
if (power.graph === undefined){
|
||||
power.graph = new SplineStackChart({
|
||||
container: '#power_graph',
|
||||
outer_width: 800,
|
||||
outer_height: 200,
|
||||
color: '#0404B4',
|
||||
time_series: true,
|
||||
domain_attr: 'x',
|
||||
range_attr: 'y',
|
||||
include_dots: true,
|
||||
titleizeDatum: (series, d)=>{
|
||||
return series.title + '<br/>' + Math.round(d.y) + ' W<br/>' + house.formatDate(d.power_datum.data.time, 'MMM D [at] HH:mm');
|
||||
}
|
||||
});
|
||||
jQuery('#power_graph').tooltip({
|
||||
selector: 'circle',
|
||||
container: 'body',
|
||||
html: true,
|
||||
title: function(){
|
||||
return this.__data__.title;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.updateGraph();
|
||||
},
|
||||
|
||||
updateGraph: function(){
|
||||
var power = this,
|
||||
house = power.props.house,
|
||||
net_power = {
|
||||
title: 'Net Power Consumption',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: Math.max(0, power_datum.data.consumption - power_datum.data.production) }
|
||||
})
|
||||
},
|
||||
savings = {
|
||||
title: 'Power Production',
|
||||
values: house.power_data.map((power_datum)=>{
|
||||
return {
|
||||
power_datum: power_datum,
|
||||
x: power_datum.time_to_date,
|
||||
y: power_datum.data.production }
|
||||
})
|
||||
};
|
||||
power.graph.drawData({
|
||||
title: power.graph_title,
|
||||
css_class: '',
|
||||
series: [net_power, savings]
|
||||
});
|
||||
},
|
||||
|
||||
initDateRange: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
if (power.date_range_slider === undefined){
|
||||
power.date_range_slider = new DateRangeSlider({
|
||||
container: '#power_date_setter',
|
||||
outer_height: 100,
|
||||
maxDelta: function(changed_date, other_date){
|
||||
if (Math.abs(changed_date.getTime() - other_date.getTime()) > 3600 * 24 * 4 * 1000){
|
||||
if (changed_date > other_date){
|
||||
return new Date(changed_date.getTime() - 3600 * 24 * 4 * 1000);
|
||||
} else {
|
||||
return new Date(changed_date.getTime() + 3600 * 24 * 4 * 1000);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
power.date_range_slider.onRangeUpdated = (min, max)=>{
|
||||
if (power.date_range_update) clearTimeout(power.date_range_update)
|
||||
power.date_range_update = setTimeout(()=>{
|
||||
house.power_date_range = [Math.round(min.getTime() / 1000), Math.round(max.getTime() / 1000)]
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
if (power.props.view === 'graph') power.updateGraph();
|
||||
else power.forceUpdate();
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
power.date_range_slider.drawData({
|
||||
abs_min: house.current_month_moment.toDate(),
|
||||
abs_max: house.end_of_current_data_moment.toDate(),
|
||||
current_min: house.toDate(house.power_date_range[0]),
|
||||
current_max: house.toDate(house.power_date_range[1])
|
||||
});
|
||||
},
|
||||
|
||||
destroyGraph: function(){
|
||||
var power = this;
|
||||
document.getElementById('power_graph').innerHTML = '';
|
||||
power.graph = undefined;
|
||||
},
|
||||
|
||||
setMonth: function(event){
|
||||
var power = this,
|
||||
house = power.props.house,
|
||||
month = event.target.dataset.value;
|
||||
if (month !== house.current_month){
|
||||
var need_update = house.setMonth(month);
|
||||
if (need_update) power.updateCurrentMonth();
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentMonth: function(){
|
||||
var power = this,
|
||||
house = power.props.house;
|
||||
power.initDateRange();
|
||||
house.setPowerData()
|
||||
.then(()=>{
|
||||
power.forceUpdate();
|
||||
if (power.props.view === 'graph') power.updateGraph();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var powerRt = Templates.forComponent('power');
|
||||
return powerRt.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
export default Power;
|
||||
@@ -1,35 +1,18 @@
|
||||
<div id="power_view">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
rt-if="this.props.house"
|
||||
rt-repeat="month in this.props.house.availableMonths()"
|
||||
rt-if="this.context.house"
|
||||
rt-repeat="month in this.context.house.availableMonths()"
|
||||
data-param="month"
|
||||
data-value="{month}"
|
||||
key="data-month-{month}"
|
||||
class="btn-warning btn btn-sm"
|
||||
rt-class="{active: month === this.props.house.current_month}"
|
||||
onClick="{this.setMonth}">{month}</button>
|
||||
rt-class="{active: month === this.context.house.state.month}"
|
||||
onClick="{this.setParam.bind(this)}">{month}</button>
|
||||
</div>
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_data">
|
||||
Retrieving power data for the {this.props.house.name} household...
|
||||
<div class="alert alert-warning" rt-if="this.state.loading_power_data">
|
||||
Retrieving power data...
|
||||
</div>
|
||||
<div id="power_date_setter"></div>
|
||||
<table rt-if="this.props.view === 'table'" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Consumption (W)</th>
|
||||
<th>Production (W)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="power_datum in this.props.house.power_data" key="{power_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{power_datum.time_to_s}</td>
|
||||
<td>{power_datum.consumption_to_s}</td>
|
||||
<td>{power_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div rt-if="this.props.view === 'graph'" id="power_graph"></div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
||||
@@ -2,26 +2,17 @@ import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatMonth1(month, monthIndex) {
|
||||
return React.createElement('button', {
|
||||
'data-param': 'month',
|
||||
'data-value': month,
|
||||
'key': 'data-month-' + month,
|
||||
'className': 'btn-warning btn btn-sm' + ' ' + _.keys(_.pick({ active: month === this.props.house.current_month }, _.identity)).join(' '),
|
||||
'onClick': this.setMonth
|
||||
'className': 'btn-warning btn btn-sm' + ' ' + _.keys(_.pick({ active: month === this.context.house.state.month }, _.identity)).join(' '),
|
||||
'onClick': this.setParam.bind(this)
|
||||
}, month);
|
||||
}
|
||||
function repeatPower_datum2(power_datum, power_datumIndex) {
|
||||
return React.createElement('tr', {
|
||||
'className': 'fuck-you',
|
||||
'key': power_datum.scoped_id
|
||||
}, React.createElement('td', {}, power_datum.data.id), React.createElement('td', {}, power_datum.time_to_s), React.createElement('td', {}, power_datum.consumption_to_s), React.createElement('td', {}, power_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return React.createElement('div', { 'id': 'power_view' }, React.createElement.apply(this, [
|
||||
'div',
|
||||
{ 'className': 'btn-group' },
|
||||
this.props.house ? _.map(this.props.house.availableMonths(), repeatMonth1.bind(this)) : null
|
||||
]), this.state.loading_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving power data for the ', this.props.house.name, ' household...\n ') : null, React.createElement('div', { 'id': 'power_date_setter' }), this.props.view === 'table' ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Time'), React.createElement('th', {}, 'Consumption (W)'), React.createElement('th', {}, 'Production (W)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.props.house.power_data, repeatPower_datum2.bind(this))
|
||||
])) : null, this.props.view === 'graph' ? React.createElement('div', { 'id': 'power_graph' }) : null);
|
||||
this.context.house ? _.map(this.context.house.availableMonths(), repeatMonth1.bind(this)) : null
|
||||
]), this.state.loading_power_data ? React.createElement('div', { 'className': 'alert alert-warning' }, '\n Retrieving power data...\n ') : null, React.createElement('div', { 'id': 'power_date_setter' }), '\n ', this.props.children, '\n');
|
||||
};
|
||||
19
client/dashboard/power/table/table.component.js
Normal file
19
client/dashboard/power/table/table.component.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import Templates from 'config/templates';
|
||||
|
||||
import House from './../../../models/house';
|
||||
|
||||
class TableComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
var powerTableRt = Templates.forComponent('power_table');
|
||||
return powerTableRt.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
TableComponent.contextTypes = {
|
||||
house: React.PropTypes.instanceOf(House),
|
||||
router: React.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default TableComponent;
|
||||
18
client/dashboard/power/table/table.rt
Normal file
18
client/dashboard/power/table/table.rt
Normal file
@@ -0,0 +1,18 @@
|
||||
<table class="table" rt-if="this.context.house">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Time</th>
|
||||
<th>Consumption (W)</th>
|
||||
<th>Production (W)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr rt-repeat="power_datum in this.context.house.power_data" key="{power_datum.scoped_id}">
|
||||
<td></td>
|
||||
<td>{power_datum.time_to_s}</td>
|
||||
<td>{power_datum.consumption_to_s}</td>
|
||||
<td>{power_datum.production_to_s}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
12
client/dashboard/power/table/table.rt.js
Normal file
12
client/dashboard/power/table/table.rt.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
function repeatPower_datum1(power_datum, power_datumIndex) {
|
||||
return React.createElement('tr', { 'key': power_datum.scoped_id }, React.createElement('td', {}), React.createElement('td', {}, power_datum.time_to_s), React.createElement('td', {}, power_datum.consumption_to_s), React.createElement('td', {}, power_datum.production_to_s));
|
||||
}
|
||||
export default function () {
|
||||
return this.context.house ? React.createElement('table', { 'className': 'table' }, React.createElement('thead', {}, React.createElement('tr', {}, React.createElement('th', {}), React.createElement('th', {}, 'Time'), React.createElement('th', {}, 'Consumption (W)'), React.createElement('th', {}, 'Production (W)'))), React.createElement.apply(this, [
|
||||
'tbody',
|
||||
{},
|
||||
_.map(this.context.house.power_data, repeatPower_datum1.bind(this))
|
||||
])) : null;
|
||||
};
|
||||
166
client/dashboard/routes.js
Normal file
166
client/dashboard/routes.js
Normal file
@@ -0,0 +1,166 @@
|
||||
import House from './house/house.component'
|
||||
import Power from './power/power.component';
|
||||
import PowerGraph from './power/graph/graph.component';
|
||||
import PowerTable from './power/table/table.component';
|
||||
import Energy from './energy/energy.component';
|
||||
import EnergyGraph from './energy/graph/graph.component';
|
||||
import EnergyTable from './energy/table/table.component';
|
||||
import About from './about/about.component';
|
||||
import Layout from './layout/layout.component';
|
||||
import ArrayUtil from './../../shared/utils/array';
|
||||
|
||||
const POWER_ROUTES = {
|
||||
path: 'power',
|
||||
component: Power,
|
||||
childRoutes: [
|
||||
{path: ':month/:year', component: PowerGraph},
|
||||
{path: ':month/:year/graph', component: PowerGraph},
|
||||
{path: ':month/:year/table', component: PowerTable}
|
||||
]
|
||||
};
|
||||
|
||||
const ENERGY_ROUTES = {
|
||||
path: 'energy',
|
||||
component: Energy,
|
||||
childRoutes: [
|
||||
{path: ':year/:graph_attr', component: EnergyGraph},
|
||||
{path: ':year/:graph_attr/graph', component: EnergyGraph},
|
||||
{path: ':year/:graph_attr/table', component: EnergyTable}
|
||||
]
|
||||
};
|
||||
|
||||
export const ROUTES = [{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
indexRoute: { component: About },
|
||||
childRoutes: [{
|
||||
path: 'houses/:house_id',
|
||||
component: House,
|
||||
childRoutes: [ENERGY_ROUTES, POWER_ROUTES]
|
||||
}]
|
||||
}];
|
||||
|
||||
export class RouteHelper {
|
||||
|
||||
constructor(house, props, update){
|
||||
var route_helper = this;
|
||||
route_helper.house = house;
|
||||
route_helper.props = props;
|
||||
route_helper.update = update || {};
|
||||
}
|
||||
|
||||
get view(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.view || (route_helper.tableSelected() ? 'table' : 'graph');
|
||||
}
|
||||
|
||||
get graph_attr(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.graph_attr || route_helper.props.params.graph_attr || 'consumption';
|
||||
}
|
||||
|
||||
get dataset(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.dataset || (route_helper.energySelected() ? 'energy' : 'power');
|
||||
}
|
||||
|
||||
get power_range(){
|
||||
var route_helper = this;
|
||||
return route_helper.update.power_range || route_helper.props.location.query['dates[]'];
|
||||
}
|
||||
|
||||
get date_params(){
|
||||
var route_helper = this;
|
||||
return {
|
||||
month: route_helper.update.month || route_helper.house.month,
|
||||
year: route_helper.update.year || route_helper.house.year };
|
||||
}
|
||||
|
||||
routeUpdated(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
return (route_helper.energySelected() && !house.matchesYearState(route_helper.date_params)) ||
|
||||
(route_helper.powerSelected() && !house.matchesMonthState(route_helper.date_params) || !house.matchesPowerRange(route_helper.power_range));
|
||||
}
|
||||
|
||||
// This will update the house state acccording to passed update parameters.
|
||||
updateHouseState(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
house.setMonthState(route_helper.date_params, route_helper.update.power_range);
|
||||
}
|
||||
|
||||
paramsHaveDateState(){
|
||||
var route_helper = this;
|
||||
return !!route_helper.props.params.year;
|
||||
}
|
||||
|
||||
// This will update the house according to URL parameters.
|
||||
updateHouseToParams(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house,
|
||||
power_range;
|
||||
if (route_helper.props.location.query['dates[]']){
|
||||
power_range = [];
|
||||
power_range[0] = +route_helper.props.location.query['dates[]'][0];
|
||||
power_range[1] = +route_helper.props.location.query['dates[]'][1];
|
||||
}
|
||||
house.setMonthState(route_helper.props.params, power_range);
|
||||
}
|
||||
|
||||
// should be run AFTER updateHouseState is called.
|
||||
newRoute(){
|
||||
var route_helper = this,
|
||||
house = route_helper.house;
|
||||
if (route_helper.dataset === 'energy'){
|
||||
return `/houses/${house.data.id}/energy/${house.state.year}/${route_helper.graph_attr}/${route_helper.view}`;
|
||||
} else {
|
||||
return `/houses/${house.data.id}/power/${house.state.month}/${house.state.year}/${route_helper.view}?${jQuery.param({dates: house.state.power_range})}`;
|
||||
}
|
||||
}
|
||||
|
||||
graphSelected(){
|
||||
return RouteHelper.graphSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static graphSelected(routes){
|
||||
if (RouteHelper.energySelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === EnergyGraph; });
|
||||
} else if (RouteHelper.powerSelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === PowerGraph; });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
tableSelected(){
|
||||
return RouteHelper.tableSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static tableSelected(routes){
|
||||
if (RouteHelper.energySelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === EnergyTable; });
|
||||
} else if (RouteHelper.powerSelected(routes)){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === PowerTable; });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
energySelected(){
|
||||
return RouteHelper.energySelected(this.props.routes);
|
||||
}
|
||||
|
||||
static energySelected(routes){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === Energy; });
|
||||
}
|
||||
|
||||
powerSelected(){
|
||||
return RouteHelper.powerSelected(this.props.routes);
|
||||
}
|
||||
|
||||
static powerSelected(routes){
|
||||
return ArrayUtil.any(routes, (route)=>{ return route.component === Power; });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class House {
|
||||
constructor(data){
|
||||
var house = this;
|
||||
house.data = data;
|
||||
house.state = {};
|
||||
Object.assign(house, Databasable);
|
||||
|
||||
var n_years = house.data_until_moment.year() - house.data_from_moment.year() + 1;
|
||||
@@ -27,9 +28,10 @@ class House {
|
||||
for (var year=house.data_from_moment.year(); year<=house.data_until_moment.year(); year+=1){
|
||||
house.years.push(year);
|
||||
}
|
||||
house.current_month = house.data_until_moment.format('MMM');
|
||||
house.current_year = house.data_until_moment.year();
|
||||
house.setCurrentMonthMoment();
|
||||
house.setMonthState({
|
||||
month: house.data_until_moment.format('MMM'),
|
||||
year: house.data_until_moment.year()
|
||||
});
|
||||
}
|
||||
|
||||
get data_from_moment(){
|
||||
@@ -42,20 +44,19 @@ class House {
|
||||
return moment.tz(house.data.data_until * 1000, house.data.timezone);
|
||||
}
|
||||
|
||||
get end_of_current_data_moment(){
|
||||
var house = this,
|
||||
end_of_month = house.current_month_moment.clone().endOf('month');
|
||||
return end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month;
|
||||
}
|
||||
|
||||
get scoped_id(){
|
||||
return `house-${this.data.id}`;
|
||||
}
|
||||
|
||||
get select_props(){
|
||||
if (this.selected) return {selected: true};
|
||||
else return {};
|
||||
}
|
||||
|
||||
availableMonths(){
|
||||
var house = this,
|
||||
all_months = moment.monthsShort(),
|
||||
year = house.current_year.toString();
|
||||
year = house.state.year.toString();
|
||||
if ((year) === house.data_from_moment.format('YYYY')){
|
||||
return all_months.slice(house.data_from_moment.month(), 12);
|
||||
} else if (year === house.data_until_moment.format('YYYY')){
|
||||
@@ -65,29 +66,76 @@ class House {
|
||||
}
|
||||
}
|
||||
|
||||
setYear(year){
|
||||
var house = this;
|
||||
house.current_year = year;
|
||||
return house.setCurrentMonthMoment();
|
||||
}
|
||||
|
||||
setMonth(month){
|
||||
var house = this;
|
||||
house.current_month = month;
|
||||
return house.setCurrentMonthMoment();
|
||||
}
|
||||
|
||||
setCurrentMonthMoment(){
|
||||
setMonthState(params, power_ranges){
|
||||
var house = this,
|
||||
month_i = moment.monthsShort().indexOf(house.current_month),
|
||||
new_month_moment = moment.tz({year: house.current_year, month: month_i, day: 1}, house.data.timezone).startOf('month');
|
||||
if (!house.current_month_moment || new_month_moment.unix() !== house.current_month_moment.unix()){
|
||||
house.current_month_moment = new_month_moment;
|
||||
house.power_date_range = [house.end_of_current_data_moment.clone().subtract(4, 'days').unix(), house.end_of_current_data_moment.unix()];
|
||||
house.energy_date_range = [house.end_of_current_data_moment.clone().startOf('year').unix(), house.end_of_current_data_moment.clone().endOf('year').unix()]
|
||||
return true;
|
||||
all_months = moment.monthsShort();
|
||||
|
||||
if (house.state.month !== params.month || house.state.year != params.year){
|
||||
var new_year = +params.year;
|
||||
if (new_year >= house.data_from_moment.year() && new_year <= house.data_until_moment.year()){
|
||||
house.state.year = params.year;
|
||||
} else if (!house.state.year){
|
||||
house.state.year = house.years[house.years.length - 1];
|
||||
}
|
||||
var available_months = house.availableMonths();
|
||||
if (available_months.indexOf(params.month) >= 0){
|
||||
house.state.month = params.month;
|
||||
} else if (!house.state.month || available_months.indexOf(house.state.month) < 0){
|
||||
house.state.month = available_months[available_months.length - 1];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
var month_i = all_months.indexOf(house.state.month),
|
||||
new_month_moment = moment.tz({year: house.state.year, month: month_i, day: 1}, house.data.timezone).startOf('month');
|
||||
if (!house.state.current_month_moment || new_month_moment.unix() !== house.state.current_month_moment.unix()){
|
||||
house.state.current_month_moment = new_month_moment;
|
||||
}
|
||||
house.setDataRanges(power_ranges);
|
||||
}
|
||||
|
||||
setDataRanges(power_ranges){
|
||||
var house = this,
|
||||
end_of_month = house.state.current_month_moment.clone().endOf('month'),
|
||||
end_of_current_data_moment = end_of_month > house.data_until_moment ? house.data_until_moment : end_of_month,
|
||||
energy_max = Math.min(end_of_current_data_moment.clone().endOf('year').unix(), house.data.data_until);
|
||||
house.state.energy_range = [end_of_current_data_moment.clone().startOf('year').unix(), energy_max];
|
||||
house.state.power_range = house.state.power_range || [];
|
||||
house.state.end_of_current_data_moment = end_of_current_data_moment;
|
||||
|
||||
var current_data_range = [house.state.current_month_moment.unix(), end_of_current_data_moment.unix()],
|
||||
power_min = house.state.power_range[0],
|
||||
power_max = house.state.power_range[1];
|
||||
if (power_ranges){
|
||||
if (DateRange.inRange(power_ranges[1], current_data_range)){
|
||||
power_max = power_ranges[1];
|
||||
}
|
||||
if (DateRange.inRange(power_ranges[0], current_data_range) && power_ranges[0] < power_max){
|
||||
power_min = power_ranges[0];
|
||||
}
|
||||
}
|
||||
if (!power_max || !DateRange.inRange(power_max, current_data_range)){
|
||||
power_max = end_of_current_data_moment.unix();
|
||||
}
|
||||
if (!power_min || !DateRange.inRange(power_min, current_data_range) ||
|
||||
power_max - power_min > 3600 * 24 * 4){
|
||||
power_min = power_max - 3600 * 24 * 4;
|
||||
}
|
||||
house.state.power_range = [power_min, power_max];
|
||||
}
|
||||
|
||||
matchesYearState(params){
|
||||
var house = this;
|
||||
return params.year == house.state.year;
|
||||
}
|
||||
|
||||
matchesMonthState(params){
|
||||
var house = this;
|
||||
return params.month == house.state.month && params.year == house.state.year;
|
||||
}
|
||||
|
||||
matchesPowerRange(dates){
|
||||
var house = this;
|
||||
return house.state.power_range[0] == dates[0] && house.state.power_range[1] == dates[1];
|
||||
}
|
||||
|
||||
offset_diff(unix){
|
||||
@@ -121,7 +169,7 @@ class House {
|
||||
.then((power_collection)=>{
|
||||
return house.ensurePowerData()
|
||||
.then(()=>{
|
||||
var params = house.rangeToLokiParams('time', house.power_date_range);
|
||||
var params = house.rangeToLokiParams('time', house.state.power_range);
|
||||
house.power_data = power_collection.find(params)
|
||||
.sort((pd1, pd2)=>{
|
||||
if (pd1.time === pd2.time) return 0;
|
||||
@@ -137,7 +185,7 @@ class House {
|
||||
var house = this,
|
||||
query_ranges;
|
||||
|
||||
query_ranges = DateRange.addRange(house.power_date_range, house.data.power_datum_ranges || []);
|
||||
query_ranges = DateRange.addRange(house.state.power_range, house.data.power_datum_ranges || []);
|
||||
if (query_ranges.gaps_filled.length > 0){
|
||||
var params = {dates: query_ranges.gaps_filled};
|
||||
return house.getPowerData(params)
|
||||
@@ -167,7 +215,7 @@ class House {
|
||||
.then((energy_collection)=>{
|
||||
return house.ensureEnergyData()
|
||||
.then(()=>{
|
||||
var params = house.rangeToLokiParams('day', house.energy_date_range);
|
||||
var params = house.rangeToLokiParams('day', house.state.energy_range);
|
||||
house.energy_data = energy_collection.find(params)
|
||||
.sort((pd1, pd2)=>{
|
||||
if (pd1.day === pd2.day) return 0;
|
||||
@@ -181,7 +229,7 @@ class House {
|
||||
|
||||
ensureEnergyData(){
|
||||
var house = this,
|
||||
query_ranges = DateRange.addRange(house.energy_date_range, house.data.energy_datum_ranges || []);
|
||||
query_ranges = DateRange.addRange(house.state.energy_range, house.data.energy_datum_ranges || []);
|
||||
if (query_ranges.gaps_filled.length > 0){
|
||||
return house.getEnergyData({dates: query_ranges.gaps_filled})
|
||||
.then(()=>{
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
0 info it worked if it ends with ok
|
||||
1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'start' ]
|
||||
2 info using npm@3.5.3
|
||||
3 info using node@v5.4.1
|
||||
4 verbose run-script [ 'prestart', 'start', 'poststart' ]
|
||||
5 info lifecycle spike_proto@0.0.0~prestart: spike_proto@0.0.0
|
||||
6 silly lifecycle spike_proto@0.0.0~prestart: no script for prestart, continuing
|
||||
7 info lifecycle spike_proto@0.0.0~start: spike_proto@0.0.0
|
||||
8 verbose lifecycle spike_proto@0.0.0~start: unsafe-perm in lifecycle true
|
||||
9 verbose lifecycle spike_proto@0.0.0~start: PATH: /usr/local/lib/node_modules/npm/bin/node-gyp-bin:/home/eric/Code/spike2/node_modules/.bin:/home/eric/.rvm/gems/ruby-1.9.3-p484@oroeco_dev/bin:/home/eric/.rvm/gems/ruby-1.9.3-p484@global/bin:/home/eric/.rvm/rubies/ruby-1.9.3-p484/bin:/home/eric/.rvm/bin:/home/eric/bin:/usr/local/heroku/bin:/home/eric/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/lampp/bin
|
||||
10 verbose lifecycle spike_proto@0.0.0~start: CWD: /home/eric/Code/spike2
|
||||
11 silly lifecycle spike_proto@0.0.0~start: Args: [ '-c', 'babel-node ./server/app.express.js' ]
|
||||
12 silly lifecycle spike_proto@0.0.0~start: Returned: code: 1 signal: null
|
||||
13 info lifecycle spike_proto@0.0.0~start: Failed to exec start script
|
||||
14 verbose stack Error: spike_proto@0.0.0 start: `babel-node ./server/app.express.js`
|
||||
14 verbose stack Exit status 1
|
||||
14 verbose stack at EventEmitter.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:232:16)
|
||||
14 verbose stack at emitTwo (events.js:87:13)
|
||||
14 verbose stack at EventEmitter.emit (events.js:172:7)
|
||||
14 verbose stack at ChildProcess.<anonymous> (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14)
|
||||
14 verbose stack at emitTwo (events.js:87:13)
|
||||
14 verbose stack at ChildProcess.emit (events.js:172:7)
|
||||
14 verbose stack at maybeClose (internal/child_process.js:821:16)
|
||||
14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5)
|
||||
15 verbose pkgid spike_proto@0.0.0
|
||||
16 verbose cwd /home/eric/Code/spike2
|
||||
17 error Linux 3.19.0-49-generic
|
||||
18 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "start"
|
||||
19 error node v5.4.1
|
||||
20 error npm v3.5.3
|
||||
21 error code ELIFECYCLE
|
||||
22 error spike_proto@0.0.0 start: `babel-node ./server/app.express.js`
|
||||
22 error Exit status 1
|
||||
23 error Failed at the spike_proto@0.0.0 start script 'babel-node ./server/app.express.js'.
|
||||
23 error Make sure you have the latest version of node.js and npm installed.
|
||||
23 error If you do, this is most likely a problem with the spike_proto package,
|
||||
23 error not with npm itself.
|
||||
23 error Tell the author that this fails on your system:
|
||||
23 error babel-node ./server/app.express.js
|
||||
23 error You can get information on how to open an issue for this project with:
|
||||
23 error npm bugs spike_proto
|
||||
23 error Or if that isn't available, you can get their info via:
|
||||
23 error npm owner ls spike_proto
|
||||
23 error There is likely additional logging output above.
|
||||
24 verbose exit [ 1, true ]
|
||||
@@ -26,6 +26,7 @@
|
||||
"express": "4.13.3",
|
||||
"react": "0.14.3",
|
||||
"react-dom": "0.14.3",
|
||||
"react-router": "2.0.0",
|
||||
"webpack": "1.12.9",
|
||||
"webpack-dev-server": "1.14.0",
|
||||
"extract-text-webpack-plugin": "1.0.1",
|
||||
|
||||
@@ -55,7 +55,7 @@ npm start
|
||||
|
||||
When developing, it can be really useful to clear all of the data saved to local client storage. A better alternative to clearing all data in your browser cache, hit the 'Refresh Data' button on the main page below the houses dropdown.
|
||||
|
||||
## Building the designer pack
|
||||
## Building the design pack
|
||||
|
||||
To build a design pack, you first need to install [sass.js](https://github.com/medialize/sass.js/) in the design build directory so the design build can compile the sass in the browser.
|
||||
|
||||
@@ -64,7 +64,7 @@ cd client/build/design
|
||||
git clone https://github.com/medialize/sass.js.git
|
||||
```
|
||||
|
||||
Then to build the app, you'll need to build the app with webpack:
|
||||
Then build the app with webpack:
|
||||
|
||||
```sh
|
||||
gulp build --design
|
||||
@@ -80,7 +80,7 @@ python -m SimpleHTTPServer 8000
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
Access the app at localhost:8000. The app will run slow because json responses are not as finely paginated and both React templates and sass are compiled in the browser before the app runs.
|
||||
Access the app at localhost:8000. The app will run slowly because json responses are not as finely paginated and both React templates and sass are compiled in the browser before the app runs.
|
||||
|
||||
The designer can change React templates and sass files in `/dashboard`. Refresh the page to see the changes reflected. They can then share this directory with us so we can update the files in this repository.
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ DB.sync().then(()=>{
|
||||
api.use(bodyParser.json());
|
||||
api.use(bodyParser.urlencoded({ extended: false }));
|
||||
|
||||
|
||||
api.listen(API_PORT, () => {
|
||||
console.log(`API is now running on http://localhost:${API_PORT}`);
|
||||
});
|
||||
@@ -74,7 +73,7 @@ app.use(assets({
|
||||
paths: ["./../node_modules"],
|
||||
build: true,
|
||||
buildDir: false,
|
||||
//compile: false,
|
||||
// compile: false,
|
||||
compress: true
|
||||
}));
|
||||
// serve public static files.
|
||||
@@ -83,44 +82,10 @@ dev_server.app.use('/', express.static(path.resolve(__dirname, 'public')));
|
||||
// view engine set up
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'jade');
|
||||
app.get("/", (req, res, next)=>{
|
||||
app.get("*", (req, res, next)=>{
|
||||
res.render("index");
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Handle Errors
|
||||
*/
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// development error handler
|
||||
// will print stacktrace
|
||||
if (app.get('env') === 'development') {
|
||||
app.use(function(err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: err
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// production error handler
|
||||
// no stacktraces leaked to user
|
||||
app.use(function(err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
|
||||
dev_server.listen(APP_PORT, () => {
|
||||
console.log(`App is now running on http://localhost:${APP_PORT}`);
|
||||
});
|
||||
|
||||
@@ -64,5 +64,11 @@ class DateRange {
|
||||
return new Date(date.getTime() + s);
|
||||
}
|
||||
|
||||
static inRange(n, min_max){
|
||||
var min = min_max[0],
|
||||
max = min_max[1];
|
||||
return ((n >= min) && (n <= max));
|
||||
}
|
||||
|
||||
}
|
||||
export default DateRange;
|
||||
|
||||
@@ -29,10 +29,4 @@ export default class {
|
||||
return minus;
|
||||
}
|
||||
|
||||
static inRange(n, min_max){
|
||||
var min = min_max[0],
|
||||
max = min_max[1];
|
||||
return ((n >= min) && (n <= max));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
85
spec/client/dashboard/models/house.test.js
Normal file
85
spec/client/dashboard/models/house.test.js
Normal file
@@ -0,0 +1,85 @@
|
||||
"use strict";
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import House from './../../../../client/models/house.js';
|
||||
|
||||
|
||||
describe('house#setMonthState', ()=>{
|
||||
|
||||
var data_until = 1456589922, // Sat, 27 Feb 2016 16:18:42 +0000
|
||||
house = new House({
|
||||
id: 1,
|
||||
name: 'Johnson',
|
||||
data_from: data_until - 3600 * 24 * 365 * 3,
|
||||
data_until: data_until,
|
||||
timezone: 'America/New_York'
|
||||
});
|
||||
|
||||
it('is updated properly on init', ()=>{
|
||||
var current_month_moment = moment.tz({year: 2016, month: 1, day: 1}, 'America/New_York'),
|
||||
energy_min = moment.tz({year: 2016, month: 0, day: 1}, 'America/New_York').unix(),
|
||||
energy_max = data_until,
|
||||
power_min = data_until - 3600 * 24 * 4,
|
||||
power_max = data_until;
|
||||
|
||||
expect(house.state.month).toEqual('Feb');
|
||||
expect(house.state.year).toEqual(2016);
|
||||
expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
|
||||
expect(house.state.energy_range).toEqual([energy_min, energy_max]);
|
||||
expect(house.state.power_range).toEqual([power_min, power_max]);
|
||||
});
|
||||
|
||||
it('is not updated when passed no params', ()=>{
|
||||
var current_month_moment = moment.tz({year: 2016, month: 1, day: 1}, 'America/New_York'),
|
||||
energy_min = moment.tz({year: 2016, month: 0, day: 1}, 'America/New_York').unix(),
|
||||
energy_max = data_until,
|
||||
power_min = data_until - 3600 * 24 * 4,
|
||||
power_max = data_until;
|
||||
|
||||
house.setMonthState({});
|
||||
expect(house.state.month).toEqual('Feb');
|
||||
expect(house.state.year).toEqual(2016);
|
||||
expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
|
||||
expect(house.state.energy_range).toEqual([energy_min, energy_max]);
|
||||
expect(house.state.power_range).toEqual([power_min, power_max]);
|
||||
});
|
||||
|
||||
it('is updated properly when passed power params', ()=>{
|
||||
var current_month_moment = moment.tz({year: 2015, month: 2, day: 1}, 'America/New_York'),
|
||||
energy_min = moment.tz({year: 2015, month: 0, day: 1}, 'America/New_York').unix(),
|
||||
energy_max = moment.tz({year: 2015, month: 0, day: 1}, 'America/New_York').endOf('year').unix(),
|
||||
power_max = current_month_moment.clone().endOf('month').subtract(3, 'days').unix(),
|
||||
power_min = current_month_moment.clone().endOf('month').subtract(6, 'days').unix()
|
||||
|
||||
house.setMonthState({
|
||||
month: 'Mar',
|
||||
year: 2015
|
||||
}, [ power_min, power_max ]);
|
||||
|
||||
expect(house.state.month).toEqual('Mar');
|
||||
expect(house.state.year).toEqual(2015);
|
||||
expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
|
||||
expect(house.state.energy_range).toEqual([energy_min, energy_max]);
|
||||
expect(house.state.power_range).toEqual([power_min, power_max]);
|
||||
});
|
||||
|
||||
it('is updated properly when passed energy params', ()=>{
|
||||
var current_month_moment = moment.tz({year: 2014, month: 9, day: 1}, 'America/New_York'),
|
||||
energy_min = moment.tz({year: 2014, month: 0, day: 1}, 'America/New_York').unix(),
|
||||
energy_max = moment.tz({year: 2014, month: 0, day: 1}, 'America/New_York').endOf('year').unix(),
|
||||
power_max = moment.tz({year: 2014, month: 9, day: 1}, 'America/New_York').endOf('month').unix(),
|
||||
power_min = power_max - 3600 * 24 * 4;
|
||||
|
||||
house.setMonthState({
|
||||
month: 'Oct',
|
||||
year: 2014
|
||||
});
|
||||
|
||||
expect(house.state.month).toEqual('Oct');
|
||||
expect(house.state.year).toEqual(2014);
|
||||
expect(house.state.current_month_moment.unix()).toEqual(current_month_moment.unix());
|
||||
expect(house.state.energy_range).toEqual([energy_min, energy_max]);
|
||||
expect(house.state.power_range).toEqual([power_min, power_max]);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user