Skip to content

Commit

Permalink
Merge branch 'development' of https://github.com/OpenEnergyDashboard/OED
Browse files Browse the repository at this point in the history
 into development
  • Loading branch information
aravindb24 committed Oct 16, 2024
2 parents 48013fa + 3b4b8dc commit 1071cc3
Show file tree
Hide file tree
Showing 19 changed files with 700 additions and 310 deletions.
121 changes: 6 additions & 115 deletions src/client/app/components/BarControlsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,147 +2,38 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as moment from 'moment';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { FormFeedback, FormGroup, Input, Label } from 'reactstrap';
import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
import { graphSlice, selectBarStacking, selectBarWidthDays } from '../redux/slices/graphSlice';
import { graphSlice, selectBarStacking } from '../redux/slices/graphSlice';
import translate from '../utils/translate';
import TooltipMarkerComponent from './TooltipMarkerComponent';
import IntervalControlsComponent from './IntervalControlsComponent';

/**
* @returns controls for the Options Ui page.
* @returns controls for bar page.
*/
export default function BarControlsComponent() {
const dispatch = useAppDispatch();

// The min/max days allowed for user selection
const MIN_BAR_DAYS = 1;
const MAX_BAR_DAYS = 366;
// Special value if custom input for standard menu.
const CUSTOM_INPUT = '-99';

// This is the current bar interval for graphic.
const barDuration = useAppSelector(selectBarWidthDays);
const barStacking = useAppSelector(selectBarStacking);
// Holds the value of standard bar duration choices used so decoupled from custom.
const [barDays, setBarDays] = React.useState<string>(barDuration.asDays().toString());
// Holds the value during custom bar duration input so only update graphic when done entering and
// separate from standard choices.
const [barDaysCustom, setBarDaysCustom] = React.useState<number>(barDuration.asDays());
// True if custom bar duration input is active.
const [showCustomBarDuration, setShowCustomBarDuration] = React.useState<boolean>(false);

const handleChangeBarStacking = () => {
dispatch(graphSlice.actions.changeBarStacking());
};

// Keeps react-level state, and redux state in sync.
// Two different layers in state may differ especially when externally updated (chart link, history buttons.)
React.useEffect(() => {
// Assume value is valid since it is coming from state.
// Do not allow bad values in state.
const isCustom = !(['1', '7', '28'].find(days => days == barDuration.asDays().toString()));
setShowCustomBarDuration(isCustom);
setBarDaysCustom(barDuration.asDays());
setBarDays(isCustom ? CUSTOM_INPUT : barDuration.asDays().toString());
}, [barDuration]);

// Returns true if this is a valid bar duration.
const barDaysValid = (barDays: number) => {
return Number.isInteger(barDays) && barDays >= MIN_BAR_DAYS && barDays <= MAX_BAR_DAYS;
};

// Updates values when the standard bar duration menu is used.
const handleBarDaysChange = (value: string) => {
if (value === CUSTOM_INPUT) {
// Set menu value for standard bar to special value to show custom
// and show the custom input area.
setBarDays(CUSTOM_INPUT);
setShowCustomBarDuration(true);
} else {
// Set the standard menu value, hide the custom bar duration input
// and bar duration for graphing.
// Since controlled values know it is a valid integer.
setShowCustomBarDuration(false);
updateBarDurationChange(Number(value));
}
};

// Updates value when the custom bar duration input is used.
const handleCustomBarDaysChange = (value: number) => {
setBarDaysCustom(value);
};

const handleEnter = (key: string) => {
// This detects the enter key and then uses the previously entered custom
// bar duration to set the bar duration for the graphic.
if (key == 'Enter') {
updateBarDurationChange(barDaysCustom);
}
};

const updateBarDurationChange = (value: number) => {
// Update if okay value. May not be okay if this came from user entry in custom form.
if (barDaysValid(value)) {
dispatch(graphSlice.actions.updateBarDuration(moment.duration(value, 'days')));
}
};

return (
<div>
<div className='checkbox'>
<div className='checkbox' style={divTopBottomPadding}>
<input type='checkbox' style={{ marginRight: '10px' }} onChange={handleChangeBarStacking} checked={barStacking} id='barStacking' />
<label htmlFor='barStacking'>{translate('bar.stacking')}</label>
<TooltipMarkerComponent page='home' helpTextId='help.home.bar.stacking.tip' />
</div>
<div style={divTopBottomPadding}>
<p style={labelStyle}>
{translate('bar.interval')}:
<TooltipMarkerComponent page='home' helpTextId='help.home.bar.days.tip' />
</p>
<Input
id='barDurationDays'
name='barDurationDays'
type='select'
value={barDays}
onChange={e => handleBarDaysChange(e.target.value)}
>
<option value='1'>{translate('day')}</option>
<option value='7'>{translate('week')}</option>
<option value='28'>{translate('4.weeks')}</option>
<option value={CUSTOM_INPUT}>{translate('custom.value')}</option>
</Input>
{/* This has a little more spacing at bottom than optimal. */}
{showCustomBarDuration &&
<FormGroup>
<Label for='barDays'>{translate('bar.days.enter')}:</Label>
<Input id='barDays' name='barDays' type='number'
onChange={e => handleCustomBarDaysChange(Number(e.target.value))}
// This grabs each key hit and then finishes input when hit enter.
onKeyDown={e => { handleEnter(e.key); }}
step='1'
min={MIN_BAR_DAYS}
max={MAX_BAR_DAYS}
value={barDaysCustom}
invalid={!barDaysValid(barDaysCustom)} />
<FormFeedback>
<FormattedMessage id="error.bounds" values={{ min: MIN_BAR_DAYS, max: MAX_BAR_DAYS }} />
</FormFeedback>
</FormGroup>
}
</div >
{<IntervalControlsComponent key='interval' />}
</div >
);
}

const divTopBottomPadding: React.CSSProperties = {
paddingTop: '15px',
paddingTop: '0px',
paddingBottom: '15px'
};

const labelStyle: React.CSSProperties = {
fontWeight: 'bold',
margin: 0
};
106 changes: 38 additions & 68 deletions src/client/app/components/CompareControlsComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import * as moment from 'moment';
import * as React from 'react';
import { Button, ButtonGroup, Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { graphSlice, selectComparePeriod, selectSortingOrder } from '../redux/slices/graphSlice';
import { Input } from 'reactstrap';
import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
import { ComparePeriod, SortingOrder } from '../utils/calculateCompare';
import { graphSlice, selectSortingOrder } from '../redux/slices/graphSlice';
import { SortingOrder } from '../utils/calculateCompare';
import translate from '../utils/translate';
import TooltipMarkerComponent from './TooltipMarkerComponent';
import IntervalControlsComponent from './IntervalControlsComponent';

/**
* @returns controls for the compare page
* @returns controls for compare page.
*/
export default function CompareControlsComponent() {
const dispatch = useAppDispatch();
const comparePeriod = useAppSelector(selectComparePeriod);

// This is the current sorting order for graphic
const compareSortingOrder = useAppSelector(selectSortingOrder);
const [compareSortingDropdownOpen, setCompareSortingDropdownOpen] = React.useState<boolean>(false);
const handleCompareButton = (comparePeriod: ComparePeriod) => {
dispatch(graphSlice.actions.updateComparePeriod({ comparePeriod, currentTime: moment() }));
};
const handleSortingButton = (sortingOrder: SortingOrder) => {

// Updates sorting order when the sort order menu is used.
const handleSortingChange = (value: string) => {
const sortingOrder = value as unknown as SortingOrder;
dispatch(graphSlice.actions.changeCompareSortingOrder(sortingOrder));
};

return (
<div>
<ButtonGroup
style={zIndexFix}
>
<Button
outline={comparePeriod !== ComparePeriod.Day}
active={comparePeriod === ComparePeriod.Day}
onClick={() => handleCompareButton(ComparePeriod.Day)}
{<IntervalControlsComponent key='interval' />}
<div style={divTopBottomPadding}>
<p style={labelStyle}>
{translate('sort')}:
<TooltipMarkerComponent page='home' helpTextId='help.home.compare.sort.tip' />
</p>
<Input
type="select"
value={compareSortingOrder?.toString()}
onChange={e => handleSortingChange(e.target.value)}
>
{translate('day')}
</Button>
<Button
outline={comparePeriod !== ComparePeriod.Week}
active={comparePeriod === ComparePeriod.Week}
onClick={() => handleCompareButton(ComparePeriod.Week)}
>
{translate('week')}
</Button>
<Button
outline={comparePeriod !== ComparePeriod.FourWeeks}
active={comparePeriod === ComparePeriod.FourWeeks}
onClick={() => handleCompareButton(ComparePeriod.FourWeeks)}
>
{translate('4.weeks')}
</Button>
</ButtonGroup>
<TooltipMarkerComponent page='home' helpTextId='help.home.compare.interval.tip' />
<Dropdown isOpen={compareSortingDropdownOpen} toggle={() => setCompareSortingDropdownOpen(current => !current)}>
<DropdownToggle caret>
{translate('sort')}
</DropdownToggle>
<TooltipMarkerComponent page='home' helpTextId='help.home.compare.sort.tip' />
<DropdownMenu>
<DropdownItem
active={compareSortingOrder === SortingOrder.Alphabetical}
onClick={() => handleSortingButton(SortingOrder.Alphabetical)}
>
{translate('alphabetically')}
</DropdownItem>
<DropdownItem
active={compareSortingOrder === SortingOrder.Ascending}
onClick={() => handleSortingButton(SortingOrder.Ascending)}
>
{translate('ascending')}
</DropdownItem>
<DropdownItem
active={compareSortingOrder === SortingOrder.Descending}
onClick={() => handleSortingButton(SortingOrder.Descending)}
>
{translate('descending')}
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
<option value={SortingOrder.Alphabetical.toString()}>{translate('alphabetically')}</option>
<option value={SortingOrder.Ascending.toString()}>{translate('ascending')}</option>
<option value={SortingOrder.Descending.toString()}>{translate('descending')}</option>
</Input>
</div>
</div >
);
}

const zIndexFix: React.CSSProperties = {
zIndex: 0
};
const divTopBottomPadding: React.CSSProperties = {
paddingTop: '0px',
paddingBottom: '15px'
};

const labelStyle: React.CSSProperties = {
fontWeight: 'bold',
margin: 0
};
Loading

0 comments on commit 1071cc3

Please sign in to comment.