223 lines
6.8 KiB
JavaScript
223 lines
6.8 KiB
JavaScript
import { map, sortBy } from 'common/collections';
|
|
import { flow } from 'common/fp';
|
|
import { toFixed } from 'common/math';
|
|
import { pureComponentHooks } from 'common/react';
|
|
import { Component, Fragment } from 'inferno';
|
|
import { Box, Button, Chart, ColorBox, Flex, Icon, LabeledList, ProgressBar, Section, Table } from '../components';
|
|
|
|
const PEAK_DRAW = 500000;
|
|
|
|
const powerRank = str => {
|
|
const unit = String(str.split(' ')[1]).toLowerCase();
|
|
return ['w', 'kw', 'mw', 'gw'].indexOf(unit);
|
|
};
|
|
|
|
export class PowerMonitor extends Component {
|
|
constructor() {
|
|
super();
|
|
this.state = {
|
|
sortByField: null,
|
|
};
|
|
}
|
|
|
|
render() {
|
|
const { state } = this.props;
|
|
const { data } = state;
|
|
const { history } = data;
|
|
const { sortByField } = this.state;
|
|
const supply = history.supply[history.supply.length - 1] || 0;
|
|
const demand = history.demand[history.demand.length - 1] || 0;
|
|
const supplyData = history.supply.map((value, i) => [i, value]);
|
|
const demandData = history.demand.map((value, i) => [i, value]);
|
|
const maxValue = Math.max(
|
|
PEAK_DRAW,
|
|
...history.supply,
|
|
...history.demand);
|
|
// Process area data
|
|
const areas = flow([
|
|
map((area, i) => ({
|
|
...area,
|
|
// Generate a unique id
|
|
id: area.name + i,
|
|
})),
|
|
sortByField === 'name' && sortBy(area => area.name),
|
|
sortByField === 'charge' && sortBy(area => -area.charge),
|
|
sortByField === 'draw' && sortBy(
|
|
area => -powerRank(area.load),
|
|
area => -parseFloat(area.load)),
|
|
])(data.areas);
|
|
return (
|
|
<Fragment>
|
|
<Flex spacing={1}>
|
|
<Flex.Item width="200px">
|
|
<Section>
|
|
<LabeledList>
|
|
<LabeledList.Item label="Supply">
|
|
<ProgressBar
|
|
value={supply}
|
|
minValue={0}
|
|
maxValue={maxValue}
|
|
color="teal"
|
|
content={toFixed(supply / 1000) + ' kW'} />
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Draw">
|
|
<ProgressBar
|
|
value={demand}
|
|
minValue={0}
|
|
maxValue={maxValue}
|
|
color="pink"
|
|
content={toFixed(demand / 1000) + ' kW'} />
|
|
</LabeledList.Item>
|
|
</LabeledList>
|
|
</Section>
|
|
</Flex.Item>
|
|
<Flex.Item grow={1}>
|
|
<Section position="relative" height="100%">
|
|
<Chart.Line
|
|
fillPositionedParent
|
|
data={supplyData}
|
|
rangeX={[0, supplyData.length - 1]}
|
|
rangeY={[0, maxValue]}
|
|
strokeColor="rgba(0, 181, 173, 1)"
|
|
fillColor="rgba(0, 181, 173, 0.25)" />
|
|
<Chart.Line
|
|
fillPositionedParent
|
|
data={demandData}
|
|
rangeX={[0, demandData.length - 1]}
|
|
rangeY={[0, maxValue]}
|
|
strokeColor="rgba(224, 57, 151, 1)"
|
|
fillColor="rgba(224, 57, 151, 0.25)" />
|
|
</Section>
|
|
</Flex.Item>
|
|
</Flex>
|
|
<Section>
|
|
<Box mb={1}>
|
|
<Box inline mr={2} color="label">
|
|
Sort by:
|
|
</Box>
|
|
<Button.Checkbox
|
|
checked={sortByField === 'name'}
|
|
content="Name"
|
|
onClick={() => this.setState({
|
|
sortByField: sortByField !== 'name' && 'name',
|
|
})} />
|
|
<Button.Checkbox
|
|
checked={sortByField === 'charge'}
|
|
content="Charge"
|
|
onClick={() => this.setState({
|
|
sortByField: sortByField !== 'charge' && 'charge',
|
|
})} />
|
|
<Button.Checkbox
|
|
checked={sortByField === 'draw'}
|
|
content="Draw"
|
|
onClick={() => this.setState({
|
|
sortByField: sortByField !== 'draw' && 'draw',
|
|
})} />
|
|
</Box>
|
|
<Table>
|
|
<Table.Row header>
|
|
<Table.Cell>
|
|
Area
|
|
</Table.Cell>
|
|
<Table.Cell collapsing>
|
|
Charge
|
|
</Table.Cell>
|
|
<Table.Cell textAlign="right">
|
|
Draw
|
|
</Table.Cell>
|
|
<Table.Cell collapsing title="Equipment">
|
|
Eqp
|
|
</Table.Cell>
|
|
<Table.Cell collapsing title="Lighting">
|
|
Lgt
|
|
</Table.Cell>
|
|
<Table.Cell collapsing title="Environment">
|
|
Env
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
{areas.map((area, i) => (
|
|
<tr
|
|
key={area.id}
|
|
className="Table__row candystripe">
|
|
<td>
|
|
{area.name}
|
|
</td>
|
|
<td className="Table__cell text-right text-nowrap">
|
|
<AreaCharge
|
|
charging={area.charging}
|
|
charge={area.charge} />
|
|
</td>
|
|
<td className="Table__cell text-right text-nowrap">
|
|
{area.load}
|
|
</td>
|
|
<td className="Table__cell text-center text-nowrap">
|
|
<AreaStatusColorBox status={area.eqp} />
|
|
</td>
|
|
<td className="Table__cell text-center text-nowrap">
|
|
<AreaStatusColorBox status={area.lgt} />
|
|
</td>
|
|
<td className="Table__cell text-center text-nowrap">
|
|
<AreaStatusColorBox status={area.env} />
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</Table>
|
|
</Section>
|
|
</Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
const AreaCharge = props => {
|
|
const { charging, charge } = props;
|
|
return (
|
|
<Fragment>
|
|
<Icon
|
|
width="18px"
|
|
textAlign="center"
|
|
name={(
|
|
charging === 0 && (
|
|
charge > 50
|
|
? 'battery-half'
|
|
: 'battery-quarter'
|
|
)
|
|
|| charging === 1 && 'bolt'
|
|
|| charging === 2 && 'battery-full'
|
|
)}
|
|
color={(
|
|
charging === 0 && (
|
|
charge > 50
|
|
? 'yellow'
|
|
: 'red'
|
|
)
|
|
|| charging === 1 && 'yellow'
|
|
|| charging === 2 && 'green'
|
|
)} />
|
|
<Box
|
|
inline
|
|
width="36px"
|
|
textAlign="right">
|
|
{toFixed(charge) + '%'}
|
|
</Box>
|
|
</Fragment>
|
|
);
|
|
};
|
|
|
|
AreaCharge.defaultHooks = pureComponentHooks;
|
|
|
|
const AreaStatusColorBox = props => {
|
|
const { status } = props;
|
|
const power = Boolean(status & 2);
|
|
const mode = Boolean(status & 1);
|
|
const tooltipText = (power ? 'On' : 'Off')
|
|
+ ` [${mode ? 'auto' : 'manual'}]`;
|
|
return (
|
|
<ColorBox
|
|
color={power ? 'good' : 'bad'}
|
|
content={mode ? undefined : 'M'}
|
|
title={tooltipText} />
|
|
);
|
|
};
|
|
|
|
AreaStatusColorBox.defaultHooks = pureComponentHooks;
|