From d6cff6a4da76f83c2b5d2c66a7ff2ac6fa1d01df Mon Sep 17 00:00:00 2001 From: Joijanae Laws Date: Thu, 3 Oct 2024 20:00:22 -0500 Subject: [PATCH 01/34] Standardize Translation Method --- src/client/app/components/AreaUnitSelectComponent.tsx | 4 ++-- src/client/app/components/BarChartComponent.tsx | 3 ++- src/client/app/components/BarControlsComponent.tsx | 3 ++- src/client/app/components/ChartLinkComponent.tsx | 3 ++- src/client/app/components/ChartSelectComponent.tsx | 3 ++- src/client/app/components/CompareControlsComponent.tsx | 3 ++- src/client/app/components/ConfirmActionModalComponent.tsx | 4 ++-- src/client/app/components/DateRangeComponent.tsx | 3 ++- src/client/app/components/ErrorBarComponent.tsx | 3 ++- src/client/app/components/FormFileUploaderComponent.tsx | 4 ++-- src/client/app/components/GraphicRateMenuComponent.tsx | 3 ++- src/client/app/components/HeaderButtonsComponent.tsx | 3 ++- src/client/app/components/LineChartComponent.tsx | 3 ++- src/client/app/components/LoginComponent.tsx | 3 ++- src/client/app/components/MapChartComponent.tsx | 4 ++-- src/client/app/components/MapControlsComponent.tsx | 3 ++- src/client/app/components/MeterAndGroupSelectComponent.tsx | 3 ++- src/client/app/components/MoreOptionsComponent.tsx | 3 ++- src/client/app/components/RadarChartComponent.tsx | 3 ++- src/client/app/components/ReadingsPerDaySelectComponent.tsx | 3 ++- src/client/app/components/ThreeDComponent.tsx | 5 ++++- src/client/app/components/ThreeDPillComponent.tsx | 3 ++- src/client/app/components/TimeZoneSelect.tsx | 3 ++- src/client/app/components/TooltipHelpComponent.tsx | 4 ++-- src/client/app/components/UnitSelectComponent.tsx | 3 ++- src/client/app/components/UnsavedWarningComponent.tsx | 3 ++- src/client/app/components/admin/PreferencesComponent.tsx | 3 ++- .../app/components/admin/users/CreateUserModalComponent.tsx | 4 ++-- .../app/components/admin/users/EditUserModalComponent.tsx | 4 ++-- src/client/app/components/admin/users/UserViewComponent.tsx | 3 ++- .../app/components/admin/users/UsersDetailComponent.tsx | 3 ++- .../app/components/conversion/ConversionViewComponent.tsx | 3 ++- .../components/conversion/CreateConversionModalComponent.tsx | 3 ++- .../components/conversion/EditConversionModalComponent.tsx | 3 ++- src/client/app/components/csv/MetersCSVUploadComponent.tsx | 3 ++- src/client/app/components/csv/ReadingsCSVUploadComponent.tsx | 4 ++-- .../app/components/groups/CreateGroupModalComponent.tsx | 3 ++- src/client/app/components/groups/EditGroupModalComponent.tsx | 3 ++- src/client/app/components/groups/GroupViewComponent.tsx | 3 ++- .../app/components/meters/CreateMeterModalComponent.tsx | 3 ++- src/client/app/components/meters/EditMeterModalComponent.tsx | 3 ++- src/client/app/components/meters/MeterViewComponent.tsx | 3 ++- src/client/app/components/router/ErrorComponent.tsx | 3 ++- src/client/app/components/router/InitializingComponent.tsx | 3 ++- src/client/app/components/unit/UnitViewComponent.tsx | 3 ++- src/client/app/containers/CompareChartContainer.ts | 3 ++- src/client/app/containers/MapChartContainer.ts | 3 ++- .../containers/maps/MapCalibrationInfoDisplayContainer.ts | 3 ++- src/client/app/redux/actions/conversions.ts | 3 ++- src/client/app/redux/actions/map.ts | 5 ++++- .../app/redux/middleware/unauthorizedAccesMiddleware.ts | 3 ++- src/client/app/redux/selectors/adminSelectors.ts | 4 ++-- src/client/app/redux/selectors/lineChartSelectors.ts | 3 ++- src/client/app/redux/thunks/exportThunk.ts | 3 ++- src/client/app/utils/calculateCompare.ts | 4 +++- src/client/app/utils/calibration.ts | 3 ++- src/client/app/utils/graphics.ts | 5 +++-- src/client/app/utils/input.ts | 3 ++- 58 files changed, 122 insertions(+), 68 deletions(-) diff --git a/src/client/app/components/AreaUnitSelectComponent.tsx b/src/client/app/components/AreaUnitSelectComponent.tsx index a42850e1e..739e6fb26 100644 --- a/src/client/app/components/AreaUnitSelectComponent.tsx +++ b/src/client/app/components/AreaUnitSelectComponent.tsx @@ -11,7 +11,7 @@ import { selectUnitDataById } from '../redux/api/unitsApi'; import { StringSelectOption } from '../types/items'; import { UnitRepresentType } from '../types/redux/units'; import { AreaUnitType } from '../utils/getAreaUnitConversion'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** @@ -20,7 +20,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; */ export default function AreaUnitSelectComponent() { const dispatch = useAppDispatch(); - + const translate = useTranslate(); const graphState = useAppSelector(selectGraphState); const unitDataById = useAppSelector(selectUnitDataById); diff --git a/src/client/app/components/BarChartComponent.tsx b/src/client/app/components/BarChartComponent.tsx index 755509308..e3a7ed65c 100644 --- a/src/client/app/components/BarChartComponent.tsx +++ b/src/client/app/components/BarChartComponent.tsx @@ -17,7 +17,7 @@ import { selectBarUnitLabel, selectIsRaw } from '../redux/selectors/plotlyDataSe import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; import { selectBarStacking } from '../redux/slices/graphSlice'; import Locales from '../types/locales'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import SpinnerComponent from './SpinnerComponent'; /** @@ -27,6 +27,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns Plotly BarChart */ export default function BarChartComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const { barMeterDeps, barGroupDeps } = useAppSelector(selectPlotlyBarDeps); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectBarChartQueryArgs); diff --git a/src/client/app/components/BarControlsComponent.tsx b/src/client/app/components/BarControlsComponent.tsx index 03e858d11..ead20b67c 100644 --- a/src/client/app/components/BarControlsComponent.tsx +++ b/src/client/app/components/BarControlsComponent.tsx @@ -8,13 +8,14 @@ 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 translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** * @returns controls for the Options Ui page. */ export default function BarControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // The min/max days allowed for user selection diff --git a/src/client/app/components/ChartLinkComponent.tsx b/src/client/app/components/ChartLinkComponent.tsx index 8eb0987e2..6de7aabae 100644 --- a/src/client/app/components/ChartLinkComponent.tsx +++ b/src/client/app/components/ChartLinkComponent.tsx @@ -11,13 +11,14 @@ import { selectChartLink } from '../redux/selectors/uiSelectors'; import { selectChartLinkHideOptions, setChartLinkOptionsVisibility } from '../redux/slices/appStateSlice'; import { selectSelectedGroups, selectSelectedMeters } from '../redux/slices/graphSlice'; import { showErrorNotification, showInfoNotification } from '../utils/notifications'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** * @returns chartLinkComponent */ export default function ChartLinkComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const [linkTextVisible, setLinkTextVisible] = React.useState(false); const linkText = useAppSelector(selectChartLink); diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx index 9625734ae..494bae623 100644 --- a/src/client/app/components/ChartSelectComponent.tsx +++ b/src/client/app/components/ChartSelectComponent.tsx @@ -13,7 +13,7 @@ import { graphSlice, selectChartToRender } from '../redux/slices/graphSlice'; import { SelectOption } from '../types/items'; import { ChartTypes } from '../types/redux/graph'; import { State } from '../types/redux/state'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** @@ -21,6 +21,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Chart select element */ export default function ChartSelectComponent() { + const translate = useTranslate(); const currentChartToRender = useAppSelector(selectChartToRender); const dispatch = useAppDispatch(); const [expand, setExpand] = useState(false); diff --git a/src/client/app/components/CompareControlsComponent.tsx b/src/client/app/components/CompareControlsComponent.tsx index d990852cd..5b9cd3447 100644 --- a/src/client/app/components/CompareControlsComponent.tsx +++ b/src/client/app/components/CompareControlsComponent.tsx @@ -8,13 +8,14 @@ import { Button, ButtonGroup, Dropdown, DropdownItem, DropdownMenu, DropdownTogg import { graphSlice, selectComparePeriod, selectSortingOrder } from '../redux/slices/graphSlice'; import { useAppDispatch, useAppSelector } from '../redux/reduxHooks'; import { ComparePeriod, SortingOrder } from '../utils/calculateCompare'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** * @returns controls for the compare page */ export default function CompareControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const comparePeriod = useAppSelector(selectComparePeriod); const compareSortingOrder = useAppSelector(selectSortingOrder); diff --git a/src/client/app/components/ConfirmActionModalComponent.tsx b/src/client/app/components/ConfirmActionModalComponent.tsx index e3730aa62..9f15d58b7 100644 --- a/src/client/app/components/ConfirmActionModalComponent.tsx +++ b/src/client/app/components/ConfirmActionModalComponent.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import '../styles/modal.css'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; interface ConfirmActionModalComponentProps { @@ -41,7 +41,7 @@ interface ConfirmActionModalComponentProps { * @returns A modal component that executes the actionFunction on confirmation and handleClose on rejection. */ export default function ConfirmActionModalComponent(props: ConfirmActionModalComponentProps) { - + const translate = useTranslate(); const handleClose = () => { props.handleClose(); }; diff --git a/src/client/app/components/DateRangeComponent.tsx b/src/client/app/components/DateRangeComponent.tsx index 22f7846ed..033b311e3 100644 --- a/src/client/app/components/DateRangeComponent.tsx +++ b/src/client/app/components/DateRangeComponent.tsx @@ -13,7 +13,7 @@ import { changeSliderRange, selectQueryTimeInterval, updateTimeInterval, selectC import '../styles/DateRangeCustom.css'; import { Dispatch } from '../types/redux/actions'; import { dateRangeToTimeInterval, timeIntervalToDateRange } from '../utils/dateRangeCompatibility'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; import { ChartTypes } from '../types/redux/graph'; @@ -22,6 +22,7 @@ import { ChartTypes } from '../types/redux/graph'; * @returns Date Range Calendar Picker */ export default function DateRangeComponent() { + const translate = useTranslate(); const dispatch: Dispatch = useAppDispatch(); const queryTimeInterval = useAppSelector(selectQueryTimeInterval); const locale = useAppSelector(selectSelectedLanguage); diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index c8116ed3e..957adb04b 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { useAppDispatch, useAppSelector } from '../redux/reduxHooks'; import { graphSlice, selectShowMinMax } from '../redux/slices/graphSlice'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** @@ -13,6 +13,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Error Bar checkbox with tooltip and label */ export default function ErrorBarComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const showMinMax = useAppSelector(selectShowMinMax); diff --git a/src/client/app/components/FormFileUploaderComponent.tsx b/src/client/app/components/FormFileUploaderComponent.tsx index be8032ee6..714001f63 100644 --- a/src/client/app/components/FormFileUploaderComponent.tsx +++ b/src/client/app/components/FormFileUploaderComponent.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Col, Input, FormGroup, Label } from 'reactstrap'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; interface FileUploader { isInvalid: boolean; @@ -17,7 +17,7 @@ interface FileUploader { * @returns File uploader element */ export default function FileUploaderComponent(props: FileUploader) { - + const translate = useTranslate(); const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] || null; props.onFileChange(file); diff --git a/src/client/app/components/GraphicRateMenuComponent.tsx b/src/client/app/components/GraphicRateMenuComponent.tsx index 163d51965..febe41352 100644 --- a/src/client/app/components/GraphicRateMenuComponent.tsx +++ b/src/client/app/components/GraphicRateMenuComponent.tsx @@ -11,7 +11,7 @@ import { graphSlice, selectGraphState } from '../redux/slices/graphSlice'; import { SelectOption } from '../types/items'; import { ChartTypes, LineGraphRate, LineGraphRates } from '../types/redux/graph'; import { UnitRepresentType } from '../types/redux/units'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** @@ -19,6 +19,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Rate selection element */ export default function GraphicRateMenuComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // Graph state diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index a48b18f56..58f41e53e 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -16,7 +16,7 @@ import { selectHelpUrl } from '../redux/slices/adminSlice'; import { selectOptionsVisibility, toggleOptionsVisibility } from '../redux/slices/appStateSlice'; import { selectHasRolePermissions, selectIsAdmin, selectIsLoggedIn } from '../redux/slices/currentUserSlice'; import { UserRole } from '../types/items'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import LanguageSelectorComponent from './LanguageSelectorComponent'; import TooltipMarkerComponent from './TooltipMarkerComponent'; @@ -25,6 +25,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Header buttons element */ export default function HeaderButtonsComponent() { + const translate = useTranslate(); const [logout] = authApi.useLogoutMutation(); const dispatch = useAppDispatch(); // Get the current page so know which one should not be shown in menu. diff --git a/src/client/app/components/LineChartComponent.tsx b/src/client/app/components/LineChartComponent.tsx index f71058629..95c0d294e 100644 --- a/src/client/app/components/LineChartComponent.tsx +++ b/src/client/app/components/LineChartComponent.tsx @@ -16,7 +16,7 @@ import { selectLineChartDeps, selectPlotlyGroupData, selectPlotlyMeterData } fro import { selectLineUnitLabel } from '../redux/selectors/plotlyDataSelectors'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; import Locales from '../types/locales'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import SpinnerComponent from './SpinnerComponent'; @@ -24,6 +24,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns plotlyLine graphic */ export default function LineChartComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // get current data fetching arguments const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectLineChartQueryArgs); diff --git a/src/client/app/components/LoginComponent.tsx b/src/client/app/components/LoginComponent.tsx index 538c03d99..d182fd13d 100644 --- a/src/client/app/components/LoginComponent.tsx +++ b/src/client/app/components/LoginComponent.tsx @@ -9,13 +9,14 @@ import { useNavigate } from 'react-router-dom'; import { Button, Form, FormGroup, Input, Label } from 'reactstrap'; import { authApi } from '../redux/api/authApi'; import { showErrorNotification, showSuccessNotification } from '../utils/notifications'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; /** * @returns The login page for users or admins. */ export default function LoginComponent() { + const translate = useTranslate(); // Local State const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); diff --git a/src/client/app/components/MapChartComponent.tsx b/src/client/app/components/MapChartComponent.tsx index e1a5dde28..baf02d3b4 100644 --- a/src/client/app/components/MapChartComponent.tsx +++ b/src/client/app/components/MapChartComponent.tsx @@ -32,14 +32,14 @@ import { } from '../utils/calibration'; import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion'; import getGraphColor from '../utils/getGraphColor'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import SpinnerComponent from './SpinnerComponent'; /** * @returns map component */ export default function MapChartComponent() { - + const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectMapChartQueryArgs); const { data: meterReadings, isLoading: meterIsFetching } = readingsApi.useBarQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsFetching } = readingsApi.useBarQuery(groupArgs, { skip: groupShouldSkip }); diff --git a/src/client/app/components/MapControlsComponent.tsx b/src/client/app/components/MapControlsComponent.tsx index ac0934d4e..9a3a10ddc 100644 --- a/src/client/app/components/MapControlsComponent.tsx +++ b/src/client/app/components/MapControlsComponent.tsx @@ -8,13 +8,14 @@ import * as React from 'react'; import { Button, ButtonGroup } from 'reactstrap'; import { useAppDispatch, useAppSelector } from '../redux/reduxHooks'; import { selectMapBarWidthDays, updateMapsBarDuration } from '../redux/slices/graphSlice'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import MapChartSelectComponent from './MapChartSelectComponent'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** * @returns Map page controls */ export default function MapControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const barDuration = useAppSelector(selectMapBarWidthDays); diff --git a/src/client/app/components/MeterAndGroupSelectComponent.tsx b/src/client/app/components/MeterAndGroupSelectComponent.tsx index 52d1e9f7d..67f0b8015 100644 --- a/src/client/app/components/MeterAndGroupSelectComponent.tsx +++ b/src/client/app/components/MeterAndGroupSelectComponent.tsx @@ -16,7 +16,7 @@ import { selectMeterGroupSelectData } from '../redux/selectors/uiSelectors'; import { selectChartToRender, updateSelectedMetersOrGroups, updateThreeDMeterOrGroupInfo } from '../redux/slices/graphSlice'; import { GroupedOption, SelectOption } from '../types/items'; import { ChartTypes, MeterOrGroup } from '../types/redux/graph'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; import { selectAnythingFetching } from '../redux/selectors/apiSelectors'; /** @@ -25,6 +25,7 @@ import { selectAnythingFetching } from '../redux/selectors/apiSelectors'; * @returns A React-Select component. */ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectProps) { + const translate = useTranslate(); const dispatch = useAppDispatch(); const { meterGroupedOptions, groupsGroupedOptions, allSelectedMeterValues, allSelectedGroupValues } = useAppSelector(selectMeterGroupSelectData); const somethingIsFetching = useAppSelector(selectAnythingFetching); diff --git a/src/client/app/components/MoreOptionsComponent.tsx b/src/client/app/components/MoreOptionsComponent.tsx index 4441d882c..bdc860ab3 100644 --- a/src/client/app/components/MoreOptionsComponent.tsx +++ b/src/client/app/components/MoreOptionsComponent.tsx @@ -15,13 +15,14 @@ import DateRangeComponent from './DateRangeComponent'; import ErrorBarComponent from './ErrorBarComponent'; import ExportComponent from '../components/ExportComponent'; import GraphicRateMenuComponent from './GraphicRateMenuComponent'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; /** * Modal popup control for various graph types * @returns Custom Modal depending on selected graph type */ export default function MoreOptionsComponent() { + const translate = useTranslate(); const chartToRender = useAppSelector(selectChartToRender); const [showModal, setShowModal] = useState(false); const handleShow = () => setShowModal(true); diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 37ce9a0b7..3ff51879b 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -22,13 +22,14 @@ import Locales from '../types/locales'; import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion'; import getGraphColor from '../utils/getGraphColor'; import { lineUnitLabel } from '../utils/graphics'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import SpinnerComponent from './SpinnerComponent'; /** * @returns radar plotly component */ export default function RadarChartComponent() { + const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectRadarChartQueryArgs); const { data: meterReadings, isLoading: meterIsLoading } = readingsApi.useLineQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsLoading } = readingsApi.useLineQuery(groupArgs, { skip: groupShouldSkip }); diff --git a/src/client/app/components/ReadingsPerDaySelectComponent.tsx b/src/client/app/components/ReadingsPerDaySelectComponent.tsx index a75d6a513..40a56cbd0 100644 --- a/src/client/app/components/ReadingsPerDaySelectComponent.tsx +++ b/src/client/app/components/ReadingsPerDaySelectComponent.tsx @@ -10,7 +10,7 @@ import { selectThreeDQueryArgs } from '../redux/selectors/chartQuerySelectors'; import { selectReadingsPerDaySelectData } from '../redux/selectors/threeDSelectors'; import { selectThreeDReadingInterval, updateThreeDReadingInterval } from '../redux/slices/graphSlice'; import { ReadingInterval } from '../types/redux/graph'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; /** @@ -18,6 +18,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns A Select menu with Readings per day options. */ export default function ReadingsPerDaySelect() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const readingInterval = useAppSelector(selectThreeDReadingInterval); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index 658df7415..ba512e23d 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -20,7 +20,7 @@ import { UnitDataById } from '../types/redux/units'; import { isValidThreeDInterval, roundTimeIntervalForFetch } from '../utils/dateRangeCompatibility'; import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion'; import { lineUnitLabel } from '../utils/graphics'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import SpinnerComponent from './SpinnerComponent'; import ThreeDPillComponent from './ThreeDPillComponent'; import Plot from 'react-plotly.js'; @@ -32,6 +32,7 @@ import Locales from '../types/locales'; * @returns 3D Plotly 3D Surface Graph */ export default function ThreeDComponent() { + const translate = useTranslate(); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); const { data, isFetching } = readingsApi.endpoints.threeD.useQuery(args, { skip: shouldSkipQuery }); const meterDataById = useAppSelector(selectMeterDataById); @@ -158,6 +159,7 @@ function formatThreeDData( // Calculate Hover Text, and populate xLabels/yLabels const hoverText = zDataToRender.map((day, i) => day.map((readings, j) => { + const translate = useTranslate(); const startTS = moment.utc(data.xData[j].startTimestamp); const endTS = moment.utc(data.xData[j].endTimestamp); const midpointTS = moment.utc(startTS.clone().add(endTS.clone().diff(startTS) / 2)); @@ -227,6 +229,7 @@ function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: numbe * @returns plotly layout object. */ function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { + const translate = useTranslate(); // Convert date strings to JavaScript Date objects and then get dataRange const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); const dataMin = Math.min(...dateObjects.map(date => date.getTime())); diff --git a/src/client/app/components/ThreeDPillComponent.tsx b/src/client/app/components/ThreeDPillComponent.tsx index a5c7b8ae2..cbc637e7a 100644 --- a/src/client/app/components/ThreeDPillComponent.tsx +++ b/src/client/app/components/ThreeDPillComponent.tsx @@ -10,13 +10,14 @@ import { useAppDispatch, useAppSelector } from '../redux/reduxHooks'; import { MeterOrGroup, MeterOrGroupPill } from '../types/redux/graph'; import { AreaUnitType } from '../utils/getAreaUnitConversion'; import { selectMeterDataById } from '../redux/api/metersApi'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; /** * A component used in the threeD graphics to select a single meter from the currently selected meters and groups. * @returns List of selected groups and meters as reactstrap Pills Badges */ export default function ThreeDPillComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const meterDataById = useAppSelector(selectMeterDataById); const groupDataById = useAppSelector(selectGroupDataById); diff --git a/src/client/app/components/TimeZoneSelect.tsx b/src/client/app/components/TimeZoneSelect.tsx index b8dd891b6..10a740e54 100644 --- a/src/client/app/components/TimeZoneSelect.tsx +++ b/src/client/app/components/TimeZoneSelect.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import Select from 'react-select'; import { TimeZoneOption } from 'types/timezone'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import * as moment from 'moment-timezone'; interface TimeZoneSelectProps { @@ -15,6 +15,7 @@ interface TimeZoneSelectProps { } const TimeZoneSelect: React.FC = ({ current, handleClick }) => { + const translate = useTranslate(); const getTimeZones = () => { const zoneNames = moment.tz.names(); diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index 10a56d79a..b7decbf4d 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -9,7 +9,7 @@ import { selectOEDVersion } from '../redux/api/versionApi'; import { useAppSelector } from '../redux/reduxHooks'; import { selectHelpUrl } from '../redux/slices/adminSlice'; import '../styles/tooltip.css'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; interface TooltipHelpProps { page: string; // Specifies which page the tip is in. @@ -20,7 +20,7 @@ interface TooltipHelpProps { * @returns ToolTipHelpComponent */ export default function TooltipHelpComponent(props: TooltipHelpProps) { - + const translate = useTranslate(); /** * @returns JSX to create the help icons with links */ diff --git a/src/client/app/components/UnitSelectComponent.tsx b/src/client/app/components/UnitSelectComponent.tsx index aee27bdbc..f07e478de 100644 --- a/src/client/app/components/UnitSelectComponent.tsx +++ b/src/client/app/components/UnitSelectComponent.tsx @@ -11,7 +11,7 @@ import { GroupedOption, SelectOption } from '../types/items'; // import { FormattedMessage } from 'react-intl'; import { Badge } from 'reactstrap'; import { graphSlice, selectSelectedUnit } from '../redux/slices/graphSlice'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import TooltipMarkerComponent from './TooltipMarkerComponent'; import { selectUnitDataById, unitsApi } from '../redux/api/unitsApi'; @@ -19,6 +19,7 @@ import { selectUnitDataById, unitsApi } from '../redux/api/unitsApi'; * @returns A React-Select component for UI Options Panel */ export default function UnitSelectComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const unitSelectOptions = useAppSelector(selectUnitSelectData); const selectedUnitID = useAppSelector(selectSelectedUnit); diff --git a/src/client/app/components/UnsavedWarningComponent.tsx b/src/client/app/components/UnsavedWarningComponent.tsx index 651c94eb4..313a288d9 100644 --- a/src/client/app/components/UnsavedWarningComponent.tsx +++ b/src/client/app/components/UnsavedWarningComponent.tsx @@ -10,7 +10,7 @@ import { useBlocker } from 'react-router-dom'; import { Button, Modal, ModalBody, ModalFooter } from 'reactstrap'; import { LocaleDataKey } from '../translations/data'; import { showErrorNotification, showSuccessNotification } from '../utils/notifications'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; export interface UnsavedWarningProps { changes: any; @@ -25,6 +25,7 @@ export interface UnsavedWarningProps { * @returns Component that prompts before navigating away from current page */ export function UnsavedWarningComponent(props: UnsavedWarningProps) { + const translate = useTranslate(); const { hasUnsavedChanges, submitChanges, changes } = props; const blocker = useBlocker(hasUnsavedChanges); const handleSubmit = async () => { diff --git a/src/client/app/components/admin/PreferencesComponent.tsx b/src/client/app/components/admin/PreferencesComponent.tsx index 825c8b758..ffc2717cb 100644 --- a/src/client/app/components/admin/PreferencesComponent.tsx +++ b/src/client/app/components/admin/PreferencesComponent.tsx @@ -13,7 +13,7 @@ import { ChartTypes } from '../../types/redux/graph'; import { LanguageTypes } from '../../types/redux/i18n'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import TimeZoneSelect from '../TimeZoneSelect'; import { defaultAdminState } from '../../redux/slices/adminSlice'; @@ -23,6 +23,7 @@ import { defaultAdminState } from '../../redux/slices/adminSlice'; * @returns Preferences Component for Administrative use */ export default function PreferencesComponent() { + const translate = useTranslate(); const { data: adminPreferences = defaultAdminState } = preferencesApi.useGetPreferencesQuery(); const [localAdminPref, setLocalAdminPref] = React.useState(cloneDeep(adminPreferences)); const [submitPreferences] = preferencesApi.useSubmitPreferencesMutation(); diff --git a/src/client/app/components/admin/users/CreateUserModalComponent.tsx b/src/client/app/components/admin/users/CreateUserModalComponent.tsx index f2a875b4e..0594c73ff 100644 --- a/src/client/app/components/admin/users/CreateUserModalComponent.tsx +++ b/src/client/app/components/admin/users/CreateUserModalComponent.tsx @@ -11,7 +11,7 @@ import { import { userApi } from '../../../redux/api/userApi'; import { User, UserRole, userDefaults } from '../../../types/items'; import { showErrorNotification, showSuccessNotification } from '../../../utils/notifications'; -import translate from '../../../utils/translate'; +import { useTranslate } from '../../../redux/componentHooks'; import TooltipHelpComponent from '../../TooltipHelpComponent'; import TooltipMarkerComponent from '../../TooltipMarkerComponent'; import { tooltipBaseStyle } from '../../../styles/modalStyle'; @@ -21,7 +21,7 @@ import { tooltipBaseStyle } from '../../../styles/modalStyle'; * @returns CreateUserModal component */ export default function CreateUserModal() { - + const translate = useTranslate(); // create user form state and use the defaults const [userDetails, setUserDetails] = useState(userDefaults); diff --git a/src/client/app/components/admin/users/EditUserModalComponent.tsx b/src/client/app/components/admin/users/EditUserModalComponent.tsx index 2048c2bfc..4568b2704 100644 --- a/src/client/app/components/admin/users/EditUserModalComponent.tsx +++ b/src/client/app/components/admin/users/EditUserModalComponent.tsx @@ -10,7 +10,7 @@ import { useAppSelector } from '../../../redux/reduxHooks'; import { selectCurrentUserProfile } from '../../../redux/slices/currentUserSlice'; import { User, UserRole, userDefaults } from '../../../types/items'; import { showErrorNotification, showSuccessNotification } from '../../../utils/notifications'; -import translate from '../../../utils/translate'; +import { useTranslate } from '../../../redux/componentHooks'; import ConfirmActionModalComponent from '../../ConfirmActionModalComponent'; import TooltipHelpComponent from '../../TooltipHelpComponent'; import TooltipMarkerComponent from '../../TooltipMarkerComponent'; @@ -29,7 +29,7 @@ interface EditUserModalComponentProps { * @returns User edit element */ export default function EditUserModalComponent(props: EditUserModalComponentProps) { - + const translate = useTranslate(); // get current logged in user const currentLoggedInUser = useAppSelector(selectCurrentUserProfile) as User; diff --git a/src/client/app/components/admin/users/UserViewComponent.tsx b/src/client/app/components/admin/users/UserViewComponent.tsx index 24650438f..0420c3032 100644 --- a/src/client/app/components/admin/users/UserViewComponent.tsx +++ b/src/client/app/components/admin/users/UserViewComponent.tsx @@ -8,7 +8,7 @@ import { useState } from 'react'; import { Button } from 'reactstrap'; import '../../../styles/card-page.css'; import { User } from '../../../types/items'; -import translate from '../../../utils/translate'; +import { useTranslate } from '../../../redux/componentHooks'; import EditUserModalComponent from './EditUserModalComponent'; interface UserViewComponentProps { @@ -21,6 +21,7 @@ interface UserViewComponentProps { * @returns User card element */ export default function UserViewComponent(props: UserViewComponentProps) { + const translate = useTranslate(); const [showEditModal, setShowEditModal] = useState(false); const handleShow = () => { diff --git a/src/client/app/components/admin/users/UsersDetailComponent.tsx b/src/client/app/components/admin/users/UsersDetailComponent.tsx index ff5a8742e..c01c382db 100644 --- a/src/client/app/components/admin/users/UsersDetailComponent.tsx +++ b/src/client/app/components/admin/users/UsersDetailComponent.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { Col, Container, Row } from 'reactstrap'; import { stableEmptyUsers, userApi } from '../../../redux/api/userApi'; -import translate from '../../../utils/translate'; +import { useTranslate } from '../../../redux/componentHooks'; import TooltipHelpComponent from '../../TooltipHelpComponent'; import TooltipMarkerComponent from '../../TooltipMarkerComponent'; import CreateUserModalComponent from './CreateUserModalComponent'; @@ -22,6 +22,7 @@ const tooltipStyle = { * @returns User Detail element */ export default function UserDetailComponent() { + const translate = useTranslate(); const { data: users = stableEmptyUsers } = userApi.useGetUsersQuery(); return ( diff --git a/src/client/app/components/conversion/ConversionViewComponent.tsx b/src/client/app/components/conversion/ConversionViewComponent.tsx index 304008f7e..c63642372 100644 --- a/src/client/app/components/conversion/ConversionViewComponent.tsx +++ b/src/client/app/components/conversion/ConversionViewComponent.tsx @@ -12,7 +12,7 @@ import { selectUnitDataById } from '../../redux/api/unitsApi'; import { useAppSelector } from '../../redux/reduxHooks'; import '../../styles/card-page.css'; import { conversionArrow } from '../../utils/conversionArrow'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import EditConversionModalComponent from './EditConversionModalComponent'; interface ConversionViewComponentProps { @@ -25,6 +25,7 @@ interface ConversionViewComponentProps { * @returns Single conversion element */ export default function ConversionViewComponent(props: ConversionViewComponentProps) { + const translate = useTranslate(); // Don't check if admin since only an admin is allow to route to this page. // Edit Modal Show diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index 6c29db3da..cb4ba8bd1 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -15,7 +15,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { showErrorNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; /** @@ -23,6 +23,7 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Conversion create element */ export default function CreateConversionModalComponent() { + const translate = useTranslate(); const [addConversionMutation] = conversionsApi.useAddConversionMutation(); // Want units in sorted order by identifier regardless of case. diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index 52973c2cb..156f52d74 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -14,7 +14,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { ConversionData } from '../../types/redux/conversions'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import ConfirmActionModalComponent from '../ConfirmActionModalComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -34,6 +34,7 @@ interface EditConversionModalComponentProps { * @returns Conversion edit element */ export default function EditConversionModalComponent(props: EditConversionModalComponentProps) { + const translate = useTranslate(); const [editConversion] = conversionsApi.useEditConversionMutation(); const [deleteConversion] = conversionsApi.useDeleteConversionMutation(); const unitDataById = useAppSelector(selectUnitDataById); diff --git a/src/client/app/components/csv/MetersCSVUploadComponent.tsx b/src/client/app/components/csv/MetersCSVUploadComponent.tsx index d7414bab1..e7a6b4dbf 100644 --- a/src/client/app/components/csv/MetersCSVUploadComponent.tsx +++ b/src/client/app/components/csv/MetersCSVUploadComponent.tsx @@ -8,7 +8,7 @@ import { MetersCSVUploadPreferences } from '../../types/csvUploadForm'; import { submitMeters } from '../../utils/api/UploadCSVApi'; import { MetersCSVUploadDefaults } from '../../utils/csvUploadDefaults'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import FormFileUploaderComponent from '../FormFileUploaderComponent'; import TooltipHelpComponent from '../TooltipHelpComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -22,6 +22,7 @@ import { selectVisibleMeterAndGroupData } from '../../redux/selectors/adminSelec * @returns CSV Meters page element */ export default function MetersCSVUploadComponent() { + const translate = useTranslate(); const [meterData, setMeterData] = React.useState(MetersCSVUploadDefaults); const [selectedFile, setSelectedFile] = React.useState(null); const [isValidFileType, setIsValidFileType] = React.useState(false); diff --git a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx index a1f34fc1a..57bf137bc 100644 --- a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx +++ b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx @@ -16,7 +16,7 @@ import { MeterData, MeterTimeSortType } from '../../types/redux/meters'; import { submitReadings } from '../../utils/api/UploadCSVApi'; import { ReadingsCSVUploadDefaults } from '../../utils/csvUploadDefaults'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import FormFileUploaderComponent from '../FormFileUploaderComponent'; import TooltipHelpComponent from '../TooltipHelpComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -27,7 +27,7 @@ import CreateMeterModalComponent from '../meters/CreateMeterModalComponent'; * @returns CSV Readings page element */ export default function ReadingsCSVUploadComponent() { - + const translate = useTranslate(); const dispatch = useAppDispatch(); // Check for admin status const isAdmin = useAppSelector(selectIsAdmin); diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 21be8a6a7..fada30578 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -29,7 +29,7 @@ import { import { AreaUnitType, getAreaUnitConversion } from '../../utils/getAreaUnitConversion'; import { getGPSString } from '../../utils/input'; import { showErrorNotification, showWarnNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import ListDisplayComponent from '../ListDisplayComponent'; import MultiSelectComponent from '../MultiSelectComponent'; import TooltipHelpComponent from '../TooltipHelpComponent'; @@ -40,6 +40,7 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Group create element */ export default function CreateGroupModalComponent() { + const translate = useTranslate(); const [createGroup] = groupsApi.useCreateGroupMutation(); // Meters state diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index d72d0b42e..ba72b81e7 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -35,7 +35,7 @@ import { import { AreaUnitType, getAreaUnitConversion } from '../../utils/getAreaUnitConversion'; import { getGPSString, nullToEmptyString } from '../../utils/input'; import { showErrorNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import ConfirmActionModalComponent from '../ConfirmActionModalComponent'; import ListDisplayComponent from '../ListDisplayComponent'; import MultiSelectComponent from '../MultiSelectComponent'; @@ -57,6 +57,7 @@ interface EditGroupModalComponentProps { * @returns Group edit element */ export default function EditGroupModalComponent(props: EditGroupModalComponentProps) { + const translate = useTranslate(); const [submitGroupEdits] = groupsApi.useEditGroupMutation(); const [deleteGroup] = groupsApi.useDeleteGroupMutation(); // Meter state diff --git a/src/client/app/components/groups/GroupViewComponent.tsx b/src/client/app/components/groups/GroupViewComponent.tsx index 273459da2..8b30ae65d 100644 --- a/src/client/app/components/groups/GroupViewComponent.tsx +++ b/src/client/app/components/groups/GroupViewComponent.tsx @@ -13,7 +13,7 @@ import { useAppSelector } from '../../redux/reduxHooks'; import { selectIsAdmin } from '../../redux/slices/currentUserSlice'; import '../../styles/card-page.css'; import { noUnitTranslated } from '../../utils/input'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import EditGroupModalComponent from './EditGroupModalComponent'; interface GroupViewComponentProps { @@ -26,6 +26,7 @@ interface GroupViewComponentProps { * @returns Group info card element */ export default function GroupViewComponent(props: GroupViewComponentProps) { + const translate = useTranslate(); // Don't check if admin since only an admin is allowed to route to this page. // Edit Modal Show diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 3a53cb3e4..1c0ba7702 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -25,7 +25,7 @@ import { MeterData, MeterTimeSortType, MeterType } from '../../types/redux/meter import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import TimeZoneSelect from '../TimeZoneSelect'; import TooltipHelpComponent from '../TooltipHelpComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -40,6 +40,7 @@ interface CreateMeterModalProps { * @returns Meter create element */ export default function CreateMeterModalComponent(props: CreateMeterModalProps): React.JSX.Element { + const translate = useTranslate(); // Tracks whether a unit/ default unit has been selected. // RTKQ Mutation to submit add meter const [submitAddMeter] = metersApi.endpoints.addMeter.useMutation(); diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 6d1db44e1..49d3bed21 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -26,7 +26,7 @@ import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { getGPSString, nullToEmptyString } from '../../utils/input'; import { showErrorNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import TimeZoneSelect from '../TimeZoneSelect'; import TooltipHelpComponent from '../TooltipHelpComponent'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -43,6 +43,7 @@ interface EditMeterModalComponentProps { * @returns Meter edit element */ export default function EditMeterModalComponent(props: EditMeterModalComponentProps) { + const translate = useTranslate(); const [editMeter] = metersApi.useEditMeterMutation(); // since this selector is shared amongst many other modals, we must use a selector factory in order // to have a single selector per modal instance. Memo ensures that this is a stable reference diff --git a/src/client/app/components/meters/MeterViewComponent.tsx b/src/client/app/components/meters/MeterViewComponent.tsx index a40c427fb..f3117e342 100644 --- a/src/client/app/components/meters/MeterViewComponent.tsx +++ b/src/client/app/components/meters/MeterViewComponent.tsx @@ -10,7 +10,7 @@ import { MeterData } from 'types/redux/meters'; import { useAppSelector } from '../../redux/reduxHooks'; import { selectGraphicName, selectUnitName } from '../../redux/selectors/adminSelectors'; import '../../styles/card-page.css'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; import EditMeterModalComponent from './EditMeterModalComponent'; import { selectIsAdmin } from '../../redux/slices/currentUserSlice'; @@ -24,6 +24,7 @@ interface MeterViewComponentProps { * @returns Meter info card element */ export default function MeterViewComponent(props: MeterViewComponentProps) { + const translate = useTranslate(); // Edit Modal Show const [showEditModal, setShowEditModal] = useState(false); // Check for admin status diff --git a/src/client/app/components/router/ErrorComponent.tsx b/src/client/app/components/router/ErrorComponent.tsx index bc58cbcee..58098df9e 100644 --- a/src/client/app/components/router/ErrorComponent.tsx +++ b/src/client/app/components/router/ErrorComponent.tsx @@ -6,12 +6,13 @@ import * as React from 'react'; import { useNavigate } from 'react-router-dom'; import { Button } from 'reactstrap'; import AppLayout from '../../components/AppLayout'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; /** * @returns A error page that then returns to main dashboard page. */ export default function ErrorComponent() { + const translate = useTranslate(); const nav = useNavigate(); const refreshPage = () => { nav('/'); diff --git a/src/client/app/components/router/InitializingComponent.tsx b/src/client/app/components/router/InitializingComponent.tsx index bd2346de5..25e449da9 100644 --- a/src/client/app/components/router/InitializingComponent.tsx +++ b/src/client/app/components/router/InitializingComponent.tsx @@ -4,12 +4,13 @@ import * as React from 'react'; import SpinnerComponent from '../SpinnerComponent'; -import translate from '../../utils/translate'; +import { useTranslate } from '../../redux/componentHooks'; /** * @returns A simple loading spinner used to indicate that the startup init sequence is in progress */ export default function InitializingComponent() { + const translate = useTranslate(); return (
{ // ensure a fetch is not currently happening if (!getState().conversions.isFetching) { diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts index 8fb9d33aa..eddeb042c 100644 --- a/src/client/app/redux/actions/map.ts +++ b/src/client/app/redux/actions/map.ts @@ -16,7 +16,7 @@ import { import {State} from '../../types/redux/state'; import {mapsApi} from '../../utils/api'; import {showErrorNotification, showSuccessNotification} from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../componentHooks'; import * as moment from 'moment'; import {browserHistory} from '../../utils/history'; import {logToServer} from './logs'; @@ -232,6 +232,7 @@ export function submitCalibratingMap(): Thunk { * submit a new map to database at the end of a calibration session */ export function submitNewMap(): Thunk { + const translate = useTranslate(); return async (dispatch: Dispatch, getState: GetState) => { const mapID = getState().maps.calibratingMap; const map = getState().maps.editedMaps[mapID]; @@ -266,6 +267,7 @@ export function submitNewMap(): Thunk { * @param mapID the edited map being updated at database */ export function submitEditedMap(mapID: number): Thunk { + const translate = useTranslate(); return async (dispatch: Dispatch, getState: GetState) => { const map = getState().maps.editedMaps[mapID]; dispatch(submitMapEdits(mapID)); @@ -307,6 +309,7 @@ export function submitEditedMap(mapID: number): Thunk { * @param mapID map to be removed */ export function removeMap(mapID: number): Thunk { + const translate = useTranslate(); return async (dispatch: Dispatch) => { try { await mapsApi.delete(mapID); diff --git a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts index 2a9b304a0..a14482140 100644 --- a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts +++ b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts @@ -4,11 +4,12 @@ import { isAsyncThunkAction, isRejected } from '@reduxjs/toolkit'; import { showErrorNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../componentHooks'; import { AppListener } from '../listenerMiddleware'; import { authApi } from '../api/authApi'; export const unauthorizedRequestListener = (startListening: AppListener) => { + const translate = useTranslate(); startListening({ predicate: action => { // Listens for rejected async thunks. if no payload then its an RTK internal call that needs to also be filtered. diff --git a/src/client/app/redux/selectors/adminSelectors.ts b/src/client/app/redux/selectors/adminSelectors.ts index 6f057c5d7..ddc2ceeeb 100644 --- a/src/client/app/redux/selectors/adminSelectors.ts +++ b/src/client/app/redux/selectors/adminSelectors.ts @@ -14,7 +14,7 @@ import { UnitData, UnitType } from '../../types/redux/units'; import { unitsCompatibleWithUnit } from '../../utils/determineCompatibleUnits'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { noUnitTranslated, potentialGraphicUnits } from '../../utils/input'; -import translate from '../../utils/translate'; +import { useTranslate } from '../componentHooks'; import { selectAllUnits, selectUnitDataById } from '../api/unitsApi'; import { selectVisibleMetersAndGroups } from './authVisibilitySelectors'; import { createAppSelector } from './selectors'; @@ -217,7 +217,7 @@ export const selectIsValidConversion = createAppSelector( Cannot mix unit represent TODO Some of these can go away when we make the menus dynamic. */ - + const translate = useTranslate(); // The destination cannot be a meter unit. if (destinationId !== -999 && unitDataById[destinationId].typeOfUnit === UnitType.meter) { return [false, translate('conversion.create.destination.meter')]; diff --git a/src/client/app/redux/selectors/lineChartSelectors.ts b/src/client/app/redux/selectors/lineChartSelectors.ts index d4ca92247..bdf5e1e6e 100644 --- a/src/client/app/redux/selectors/lineChartSelectors.ts +++ b/src/client/app/redux/selectors/lineChartSelectors.ts @@ -6,7 +6,7 @@ import * as moment from 'moment'; import { selectShowMinMax } from '../../redux/slices/graphSlice'; import { DataType } from '../../types/Datasources'; import getGraphColor from '../../utils/getGraphColor'; -import translate from '../../utils/translate'; +import { useTranslate } from '../componentHooks'; import { createAppSelector } from './selectors'; import { selectScalingFromEntity, selectNameFromEntity } from './entitySelectors'; import { selectPlotlyMeterDeps, selectPlotlyGroupDeps, selectFromLineReadingsResult } from './plotlyDataSelectors'; @@ -57,6 +57,7 @@ export const selectPlotlyMeterData = selectFromLineReadingsResult( yData.push(readingValue); // All hover have the date, meter name and value. const hoverStart = ` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${lineUnitLabel}`; + const translate = useTranslate(); if (showMinMax && reading.max != null) { // We want to show min/max. Note if the data is raw for this meter then all the min/max values are null. // In this case we still push the min/max but plotly will not show them. This is a little extra work diff --git a/src/client/app/redux/thunks/exportThunk.ts b/src/client/app/redux/thunks/exportThunk.ts index 06eb6cc75..9cfa8023f 100644 --- a/src/client/app/redux/thunks/exportThunk.ts +++ b/src/client/app/redux/thunks/exportThunk.ts @@ -20,7 +20,7 @@ import { ConversionData } from '../../types/redux/conversions'; import { ChartTypes, MeterOrGroup } from '../../types/redux/graph'; import graphExport, { downloadRawCSV } from '../../utils/exportData'; import { showErrorNotification } from '../../utils/notifications'; -import translate from '../../utils/translate'; +import { useTranslate } from '../componentHooks'; import { createAppThunk } from './appThunk'; import { selectAnythingFetching } from '../../redux/selectors/apiSelectors'; import { RootState } from '../../store'; @@ -115,6 +115,7 @@ export const exportGraphReadingsThunk = createAppThunk( export const exportRawReadings = createAppThunk( 'graph/ExportRaw', async (_arg, api) => { + const translate = useTranslate(); const state = api.getState(); if (!selectCanExport(state)) { return api.rejectWithValue('Data Fetch In Progress, Or No data'); diff --git a/src/client/app/utils/calculateCompare.ts b/src/client/app/utils/calculateCompare.ts index 499c5bac1..ff9c1b3bb 100644 --- a/src/client/app/utils/calculateCompare.ts +++ b/src/client/app/utils/calculateCompare.ts @@ -4,7 +4,7 @@ import { TimeInterval } from '../../../common/TimeInterval'; import * as moment from 'moment'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; /** * 'Day', 'Week' or 'FourWeeks' @@ -159,6 +159,7 @@ export interface ComparePeriodLabels { * @returns human-readable names for the compare period as {{prev: string, current: string}} */ export function getComparePeriodLabels(comparePeriod: ComparePeriod): ComparePeriodLabels { + const translate = useTranslate(); switch (comparePeriod) { case ComparePeriod.Day: return { prev: translate('yesterday'), current: translate('today') }; @@ -180,6 +181,7 @@ export function getComparePeriodLabels(comparePeriod: ComparePeriod): ComparePer * @returns The label summary */ export function getCompareChangeSummary(change: number, name: string, labels: ComparePeriodLabels): string { + const translate = useTranslate(); if (isNaN(change)) { return `${name} ${translate('has.no.data')}`; } diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 7ec057a4b..2e197b2dc 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -6,7 +6,7 @@ import { showErrorNotification } from './notifications'; import { logToServer } from '../redux/actions/logs'; import { DataType } from '../types/Datasources'; import { MapMetadata } from '../types/redux/map'; -import translate from './translate'; +import { useTranslate } from '../redux/componentHooks'; /** * Defines a Cartesian Point with x & y @@ -107,6 +107,7 @@ export function itemDisplayableOnMap(size: Dimensions, point: CartesianPoint): b * @returns true if string is GPS and false otherwise. */ export function isValidGPSInput(input: string): boolean { + const translate = useTranslate(); if (input.indexOf(',') === -1) { // if there is no comma // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. return false; diff --git a/src/client/app/utils/graphics.ts b/src/client/app/utils/graphics.ts index cb2e64d34..08f03c897 100644 --- a/src/client/app/utils/graphics.ts +++ b/src/client/app/utils/graphics.ts @@ -4,7 +4,7 @@ import { LineGraphRate } from 'types/redux/graph'; import { UnitData, UnitRepresentType } from '../types/redux/units'; -import translate from '../utils/translate'; +import { useTranslate } from '../redux/componentHooks'; import { AreaUnitType } from './getAreaUnitConversion'; // Has functions for use with graphics @@ -19,7 +19,7 @@ import { AreaUnitType } from './getAreaUnitConversion'; */ export function lineUnitLabel(selectUnitState: UnitData, currentSelectedRate: LineGraphRate, areaNormalization: boolean, selectedAreaUnit: AreaUnitType): { unitLabel: string, needsRateScaling: boolean } { - + const translate = useTranslate(); let unitLabel: string = ''; let needsRateScaling = false; // Quantity and flow units have different unit labels. @@ -60,6 +60,7 @@ export function lineUnitLabel(selectUnitState: UnitData, currentSelectedRate: Li * @returns y-axis label */ export function barUnitLabel(selectUnitState: UnitData, areaNormalization: boolean, selectedAreaUnit: AreaUnitType): string { + const translate = useTranslate(); let unitLabel: string = ''; // Quantity and flow units have different unit labels. // Look up the type of unit if it is for quantity/flow (should not be raw) and decide what to do. diff --git a/src/client/app/utils/input.ts b/src/client/app/utils/input.ts index d6c67fae4..da34ab302 100644 --- a/src/client/app/utils/input.ts +++ b/src/client/app/utils/input.ts @@ -4,7 +4,7 @@ import { GPSPoint } from './calibration'; import { UnitData, DisplayableType, UnitRepresentType, UnitType, UnitDataById } from '../types/redux/units'; -import translate from './translate'; +import { useTranslate } from '../redux/componentHooks'; import { sortBy } from 'lodash'; /** @@ -87,6 +87,7 @@ export const NoUnit: UnitData = { * @returns a unit to represent no unit with translated identifier */ export function noUnitTranslated(): UnitData { + const translate = useTranslate(); // Untranslated no unit. const unit = NoUnit; // Make the identifier be translated. From 6e12e8f88d1040f6f12be01f76afe541ef8a078c Mon Sep 17 00:00:00 2001 From: Joijanae Laws Date: Fri, 4 Oct 2024 19:01:29 -0500 Subject: [PATCH 02/34] Standardize translation method --- src/client/app/components/AreaUnitSelectComponent.tsx | 2 +- src/client/app/components/BarChartComponent.tsx | 2 +- src/client/app/components/BarControlsComponent.tsx | 2 +- src/client/app/components/ChartLinkComponent.tsx | 2 +- src/client/app/components/ChartSelectComponent.tsx | 2 +- src/client/app/components/CompareControlsComponent.tsx | 2 +- src/client/app/components/ConfirmActionModalComponent.tsx | 2 +- src/client/app/components/DateRangeComponent.tsx | 2 +- src/client/app/components/ErrorBarComponent.tsx | 2 +- src/client/app/components/FormFileUploaderComponent.tsx | 2 +- src/client/app/components/GraphicRateMenuComponent.tsx | 2 +- src/client/app/components/HeaderButtonsComponent.tsx | 2 +- src/client/app/components/LineChartComponent.tsx | 2 +- src/client/app/components/LoginComponent.tsx | 2 +- src/client/app/components/MapChartComponent.tsx | 2 +- src/client/app/components/MapControlsComponent.tsx | 2 +- src/client/app/components/MeterAndGroupSelectComponent.tsx | 2 +- src/client/app/components/MoreOptionsComponent.tsx | 2 +- src/client/app/components/RadarChartComponent.tsx | 2 +- src/client/app/components/ReadingsPerDaySelectComponent.tsx | 2 +- src/client/app/components/ThreeDComponent.tsx | 6 +++--- src/client/app/components/ThreeDPillComponent.tsx | 2 +- src/client/app/components/TimeZoneSelect.tsx | 2 +- src/client/app/components/TooltipHelpComponent.tsx | 2 +- src/client/app/components/UnitSelectComponent.tsx | 2 +- src/client/app/components/UnsavedWarningComponent.tsx | 2 +- src/client/app/components/admin/PreferencesComponent.tsx | 2 +- .../app/components/admin/users/CreateUserModalComponent.tsx | 2 +- .../app/components/admin/users/EditUserModalComponent.tsx | 2 +- src/client/app/components/admin/users/UserViewComponent.tsx | 2 +- .../app/components/admin/users/UsersDetailComponent.tsx | 2 +- .../app/components/conversion/ConversionViewComponent.tsx | 2 +- .../conversion/CreateConversionModalComponent.tsx | 2 +- .../components/conversion/EditConversionModalComponent.tsx | 2 +- src/client/app/components/csv/MetersCSVUploadComponent.tsx | 2 +- .../app/components/csv/ReadingsCSVUploadComponent.tsx | 2 +- .../app/components/groups/CreateGroupModalComponent.tsx | 2 +- .../app/components/groups/EditGroupModalComponent.tsx | 2 +- src/client/app/components/groups/GroupViewComponent.tsx | 2 +- .../app/components/meters/CreateMeterModalComponent.tsx | 2 +- .../app/components/meters/EditMeterModalComponent.tsx | 2 +- src/client/app/components/meters/MeterViewComponent.tsx | 2 +- src/client/app/components/router/ErrorComponent.tsx | 2 +- src/client/app/components/unit/UnitViewComponent.tsx | 2 +- src/client/app/containers/CompareChartContainer.ts | 2 +- src/client/app/containers/MapChartContainer.ts | 2 +- .../containers/maps/MapCalibrationInfoDisplayContainer.ts | 2 +- src/client/app/redux/actions/conversions.ts | 4 +++- src/client/app/redux/actions/map.ts | 6 +++--- .../app/redux/middleware/unauthorizedAccesMiddleware.ts | 2 +- src/client/app/redux/thunks/exportThunk.ts | 2 +- src/client/app/utils/calibration.ts | 2 +- src/client/app/utils/graphics.ts | 2 +- src/client/app/utils/input.ts | 2 +- 54 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/client/app/components/AreaUnitSelectComponent.tsx b/src/client/app/components/AreaUnitSelectComponent.tsx index 739e6fb26..58ad7266b 100644 --- a/src/client/app/components/AreaUnitSelectComponent.tsx +++ b/src/client/app/components/AreaUnitSelectComponent.tsx @@ -20,12 +20,12 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; */ export default function AreaUnitSelectComponent() { const dispatch = useAppDispatch(); - const translate = useTranslate(); const graphState = useAppSelector(selectGraphState); const unitDataById = useAppSelector(selectUnitDataById); // Array of select options created from the area unit enum const unitOptions: StringSelectOption[] = []; + const translate = useTranslate(); Object.keys(AreaUnitType).forEach(unitKey => { // don't allow normalization by no unit diff --git a/src/client/app/components/BarChartComponent.tsx b/src/client/app/components/BarChartComponent.tsx index e3a7ed65c..2d7e3fad1 100644 --- a/src/client/app/components/BarChartComponent.tsx +++ b/src/client/app/components/BarChartComponent.tsx @@ -27,7 +27,6 @@ import SpinnerComponent from './SpinnerComponent'; * @returns Plotly BarChart */ export default function BarChartComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const { barMeterDeps, barGroupDeps } = useAppSelector(selectPlotlyBarDeps); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectBarChartQueryArgs); @@ -56,6 +55,7 @@ export default function BarChartComponent() { // useQueryHooks for data fetching const datasets: Partial[] = meterReadings.concat(groupData); + const translate = useTranslate(); if (meterIsFetching || groupIsFetching) { return ; diff --git a/src/client/app/components/BarControlsComponent.tsx b/src/client/app/components/BarControlsComponent.tsx index ead20b67c..8b89408df 100644 --- a/src/client/app/components/BarControlsComponent.tsx +++ b/src/client/app/components/BarControlsComponent.tsx @@ -15,7 +15,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns controls for the Options Ui page. */ export default function BarControlsComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); // The min/max days allowed for user selection @@ -90,6 +89,7 @@ export default function BarControlsComponent() { dispatch(graphSlice.actions.updateBarDuration(moment.duration(value, 'days'))); } }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/ChartLinkComponent.tsx b/src/client/app/components/ChartLinkComponent.tsx index 6de7aabae..547abea7d 100644 --- a/src/client/app/components/ChartLinkComponent.tsx +++ b/src/client/app/components/ChartLinkComponent.tsx @@ -18,7 +18,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns chartLinkComponent */ export default function ChartLinkComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const [linkTextVisible, setLinkTextVisible] = React.useState(false); const linkText = useAppSelector(selectChartLink); @@ -26,6 +25,7 @@ export default function ChartLinkComponent() { const selectedMeters = useAppSelector(selectSelectedMeters); const selectedGroups = useAppSelector(selectSelectedGroups); const ref = React.useRef(null); + const translate = useTranslate(); const handleButtonClick = () => { // First attempt to write directly to user's clipboard. navigator.clipboard.writeText(linkText) diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx index 494bae623..739226a2a 100644 --- a/src/client/app/components/ChartSelectComponent.tsx +++ b/src/client/app/components/ChartSelectComponent.tsx @@ -21,7 +21,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Chart select element */ export default function ChartSelectComponent() { - const translate = useTranslate(); const currentChartToRender = useAppSelector(selectChartToRender); const dispatch = useAppDispatch(); const [expand, setExpand] = useState(false); @@ -29,6 +28,7 @@ export default function ChartSelectComponent() { const sortedMaps = sortBy(values(mapsById).map(map => ( { value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) } as SelectOption )), 'label'); + const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/CompareControlsComponent.tsx b/src/client/app/components/CompareControlsComponent.tsx index 5b9cd3447..76d6c8c83 100644 --- a/src/client/app/components/CompareControlsComponent.tsx +++ b/src/client/app/components/CompareControlsComponent.tsx @@ -15,7 +15,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns controls for the compare page */ export default function CompareControlsComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const comparePeriod = useAppSelector(selectComparePeriod); const compareSortingOrder = useAppSelector(selectSortingOrder); @@ -26,6 +25,7 @@ export default function CompareControlsComponent() { const handleSortingButton = (sortingOrder: SortingOrder) => { dispatch(graphSlice.actions.changeCompareSortingOrder(sortingOrder)); }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/ConfirmActionModalComponent.tsx b/src/client/app/components/ConfirmActionModalComponent.tsx index 9f15d58b7..b6aa27e74 100644 --- a/src/client/app/components/ConfirmActionModalComponent.tsx +++ b/src/client/app/components/ConfirmActionModalComponent.tsx @@ -41,10 +41,10 @@ interface ConfirmActionModalComponentProps { * @returns A modal component that executes the actionFunction on confirmation and handleClose on rejection. */ export default function ConfirmActionModalComponent(props: ConfirmActionModalComponentProps) { - const translate = useTranslate(); const handleClose = () => { props.handleClose(); }; + const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/DateRangeComponent.tsx b/src/client/app/components/DateRangeComponent.tsx index 033b311e3..8cc374d33 100644 --- a/src/client/app/components/DateRangeComponent.tsx +++ b/src/client/app/components/DateRangeComponent.tsx @@ -22,7 +22,6 @@ import { ChartTypes } from '../types/redux/graph'; * @returns Date Range Calendar Picker */ export default function DateRangeComponent() { - const translate = useTranslate(); const dispatch: Dispatch = useAppDispatch(); const queryTimeInterval = useAppSelector(selectQueryTimeInterval); const locale = useAppSelector(selectSelectedLanguage); @@ -33,6 +32,7 @@ export default function DateRangeComponent() { dispatch(updateTimeInterval(dateRangeToTimeInterval(value))); dispatch(changeSliderRange(dateRangeToTimeInterval(value))); }; + const translate = useTranslate(); return ( diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index 957adb04b..9fe1f8282 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -13,9 +13,9 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Error Bar checkbox with tooltip and label */ export default function ErrorBarComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const showMinMax = useAppSelector(selectShowMinMax); + const translate = useTranslate(); return (
diff --git a/src/client/app/components/FormFileUploaderComponent.tsx b/src/client/app/components/FormFileUploaderComponent.tsx index 714001f63..2d90906cf 100644 --- a/src/client/app/components/FormFileUploaderComponent.tsx +++ b/src/client/app/components/FormFileUploaderComponent.tsx @@ -17,11 +17,11 @@ interface FileUploader { * @returns File uploader element */ export default function FileUploaderComponent(props: FileUploader) { - const translate = useTranslate(); const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] || null; props.onFileChange(file); }; + const translate = useTranslate(); return ( diff --git a/src/client/app/components/GraphicRateMenuComponent.tsx b/src/client/app/components/GraphicRateMenuComponent.tsx index febe41352..9d8717964 100644 --- a/src/client/app/components/GraphicRateMenuComponent.tsx +++ b/src/client/app/components/GraphicRateMenuComponent.tsx @@ -19,7 +19,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Rate selection element */ export default function GraphicRateMenuComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); // Graph state @@ -49,6 +48,7 @@ export default function GraphicRateMenuComponent() { } // Array of select options created from the rates const rateOptions: SelectOption[] = []; + const translate = useTranslate(); //Loop over our rates object to create the selects for the dropdown Object.entries(LineGraphRates).forEach(([rateKey, rateValue]) => { diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 58f41e53e..fc9a67b47 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -25,7 +25,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Header buttons element */ export default function HeaderButtonsComponent() { - const translate = useTranslate(); const [logout] = authApi.useLogoutMutation(); const dispatch = useAppDispatch(); // Get the current page so know which one should not be shown in menu. @@ -75,6 +74,7 @@ export default function HeaderButtonsComponent() { // TODO Re-implement AFTER RTK Migration // hard-coded for the time being. Rework w/admin pages const unsavedChangesState = false; + const translate = useTranslate(); // Must update in case the version was not set when the page was loaded. diff --git a/src/client/app/components/LineChartComponent.tsx b/src/client/app/components/LineChartComponent.tsx index 95c0d294e..adf658d3b 100644 --- a/src/client/app/components/LineChartComponent.tsx +++ b/src/client/app/components/LineChartComponent.tsx @@ -24,7 +24,6 @@ import SpinnerComponent from './SpinnerComponent'; * @returns plotlyLine graphic */ export default function LineChartComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); // get current data fetching arguments const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectLineChartQueryArgs); @@ -67,6 +66,7 @@ export default function LineChartComponent() { // Check if there is at least one valid graph const enoughData = data.find(data => data.x!.length > 1); + const translate = useTranslate(); // Customize the layout of the plot // See https://community.plotly.com/t/replacing-an-empty-graph-with-a-message/31497 for showing text not plot. if (data.length === 0) { diff --git a/src/client/app/components/LoginComponent.tsx b/src/client/app/components/LoginComponent.tsx index d182fd13d..399199312 100644 --- a/src/client/app/components/LoginComponent.tsx +++ b/src/client/app/components/LoginComponent.tsx @@ -16,7 +16,6 @@ import { useTranslate } from '../redux/componentHooks'; * @returns The login page for users or admins. */ export default function LoginComponent() { - const translate = useTranslate(); // Local State const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); @@ -29,6 +28,7 @@ export default function LoginComponent() { // The naming of the returned objects is arbitrary // Equivalent Auto-Derived Method const [login] = authApi.endpoints.login.useMutation(); // authApi.useLoginMutation() + const translate = useTranslate(); const handleSubmit = async (event: React.MouseEvent) => { event.preventDefault(); diff --git a/src/client/app/components/MapChartComponent.tsx b/src/client/app/components/MapChartComponent.tsx index baf02d3b4..a0e64a35d 100644 --- a/src/client/app/components/MapChartComponent.tsx +++ b/src/client/app/components/MapChartComponent.tsx @@ -39,7 +39,6 @@ import SpinnerComponent from './SpinnerComponent'; * @returns map component */ export default function MapChartComponent() { - const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectMapChartQueryArgs); const { data: meterReadings, isLoading: meterIsFetching } = readingsApi.useBarQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsFetching } = readingsApi.useBarQuery(groupArgs, { skip: groupShouldSkip }); @@ -71,6 +70,7 @@ export default function MapChartComponent() { const data = []; // Holds the image to use. let image; + const translate = useTranslate(); if (selectedMap !== 0) { const mapID = selectedMap; if (byMapID[mapID]) { diff --git a/src/client/app/components/MapControlsComponent.tsx b/src/client/app/components/MapControlsComponent.tsx index 9a3a10ddc..73849f0d0 100644 --- a/src/client/app/components/MapControlsComponent.tsx +++ b/src/client/app/components/MapControlsComponent.tsx @@ -15,7 +15,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Map page controls */ export default function MapControlsComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const barDuration = useAppSelector(selectMapBarWidthDays); @@ -24,6 +23,7 @@ export default function MapControlsComponent() { }; const barDurationDays = barDuration.asDays(); + const translate = useTranslate(); return (
diff --git a/src/client/app/components/MeterAndGroupSelectComponent.tsx b/src/client/app/components/MeterAndGroupSelectComponent.tsx index 67f0b8015..b85001741 100644 --- a/src/client/app/components/MeterAndGroupSelectComponent.tsx +++ b/src/client/app/components/MeterAndGroupSelectComponent.tsx @@ -25,7 +25,6 @@ import { selectAnythingFetching } from '../redux/selectors/apiSelectors'; * @returns A React-Select component. */ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectProps) { - const translate = useTranslate(); const dispatch = useAppDispatch(); const { meterGroupedOptions, groupsGroupedOptions, allSelectedMeterValues, allSelectedGroupValues } = useAppSelector(selectMeterGroupSelectData); const somethingIsFetching = useAppSelector(selectAnythingFetching); @@ -40,6 +39,7 @@ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectP const newMetersOrGroups = newValues.map(option => option.value); dispatch(updateSelectedMetersOrGroups({ newMetersOrGroups, meta })); }; + const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/MoreOptionsComponent.tsx b/src/client/app/components/MoreOptionsComponent.tsx index bdc860ab3..be5047594 100644 --- a/src/client/app/components/MoreOptionsComponent.tsx +++ b/src/client/app/components/MoreOptionsComponent.tsx @@ -22,13 +22,13 @@ import { useTranslate } from '../redux/componentHooks'; * @returns Custom Modal depending on selected graph type */ export default function MoreOptionsComponent() { - const translate = useTranslate(); const chartToRender = useAppSelector(selectChartToRender); const [showModal, setShowModal] = useState(false); const handleShow = () => setShowModal(true); const handleClose = () => { setShowModal(false); }; + const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 3ff51879b..5c382bc17 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -29,7 +29,6 @@ import SpinnerComponent from './SpinnerComponent'; * @returns radar plotly component */ export default function RadarChartComponent() { - const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectRadarChartQueryArgs); const { data: meterReadings, isLoading: meterIsLoading } = readingsApi.useLineQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsLoading } = readingsApi.useLineQuery(groupArgs, { skip: groupShouldSkip }); @@ -67,6 +66,7 @@ export default function RadarChartComponent() { } // The rate will be 1 if it is per hour (since state readings are per hour) or no rate scaling so no change. const rateScaling = needsRateScaling ? currentSelectedRate.rate : 1; + const translate = useTranslate(); // Add all valid data from existing meters to the radar plot for (const meterID of selectedMeters) { diff --git a/src/client/app/components/ReadingsPerDaySelectComponent.tsx b/src/client/app/components/ReadingsPerDaySelectComponent.tsx index 40a56cbd0..c7e7511b6 100644 --- a/src/client/app/components/ReadingsPerDaySelectComponent.tsx +++ b/src/client/app/components/ReadingsPerDaySelectComponent.tsx @@ -18,7 +18,6 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns A Select menu with Readings per day options. */ export default function ReadingsPerDaySelect() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const readingInterval = useAppSelector(selectThreeDReadingInterval); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); @@ -30,6 +29,7 @@ export default function ReadingsPerDaySelect() { ...selectReadingsPerDaySelectData(currentData ?? stableEmptyThreeDReadings, readingInterval) }) }); + const translate = useTranslate(); return (
diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index ba512e23d..f56970755 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -32,7 +32,6 @@ import Locales from '../types/locales'; * @returns 3D Plotly 3D Surface Graph */ export default function ThreeDComponent() { - const translate = useTranslate(); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); const { data, isFetching } = readingsApi.endpoints.threeD.useQuery(args, { skip: shouldSkipQuery }); const meterDataById = useAppSelector(selectMeterDataById); @@ -47,6 +46,7 @@ export default function ThreeDComponent() { const threeDData = data; let layout = {}; let dataToRender = null; + const translate = useTranslate(); if (!meterOrGroupID) { @@ -125,6 +125,7 @@ function formatThreeDData( const currentSelectedRate = graphState.lineGraphRate; let unitLabel = ''; let needsRateScaling = false; + const translate = useTranslate(); if (graphingUnit !== -99) { const selectUnitState = unitDataById[graphState.selectedUnit]; if (selectUnitState !== undefined) { @@ -159,7 +160,6 @@ function formatThreeDData( // Calculate Hover Text, and populate xLabels/yLabels const hoverText = zDataToRender.map((day, i) => day.map((readings, j) => { - const translate = useTranslate(); const startTS = moment.utc(data.xData[j].startTimestamp); const endTS = moment.utc(data.xData[j].endTimestamp); const midpointTS = moment.utc(startTS.clone().add(endTS.clone().diff(startTS) / 2)); @@ -229,12 +229,12 @@ function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: numbe * @returns plotly layout object. */ function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { - const translate = useTranslate(); // Convert date strings to JavaScript Date objects and then get dataRange const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); const dataMin = Math.min(...dateObjects.map(date => date.getTime())); const dataMax = Math.max(...dateObjects.map(date => date.getTime())); const dataRange = dataMax - dataMin; + const translate = useTranslate(); //Calculate nTicks for small num of days on y-axis; possibly a better way let nTicks, dTick = 'd1'; diff --git a/src/client/app/components/ThreeDPillComponent.tsx b/src/client/app/components/ThreeDPillComponent.tsx index cbc637e7a..3f8610cf7 100644 --- a/src/client/app/components/ThreeDPillComponent.tsx +++ b/src/client/app/components/ThreeDPillComponent.tsx @@ -17,7 +17,6 @@ import { useTranslate } from '../redux/componentHooks'; * @returns List of selected groups and meters as reactstrap Pills Badges */ export default function ThreeDPillComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const meterDataById = useAppSelector(selectMeterDataById); const groupDataById = useAppSelector(selectGroupDataById); @@ -82,6 +81,7 @@ export default function ThreeDPillComponent() { ); }); }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/TimeZoneSelect.tsx b/src/client/app/components/TimeZoneSelect.tsx index 10a740e54..50c3e5d9b 100644 --- a/src/client/app/components/TimeZoneSelect.tsx +++ b/src/client/app/components/TimeZoneSelect.tsx @@ -15,7 +15,6 @@ interface TimeZoneSelectProps { } const TimeZoneSelect: React.FC = ({ current, handleClick }) => { - const translate = useTranslate(); const getTimeZones = () => { const zoneNames = moment.tz.names(); @@ -25,6 +24,7 @@ const TimeZoneSelect: React.FC = ({ current, handleClick }) return { value: zoneName, label: `${zoneName} (${abbrev})` }; }); }; + const translate = useTranslate(); const resetTimeZone = [{ value: null, label: translate('timezone.no') }]; const options = [...resetTimeZone, ...getTimeZones()]; diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index b7decbf4d..5c08947bb 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -20,7 +20,6 @@ interface TooltipHelpProps { * @returns ToolTipHelpComponent */ export default function TooltipHelpComponent(props: TooltipHelpProps) { - const translate = useTranslate(); /** * @returns JSX to create the help icons with links */ @@ -73,6 +72,7 @@ export default function TooltipHelpComponent(props: TooltipHelpProps) { 'help.groups.groupview': { link: `${helpUrl}/groupViewing/` }, 'help.meters.meterview': { link: `${helpUrl}/meterViewing/` } }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/UnitSelectComponent.tsx b/src/client/app/components/UnitSelectComponent.tsx index f07e478de..a4689a10a 100644 --- a/src/client/app/components/UnitSelectComponent.tsx +++ b/src/client/app/components/UnitSelectComponent.tsx @@ -19,7 +19,6 @@ import { selectUnitDataById, unitsApi } from '../redux/api/unitsApi'; * @returns A React-Select component for UI Options Panel */ export default function UnitSelectComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); const unitSelectOptions = useAppSelector(selectUnitSelectData); const selectedUnitID = useAppSelector(selectSelectedUnit); @@ -39,6 +38,7 @@ export default function UnitSelectComponent() { } const onChange = (newValue: SelectOption) => dispatch(graphSlice.actions.updateSelectedUnit(newValue?.value)); + const translate = useTranslate(); return (
diff --git a/src/client/app/components/UnsavedWarningComponent.tsx b/src/client/app/components/UnsavedWarningComponent.tsx index 313a288d9..d68254c93 100644 --- a/src/client/app/components/UnsavedWarningComponent.tsx +++ b/src/client/app/components/UnsavedWarningComponent.tsx @@ -25,9 +25,9 @@ export interface UnsavedWarningProps { * @returns Component that prompts before navigating away from current page */ export function UnsavedWarningComponent(props: UnsavedWarningProps) { - const translate = useTranslate(); const { hasUnsavedChanges, submitChanges, changes } = props; const blocker = useBlocker(hasUnsavedChanges); + const translate = useTranslate(); const handleSubmit = async () => { submitChanges(changes) .unwrap() diff --git a/src/client/app/components/admin/PreferencesComponent.tsx b/src/client/app/components/admin/PreferencesComponent.tsx index ffc2717cb..5628f5e17 100644 --- a/src/client/app/components/admin/PreferencesComponent.tsx +++ b/src/client/app/components/admin/PreferencesComponent.tsx @@ -23,7 +23,6 @@ import { defaultAdminState } from '../../redux/slices/adminSlice'; * @returns Preferences Component for Administrative use */ export default function PreferencesComponent() { - const translate = useTranslate(); const { data: adminPreferences = defaultAdminState } = preferencesApi.useGetPreferencesQuery(); const [localAdminPref, setLocalAdminPref] = React.useState(cloneDeep(adminPreferences)); const [submitPreferences] = preferencesApi.useSubmitPreferencesMutation(); @@ -42,6 +41,7 @@ export default function PreferencesComponent() { const discardChanges = () => { setLocalAdminPref(cloneDeep(adminPreferences)); }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/admin/users/CreateUserModalComponent.tsx b/src/client/app/components/admin/users/CreateUserModalComponent.tsx index 0594c73ff..2319f0b29 100644 --- a/src/client/app/components/admin/users/CreateUserModalComponent.tsx +++ b/src/client/app/components/admin/users/CreateUserModalComponent.tsx @@ -21,7 +21,6 @@ import { tooltipBaseStyle } from '../../../styles/modalStyle'; * @returns CreateUserModal component */ export default function CreateUserModal() { - const translate = useTranslate(); // create user form state and use the defaults const [userDetails, setUserDetails] = useState(userDefaults); @@ -86,6 +85,7 @@ export default function CreateUserModal() { setShowModal(false); }; // End Modal show/close + const translate = useTranslate(); const handleSubmit = async () => { const newUser: User = { username: userDetails.username, role: userDetails.role, password: userDetails.password, note: userDetails.note }; diff --git a/src/client/app/components/admin/users/EditUserModalComponent.tsx b/src/client/app/components/admin/users/EditUserModalComponent.tsx index 4568b2704..fad2a83f6 100644 --- a/src/client/app/components/admin/users/EditUserModalComponent.tsx +++ b/src/client/app/components/admin/users/EditUserModalComponent.tsx @@ -29,7 +29,6 @@ interface EditUserModalComponentProps { * @returns User edit element */ export default function EditUserModalComponent(props: EditUserModalComponentProps) { - const translate = useTranslate(); // get current logged in user const currentLoggedInUser = useAppSelector(selectCurrentUserProfile) as User; @@ -86,6 +85,7 @@ export default function EditUserModalComponent(props: EditUserModalComponentProp confirmPassword: '' })); }; + const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/admin/users/UserViewComponent.tsx b/src/client/app/components/admin/users/UserViewComponent.tsx index 0420c3032..099d369c0 100644 --- a/src/client/app/components/admin/users/UserViewComponent.tsx +++ b/src/client/app/components/admin/users/UserViewComponent.tsx @@ -21,7 +21,6 @@ interface UserViewComponentProps { * @returns User card element */ export default function UserViewComponent(props: UserViewComponentProps) { - const translate = useTranslate(); const [showEditModal, setShowEditModal] = useState(false); const handleShow = () => { @@ -31,6 +30,7 @@ export default function UserViewComponent(props: UserViewComponentProps) { const handleClose = () => { setShowEditModal(false); }; + const translate = useTranslate(); return (
diff --git a/src/client/app/components/admin/users/UsersDetailComponent.tsx b/src/client/app/components/admin/users/UsersDetailComponent.tsx index c01c382db..83e819d00 100644 --- a/src/client/app/components/admin/users/UsersDetailComponent.tsx +++ b/src/client/app/components/admin/users/UsersDetailComponent.tsx @@ -22,8 +22,8 @@ const tooltipStyle = { * @returns User Detail element */ export default function UserDetailComponent() { - const translate = useTranslate(); const { data: users = stableEmptyUsers } = userApi.useGetUsersQuery(); + const translate = useTranslate(); return (
diff --git a/src/client/app/components/conversion/ConversionViewComponent.tsx b/src/client/app/components/conversion/ConversionViewComponent.tsx index c63642372..749c577cb 100644 --- a/src/client/app/components/conversion/ConversionViewComponent.tsx +++ b/src/client/app/components/conversion/ConversionViewComponent.tsx @@ -25,7 +25,6 @@ interface ConversionViewComponentProps { * @returns Single conversion element */ export default function ConversionViewComponent(props: ConversionViewComponentProps) { - const translate = useTranslate(); // Don't check if admin since only an admin is allow to route to this page. // Edit Modal Show @@ -45,6 +44,7 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr unitDataById[props.conversion.destinationId]?.identifier); // Unlike the details component, we don't check if units are loaded since must come through that page. + const translate = useTranslate(); return (
diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index cb4ba8bd1..f383555c2 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -23,7 +23,6 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Conversion create element */ export default function CreateConversionModalComponent() { - const translate = useTranslate(); const [addConversionMutation] = conversionsApi.useAddConversionMutation(); // Want units in sorted order by identifier regardless of case. @@ -99,6 +98,7 @@ export default function CreateConversionModalComponent() { ...tooltipBaseStyle, tooltipCreateConversionView: 'help.admin.conversioncreate' }; + const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index 156f52d74..155aa8464 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -34,7 +34,6 @@ interface EditConversionModalComponentProps { * @returns Conversion edit element */ export default function EditConversionModalComponent(props: EditConversionModalComponentProps) { - const translate = useTranslate(); const [editConversion] = conversionsApi.useEditConversionMutation(); const [deleteConversion] = conversionsApi.useDeleteConversionMutation(); const unitDataById = useAppSelector(selectUnitDataById); @@ -58,6 +57,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC setState({ ...state, [e.target.name]: Number(e.target.value) }); }; /* End State */ + const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/csv/MetersCSVUploadComponent.tsx b/src/client/app/components/csv/MetersCSVUploadComponent.tsx index e7a6b4dbf..eee66614e 100644 --- a/src/client/app/components/csv/MetersCSVUploadComponent.tsx +++ b/src/client/app/components/csv/MetersCSVUploadComponent.tsx @@ -22,7 +22,6 @@ import { selectVisibleMeterAndGroupData } from '../../redux/selectors/adminSelec * @returns CSV Meters page element */ export default function MetersCSVUploadComponent() { - const translate = useTranslate(); const [meterData, setMeterData] = React.useState(MetersCSVUploadDefaults); const [selectedFile, setSelectedFile] = React.useState(null); const [isValidFileType, setIsValidFileType] = React.useState(false); @@ -52,6 +51,7 @@ export default function MetersCSVUploadComponent() { [name]: checked })); }; + const translate = useTranslate(); const handleFileChange = (file: File) => { setSelectedFile(file); diff --git a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx index 57bf137bc..47c990de7 100644 --- a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx +++ b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx @@ -27,7 +27,6 @@ import CreateMeterModalComponent from '../meters/CreateMeterModalComponent'; * @returns CSV Readings page element */ export default function ReadingsCSVUploadComponent() { - const translate = useTranslate(); const dispatch = useAppDispatch(); // Check for admin status const isAdmin = useAppSelector(selectIsAdmin); @@ -88,6 +87,7 @@ export default function ReadingsCSVUploadComponent() { [name]: value === 'true' })); }; + const translate = useTranslate(); const handleFileChange = (file: File) => { if (file) { diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index fada30578..a2dbc47d5 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -40,7 +40,6 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Group create element */ export default function CreateGroupModalComponent() { - const translate = useTranslate(); const [createGroup] = groupsApi.useCreateGroupMutation(); // Meters state @@ -126,6 +125,7 @@ export default function CreateGroupModalComponent() { ); }, [state.area, state.areaUnit, state.name, state.deepMeters]); /* End State */ + const translate = useTranslate(); // Sums the area of the group's deep meters. It will tell the admin if any meters are omitted from the calculation, // or if any other errors are encountered. diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index ba72b81e7..9fc087537 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -57,7 +57,6 @@ interface EditGroupModalComponentProps { * @returns Group edit element */ export default function EditGroupModalComponent(props: EditGroupModalComponentProps) { - const translate = useTranslate(); const [submitGroupEdits] = groupsApi.useEditGroupMutation(); const [deleteGroup] = groupsApi.useDeleteGroupMutation(); // Meter state @@ -148,6 +147,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr ); }, [groupState.area, groupState.areaUnit, groupState.name, groupState.deepMeters]); /* End State */ + const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/groups/GroupViewComponent.tsx b/src/client/app/components/groups/GroupViewComponent.tsx index 8b30ae65d..748a3f764 100644 --- a/src/client/app/components/groups/GroupViewComponent.tsx +++ b/src/client/app/components/groups/GroupViewComponent.tsx @@ -26,7 +26,6 @@ interface GroupViewComponentProps { * @returns Group info card element */ export default function GroupViewComponent(props: GroupViewComponentProps) { - const translate = useTranslate(); // Don't check if admin since only an admin is allowed to route to this page. // Edit Modal Show @@ -46,6 +45,7 @@ export default function GroupViewComponent(props: GroupViewComponentProps) { // Set up to display the units associated with the group as the unit identifier. // unit state const unitDataById = useAppSelector(selectUnitDataById); + const translate = useTranslate(); return ( diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 1c0ba7702..6301e6302 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -40,7 +40,6 @@ interface CreateMeterModalProps { * @returns Meter create element */ export default function CreateMeterModalComponent(props: CreateMeterModalProps): React.JSX.Element { - const translate = useTranslate(); // Tracks whether a unit/ default unit has been selected. // RTKQ Mutation to submit add meter const [submitAddMeter] = metersApi.endpoints.addMeter.useMutation(); @@ -101,6 +100,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): setShowModal(false); resetState(); }; + const translate = useTranslate(); // Unlike edit, we decided to discard and inputs when you choose to leave the page. The reasoning is // that create starts from an empty template. diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 49d3bed21..bcdea7cbd 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -43,7 +43,6 @@ interface EditMeterModalComponentProps { * @returns Meter edit element */ export default function EditMeterModalComponent(props: EditMeterModalComponentProps) { - const translate = useTranslate(); const [editMeter] = metersApi.useEditMeterMutation(); // since this selector is shared amongst many other modals, we must use a selector factory in order // to have a single selector per modal instance. Memo ensures that this is a stable reference @@ -69,6 +68,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr useEffect(() => { setValidMeter(isValidMeter(localMeterEdits)); }, [localMeterEdits]); /* End State */ + const translate = useTranslate(); React.useEffect(() => { if (localMeterEdits.cumulative === false) { diff --git a/src/client/app/components/meters/MeterViewComponent.tsx b/src/client/app/components/meters/MeterViewComponent.tsx index f3117e342..299b2586f 100644 --- a/src/client/app/components/meters/MeterViewComponent.tsx +++ b/src/client/app/components/meters/MeterViewComponent.tsx @@ -24,7 +24,6 @@ interface MeterViewComponentProps { * @returns Meter info card element */ export default function MeterViewComponent(props: MeterViewComponentProps) { - const translate = useTranslate(); // Edit Modal Show const [showEditModal, setShowEditModal] = useState(false); // Check for admin status @@ -43,6 +42,7 @@ export default function MeterViewComponent(props: MeterViewComponentProps) { setShowEditModal(false); }; // Only display limited data if not an admin. + const translate = useTranslate(); return (
diff --git a/src/client/app/components/router/ErrorComponent.tsx b/src/client/app/components/router/ErrorComponent.tsx index 58098df9e..e30ab4c4a 100644 --- a/src/client/app/components/router/ErrorComponent.tsx +++ b/src/client/app/components/router/ErrorComponent.tsx @@ -12,12 +12,12 @@ import { useTranslate } from '../../redux/componentHooks'; * @returns A error page that then returns to main dashboard page. */ export default function ErrorComponent() { - const translate = useTranslate(); const nav = useNavigate(); const refreshPage = () => { nav('/'); window.location.reload(); }; + const translate = useTranslate(); return ( {/* Pass div as child prop to AppLayout */} diff --git a/src/client/app/components/unit/UnitViewComponent.tsx b/src/client/app/components/unit/UnitViewComponent.tsx index 4dceca6cf..40e479ab9 100644 --- a/src/client/app/components/unit/UnitViewComponent.tsx +++ b/src/client/app/components/unit/UnitViewComponent.tsx @@ -23,7 +23,6 @@ interface UnitViewComponentProps { * @returns Unit info card element */ export default function UnitViewComponent(props: UnitViewComponentProps) { - const translate = useTranslate(); // Don't check if admin since only an admin is allow to route to this page. // Edit Modal Show @@ -36,6 +35,7 @@ export default function UnitViewComponent(props: UnitViewComponentProps) { const handleClose = () => { setShowEditModal(false); }; + const translate = useTranslate(); return (
diff --git a/src/client/app/containers/CompareChartContainer.ts b/src/client/app/containers/CompareChartContainer.ts index 277768a62..e6172c322 100644 --- a/src/client/app/containers/CompareChartContainer.ts +++ b/src/client/app/containers/CompareChartContainer.ts @@ -47,7 +47,6 @@ interface CompareChartContainerProps { * @returns The props object */ function mapStateToProps(state: RootState, ownProps: CompareChartContainerProps): any { - const translate = useTranslate(); const comparePeriod = selectComparePeriod(state); const compareTimeInterval = selectCompareTimeInterval(state); const datasets: any[] = []; @@ -134,6 +133,7 @@ function mapStateToProps(state: RootState, ownProps: CompareChartContainerProps) let previousPeriod = entity.prevUsage; let currentPeriod = entity.currUsage; const areaNormalization = selectGraphAreaNormalization(state); + const translate = useTranslate(); // Check if there is data to graph. if (previousPeriod !== null && currentPeriod !== null) { if (areaNormalization) { diff --git a/src/client/app/containers/MapChartContainer.ts b/src/client/app/containers/MapChartContainer.ts index a26f96074..22afdea4c 100644 --- a/src/client/app/containers/MapChartContainer.ts +++ b/src/client/app/containers/MapChartContainer.ts @@ -25,7 +25,6 @@ import getGraphColor from '../utils/getGraphColor'; import { useTranslate } from '../redux/componentHooks'; function mapStateToProps(state: State) { - const translate = useTranslate(); const unitID = state.graph.selectedUnit; // Map to use. let map; @@ -33,6 +32,7 @@ function mapStateToProps(state: State) { const data = []; // Holds the image to use. let image; + const translate = useTranslate(); if (state.maps.selectedMap !== 0) { const mapID = state.maps.selectedMap; if (state.maps.byMapID[mapID]) { diff --git a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts b/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts index c71be1b74..4f8a4fb65 100644 --- a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts +++ b/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts @@ -12,9 +12,9 @@ import {logToServer} from '../../redux/actions/logs'; import { useTranslate } from '../../redux/componentHooks'; function mapStateToProps(state: State) { - const translate = useTranslate(); const mapID = state.maps.calibratingMap; const map = state.maps.editedMaps[mapID]; + const translate = useTranslate(); const resultDisplay = (map.calibrationResult) ? `x: ${map.calibrationResult.maxError.x}%, y: ${map.calibrationResult.maxError.y}%` : translate('need.more.points'); diff --git a/src/client/app/redux/actions/conversions.ts b/src/client/app/redux/actions/conversions.ts index 21f3a23fb..768683d90 100644 --- a/src/client/app/redux/actions/conversions.ts +++ b/src/client/app/redux/actions/conversions.ts @@ -18,7 +18,6 @@ import { conversionsSlice } from '../reducers/conversions'; export function fetchConversionsDetails(): Thunk { - const translate = useTranslate(); return async (dispatch: Dispatch, getState: GetState) => { // ensure a fetch is not currently happening if (!getState().conversions.isFetching) { @@ -59,6 +58,7 @@ export function submitEditedConversion(editedConversion: t.ConversionData, shoul const conversionDataIndex = getState().conversions.submitting.findIndex(conversionData => (( conversionData.sourceId === editedConversion.sourceId) && conversionData.destinationId === editedConversion.destinationId)); + const translate = useTranslate(); // If the editedConversion is not already being submitted if (conversionDataIndex === -1) { @@ -87,6 +87,7 @@ export function submitEditedConversion(editedConversion: t.ConversionData, shoul // Add conversion to database export function addConversion(conversion: t.ConversionData): Dispatch { return async (dispatch: Dispatch) => { + const translate = useTranslate(); try { // Attempt to add conversion to database await conversionsApi.addConversion(conversion); @@ -117,6 +118,7 @@ export function deleteConversion(conversion: t.ConversionData): (dispatch: Dispa // Inform the store we are about to work on this conversion // Update the submitting state array dispatch(conversionsSlice.actions.submitEditedConversion(conversion)); + const translate = useTranslate(); try { // Attempt to delete the conversion from the database await conversionsApi.delete(conversion); diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts index eddeb042c..d58f61f1b 100644 --- a/src/client/app/redux/actions/map.ts +++ b/src/client/app/redux/actions/map.ts @@ -232,10 +232,10 @@ export function submitCalibratingMap(): Thunk { * submit a new map to database at the end of a calibration session */ export function submitNewMap(): Thunk { - const translate = useTranslate(); return async (dispatch: Dispatch, getState: GetState) => { const mapID = getState().maps.calibratingMap; const map = getState().maps.editedMaps[mapID]; + const translate = useTranslate(); try { const acceptableMap: MapData = { ...map, @@ -267,10 +267,10 @@ export function submitNewMap(): Thunk { * @param mapID the edited map being updated at database */ export function submitEditedMap(mapID: number): Thunk { - const translate = useTranslate(); return async (dispatch: Dispatch, getState: GetState) => { const map = getState().maps.editedMaps[mapID]; dispatch(submitMapEdits(mapID)); + const translate = useTranslate(); try { const acceptableMap: MapData = { ...map, @@ -309,8 +309,8 @@ export function submitEditedMap(mapID: number): Thunk { * @param mapID map to be removed */ export function removeMap(mapID: number): Thunk { - const translate = useTranslate(); return async (dispatch: Dispatch) => { + const translate = useTranslate(); try { await mapsApi.delete(mapID); dispatch(deleteMap(mapID)); diff --git a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts index a14482140..022cb053e 100644 --- a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts +++ b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts @@ -9,7 +9,6 @@ import { AppListener } from '../listenerMiddleware'; import { authApi } from '../api/authApi'; export const unauthorizedRequestListener = (startListening: AppListener) => { - const translate = useTranslate(); startListening({ predicate: action => { // Listens for rejected async thunks. if no payload then its an RTK internal call that needs to also be filtered. @@ -18,6 +17,7 @@ export const unauthorizedRequestListener = (startListening: AppListener) => { effect: (action: any, { dispatch }): void => { // Look for token failed responses from server const unAuthorizedTokenRequest = (action.payload.status === 401 || action.payload.data?.message === 'Failed to authenticate token.'); + const translate = useTranslate(); if (unAuthorizedTokenRequest) { dispatch(authApi.endpoints.logout.initiate()); showErrorNotification(translate('invalid.token.login')); diff --git a/src/client/app/redux/thunks/exportThunk.ts b/src/client/app/redux/thunks/exportThunk.ts index 9cfa8023f..7de8afcc1 100644 --- a/src/client/app/redux/thunks/exportThunk.ts +++ b/src/client/app/redux/thunks/exportThunk.ts @@ -115,7 +115,6 @@ export const exportGraphReadingsThunk = createAppThunk( export const exportRawReadings = createAppThunk( 'graph/ExportRaw', async (_arg, api) => { - const translate = useTranslate(); const state = api.getState(); if (!selectCanExport(state)) { return api.rejectWithValue('Data Fetch In Progress, Or No data'); @@ -140,6 +139,7 @@ export const exportRawReadings = createAppThunk( const fileSize = (count * 0.082 / 1000); // Decides if the readings should be exported, true if should. let shouldDownload = false; + const translate = useTranslate(); if (fileSize <= adminState.defaultWarningFileSize) { // File sizes that anyone can download without prompting so fine shouldDownload = true; diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 2e197b2dc..e51fd3f5a 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -107,7 +107,6 @@ export function itemDisplayableOnMap(size: Dimensions, point: CartesianPoint): b * @returns true if string is GPS and false otherwise. */ export function isValidGPSInput(input: string): boolean { - const translate = useTranslate(); if (input.indexOf(',') === -1) { // if there is no comma // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. return false; @@ -121,6 +120,7 @@ export function isValidGPSInput(input: string): boolean { const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; const result = latitudeConstraint && longitudeConstraint; + const translate = useTranslate(); if (!result) { // TODO It would be nice to return the error and then notify as desired. showErrorNotification(translate('input.gps.range') + input); diff --git a/src/client/app/utils/graphics.ts b/src/client/app/utils/graphics.ts index 08f03c897..5c9eedef4 100644 --- a/src/client/app/utils/graphics.ts +++ b/src/client/app/utils/graphics.ts @@ -19,9 +19,9 @@ import { AreaUnitType } from './getAreaUnitConversion'; */ export function lineUnitLabel(selectUnitState: UnitData, currentSelectedRate: LineGraphRate, areaNormalization: boolean, selectedAreaUnit: AreaUnitType): { unitLabel: string, needsRateScaling: boolean } { - const translate = useTranslate(); let unitLabel: string = ''; let needsRateScaling = false; + const translate = useTranslate(); // Quantity and flow units have different unit labels. // Look up the type of unit if it is for quantity/flow/raw and decide what to do. // Bar graphics are always quantities. diff --git a/src/client/app/utils/input.ts b/src/client/app/utils/input.ts index da34ab302..86c8f610f 100644 --- a/src/client/app/utils/input.ts +++ b/src/client/app/utils/input.ts @@ -87,9 +87,9 @@ export const NoUnit: UnitData = { * @returns a unit to represent no unit with translated identifier */ export function noUnitTranslated(): UnitData { - const translate = useTranslate(); // Untranslated no unit. const unit = NoUnit; + const translate = useTranslate(); // Make the identifier be translated. unit.identifier = translate('unit.none'); return unit; From 46c670ac56c4f0749b5a6bf61ca789460964761c Mon Sep 17 00:00:00 2001 From: Tien Han Date: Fri, 18 Oct 2024 11:45:34 -0700 Subject: [PATCH 03/34] Add in testcase C16 Co-authored-by: SageMar <115049261+SageMar@users.noreply.github.com> Co-authored-by: cmatthews444 <155667279+cmatthews444@users.noreply.github.com> --- .../test/web/readingsCompareMeterFlow.js | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index c74447212..e458799f1 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -7,6 +7,7 @@ See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); +const Unit = require('../../models/Unit'); const { prepareTest, expectCompareToEqualExpected, getUnitId, @@ -21,7 +22,85 @@ mocha.describe('readings API', () => { mocha.describe('for meters', () => { // Add C15 here - // Add C16 here + // C16 - Test 15 minute intervals and flow units and thing as "thing where rate is 36" + mocha.it('C16: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & thing as thing where rate is 36', async () => { + //Create a 2D array to feed into the database + const unitDataThing = [ + { + // u14 + name: 'Thing_36', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 36, + typeOfUnit: Unit.unitType.METER, + suffix: '', + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: 'special unit' + }, + { + // u15 + name: 'thing unit', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: '', + displayable: Unit.displayableType.ALL, + preferredDisplay: false, + note: 'special unit' + } + ]; + + const converstionDataThing_36 = [ + { + // c15 + sourceName: 'Thing_36', + destinationName: 'thing unit', + bidirectional: false, + //Our test case says slope is 100, but the definition in C15 is 1 + slope: 1, + intercept: 0, + note: 'Thing_36 → thing unit' + } + ]; + + const meterDataThing_36 = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } + ] + + // Initialize test database with this data + await prepareTest(unitDataThing, converstionDataThing_36, meterDataThing_36); + + // Get the unit ID since the DB could use any value + const unitId = await getUnitId('thing unit'); + // Expected was taken from the `curr use, prev use` column for this test case, since this is a compare readings test + const expected = [4855.01888481568, 5018.56560262927]; + + // Create a request to the API and save the response + // Note: the api paths are located in app.js, but our specific one points to compareReadings.js + const res = await chai.request(app).get(`/api/compareReadings/meters/${METER_ID}`) + .query({ + curr_start: '2022-10-30 00:00:00', + curr_end: '2022-10-31 17:00:00', + shift: 'P7D', + graphicUnitId: unitId + }); + + // Check that the API reading is equal to what it is expected to equal + expectCompareToEqualExpected(res, expected, METER_ID); + }) }); }); }); From 01ba49ac4e009b5ed166ccb0497677b050b7dee9 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Sat, 19 Oct 2024 11:24:27 -0500 Subject: [PATCH 04/34] fix compare sql & migrations - Compare readings had a bug with flow units where it was summing rather than ave. Fixed by using hourly table that is a rate for all units. This is also more efficient than looking at meter readings but does limit to full hour data that was current usage anyway. - The users migrations were in the previously planned 1.0.0-1.1.1 and was moved to current 1.0.0-2.0.0. The index already listed it there. --- src/server/migrations/1.0.0-2.0.0/index.js | 1 + .../create_function_get_compare_readings.sql | 152 ++++++++++++++++++ .../sql/users/add_users_note.sql | 0 .../sql/users/alter_users_table.sql | 0 .../create_function_get_compare_readings.sql | 34 ++-- 5 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 src/server/migrations/1.0.0-2.0.0/sql/readings/create_function_get_compare_readings.sql rename src/server/migrations/{1.0.0-1.1.0 => 1.0.0-2.0.0}/sql/users/add_users_note.sql (100%) rename src/server/migrations/{1.0.0-1.1.0 => 1.0.0-2.0.0}/sql/users/alter_users_table.sql (100%) diff --git a/src/server/migrations/1.0.0-2.0.0/index.js b/src/server/migrations/1.0.0-2.0.0/index.js index 0e796f33d..8444613e3 100644 --- a/src/server/migrations/1.0.0-2.0.0/index.js +++ b/src/server/migrations/1.0.0-2.0.0/index.js @@ -10,6 +10,7 @@ module.exports = { toVersion: '2.0.0', up: async db => { await db.none(sqlFile('../migrations/1.0.0-2.0.0/sql/readings/create_reading_views.sql')); + await db.none(sqlFile('../migrations/1.0.0-2.0.0/sql/readings/create_function_get_compare_readings.sql')); await db.none(sqlFile('../migrations/1.0.0-2.0.0/sql/meter/add_meter_pipeline_checks.sql')); await db.none(sqlFile('../migrations/1.0.0-2.0.0/sql/preferences/add_preferences_pipeline_checks.sql')); await db.none(sqlFile('../migrations/1.0.0-2.0.0/sql/preferences/add_graph_type.sql')); diff --git a/src/server/migrations/1.0.0-2.0.0/sql/readings/create_function_get_compare_readings.sql b/src/server/migrations/1.0.0-2.0.0/sql/readings/create_function_get_compare_readings.sql new file mode 100644 index 000000000..72c0d7612 --- /dev/null +++ b/src/server/migrations/1.0.0-2.0.0/sql/readings/create_function_get_compare_readings.sql @@ -0,0 +1,152 @@ +/* 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/. */ + +/* +This isn't technically needed as no function signature changed and a startup of OED should +replace the functions but do just to show/be safe. + */ + +/* +This shouldn't ever be looking at more than a few weeks of data, so we don't need to deal with compression. + */ + +/* +TODO This function can probably be improved for two reasons: +1) While it was noted that you don't look at too many days, it does limit its usage to modest time lengths. +There has been thought to allowing comparisons, for example, or a whole year. It also assumes that the +frequency of readings is not too high so the number of readings looked at could be large even over +modest time frames. +2) Readings are only included if they are within the current time period. This means that if you have +a reading that crosses the timeframe they are excluded. This can be viewed as either a good thing or +a bad thing. However, if the frequency of readings is high, then large segments of time can be excluded. +For example, monthly readings would not be includes in either day, week or four week comparisons that +OED currently does. Also note that the daily and hourly views do include readings that span a time frame +so including them would be consistent. + +We need to think about how best to deal with this but one option is to use the daily and hourly tables +to get the reading values as in done with bar graphs. This needs to be a little different since partial +days can be involved. However, getting the full days from the daily table and then the hours from the +hourly table to be combined would solve this. (One could get the subhour from the raw readings but it is +unclear users would want that.) Another consider is that the current system, as is the case for bar graphs, +does not take into account missing times in readings which can lead to lower than expected values. The +daily and hourly readings would help fix this. +*/ + +/* +The following function returns data for plotting compare graphs. It works on meters. +It should not be used on raw readings. +This only works when the curr start/end times are on the hour as it uses the hourly reading view. +It will truncate to lose partial hours if they are given. The current client code will only +send full hours so this should be fine. +It is the new version of compare_readings that works with units. It takes these parameters: +meter_ids: A array of meter ids to query. +graphic_unit_id: The unit id of the unit to use for the graph. +curr_start: When the current/this time period begins for the compare. +curr_end: When the current/this time period ends for the compare. +shift: How far back in time to shift the curr_start and curr_end date/time to get the previous + times to compare. + */ +CREATE OR REPLACE FUNCTION meter_compare_readings_unit ( + meter_ids INTEGER[], + graphic_unit_id INTEGER, + curr_start TIMESTAMP, + curr_end TIMESTAMP, + shift INTERVAL +) + RETURNS TABLE(meter_id INTEGER, curr_use FLOAT, prev_use FLOAT) +AS $$ +DECLARE + curr_tsrange TSRANGE; + prev_tsrange TSRANGE; +BEGIN + curr_tsrange := tsrange(curr_start, curr_end); + prev_tsrange := tsrange(curr_start - shift, curr_end - shift); + + RETURN QUERY + WITH + curr_period AS ( + SELECT + meters.id AS meter_id, + -- Convert the reading based on the conversion found below. + -- It is okay to sum the flow units because hourly readings has a rate of per hour so + -- to convert to a quantity would multiply by 1 so it is the same formula. + SUM(hourly.reading_rate) * c.slope + c.intercept AS reading + FROM (((hourly_readings_unit hourly + INNER JOIN unnest(meter_ids) meters(id) ON hourly.meter_id = meters.id) + INNER JOIN meters m ON m.id = meters.id) + -- This is getting the conversion for the meter and unit to graph. + -- The slope and intercept are used above the transform the reading to the desired unit. + INNER JOIN cik c on c.source_id = m.unit_id AND c.destination_id = graphic_unit_id) + WHERE curr_tsrange @> hourly.time_interval + GROUP BY meters.id, c.slope, c.intercept + ), + prev_period AS ( + SELECT + meters.id AS meter_id, + -- Convert the reading based on the conversion found below. + -- It is okay to sum the flow units because hourly readings has a rate of per hour so + -- to convert to a quantity would multiply by 1 so it is the same formula. + SUM(hourly.reading_rate) * c.slope + c.intercept AS reading + FROM (((hourly_readings_unit hourly + INNER JOIN unnest(meter_ids) meters(id) ON hourly.meter_id = meters.id) + INNER JOIN meters m ON m.id = meters.id) + -- This is getting the conversion for the meter and unit to graph. + -- The slope and intercept are used above the transform the reading to the desired unit. + INNER JOIN cik c on c.source_id = m.unit_id AND c.destination_id = graphic_unit_id) + WHERE prev_tsrange @> hourly.time_interval + GROUP BY meters.id, c.slope, c.intercept + ) + SELECT + meters.id AS meter_id, + curr_period.reading::FLOAT AS curr_use, + prev_period.reading::FLOAT AS prev_use + FROM + unnest(meter_ids) meters(id) + -- Left joins here so we get nulls instead of missing rows if readings don't exist for some time intervals + LEFT JOIN prev_period ON meters.id = prev_period.meter_id + LEFT JOIN curr_period ON meters.id = curr_period.meter_id; +END; +$$ LANGUAGE 'plpgsql'; + + +/* +The following function returns data for plotting compare graphs. It works on groups. +It should not be used on raw readings. +See meter version for needing start/end on the hour. +It is the new version of group_compare_readings that works with units. It takes these parameters: +group_ids: A array of group ids to query. +graphic_unit_id: The unit id of the unit to use for the graph. +curr_start: When the current/this time period begins for the compare. +curr_end: When the current/this time period ends for the compare. +shift: How far back in time to shift the curr_start and curr_end date/time to get the previous + times to compare. + */ +CREATE OR REPLACE FUNCTION group_compare_readings_unit ( + group_ids INTEGER[], + graphic_unit_id INTEGER, + curr_start TIMESTAMP, + curr_end TIMESTAMP, + shift INTERVAL +) + RETURNS TABLE(group_id INTEGER, curr_use FLOAT, prev_use FLOAT) +AS $$ +DECLARE + meter_ids INTEGER[]; +BEGIN + SELECT array_agg(DISTINCT meter_id) INTO meter_ids + FROM unnest(group_ids) gids(id) + INNER JOIN groups_deep_meters gdm ON gdm.group_id = gids.id; + + RETURN QUERY + SELECT + gids.id AS group_id, + SUM(cr.curr_use) AS curr_use, + SUM(cr.prev_use) AS prev_use + FROM unnest(group_ids) gids(id) + INNER JOIN groups_deep_meters gdm ON gdm.group_id = gids.id + INNER JOIN meter_compare_readings_unit(meter_ids, graphic_unit_id, curr_start, curr_end, shift) cr + ON cr.meter_id = gdm.meter_id + GROUP by gids.id; +END; +$$ LANGUAGE 'plpgsql'; diff --git a/src/server/migrations/1.0.0-1.1.0/sql/users/add_users_note.sql b/src/server/migrations/1.0.0-2.0.0/sql/users/add_users_note.sql similarity index 100% rename from src/server/migrations/1.0.0-1.1.0/sql/users/add_users_note.sql rename to src/server/migrations/1.0.0-2.0.0/sql/users/add_users_note.sql diff --git a/src/server/migrations/1.0.0-1.1.0/sql/users/alter_users_table.sql b/src/server/migrations/1.0.0-2.0.0/sql/users/alter_users_table.sql similarity index 100% rename from src/server/migrations/1.0.0-1.1.0/sql/users/alter_users_table.sql rename to src/server/migrations/1.0.0-2.0.0/sql/users/alter_users_table.sql diff --git a/src/server/sql/reading/create_function_get_compare_readings.sql b/src/server/sql/reading/create_function_get_compare_readings.sql index cee092b6c..98166c287 100644 --- a/src/server/sql/reading/create_function_get_compare_readings.sql +++ b/src/server/sql/reading/create_function_get_compare_readings.sql @@ -31,6 +31,9 @@ daily and hourly readings would help fix this. /* The following function returns data for plotting compare graphs. It works on meters. It should not be used on raw readings. +This only works when the curr start/end times are on the hour as it uses the hourly reading view. +It will truncate to lose partial hours if they are given. The current client code will only +send full hours so this should be fine. It is the new version of compare_readings that works with units. It takes these parameters: meter_ids: A array of meter ids to query. graphic_unit_id: The unit id of the unit to use for the graph. @@ -49,11 +52,11 @@ CREATE OR REPLACE FUNCTION meter_compare_readings_unit ( RETURNS TABLE(meter_id INTEGER, curr_use FLOAT, prev_use FLOAT) AS $$ DECLARE - prev_start TIMESTAMP; - prev_end TIMESTAMP; + curr_tsrange TSRANGE; + prev_tsrange TSRANGE; BEGIN - prev_start := curr_start - shift; - prev_end := curr_end - shift; + curr_tsrange := tsrange(curr_start, curr_end); + prev_tsrange := tsrange(curr_start - shift, curr_end - shift); RETURN QUERY WITH @@ -61,28 +64,32 @@ BEGIN SELECT meters.id AS meter_id, -- Convert the reading based on the conversion found below. - SUM(r.reading) * c.slope + c.intercept AS reading - FROM (((readings r - INNER JOIN unnest(meter_ids) meters(id) ON r.meter_id = meters.id) + -- It is okay to sum the flow units because hourly readings has a rate of per hour so + -- to convert to a quantity would multiply by 1 so it is the same formula. + SUM(hourly.reading_rate) * c.slope + c.intercept AS reading + FROM (((hourly_readings_unit hourly + INNER JOIN unnest(meter_ids) meters(id) ON hourly.meter_id = meters.id) INNER JOIN meters m ON m.id = meters.id) -- This is getting the conversion for the meter and unit to graph. -- The slope and intercept are used above the transform the reading to the desired unit. INNER JOIN cik c on c.source_id = m.unit_id AND c.destination_id = graphic_unit_id) - WHERE r.start_timestamp >= curr_start AND r.end_timestamp <= curr_end + WHERE curr_tsrange @> hourly.time_interval GROUP BY meters.id, c.slope, c.intercept ), prev_period AS ( SELECT meters.id AS meter_id, -- Convert the reading based on the conversion found below. - SUM(r.reading) * c.slope + c.intercept AS reading - FROM (((readings r - INNER JOIN unnest(meter_ids) meters(id) ON r.meter_id = meters.id) + -- It is okay to sum the flow units because hourly readings has a rate of per hour so + -- to convert to a quantity would multiply by 1 so it is the same formula. + SUM(hourly.reading_rate) * c.slope + c.intercept AS reading + FROM (((hourly_readings_unit hourly + INNER JOIN unnest(meter_ids) meters(id) ON hourly.meter_id = meters.id) INNER JOIN meters m ON m.id = meters.id) -- This is getting the conversion for the meter and unit to graph. -- The slope and intercept are used above the transform the reading to the desired unit. INNER JOIN cik c on c.source_id = m.unit_id AND c.destination_id = graphic_unit_id) - WHERE r.start_timestamp >= prev_start AND r.end_timestamp <= prev_end + WHERE prev_tsrange @> hourly.time_interval GROUP BY meters.id, c.slope, c.intercept ) SELECT @@ -99,8 +106,9 @@ $$ LANGUAGE 'plpgsql'; /* -The following function returns data for plotting bacompare graphs. It works on groups. +The following function returns data for plotting compare graphs. It works on groups. It should not be used on raw readings. +See meter version for needing start/end on the hour. It is the new version of group_compare_readings that works with units. It takes these parameters: group_ids: A array of group ids to query. graphic_unit_id: The unit id of the unit to use for the graph. From bd29f4bec6684054e2e315f30d4b3a6e7c9e470d Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Sat, 19 Oct 2024 12:01:31 -0500 Subject: [PATCH 05/34] fix compare test Needed to refresh readings because compare now uses hourly table. Needed to check each value since roundoff needs closeTo check. --- src/server/test/db/compareTests.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/server/test/db/compareTests.js b/src/server/test/db/compareTests.js index 5398b9769..03f0914bc 100644 --- a/src/server/test/db/compareTests.js +++ b/src/server/test/db/compareTests.js @@ -13,6 +13,7 @@ const Unit = require('../../models/Unit'); const { insertStandardUnits, insertStandardConversions } = require('../../util/insertData'); const { insertSpecialUnits, insertSpecialConversions } = require('../../data/automatedTestingData'); const { redoCik } = require('../../services/graph/redoCik'); +const { refreshAllReadingViews } = require('../../services/refreshAllReadingViews'); mocha.describe('Compare readings', () => { let meter, graphicUnitId, conversionSlope, conn; @@ -72,13 +73,15 @@ mocha.describe('Compare readings', () => { graphicUnitId = (await Unit.getByName('MJ', conn)).id; // The conversion should be 3.6 from kWh -> MJ. conversionSlope = 3.6; + await refreshAllReadingViews(); }); // TODO test readings, units. mocha.it('Works for meters', async () => { const result = await Reading.getMeterCompareReadings([meter.id], graphicUnitId, currStart, currEnd, shift, conn); - expect(result).to.deep.equal({ [meter.id]: { curr_use: 10 * conversionSlope, prev_use: 1 * conversionSlope } }); + expect(result).to.have.property(`${meter.id}`).to.have.property('curr_use').to.be.closeTo(10 * conversionSlope, 0.0000001); + expect(result).to.have.property(`${meter.id}`).to.have.property('prev_use').to.be.closeTo(1 * conversionSlope, 0.0000001); }); mocha.it('Works for groups', async () => { @@ -86,6 +89,7 @@ mocha.describe('Compare readings', () => { const group = await Group.getByName('Group', conn); await group.adoptMeter(meter.id, conn); const result = await Reading.getGroupCompareReadings([group.id], graphicUnitId, currStart, currEnd, shift, conn); - expect(result).to.deep.equal({ [group.id]: { curr_use: 10 * conversionSlope, prev_use: 1 * conversionSlope } }); + expect(result).to.have.property(`${group.id}`).to.have.property('curr_use').to.be.closeTo(10 * conversionSlope, 0.0000001); + expect(result).to.have.property(`${group.id}`).to.have.property('prev_use').to.be.closeTo(1 * conversionSlope, 0.0000001); }); }); From 576ee5cb75f50b1c34b660344d06a7fbedad470b Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sat, 19 Oct 2024 17:43:45 -0700 Subject: [PATCH 06/34] Update readingsBarGroupFlow.js --- src/server/test/web/readingsBarGroupFlow.js | 51 +++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index c87a8df87..aa53e7f1b 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -113,6 +113,57 @@ mocha.describe('readings API', () => { }); // Add BG16 here + const unitData = [ + { + // u14 + name: "Thing_36", + identifier: "", + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 36, + typeOfUnit: Unit.unitType.METER, + suffix: "", + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: "special unit" + }, + { + // u15 + name: "thing unit", + identifier: "", + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: "", + displayable: Unit.displayableType.ALL, + preferredDisplay: false, + note: "special unit" + } + ]; + const conversionData = [ + { + // c15 + sourceName: "Thing_36", + destinationName: "thing unit", + bidirectional: false, + slope: 1, + intercept: 0, + note: "Thing_36 → thing unit" + } + ]; + const meterData = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } + ]; }); }); From b1375aac9618a13ebe1a89ef7c194443dc8df810 Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sat, 19 Oct 2024 17:52:03 -0700 Subject: [PATCH 07/34] wrapped the data in the mocha.it call --- src/server/test/web/readingsBarGroupFlow.js | 105 ++++++++++---------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index aa53e7f1b..59e471240 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -113,57 +113,60 @@ mocha.describe('readings API', () => { }); // Add BG16 here - const unitData = [ - { - // u14 - name: "Thing_36", - identifier: "", - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 36, - typeOfUnit: Unit.unitType.METER, - suffix: "", - displayable: Unit.displayableType.NONE, - preferredDisplay: false, - note: "special unit" - }, - { - // u15 - name: "thing unit", - identifier: "", - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 3600, - typeOfUnit: Unit.unitType.UNIT, - suffix: "", - displayable: Unit.displayableType.ALL, - preferredDisplay: false, - note: "special unit" - } - ]; - const conversionData = [ - { - // c15 - sourceName: "Thing_36", - destinationName: "thing unit", - bidirectional: false, - slope: 1, - intercept: 0, - note: "Thing_36 → thing unit" - } - ]; - const meterData = [ - { - name: 'Thing_36 thing unit', - unit: 'Thing_36', - defaultGraphicUnit: 'thing unit', - displayable: true, - gps: undefined, - note: 'special meter', - file: 'test/web/readingsData/readings_ri_15_days_75.csv', - deleteFile: false, - readingFrequency: '15 minutes', - id: METER_ID - } - ]; + mocha.it('BG16: should have daily points for 15 + 20 minute reading intervals and flow units with +-inf start/end time & thing as thing where rate is 36', async () => { + const unitData = [ + { + // u14 + name: "Thing_36", + identifier: "", + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 36, + typeOfUnit: Unit.unitType.METER, + suffix: "", + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: "special unit" + }, + { + // u15 + name: "thing unit", + identifier: "", + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: "", + displayable: Unit.displayableType.ALL, + preferredDisplay: false, + note: "special unit" + } + ]; + const conversionData = [ + { + // c15 + sourceName: "Thing_36", + destinationName: "thing unit", + bidirectional: false, + slope: 1, + intercept: 0, + note: "Thing_36 → thing unit" + } + ]; + const meterData = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } + ]; + }); + }); }); From bcc45e5086f9ebee46aa66abe3923990982344e1 Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sat, 19 Oct 2024 18:01:38 -0700 Subject: [PATCH 08/34] removed accidental constant --- src/server/test/web/readingsBarGroupFlow.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index 59e471240..1919b4e1e 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -151,20 +151,6 @@ mocha.describe('readings API', () => { note: "Thing_36 → thing unit" } ]; - const meterData = [ - { - name: 'Thing_36 thing unit', - unit: 'Thing_36', - defaultGraphicUnit: 'thing unit', - displayable: true, - gps: undefined, - note: 'special meter', - file: 'test/web/readingsData/readings_ri_15_days_75.csv', - deleteFile: false, - readingFrequency: '15 minutes', - id: METER_ID - } - ]; }); From 19f3967c2db1bd1a89afd5bf764d7567fcf520c0 Mon Sep 17 00:00:00 2001 From: Dale Rivera Date: Sat, 19 Oct 2024 19:10:25 -0700 Subject: [PATCH 09/34] import expected readings file for test BG16 --- ...up_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/server/test/web/readingsData/expected_bar_group_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv diff --git a/src/server/test/web/readingsData/expected_bar_group_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv b/src/server/test/web/readingsData/expected_bar_group_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv new file mode 100644 index 000000000..8ccc6c70a --- /dev/null +++ b/src/server/test/web/readingsData/expected_bar_group_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv @@ -0,0 +1,6 @@ +reading,start time,end time +3098568.24778472,2022-08-28 00:00:00,2022-09-10 00:00:00 +3158442.06007844,2022-09-10 00:00:00,2022-09-23 00:00:00 +3078678.36050569,2022-09-23 00:00:00,2022-10-06 00:00:00 +3096487.3496156,2022-10-06 00:00:00,2022-10-19 00:00:00 +3121656.58075156,2022-10-19 00:00:00,2022-11-01 00:00:00 \ No newline at end of file From b449f3f276d3f37e5a369709ac6758829bb6ff9a Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sat, 19 Oct 2024 20:37:22 -0700 Subject: [PATCH 10/34] defined group thing TODO: verify that name and unit are correct --- src/server/test/web/readingsBarGroupFlow.js | 42 ++++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index 1919b4e1e..0587dacdb 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -114,7 +114,7 @@ mocha.describe('readings API', () => { // Add BG16 here mocha.it('BG16: should have daily points for 15 + 20 minute reading intervals and flow units with +-inf start/end time & thing as thing where rate is 36', async () => { - const unitData = [ + const unitDataThing = [ { // u14 name: "Thing_36", @@ -140,7 +140,7 @@ mocha.describe('readings API', () => { note: "special unit" } ]; - const conversionData = [ + const conversionDataThing = [ { // c15 sourceName: "Thing_36", @@ -151,6 +151,44 @@ mocha.describe('readings API', () => { note: "Thing_36 → thing unit" } ]; + const meterDataThingGroups = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + }, + { + name: 'Thing_36 Other', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_20_days_75.csv', + deleteFile: false, + readingFrequency: '20 minutes', + id: (METER_ID + 1) + } + ]; + const groupThing = [ + { + id: GROUP_ID, + name: 'Thing_36 thing unit + Thing_36 Other', + displayable: true, + note: 'special group', + defaultGraphicUnit: 'thing unit', + childMeters: ['Thing_36 thing unit', 'Thing_36 Other'], + childGroups: [], + } + ] + }); From 2b28c203e3bfaa36b798d272da79939f0a21c63d Mon Sep 17 00:00:00 2001 From: Dale Rivera Date: Sun, 20 Oct 2024 13:25:19 -0700 Subject: [PATCH 11/34] added testing sequence for BG16 Co-authored-by: Matthew Tran Co Co-authored-by: Suchith Gali --- src/server/test/web/readingsBarGroupFlow.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index 0587dacdb..8ab16d1d5 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -188,10 +188,21 @@ mocha.describe('readings API', () => { childGroups: [], } ] - + //load data into database + await prepareTest(unitDataThing, conversionDataThing, meterDataThingGroups, groupThing); + //get unit ID since the DB could use any value. + const unitId = await getUnitId('thing unit'); + // Load the expected response data from the corresponding csv file + const expected = await parseExpectedCsv('src/server/test/web/readingsData/expected_bar_group_ri_15-20_mu_Thing36_gu_thing_st_-inf_et_inf_bd_13.csv'); + // Create a request to the API for unbounded reading times and save the response + const res = await chai.request(app).get(`/api/unitReadings/bar/groups/${GROUP_ID}`) + .query({ + timeInterval: ETERNITY.toString(), + barWidthDays: '13', + graphicUnitId: unitId }); + // Check that the API reading is equal to what it is expected to equal + expectReadingToEqualExpected(res, expected, GROUP_ID); }); - - }); }); }); From 67fe6257417b6319b8fee32f8774c07361128ed7 Mon Sep 17 00:00:00 2001 From: danielshid Date: Mon, 21 Oct 2024 14:03:50 -0400 Subject: [PATCH 12/34] added admin alert for meter displayable setting --- .../groups/EditGroupModalComponent.tsx | 2 +- .../meters/EditMeterModalComponent.tsx | 27 +++++++++++++++++++ src/client/app/translations/data.ts | 9 ++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index d72d0b42e..dcc29ee4d 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -818,7 +818,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr showErrorNotification(msg); } else { // If msg is not empty, warns the admin and asks if they want to apply changes. - msg += `\n${translate('group.edit.verify')}`; + msg += `\n${translate('edit.verify')}`; cancel = !window.confirm(msg); } } diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 6d1db44e1..333669058 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -75,6 +75,33 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr } }, [localMeterEdits.cumulative]); + useEffect(() => { + if (localMeterEdits.displayable === false) { + // This will hold the overall message for the admin alert. + let msg = ''; + // This will hold the names of groups that are affected. + let groups = ''; + // Tells if the change should be cancelled. + let cancel = false; + // Checks every group for the meter being edited. + for (const groupId of Object.values(groupDataByID)) { + if (groupId.deepMeters.includes(meterState.id)) { + groups += `${groupId.name}\n`; + } + } + if (groups != '') { + // There is a message to display to the user. + msg += `${translate('meter')} "${meterState.name}" ${translate('meter.displayable.verify')}\n` + msg += `${groups + '\n' + translate('edit.verify')}\n` + cancel = !window.confirm(msg); + if (cancel) { + // User asks to remove change + setLocalMeterEdits(details => ({ ...details, displayable: true })); + } + } + } + }, [localMeterEdits.displayable]) + // Save changes // Currently using the old functionality which is to compare inherited prop values to state values // If there is a difference between props and state, then a change was made diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 740311d8c..3102f1460 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -163,6 +163,7 @@ const LocaleTranslationData = { "edit.meter": "Details/Edit Meter", "edit.unit": "Edit Unit", "edit.user": "Edit User", + "edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?", "enable": "Enable", "error.bar": "Show error bars", "export.graph.data": "Export graph data", @@ -197,7 +198,6 @@ const LocaleTranslationData = { "group.edit.empty": "Removing this meter/group means there are no child meters or groups which is not allowed . Delete the group if you want to remove it.", "group.edit.nocompatible": "would have no compatible units by the edit to this group so the edit is cancelled", "group.edit.nounit": "will have its compatible units changed and its default graphic unit set to \"no unit\" by the edit to this group", - "group.edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?", "group.failed.to.create.group": "Failed to create a group with message: ", "group.failed.to.edit.group": "Failed to edit group with message: ", "group.gps.error": "Please input a valid GPS: (latitude, longitude)", @@ -328,6 +328,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:", "meter.cumulativeResetEnd": "Cumulative Reset End:", "meter.cumulativeResetStart": "Cumulative Reset Start:", + "meter.displayable.verify": "is not displayable but is used by the following groups:", "meter.enabled": "Updates:", "meter.endOnlyTime": "Only End Times:", "meter.endTimeStamp": "End Time Stamp:", @@ -664,6 +665,7 @@ const LocaleTranslationData = { "edit.meter": "Details/Modifier Métre\u{26A1}", "edit.unit": "Edit Unit\u{26A1}", "edit.user": "Modifier l'utilisateur", + "edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "enable": "Activer", "error.bar": "Show error bars\u{26A1}", "export.graph.data": "Exporter les données du diagramme", @@ -698,7 +700,6 @@ const LocaleTranslationData = { "group.edit.empty": "Removing this meter/group means there are no child meters or groups which is not allowed. Delete the group if you want to remove it.\u{26A1}", "group.edit.nocompatible": "would have no compatible units by the edit to this group so the edit is cancelled\u{26A1}", "group.edit.nounit": "will have its compatible units changed and its default graphic unit set to \"no unit\" by the edit to this group\u{26A1}", - "group.edit.verify": "or continue (click OK)?\u{26A1}", "group.failed.to.create.group": "Failed to create a group with message: \u{26A1}", "group.failed.to.edit.group": "Failed to edit group with message: \u{26A1}", "group.gps.error": "Please input a valid GPS: (latitude, longitude)\u{26A1}", @@ -829,6 +830,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:\u{26A1}", "meter.cumulativeResetEnd": "Cumulative Reset End:\u{26A1}", "meter.cumulativeResetStart": "Cumulative Reset Start:\u{26A1}", + "meter.displayable.verify": "is not displayable but is used by the following groups:\u{26A1}", "meter.enabled": "Mises à Jour du Mèters", "meter.endOnlyTime": "End Only Time:\u{26A1}", "meter.endTimeStamp": "End Time Stamp:\u{26A1}", @@ -1166,6 +1168,7 @@ const LocaleTranslationData = { "edit.meter": "Details/Editar medidor\u{26A1}", "edit.unit": "Editar unidad", "edit.user": "Editar Usuario", + "edit.verify": "Dados estos mensajes, ¿quieres cancelar este cambio (selecciona Cancelar) o continuar (selecciona OK)?", "enable": "Activar", "error.bar": "Mostrar las barras de errores", "export.graph.data": "Exportar los datos del gráfico", @@ -1200,7 +1203,6 @@ const LocaleTranslationData = { "group.edit.empty": "Quitar este medidor/grupo significa que no hay medidores o grupos secundarios, que no está permitido. Borra este grupo si quieres quitarlo.", "group.edit.nocompatible": "no tendría unidades compatibles por la edición a este grupo, por tanto se cancela la edición", "group.edit.nounit": "habrá cambios a sus unidades compatibles y su unidad de gráfico predeterminada puesto como \"sin unidad\" por la edición a este grupo", - "group.edit.verify": "Dados estos mensajes, ¿quieres cancelar este cambio (selecciona Cancelar) o continuar (selecciona OK)?", "group.failed.to.create.group": "No se pudo crear un grupo con el mensaje: ", "group.failed.to.edit.group": "No se pudo editar el grupo con el mensaje: ", "group.gps.error": "Por favor indique un punto GPS valído: (latitud, longitud)", @@ -1331,6 +1333,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Reinicio cumulativo:", "meter.cumulativeResetEnd": "Final del reinicio cumulativo:", "meter.cumulativeResetStart": "Comienzo del reinicio cumulativo:", + "meter.displayable.verify": "is not displayable but is used by the following groups:\u{26A1}", "meter.enabled": "Medidor activado", "meter.endOnlyTime": "Solo tiempos finales.", "meter.endTimeStamp": "Marca de tiempo al final:", From 908c42222b3ec8586d9a1aa187dae3cf287b2a86 Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 22 Oct 2024 14:40:36 -0700 Subject: [PATCH 13/34] Add thing data, conversion, and meter 2D array to readingUtils and use these imported values in test C16 --- .../test/web/readingsCompareMeterFlow.js | 72 ++----------------- src/server/util/readingsUtils.js | 62 +++++++++++++++- 2 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index e458799f1..80309165f 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -7,14 +7,13 @@ See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); -const Unit = require('../../models/Unit'); const { prepareTest, expectCompareToEqualExpected, getUnitId, METER_ID, - unitDatakWh, - conversionDatakWh, - meterDatakWh } = require('../../util/readingsUtils'); + unitDataThing, + conversionDataThing_36, + meterDataThing_36} = require('../../util/readingsUtils'); mocha.describe('readings API', () => { mocha.describe('readings test, test if data returned by API is as expected', () => { @@ -22,74 +21,17 @@ mocha.describe('readings API', () => { mocha.describe('for meters', () => { // Add C15 here - // C16 - Test 15 minute intervals and flow units and thing as "thing where rate is 36" mocha.it('C16: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & thing as thing where rate is 36', async () => { - //Create a 2D array to feed into the database - const unitDataThing = [ - { - // u14 - name: 'Thing_36', - identifier: '', - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 36, - typeOfUnit: Unit.unitType.METER, - suffix: '', - displayable: Unit.displayableType.NONE, - preferredDisplay: false, - note: 'special unit' - }, - { - // u15 - name: 'thing unit', - identifier: '', - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 3600, - typeOfUnit: Unit.unitType.UNIT, - suffix: '', - displayable: Unit.displayableType.ALL, - preferredDisplay: false, - note: 'special unit' - } - ]; - - const converstionDataThing_36 = [ - { - // c15 - sourceName: 'Thing_36', - destinationName: 'thing unit', - bidirectional: false, - //Our test case says slope is 100, but the definition in C15 is 1 - slope: 1, - intercept: 0, - note: 'Thing_36 → thing unit' - } - ]; - - const meterDataThing_36 = [ - { - name: 'Thing_36 thing unit', - unit: 'Thing_36', - defaultGraphicUnit: 'thing unit', - displayable: true, - gps: undefined, - note: 'special meter', - file: 'test/web/readingsData/readings_ri_15_days_75.csv', - deleteFile: false, - readingFrequency: '15 minutes', - id: METER_ID - } - ] - - // Initialize test database with this data - await prepareTest(unitDataThing, converstionDataThing_36, meterDataThing_36); + // Initialize test database with "thing" data + await prepareTest(unitDataThing, conversionDataThing_36, meterDataThing_36); // Get the unit ID since the DB could use any value const unitId = await getUnitId('thing unit'); // Expected was taken from the `curr use, prev use` column for this test case, since this is a compare readings test - const expected = [4855.01888481568, 5018.56560262927]; + const expected = [796223.09710977, 823044.7588312]; // Create a request to the API and save the response - // Note: the api paths are located in app.js, but our specific one points to compareReadings.js + // Note: the api paths are located in app.js, but this specific one points to compareReadings.js const res = await chai.request(app).get(`/api/compareReadings/meters/${METER_ID}`) .query({ curr_start: '2022-10-30 00:00:00', diff --git a/src/server/util/readingsUtils.js b/src/server/util/readingsUtils.js index 49e54adf6..ce4b02ecb 100644 --- a/src/server/util/readingsUtils.js +++ b/src/server/util/readingsUtils.js @@ -277,6 +277,62 @@ const groupDatakWh = [ } ]; +// These are the 2D arrays for units and conversions to feed into the database +// For Thing units. +const unitDataThing = [ + { + // u14 + name: 'Thing_36', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 36, + typeOfUnit: Unit.unitType.METER, + suffix: '', + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: 'special unit' + }, + { + // u15 + name: 'thing unit', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: '', + displayable: Unit.displayableType.ALL, + preferredDisplay: false, + note: 'special unit' + } +]; + +const conversionDataThing_36 = [ + { + // c15 + sourceName: 'Thing_36', + destinationName: 'thing unit', + bidirectional: false, + slope: 1, + intercept: 0, + note: 'Thing_36 → thing unit' + } +]; + +const meterDataThing_36 = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } +] + module.exports = { prepareTest, parseExpectedCsv, @@ -295,5 +351,9 @@ module.exports = { conversionDatakWh, meterDatakWh, meterDatakWhGroups, - groupDatakWh + groupDatakWh, + unitDataThing, + conversionDataThing_36, + meterDataThing_36 }; +// groupDatakWh (meterDatakWhGroups with meterDatakWh, meterDatakWhOther) \ No newline at end of file From 77e1f5f8000eb978beea2463817b0d7a7095dabe Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 22 Oct 2024 14:48:47 -0700 Subject: [PATCH 14/34] Update CLA link in README for project --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19d421e2e..6a4df94bd 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ See the full licensing agreement [here](LICENSE.txt) ## Contributions ## -We welcome others to contribute to this project by writing code for submission or collaborating with us. Before contributing, please sign our [Contributor License Agreement](https://openenergydashboard.github.io/developer/cla.html). Web pages with [information for developers](https://openenergydashboard.github.io/developer/) is available. If you have any questions or concerns feel free to at engage@OpenEnergyDashboard.org. +We welcome others to contribute to this project by writing code for submission or collaborating with us. Before contributing, please sign our [Contributor License Agreement](https://openenergydashboard.org/developer/cla/). Web pages with [information for developers](https://openenergydashboard.github.io/developer/) is available. If you have any questions or concerns feel free to at engage@OpenEnergyDashboard.org. ## Code of Conduct ## From 01db3d7f8049480d2a4c9b855e081feab828c30b Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 22 Oct 2024 15:11:46 -0700 Subject: [PATCH 15/34] Update test C16's expected value --- src/server/test/web/readingsCompareMeterFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index 80309165f..59296ee88 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -28,7 +28,7 @@ mocha.describe('readings API', () => { // Get the unit ID since the DB could use any value const unitId = await getUnitId('thing unit'); // Expected was taken from the `curr use, prev use` column for this test case, since this is a compare readings test - const expected = [796223.09710977, 823044.7588312]; + const expected = [7962.23097109771, 8230.447588311996]; // Create a request to the API and save the response // Note: the api paths are located in app.js, but this specific one points to compareReadings.js From a15fc27d7ccf20675c626d428086c89ee4121d71 Mon Sep 17 00:00:00 2001 From: danielshid Date: Tue, 22 Oct 2024 22:58:57 -0400 Subject: [PATCH 16/34] fixed alert popping up on refresh --- .../groups/EditGroupModalComponent.tsx | 2 +- .../meters/EditMeterModalComponent.tsx | 57 ++++++++++--------- src/client/app/translations/data.ts | 15 +++-- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index dcc29ee4d..d72d0b42e 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -818,7 +818,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr showErrorNotification(msg); } else { // If msg is not empty, warns the admin and asks if they want to apply changes. - msg += `\n${translate('edit.verify')}`; + msg += `\n${translate('group.edit.verify')}`; cancel = !window.confirm(msg); } } diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 333669058..9347320d2 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -75,33 +75,6 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr } }, [localMeterEdits.cumulative]); - useEffect(() => { - if (localMeterEdits.displayable === false) { - // This will hold the overall message for the admin alert. - let msg = ''; - // This will hold the names of groups that are affected. - let groups = ''; - // Tells if the change should be cancelled. - let cancel = false; - // Checks every group for the meter being edited. - for (const groupId of Object.values(groupDataByID)) { - if (groupId.deepMeters.includes(meterState.id)) { - groups += `${groupId.name}\n`; - } - } - if (groups != '') { - // There is a message to display to the user. - msg += `${translate('meter')} "${meterState.name}" ${translate('meter.displayable.verify')}\n` - msg += `${groups + '\n' + translate('edit.verify')}\n` - cancel = !window.confirm(msg); - if (cancel) { - // User asks to remove change - setLocalMeterEdits(details => ({ ...details, displayable: true })); - } - } - } - }, [localMeterEdits.displayable]) - // Save changes // Currently using the old functionality which is to compare inherited prop values to state values // If there is a difference between props and state, then a change was made @@ -207,6 +180,34 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr setLocalMeterEdits({ ...localMeterEdits, [e.target.name]: JSON.parse(e.target.value) }); }; + // Function handles the selection of a new displayable. + const handleDisplayableChange = (e: React.ChangeEvent) => { + // If there is a potential issue then the admin will decide if save happens. Otherwise, the value is put into state. + let save = true; + if (!JSON.parse(e.target.value)) { + // This will hold the overall message for the admin alert. + let msg = ''; + // This will hold the names of groups that are affected. + let groups = ''; + // Tells if the change should be cancelled. + // Checks for groups that include the meter being edited. + for (const groupId of Object.values(groupDataByID)) { + if (groupId.deepMeters.includes(meterState.id)) { + groups += `${groupId.name}\n`; + } + } + if (groups != '') { + // There is a message to display to the user. + msg += `${translate('meter')} "${meterState.name}" ${translate('meter.edit.displayable.warning')}\n` + msg += `${groups + '\n' + translate('meter.edit.displayable.verify')}\n` + save = window.confirm(msg); + } + } + if (save) { + handleBooleanChange(e); + } + }; + const handleNumberChange = (e: React.ChangeEvent) => { setLocalMeterEdits({ ...localMeterEdits, [e.target.name]: Number(e.target.value) }); }; @@ -333,7 +334,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr name='displayable' type='select' value={localMeterEdits.displayable?.toString()} - onChange={e => handleBooleanChange(e)} + onChange={e => handleDisplayableChange(e)} invalid={localMeterEdits.displayable && localMeterEdits.unitId === -99}> {Object.keys(TrueFalseType).map(key => { return (); diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 3102f1460..2e90dd0c4 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -163,7 +163,6 @@ const LocaleTranslationData = { "edit.meter": "Details/Edit Meter", "edit.unit": "Edit Unit", "edit.user": "Edit User", - "edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?", "enable": "Enable", "error.bar": "Show error bars", "export.graph.data": "Export graph data", @@ -198,6 +197,7 @@ const LocaleTranslationData = { "group.edit.empty": "Removing this meter/group means there are no child meters or groups which is not allowed . Delete the group if you want to remove it.", "group.edit.nocompatible": "would have no compatible units by the edit to this group so the edit is cancelled", "group.edit.nounit": "will have its compatible units changed and its default graphic unit set to \"no unit\" by the edit to this group", + "group.edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?", "group.failed.to.create.group": "Failed to create a group with message: ", "group.failed.to.edit.group": "Failed to edit group with message: ", "group.gps.error": "Please input a valid GPS: (latitude, longitude)", @@ -328,7 +328,8 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:", "meter.cumulativeResetEnd": "Cumulative Reset End:", "meter.cumulativeResetStart": "Cumulative Reset Start:", - "meter.displayable.verify": "is not displayable but is used by the following groups:", + "meter.edit.displayable.warning": "is not displayable but is used by the following groups:", + "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?", "meter.enabled": "Updates:", "meter.endOnlyTime": "Only End Times:", "meter.endTimeStamp": "End Time Stamp:", @@ -665,7 +666,6 @@ const LocaleTranslationData = { "edit.meter": "Details/Modifier Métre\u{26A1}", "edit.unit": "Edit Unit\u{26A1}", "edit.user": "Modifier l'utilisateur", - "edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "enable": "Activer", "error.bar": "Show error bars\u{26A1}", "export.graph.data": "Exporter les données du diagramme", @@ -700,6 +700,7 @@ const LocaleTranslationData = { "group.edit.empty": "Removing this meter/group means there are no child meters or groups which is not allowed. Delete the group if you want to remove it.\u{26A1}", "group.edit.nocompatible": "would have no compatible units by the edit to this group so the edit is cancelled\u{26A1}", "group.edit.nounit": "will have its compatible units changed and its default graphic unit set to \"no unit\" by the edit to this group\u{26A1}", + "group.edit.verify": "Given the messages, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "group.failed.to.create.group": "Failed to create a group with message: \u{26A1}", "group.failed.to.edit.group": "Failed to edit group with message: \u{26A1}", "group.gps.error": "Please input a valid GPS: (latitude, longitude)\u{26A1}", @@ -830,7 +831,8 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:\u{26A1}", "meter.cumulativeResetEnd": "Cumulative Reset End:\u{26A1}", "meter.cumulativeResetStart": "Cumulative Reset Start:\u{26A1}", - "meter.displayable.verify": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.warning": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "meter.enabled": "Mises à Jour du Mèters", "meter.endOnlyTime": "End Only Time:\u{26A1}", "meter.endTimeStamp": "End Time Stamp:\u{26A1}", @@ -1168,7 +1170,6 @@ const LocaleTranslationData = { "edit.meter": "Details/Editar medidor\u{26A1}", "edit.unit": "Editar unidad", "edit.user": "Editar Usuario", - "edit.verify": "Dados estos mensajes, ¿quieres cancelar este cambio (selecciona Cancelar) o continuar (selecciona OK)?", "enable": "Activar", "error.bar": "Mostrar las barras de errores", "export.graph.data": "Exportar los datos del gráfico", @@ -1203,6 +1204,7 @@ const LocaleTranslationData = { "group.edit.empty": "Quitar este medidor/grupo significa que no hay medidores o grupos secundarios, que no está permitido. Borra este grupo si quieres quitarlo.", "group.edit.nocompatible": "no tendría unidades compatibles por la edición a este grupo, por tanto se cancela la edición", "group.edit.nounit": "habrá cambios a sus unidades compatibles y su unidad de gráfico predeterminada puesto como \"sin unidad\" por la edición a este grupo", + "group.edit.verify": "Dados estos mensajes, ¿quieres cancelar este cambio (selecciona Cancelar) o continuar (selecciona OK)?", "group.failed.to.create.group": "No se pudo crear un grupo con el mensaje: ", "group.failed.to.edit.group": "No se pudo editar el grupo con el mensaje: ", "group.gps.error": "Por favor indique un punto GPS valído: (latitud, longitud)", @@ -1333,7 +1335,8 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Reinicio cumulativo:", "meter.cumulativeResetEnd": "Final del reinicio cumulativo:", "meter.cumulativeResetStart": "Comienzo del reinicio cumulativo:", - "meter.displayable.verify": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.warning": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "meter.enabled": "Medidor activado", "meter.endOnlyTime": "Solo tiempos finales.", "meter.endTimeStamp": "Marca de tiempo al final:", From 58e212850746195af9b098659c5777aa1d42e5cd Mon Sep 17 00:00:00 2001 From: danielshid Date: Tue, 22 Oct 2024 23:35:46 -0400 Subject: [PATCH 17/34] added missing semicolons --- src/client/app/components/meters/EditMeterModalComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 9347320d2..5ddb9ee60 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -198,8 +198,8 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr } if (groups != '') { // There is a message to display to the user. - msg += `${translate('meter')} "${meterState.name}" ${translate('meter.edit.displayable.warning')}\n` - msg += `${groups + '\n' + translate('meter.edit.displayable.verify')}\n` + msg += `${translate('meter')} "${meterState.name}" ${translate('meter.edit.displayable.warning')}\n`; + msg += `${groups + '\n' + translate('meter.edit.displayable.verify')}\n`; save = window.confirm(msg); } } From 2fb33af73f5a86586d7e7651d00228a2bd0b5fec Mon Sep 17 00:00:00 2001 From: danielshid Date: Fri, 25 Oct 2024 02:03:35 -0400 Subject: [PATCH 18/34] admin alert does not include non-displayable groups --- src/client/app/components/meters/EditMeterModalComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 5ddb9ee60..8147085cd 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -192,7 +192,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // Tells if the change should be cancelled. // Checks for groups that include the meter being edited. for (const groupId of Object.values(groupDataByID)) { - if (groupId.deepMeters.includes(meterState.id)) { + if (groupId.displayable && groupId.deepMeters.includes(meterState.id)) { groups += `${groupId.name}\n`; } } From 987bec75ddb58973802a9469cc3f4ba5009e940b Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Fri, 25 Oct 2024 10:18:23 -0500 Subject: [PATCH 19/34] slight wording change --- src/client/app/translations/data.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 2e90dd0c4..dfceea2a6 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -328,7 +328,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:", "meter.cumulativeResetEnd": "Cumulative Reset End:", "meter.cumulativeResetStart": "Cumulative Reset Start:", - "meter.edit.displayable.warning": "is not displayable but is used by the following groups:", + "meter.edit.displayable.warning": "is not displayable but is used by the following displayable groups:", "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?", "meter.enabled": "Updates:", "meter.endOnlyTime": "Only End Times:", @@ -831,7 +831,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Cumulative Reset:\u{26A1}", "meter.cumulativeResetEnd": "Cumulative Reset End:\u{26A1}", "meter.cumulativeResetStart": "Cumulative Reset Start:\u{26A1}", - "meter.edit.displayable.warning": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.warning": "is not displayable but is used by the following displayable groups:\u{26A1}", "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "meter.enabled": "Mises à Jour du Mèters", "meter.endOnlyTime": "End Only Time:\u{26A1}", @@ -1335,7 +1335,7 @@ const LocaleTranslationData = { "meter.cumulativeReset": "Reinicio cumulativo:", "meter.cumulativeResetEnd": "Final del reinicio cumulativo:", "meter.cumulativeResetStart": "Comienzo del reinicio cumulativo:", - "meter.edit.displayable.warning": "is not displayable but is used by the following groups:\u{26A1}", + "meter.edit.displayable.warning": "is not displayable but is used by the following displayable groups:\u{26A1}", "meter.edit.displayable.verify": "Given the group(s) listed above, do you want to cancel this change (click Cancel) or continue (click OK)?\u{26A1}", "meter.enabled": "Medidor activado", "meter.endOnlyTime": "Solo tiempos finales.", From a045430fc1dfd5e7d088cc27c348735de3c4a2c1 Mon Sep 17 00:00:00 2001 From: Rakesh Ranga Buram Date: Fri, 25 Oct 2024 12:02:55 -0500 Subject: [PATCH 20/34] Standardize Translation Method --- src/client/app/components/AreaUnitSelectComponent.tsx | 2 +- src/client/app/components/BarChartComponent.tsx | 2 +- src/client/app/components/BarControlsComponent.tsx | 2 +- src/client/app/components/ChartLinkComponent.tsx | 2 +- src/client/app/components/ChartSelectComponent.tsx | 2 +- src/client/app/components/CompareControlsComponent.tsx | 2 +- src/client/app/components/ConfirmActionModalComponent.tsx | 2 +- src/client/app/components/DateRangeComponent.tsx | 2 +- src/client/app/components/ErrorBarComponent.tsx | 2 +- src/client/app/components/FormFileUploaderComponent.tsx | 2 +- src/client/app/components/GraphicRateMenuComponent.tsx | 2 +- src/client/app/components/HeaderButtonsComponent.tsx | 2 +- src/client/app/components/LineChartComponent.tsx | 2 +- src/client/app/components/LoginComponent.tsx | 2 +- src/client/app/components/MapChartComponent.tsx | 2 +- src/client/app/components/MapControlsComponent.tsx | 2 +- src/client/app/components/MeterAndGroupSelectComponent.tsx | 2 +- src/client/app/components/MoreOptionsComponent.tsx | 2 +- src/client/app/components/RadarChartComponent.tsx | 2 +- src/client/app/components/ReadingsPerDaySelectComponent.tsx | 2 +- src/client/app/components/ThreeDComponent.tsx | 6 +++--- src/client/app/components/ThreeDPillComponent.tsx | 2 +- src/client/app/components/TimeZoneSelect.tsx | 3 +-- src/client/app/components/TooltipHelpComponent.tsx | 3 +-- src/client/app/components/UnitSelectComponent.tsx | 2 +- src/client/app/components/UnsavedWarningComponent.tsx | 2 +- src/client/app/components/admin/PreferencesComponent.tsx | 2 +- .../app/components/admin/users/CreateUserModalComponent.tsx | 2 +- .../app/components/admin/users/EditUserModalComponent.tsx | 2 +- src/client/app/components/admin/users/UserViewComponent.tsx | 2 +- .../app/components/admin/users/UsersDetailComponent.tsx | 2 +- .../app/components/conversion/ConversionViewComponent.tsx | 2 +- .../conversion/CreateConversionModalComponent.tsx | 2 +- .../components/conversion/EditConversionModalComponent.tsx | 2 +- src/client/app/components/csv/MetersCSVUploadComponent.tsx | 2 +- .../app/components/csv/ReadingsCSVUploadComponent.tsx | 2 +- .../app/components/groups/CreateGroupModalComponent.tsx | 2 +- .../app/components/groups/EditGroupModalComponent.tsx | 2 +- src/client/app/components/groups/GroupViewComponent.tsx | 2 +- .../app/components/meters/CreateMeterModalComponent.tsx | 2 +- .../app/components/meters/EditMeterModalComponent.tsx | 2 +- src/client/app/components/meters/MeterViewComponent.tsx | 2 +- src/client/app/components/router/ErrorComponent.tsx | 2 +- src/client/app/components/unit/CreateUnitModalComponent.tsx | 2 +- src/client/app/components/unit/EditUnitModalComponent.tsx | 2 +- src/client/app/components/unit/UnitViewComponent.tsx | 2 +- src/client/app/containers/CompareChartContainer.ts | 4 ++-- src/client/app/containers/MapChartContainer.ts | 3 +-- .../containers/maps/MapCalibrationInfoDisplayContainer.ts | 3 +-- src/client/app/redux/actions/conversions.ts | 5 +---- src/client/app/redux/actions/map.ts | 5 +---- .../app/redux/middleware/unauthorizedAccesMiddleware.ts | 3 +-- src/client/app/redux/selectors/adminSelectors.ts | 3 +-- src/client/app/redux/selectors/lineChartSelectors.ts | 3 +-- src/client/app/redux/thunks/exportThunk.ts | 3 +-- src/client/app/utils/calculateCompare.ts | 4 +--- src/client/app/utils/calibration.ts | 3 +-- src/client/app/utils/graphics.ts | 4 +--- src/client/app/utils/input.ts | 3 +-- 59 files changed, 62 insertions(+), 82 deletions(-) diff --git a/src/client/app/components/AreaUnitSelectComponent.tsx b/src/client/app/components/AreaUnitSelectComponent.tsx index 58ad7266b..b3fe612f3 100644 --- a/src/client/app/components/AreaUnitSelectComponent.tsx +++ b/src/client/app/components/AreaUnitSelectComponent.tsx @@ -19,13 +19,13 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Area unit select element */ export default function AreaUnitSelectComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const graphState = useAppSelector(selectGraphState); const unitDataById = useAppSelector(selectUnitDataById); // Array of select options created from the area unit enum const unitOptions: StringSelectOption[] = []; - const translate = useTranslate(); Object.keys(AreaUnitType).forEach(unitKey => { // don't allow normalization by no unit diff --git a/src/client/app/components/BarChartComponent.tsx b/src/client/app/components/BarChartComponent.tsx index 2d7e3fad1..e3a7ed65c 100644 --- a/src/client/app/components/BarChartComponent.tsx +++ b/src/client/app/components/BarChartComponent.tsx @@ -27,6 +27,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns Plotly BarChart */ export default function BarChartComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const { barMeterDeps, barGroupDeps } = useAppSelector(selectPlotlyBarDeps); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectBarChartQueryArgs); @@ -55,7 +56,6 @@ export default function BarChartComponent() { // useQueryHooks for data fetching const datasets: Partial[] = meterReadings.concat(groupData); - const translate = useTranslate(); if (meterIsFetching || groupIsFetching) { return ; diff --git a/src/client/app/components/BarControlsComponent.tsx b/src/client/app/components/BarControlsComponent.tsx index 8b89408df..ead20b67c 100644 --- a/src/client/app/components/BarControlsComponent.tsx +++ b/src/client/app/components/BarControlsComponent.tsx @@ -15,6 +15,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns controls for the Options Ui page. */ export default function BarControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // The min/max days allowed for user selection @@ -89,7 +90,6 @@ export default function BarControlsComponent() { dispatch(graphSlice.actions.updateBarDuration(moment.duration(value, 'days'))); } }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/ChartLinkComponent.tsx b/src/client/app/components/ChartLinkComponent.tsx index 547abea7d..6de7aabae 100644 --- a/src/client/app/components/ChartLinkComponent.tsx +++ b/src/client/app/components/ChartLinkComponent.tsx @@ -18,6 +18,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns chartLinkComponent */ export default function ChartLinkComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const [linkTextVisible, setLinkTextVisible] = React.useState(false); const linkText = useAppSelector(selectChartLink); @@ -25,7 +26,6 @@ export default function ChartLinkComponent() { const selectedMeters = useAppSelector(selectSelectedMeters); const selectedGroups = useAppSelector(selectSelectedGroups); const ref = React.useRef(null); - const translate = useTranslate(); const handleButtonClick = () => { // First attempt to write directly to user's clipboard. navigator.clipboard.writeText(linkText) diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx index 739226a2a..494bae623 100644 --- a/src/client/app/components/ChartSelectComponent.tsx +++ b/src/client/app/components/ChartSelectComponent.tsx @@ -21,6 +21,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Chart select element */ export default function ChartSelectComponent() { + const translate = useTranslate(); const currentChartToRender = useAppSelector(selectChartToRender); const dispatch = useAppDispatch(); const [expand, setExpand] = useState(false); @@ -28,7 +29,6 @@ export default function ChartSelectComponent() { const sortedMaps = sortBy(values(mapsById).map(map => ( { value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) } as SelectOption )), 'label'); - const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/CompareControlsComponent.tsx b/src/client/app/components/CompareControlsComponent.tsx index 76d6c8c83..5b9cd3447 100644 --- a/src/client/app/components/CompareControlsComponent.tsx +++ b/src/client/app/components/CompareControlsComponent.tsx @@ -15,6 +15,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns controls for the compare page */ export default function CompareControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const comparePeriod = useAppSelector(selectComparePeriod); const compareSortingOrder = useAppSelector(selectSortingOrder); @@ -25,7 +26,6 @@ export default function CompareControlsComponent() { const handleSortingButton = (sortingOrder: SortingOrder) => { dispatch(graphSlice.actions.changeCompareSortingOrder(sortingOrder)); }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/ConfirmActionModalComponent.tsx b/src/client/app/components/ConfirmActionModalComponent.tsx index b6aa27e74..9f15d58b7 100644 --- a/src/client/app/components/ConfirmActionModalComponent.tsx +++ b/src/client/app/components/ConfirmActionModalComponent.tsx @@ -41,10 +41,10 @@ interface ConfirmActionModalComponentProps { * @returns A modal component that executes the actionFunction on confirmation and handleClose on rejection. */ export default function ConfirmActionModalComponent(props: ConfirmActionModalComponentProps) { + const translate = useTranslate(); const handleClose = () => { props.handleClose(); }; - const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/DateRangeComponent.tsx b/src/client/app/components/DateRangeComponent.tsx index 8cc374d33..033b311e3 100644 --- a/src/client/app/components/DateRangeComponent.tsx +++ b/src/client/app/components/DateRangeComponent.tsx @@ -22,6 +22,7 @@ import { ChartTypes } from '../types/redux/graph'; * @returns Date Range Calendar Picker */ export default function DateRangeComponent() { + const translate = useTranslate(); const dispatch: Dispatch = useAppDispatch(); const queryTimeInterval = useAppSelector(selectQueryTimeInterval); const locale = useAppSelector(selectSelectedLanguage); @@ -32,7 +33,6 @@ export default function DateRangeComponent() { dispatch(updateTimeInterval(dateRangeToTimeInterval(value))); dispatch(changeSliderRange(dateRangeToTimeInterval(value))); }; - const translate = useTranslate(); return ( diff --git a/src/client/app/components/ErrorBarComponent.tsx b/src/client/app/components/ErrorBarComponent.tsx index 9fe1f8282..957adb04b 100644 --- a/src/client/app/components/ErrorBarComponent.tsx +++ b/src/client/app/components/ErrorBarComponent.tsx @@ -13,9 +13,9 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Error Bar checkbox with tooltip and label */ export default function ErrorBarComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const showMinMax = useAppSelector(selectShowMinMax); - const translate = useTranslate(); return (
diff --git a/src/client/app/components/FormFileUploaderComponent.tsx b/src/client/app/components/FormFileUploaderComponent.tsx index 2d90906cf..714001f63 100644 --- a/src/client/app/components/FormFileUploaderComponent.tsx +++ b/src/client/app/components/FormFileUploaderComponent.tsx @@ -17,11 +17,11 @@ interface FileUploader { * @returns File uploader element */ export default function FileUploaderComponent(props: FileUploader) { + const translate = useTranslate(); const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] || null; props.onFileChange(file); }; - const translate = useTranslate(); return ( diff --git a/src/client/app/components/GraphicRateMenuComponent.tsx b/src/client/app/components/GraphicRateMenuComponent.tsx index 9d8717964..febe41352 100644 --- a/src/client/app/components/GraphicRateMenuComponent.tsx +++ b/src/client/app/components/GraphicRateMenuComponent.tsx @@ -19,6 +19,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Rate selection element */ export default function GraphicRateMenuComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // Graph state @@ -48,7 +49,6 @@ export default function GraphicRateMenuComponent() { } // Array of select options created from the rates const rateOptions: SelectOption[] = []; - const translate = useTranslate(); //Loop over our rates object to create the selects for the dropdown Object.entries(LineGraphRates).forEach(([rateKey, rateValue]) => { diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index fc9a67b47..58f41e53e 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -25,6 +25,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Header buttons element */ export default function HeaderButtonsComponent() { + const translate = useTranslate(); const [logout] = authApi.useLogoutMutation(); const dispatch = useAppDispatch(); // Get the current page so know which one should not be shown in menu. @@ -74,7 +75,6 @@ export default function HeaderButtonsComponent() { // TODO Re-implement AFTER RTK Migration // hard-coded for the time being. Rework w/admin pages const unsavedChangesState = false; - const translate = useTranslate(); // Must update in case the version was not set when the page was loaded. diff --git a/src/client/app/components/LineChartComponent.tsx b/src/client/app/components/LineChartComponent.tsx index adf658d3b..95c0d294e 100644 --- a/src/client/app/components/LineChartComponent.tsx +++ b/src/client/app/components/LineChartComponent.tsx @@ -24,6 +24,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns plotlyLine graphic */ export default function LineChartComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // get current data fetching arguments const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectLineChartQueryArgs); @@ -66,7 +67,6 @@ export default function LineChartComponent() { // Check if there is at least one valid graph const enoughData = data.find(data => data.x!.length > 1); - const translate = useTranslate(); // Customize the layout of the plot // See https://community.plotly.com/t/replacing-an-empty-graph-with-a-message/31497 for showing text not plot. if (data.length === 0) { diff --git a/src/client/app/components/LoginComponent.tsx b/src/client/app/components/LoginComponent.tsx index 399199312..d182fd13d 100644 --- a/src/client/app/components/LoginComponent.tsx +++ b/src/client/app/components/LoginComponent.tsx @@ -16,6 +16,7 @@ import { useTranslate } from '../redux/componentHooks'; * @returns The login page for users or admins. */ export default function LoginComponent() { + const translate = useTranslate(); // Local State const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); @@ -28,7 +29,6 @@ export default function LoginComponent() { // The naming of the returned objects is arbitrary // Equivalent Auto-Derived Method const [login] = authApi.endpoints.login.useMutation(); // authApi.useLoginMutation() - const translate = useTranslate(); const handleSubmit = async (event: React.MouseEvent) => { event.preventDefault(); diff --git a/src/client/app/components/MapChartComponent.tsx b/src/client/app/components/MapChartComponent.tsx index a0e64a35d..baf02d3b4 100644 --- a/src/client/app/components/MapChartComponent.tsx +++ b/src/client/app/components/MapChartComponent.tsx @@ -39,6 +39,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns map component */ export default function MapChartComponent() { + const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectMapChartQueryArgs); const { data: meterReadings, isLoading: meterIsFetching } = readingsApi.useBarQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsFetching } = readingsApi.useBarQuery(groupArgs, { skip: groupShouldSkip }); @@ -70,7 +71,6 @@ export default function MapChartComponent() { const data = []; // Holds the image to use. let image; - const translate = useTranslate(); if (selectedMap !== 0) { const mapID = selectedMap; if (byMapID[mapID]) { diff --git a/src/client/app/components/MapControlsComponent.tsx b/src/client/app/components/MapControlsComponent.tsx index 73849f0d0..9a3a10ddc 100644 --- a/src/client/app/components/MapControlsComponent.tsx +++ b/src/client/app/components/MapControlsComponent.tsx @@ -15,6 +15,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns Map page controls */ export default function MapControlsComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const barDuration = useAppSelector(selectMapBarWidthDays); @@ -23,7 +24,6 @@ export default function MapControlsComponent() { }; const barDurationDays = barDuration.asDays(); - const translate = useTranslate(); return (
diff --git a/src/client/app/components/MeterAndGroupSelectComponent.tsx b/src/client/app/components/MeterAndGroupSelectComponent.tsx index b85001741..67f0b8015 100644 --- a/src/client/app/components/MeterAndGroupSelectComponent.tsx +++ b/src/client/app/components/MeterAndGroupSelectComponent.tsx @@ -25,6 +25,7 @@ import { selectAnythingFetching } from '../redux/selectors/apiSelectors'; * @returns A React-Select component. */ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectProps) { + const translate = useTranslate(); const dispatch = useAppDispatch(); const { meterGroupedOptions, groupsGroupedOptions, allSelectedMeterValues, allSelectedGroupValues } = useAppSelector(selectMeterGroupSelectData); const somethingIsFetching = useAppSelector(selectAnythingFetching); @@ -39,7 +40,6 @@ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectP const newMetersOrGroups = newValues.map(option => option.value); dispatch(updateSelectedMetersOrGroups({ newMetersOrGroups, meta })); }; - const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/MoreOptionsComponent.tsx b/src/client/app/components/MoreOptionsComponent.tsx index be5047594..bdc860ab3 100644 --- a/src/client/app/components/MoreOptionsComponent.tsx +++ b/src/client/app/components/MoreOptionsComponent.tsx @@ -22,13 +22,13 @@ import { useTranslate } from '../redux/componentHooks'; * @returns Custom Modal depending on selected graph type */ export default function MoreOptionsComponent() { + const translate = useTranslate(); const chartToRender = useAppSelector(selectChartToRender); const [showModal, setShowModal] = useState(false); const handleShow = () => setShowModal(true); const handleClose = () => { setShowModal(false); }; - const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 5c382bc17..3ff51879b 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -29,6 +29,7 @@ import SpinnerComponent from './SpinnerComponent'; * @returns radar plotly component */ export default function RadarChartComponent() { + const translate = useTranslate(); const { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip } = useAppSelector(selectRadarChartQueryArgs); const { data: meterReadings, isLoading: meterIsLoading } = readingsApi.useLineQuery(meterArgs, { skip: meterShouldSkip }); const { data: groupData, isLoading: groupIsLoading } = readingsApi.useLineQuery(groupArgs, { skip: groupShouldSkip }); @@ -66,7 +67,6 @@ export default function RadarChartComponent() { } // The rate will be 1 if it is per hour (since state readings are per hour) or no rate scaling so no change. const rateScaling = needsRateScaling ? currentSelectedRate.rate : 1; - const translate = useTranslate(); // Add all valid data from existing meters to the radar plot for (const meterID of selectedMeters) { diff --git a/src/client/app/components/ReadingsPerDaySelectComponent.tsx b/src/client/app/components/ReadingsPerDaySelectComponent.tsx index c7e7511b6..40a56cbd0 100644 --- a/src/client/app/components/ReadingsPerDaySelectComponent.tsx +++ b/src/client/app/components/ReadingsPerDaySelectComponent.tsx @@ -18,6 +18,7 @@ import TooltipMarkerComponent from './TooltipMarkerComponent'; * @returns A Select menu with Readings per day options. */ export default function ReadingsPerDaySelect() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const readingInterval = useAppSelector(selectThreeDReadingInterval); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); @@ -29,7 +30,6 @@ export default function ReadingsPerDaySelect() { ...selectReadingsPerDaySelectData(currentData ?? stableEmptyThreeDReadings, readingInterval) }) }); - const translate = useTranslate(); return (
diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index f56970755..4790be383 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -32,6 +32,7 @@ import Locales from '../types/locales'; * @returns 3D Plotly 3D Surface Graph */ export default function ThreeDComponent() { + const translate = useTranslate(); const { args, shouldSkipQuery } = useAppSelector(selectThreeDQueryArgs); const { data, isFetching } = readingsApi.endpoints.threeD.useQuery(args, { skip: shouldSkipQuery }); const meterDataById = useAppSelector(selectMeterDataById); @@ -46,7 +47,6 @@ export default function ThreeDComponent() { const threeDData = data; let layout = {}; let dataToRender = null; - const translate = useTranslate(); if (!meterOrGroupID) { @@ -111,6 +111,7 @@ function formatThreeDData( graphState: GraphState, unitDataById: UnitDataById ) { + const translate = useTranslate(); // Initialize Plotly Data const xDataToRender: string[] = []; const yDataToRender: string[] = []; @@ -125,7 +126,6 @@ function formatThreeDData( const currentSelectedRate = graphState.lineGraphRate; let unitLabel = ''; let needsRateScaling = false; - const translate = useTranslate(); if (graphingUnit !== -99) { const selectUnitState = unitDataById[graphState.selectedUnit]; if (selectUnitState !== undefined) { @@ -229,12 +229,12 @@ function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: numbe * @returns plotly layout object. */ function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { + const translate = useTranslate(); // Convert date strings to JavaScript Date objects and then get dataRange const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); const dataMin = Math.min(...dateObjects.map(date => date.getTime())); const dataMax = Math.max(...dateObjects.map(date => date.getTime())); const dataRange = dataMax - dataMin; - const translate = useTranslate(); //Calculate nTicks for small num of days on y-axis; possibly a better way let nTicks, dTick = 'd1'; diff --git a/src/client/app/components/ThreeDPillComponent.tsx b/src/client/app/components/ThreeDPillComponent.tsx index 3f8610cf7..cbc637e7a 100644 --- a/src/client/app/components/ThreeDPillComponent.tsx +++ b/src/client/app/components/ThreeDPillComponent.tsx @@ -17,6 +17,7 @@ import { useTranslate } from '../redux/componentHooks'; * @returns List of selected groups and meters as reactstrap Pills Badges */ export default function ThreeDPillComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const meterDataById = useAppSelector(selectMeterDataById); const groupDataById = useAppSelector(selectGroupDataById); @@ -81,7 +82,6 @@ export default function ThreeDPillComponent() { ); }); }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/TimeZoneSelect.tsx b/src/client/app/components/TimeZoneSelect.tsx index 50c3e5d9b..9abc63b95 100644 --- a/src/client/app/components/TimeZoneSelect.tsx +++ b/src/client/app/components/TimeZoneSelect.tsx @@ -15,7 +15,7 @@ interface TimeZoneSelectProps { } const TimeZoneSelect: React.FC = ({ current, handleClick }) => { - + const translate = useTranslate(); const getTimeZones = () => { const zoneNames = moment.tz.names(); return zoneNames.map(zoneName => { @@ -24,7 +24,6 @@ const TimeZoneSelect: React.FC = ({ current, handleClick }) return { value: zoneName, label: `${zoneName} (${abbrev})` }; }); }; - const translate = useTranslate(); const resetTimeZone = [{ value: null, label: translate('timezone.no') }]; const options = [...resetTimeZone, ...getTimeZones()]; diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index 5c08947bb..573d4470b 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -23,7 +23,7 @@ export default function TooltipHelpComponent(props: TooltipHelpProps) { /** * @returns JSX to create the help icons with links */ - + const translate = useTranslate(); const version = useAppSelector(selectOEDVersion); const helpUrl = useAppSelector(selectHelpUrl); @@ -72,7 +72,6 @@ export default function TooltipHelpComponent(props: TooltipHelpProps) { 'help.groups.groupview': { link: `${helpUrl}/groupViewing/` }, 'help.meters.meterview': { link: `${helpUrl}/meterViewing/` } }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/UnitSelectComponent.tsx b/src/client/app/components/UnitSelectComponent.tsx index a4689a10a..f07e478de 100644 --- a/src/client/app/components/UnitSelectComponent.tsx +++ b/src/client/app/components/UnitSelectComponent.tsx @@ -19,6 +19,7 @@ import { selectUnitDataById, unitsApi } from '../redux/api/unitsApi'; * @returns A React-Select component for UI Options Panel */ export default function UnitSelectComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); const unitSelectOptions = useAppSelector(selectUnitSelectData); const selectedUnitID = useAppSelector(selectSelectedUnit); @@ -38,7 +39,6 @@ export default function UnitSelectComponent() { } const onChange = (newValue: SelectOption) => dispatch(graphSlice.actions.updateSelectedUnit(newValue?.value)); - const translate = useTranslate(); return (
diff --git a/src/client/app/components/UnsavedWarningComponent.tsx b/src/client/app/components/UnsavedWarningComponent.tsx index d68254c93..313a288d9 100644 --- a/src/client/app/components/UnsavedWarningComponent.tsx +++ b/src/client/app/components/UnsavedWarningComponent.tsx @@ -25,9 +25,9 @@ export interface UnsavedWarningProps { * @returns Component that prompts before navigating away from current page */ export function UnsavedWarningComponent(props: UnsavedWarningProps) { + const translate = useTranslate(); const { hasUnsavedChanges, submitChanges, changes } = props; const blocker = useBlocker(hasUnsavedChanges); - const translate = useTranslate(); const handleSubmit = async () => { submitChanges(changes) .unwrap() diff --git a/src/client/app/components/admin/PreferencesComponent.tsx b/src/client/app/components/admin/PreferencesComponent.tsx index 5628f5e17..ffc2717cb 100644 --- a/src/client/app/components/admin/PreferencesComponent.tsx +++ b/src/client/app/components/admin/PreferencesComponent.tsx @@ -23,6 +23,7 @@ import { defaultAdminState } from '../../redux/slices/adminSlice'; * @returns Preferences Component for Administrative use */ export default function PreferencesComponent() { + const translate = useTranslate(); const { data: adminPreferences = defaultAdminState } = preferencesApi.useGetPreferencesQuery(); const [localAdminPref, setLocalAdminPref] = React.useState(cloneDeep(adminPreferences)); const [submitPreferences] = preferencesApi.useSubmitPreferencesMutation(); @@ -41,7 +42,6 @@ export default function PreferencesComponent() { const discardChanges = () => { setLocalAdminPref(cloneDeep(adminPreferences)); }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/admin/users/CreateUserModalComponent.tsx b/src/client/app/components/admin/users/CreateUserModalComponent.tsx index 2319f0b29..0594c73ff 100644 --- a/src/client/app/components/admin/users/CreateUserModalComponent.tsx +++ b/src/client/app/components/admin/users/CreateUserModalComponent.tsx @@ -21,6 +21,7 @@ import { tooltipBaseStyle } from '../../../styles/modalStyle'; * @returns CreateUserModal component */ export default function CreateUserModal() { + const translate = useTranslate(); // create user form state and use the defaults const [userDetails, setUserDetails] = useState(userDefaults); @@ -85,7 +86,6 @@ export default function CreateUserModal() { setShowModal(false); }; // End Modal show/close - const translate = useTranslate(); const handleSubmit = async () => { const newUser: User = { username: userDetails.username, role: userDetails.role, password: userDetails.password, note: userDetails.note }; diff --git a/src/client/app/components/admin/users/EditUserModalComponent.tsx b/src/client/app/components/admin/users/EditUserModalComponent.tsx index fad2a83f6..4568b2704 100644 --- a/src/client/app/components/admin/users/EditUserModalComponent.tsx +++ b/src/client/app/components/admin/users/EditUserModalComponent.tsx @@ -29,6 +29,7 @@ interface EditUserModalComponentProps { * @returns User edit element */ export default function EditUserModalComponent(props: EditUserModalComponentProps) { + const translate = useTranslate(); // get current logged in user const currentLoggedInUser = useAppSelector(selectCurrentUserProfile) as User; @@ -85,7 +86,6 @@ export default function EditUserModalComponent(props: EditUserModalComponentProp confirmPassword: '' })); }; - const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/admin/users/UserViewComponent.tsx b/src/client/app/components/admin/users/UserViewComponent.tsx index 099d369c0..0420c3032 100644 --- a/src/client/app/components/admin/users/UserViewComponent.tsx +++ b/src/client/app/components/admin/users/UserViewComponent.tsx @@ -21,6 +21,7 @@ interface UserViewComponentProps { * @returns User card element */ export default function UserViewComponent(props: UserViewComponentProps) { + const translate = useTranslate(); const [showEditModal, setShowEditModal] = useState(false); const handleShow = () => { @@ -30,7 +31,6 @@ export default function UserViewComponent(props: UserViewComponentProps) { const handleClose = () => { setShowEditModal(false); }; - const translate = useTranslate(); return (
diff --git a/src/client/app/components/admin/users/UsersDetailComponent.tsx b/src/client/app/components/admin/users/UsersDetailComponent.tsx index 83e819d00..c01c382db 100644 --- a/src/client/app/components/admin/users/UsersDetailComponent.tsx +++ b/src/client/app/components/admin/users/UsersDetailComponent.tsx @@ -22,8 +22,8 @@ const tooltipStyle = { * @returns User Detail element */ export default function UserDetailComponent() { - const { data: users = stableEmptyUsers } = userApi.useGetUsersQuery(); const translate = useTranslate(); + const { data: users = stableEmptyUsers } = userApi.useGetUsersQuery(); return (
diff --git a/src/client/app/components/conversion/ConversionViewComponent.tsx b/src/client/app/components/conversion/ConversionViewComponent.tsx index 749c577cb..c63642372 100644 --- a/src/client/app/components/conversion/ConversionViewComponent.tsx +++ b/src/client/app/components/conversion/ConversionViewComponent.tsx @@ -25,6 +25,7 @@ interface ConversionViewComponentProps { * @returns Single conversion element */ export default function ConversionViewComponent(props: ConversionViewComponentProps) { + const translate = useTranslate(); // Don't check if admin since only an admin is allow to route to this page. // Edit Modal Show @@ -44,7 +45,6 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr unitDataById[props.conversion.destinationId]?.identifier); // Unlike the details component, we don't check if units are loaded since must come through that page. - const translate = useTranslate(); return (
diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index f383555c2..cb4ba8bd1 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -23,6 +23,7 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Conversion create element */ export default function CreateConversionModalComponent() { + const translate = useTranslate(); const [addConversionMutation] = conversionsApi.useAddConversionMutation(); // Want units in sorted order by identifier regardless of case. @@ -98,7 +99,6 @@ export default function CreateConversionModalComponent() { ...tooltipBaseStyle, tooltipCreateConversionView: 'help.admin.conversioncreate' }; - const translate = useTranslate(); return ( <> diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index 155aa8464..156f52d74 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -34,6 +34,7 @@ interface EditConversionModalComponentProps { * @returns Conversion edit element */ export default function EditConversionModalComponent(props: EditConversionModalComponentProps) { + const translate = useTranslate(); const [editConversion] = conversionsApi.useEditConversionMutation(); const [deleteConversion] = conversionsApi.useDeleteConversionMutation(); const unitDataById = useAppSelector(selectUnitDataById); @@ -57,7 +58,6 @@ export default function EditConversionModalComponent(props: EditConversionModalC setState({ ...state, [e.target.name]: Number(e.target.value) }); }; /* End State */ - const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/csv/MetersCSVUploadComponent.tsx b/src/client/app/components/csv/MetersCSVUploadComponent.tsx index eee66614e..e7a6b4dbf 100644 --- a/src/client/app/components/csv/MetersCSVUploadComponent.tsx +++ b/src/client/app/components/csv/MetersCSVUploadComponent.tsx @@ -22,6 +22,7 @@ import { selectVisibleMeterAndGroupData } from '../../redux/selectors/adminSelec * @returns CSV Meters page element */ export default function MetersCSVUploadComponent() { + const translate = useTranslate(); const [meterData, setMeterData] = React.useState(MetersCSVUploadDefaults); const [selectedFile, setSelectedFile] = React.useState(null); const [isValidFileType, setIsValidFileType] = React.useState(false); @@ -51,7 +52,6 @@ export default function MetersCSVUploadComponent() { [name]: checked })); }; - const translate = useTranslate(); const handleFileChange = (file: File) => { setSelectedFile(file); diff --git a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx index 47c990de7..57bf137bc 100644 --- a/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx +++ b/src/client/app/components/csv/ReadingsCSVUploadComponent.tsx @@ -27,6 +27,7 @@ import CreateMeterModalComponent from '../meters/CreateMeterModalComponent'; * @returns CSV Readings page element */ export default function ReadingsCSVUploadComponent() { + const translate = useTranslate(); const dispatch = useAppDispatch(); // Check for admin status const isAdmin = useAppSelector(selectIsAdmin); @@ -87,7 +88,6 @@ export default function ReadingsCSVUploadComponent() { [name]: value === 'true' })); }; - const translate = useTranslate(); const handleFileChange = (file: File) => { if (file) { diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index a2dbc47d5..fada30578 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -40,6 +40,7 @@ import TooltipMarkerComponent from '../TooltipMarkerComponent'; * @returns Group create element */ export default function CreateGroupModalComponent() { + const translate = useTranslate(); const [createGroup] = groupsApi.useCreateGroupMutation(); // Meters state @@ -125,7 +126,6 @@ export default function CreateGroupModalComponent() { ); }, [state.area, state.areaUnit, state.name, state.deepMeters]); /* End State */ - const translate = useTranslate(); // Sums the area of the group's deep meters. It will tell the admin if any meters are omitted from the calculation, // or if any other errors are encountered. diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index 9fc087537..ba72b81e7 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -57,6 +57,7 @@ interface EditGroupModalComponentProps { * @returns Group edit element */ export default function EditGroupModalComponent(props: EditGroupModalComponentProps) { + const translate = useTranslate(); const [submitGroupEdits] = groupsApi.useEditGroupMutation(); const [deleteGroup] = groupsApi.useDeleteGroupMutation(); // Meter state @@ -147,7 +148,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr ); }, [groupState.area, groupState.areaUnit, groupState.name, groupState.deepMeters]); /* End State */ - const translate = useTranslate(); /* Confirm Delete Modal */ // Separate from state comment to keep everything related to the warning confirmation modal together diff --git a/src/client/app/components/groups/GroupViewComponent.tsx b/src/client/app/components/groups/GroupViewComponent.tsx index 748a3f764..8b30ae65d 100644 --- a/src/client/app/components/groups/GroupViewComponent.tsx +++ b/src/client/app/components/groups/GroupViewComponent.tsx @@ -26,6 +26,7 @@ interface GroupViewComponentProps { * @returns Group info card element */ export default function GroupViewComponent(props: GroupViewComponentProps) { + const translate = useTranslate(); // Don't check if admin since only an admin is allowed to route to this page. // Edit Modal Show @@ -45,7 +46,6 @@ export default function GroupViewComponent(props: GroupViewComponentProps) { // Set up to display the units associated with the group as the unit identifier. // unit state const unitDataById = useAppSelector(selectUnitDataById); - const translate = useTranslate(); return ( diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 6301e6302..1c0ba7702 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -40,6 +40,7 @@ interface CreateMeterModalProps { * @returns Meter create element */ export default function CreateMeterModalComponent(props: CreateMeterModalProps): React.JSX.Element { + const translate = useTranslate(); // Tracks whether a unit/ default unit has been selected. // RTKQ Mutation to submit add meter const [submitAddMeter] = metersApi.endpoints.addMeter.useMutation(); @@ -100,7 +101,6 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): setShowModal(false); resetState(); }; - const translate = useTranslate(); // Unlike edit, we decided to discard and inputs when you choose to leave the page. The reasoning is // that create starts from an empty template. diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index bcdea7cbd..49d3bed21 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -43,6 +43,7 @@ interface EditMeterModalComponentProps { * @returns Meter edit element */ export default function EditMeterModalComponent(props: EditMeterModalComponentProps) { + const translate = useTranslate(); const [editMeter] = metersApi.useEditMeterMutation(); // since this selector is shared amongst many other modals, we must use a selector factory in order // to have a single selector per modal instance. Memo ensures that this is a stable reference @@ -68,7 +69,6 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr useEffect(() => { setValidMeter(isValidMeter(localMeterEdits)); }, [localMeterEdits]); /* End State */ - const translate = useTranslate(); React.useEffect(() => { if (localMeterEdits.cumulative === false) { diff --git a/src/client/app/components/meters/MeterViewComponent.tsx b/src/client/app/components/meters/MeterViewComponent.tsx index 299b2586f..f3117e342 100644 --- a/src/client/app/components/meters/MeterViewComponent.tsx +++ b/src/client/app/components/meters/MeterViewComponent.tsx @@ -24,6 +24,7 @@ interface MeterViewComponentProps { * @returns Meter info card element */ export default function MeterViewComponent(props: MeterViewComponentProps) { + const translate = useTranslate(); // Edit Modal Show const [showEditModal, setShowEditModal] = useState(false); // Check for admin status @@ -42,7 +43,6 @@ export default function MeterViewComponent(props: MeterViewComponentProps) { setShowEditModal(false); }; // Only display limited data if not an admin. - const translate = useTranslate(); return (
diff --git a/src/client/app/components/router/ErrorComponent.tsx b/src/client/app/components/router/ErrorComponent.tsx index e30ab4c4a..58098df9e 100644 --- a/src/client/app/components/router/ErrorComponent.tsx +++ b/src/client/app/components/router/ErrorComponent.tsx @@ -12,12 +12,12 @@ import { useTranslate } from '../../redux/componentHooks'; * @returns A error page that then returns to main dashboard page. */ export default function ErrorComponent() { + const translate = useTranslate(); const nav = useNavigate(); const refreshPage = () => { nav('/'); window.location.reload(); }; - const translate = useTranslate(); return ( {/* Pass div as child prop to AppLayout */} diff --git a/src/client/app/components/unit/CreateUnitModalComponent.tsx b/src/client/app/components/unit/CreateUnitModalComponent.tsx index e6e06023a..621f71b23 100644 --- a/src/client/app/components/unit/CreateUnitModalComponent.tsx +++ b/src/client/app/components/unit/CreateUnitModalComponent.tsx @@ -21,8 +21,8 @@ import { showSuccessNotification, showErrorNotification } from '../../utils/noti * @returns Unit create element */ export default function CreateUnitModalComponent() { - const [submitCreateUnit] = unitsApi.useAddUnitMutation(); const translate = useTranslate(); + const [submitCreateUnit] = unitsApi.useAddUnitMutation(); const defaultValues = { name: '', diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index 137d24345..046a7e0b1 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -37,9 +37,9 @@ interface EditUnitModalComponentProps { * @returns Unit edit element */ export default function EditUnitModalComponent(props: EditUnitModalComponentProps) { + const translate = useTranslate(); const [submitEditedUnit] = unitsApi.useEditUnitMutation(); const [deleteUnit] = unitsApi.useDeleteUnitMutation(); - const translate = useTranslate(); // Set existing unit values const values = { ...props.unit }; diff --git a/src/client/app/components/unit/UnitViewComponent.tsx b/src/client/app/components/unit/UnitViewComponent.tsx index 40e479ab9..d475a2347 100644 --- a/src/client/app/components/unit/UnitViewComponent.tsx +++ b/src/client/app/components/unit/UnitViewComponent.tsx @@ -24,6 +24,7 @@ interface UnitViewComponentProps { */ export default function UnitViewComponent(props: UnitViewComponentProps) { // Don't check if admin since only an admin is allow to route to this page. + const translate = useTranslate(); // Edit Modal Show const [showEditModal, setShowEditModal] = useState(false); @@ -35,7 +36,6 @@ export default function UnitViewComponent(props: UnitViewComponentProps) { const handleClose = () => { setShowEditModal(false); }; - const translate = useTranslate(); return (
diff --git a/src/client/app/containers/CompareChartContainer.ts b/src/client/app/containers/CompareChartContainer.ts index e6172c322..139478a02 100644 --- a/src/client/app/containers/CompareChartContainer.ts +++ b/src/client/app/containers/CompareChartContainer.ts @@ -6,7 +6,8 @@ import { connect } from 'react-redux'; import { getComparePeriodLabels, getCompareChangeSummary, calculateCompareShift } from '../utils/calculateCompare'; -import { useTranslate } from '../redux/componentHooks'; +// When this container gets converted to component,migrate to useTranslate() from componentHooks.ts +import translate from '../utils/translate'; import Plot from 'react-plotly.js'; import Locales from '../types/locales'; import * as moment from 'moment'; @@ -133,7 +134,6 @@ function mapStateToProps(state: RootState, ownProps: CompareChartContainerProps) let previousPeriod = entity.prevUsage; let currentPeriod = entity.currUsage; const areaNormalization = selectGraphAreaNormalization(state); - const translate = useTranslate(); // Check if there is data to graph. if (previousPeriod !== null && currentPeriod !== null) { if (areaNormalization) { diff --git a/src/client/app/containers/MapChartContainer.ts b/src/client/app/containers/MapChartContainer.ts index 22afdea4c..45a8049c6 100644 --- a/src/client/app/containers/MapChartContainer.ts +++ b/src/client/app/containers/MapChartContainer.ts @@ -22,7 +22,7 @@ import { } from '../utils/calibration'; import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion'; import getGraphColor from '../utils/getGraphColor'; -import { useTranslate } from '../redux/componentHooks'; +import translate from '../utils/translate'; function mapStateToProps(state: State) { const unitID = state.graph.selectedUnit; @@ -32,7 +32,6 @@ function mapStateToProps(state: State) { const data = []; // Holds the image to use. let image; - const translate = useTranslate(); if (state.maps.selectedMap !== 0) { const mapID = state.maps.selectedMap; if (state.maps.byMapID[mapID]) { diff --git a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts b/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts index 4f8a4fb65..94e27983c 100644 --- a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts +++ b/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts @@ -9,12 +9,11 @@ import MapCalibrationInfoDisplayComponent from '../../components/maps/MapCalibra import {changeGridDisplay, dropCalibration, offerCurrentGPS, submitCalibratingMap} from '../../redux/actions/map'; import {GPSPoint} from '../../utils/calibration'; import {logToServer} from '../../redux/actions/logs'; -import { useTranslate } from '../../redux/componentHooks'; +import translate from '../../utils/translate'; function mapStateToProps(state: State) { const mapID = state.maps.calibratingMap; const map = state.maps.editedMaps[mapID]; - const translate = useTranslate(); const resultDisplay = (map.calibrationResult) ? `x: ${map.calibrationResult.maxError.x}%, y: ${map.calibrationResult.maxError.y}%` : translate('need.more.points'); diff --git a/src/client/app/redux/actions/conversions.ts b/src/client/app/redux/actions/conversions.ts index 768683d90..782cb48f6 100644 --- a/src/client/app/redux/actions/conversions.ts +++ b/src/client/app/redux/actions/conversions.ts @@ -10,7 +10,7 @@ import { Thunk, Dispatch, GetState } from '../../types/redux/actions'; import { showSuccessNotification, showErrorNotification } from '../../utils/notifications'; -import { useTranslate } from '../../redux/componentHooks'; +import translate from '../../utils/translate'; import * as t from '../../types/redux/conversions'; import { conversionsApi } from '../../utils/api'; import { updateCikAndDBViewsIfNeeded } from './admin'; @@ -58,7 +58,6 @@ export function submitEditedConversion(editedConversion: t.ConversionData, shoul const conversionDataIndex = getState().conversions.submitting.findIndex(conversionData => (( conversionData.sourceId === editedConversion.sourceId) && conversionData.destinationId === editedConversion.destinationId)); - const translate = useTranslate(); // If the editedConversion is not already being submitted if (conversionDataIndex === -1) { @@ -87,7 +86,6 @@ export function submitEditedConversion(editedConversion: t.ConversionData, shoul // Add conversion to database export function addConversion(conversion: t.ConversionData): Dispatch { return async (dispatch: Dispatch) => { - const translate = useTranslate(); try { // Attempt to add conversion to database await conversionsApi.addConversion(conversion); @@ -118,7 +116,6 @@ export function deleteConversion(conversion: t.ConversionData): (dispatch: Dispa // Inform the store we are about to work on this conversion // Update the submitting state array dispatch(conversionsSlice.actions.submitEditedConversion(conversion)); - const translate = useTranslate(); try { // Attempt to delete the conversion from the database await conversionsApi.delete(conversion); diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts index d58f61f1b..8fb9d33aa 100644 --- a/src/client/app/redux/actions/map.ts +++ b/src/client/app/redux/actions/map.ts @@ -16,7 +16,7 @@ import { import {State} from '../../types/redux/state'; import {mapsApi} from '../../utils/api'; import {showErrorNotification, showSuccessNotification} from '../../utils/notifications'; -import { useTranslate } from '../componentHooks'; +import translate from '../../utils/translate'; import * as moment from 'moment'; import {browserHistory} from '../../utils/history'; import {logToServer} from './logs'; @@ -235,7 +235,6 @@ export function submitNewMap(): Thunk { return async (dispatch: Dispatch, getState: GetState) => { const mapID = getState().maps.calibratingMap; const map = getState().maps.editedMaps[mapID]; - const translate = useTranslate(); try { const acceptableMap: MapData = { ...map, @@ -270,7 +269,6 @@ export function submitEditedMap(mapID: number): Thunk { return async (dispatch: Dispatch, getState: GetState) => { const map = getState().maps.editedMaps[mapID]; dispatch(submitMapEdits(mapID)); - const translate = useTranslate(); try { const acceptableMap: MapData = { ...map, @@ -310,7 +308,6 @@ export function submitEditedMap(mapID: number): Thunk { */ export function removeMap(mapID: number): Thunk { return async (dispatch: Dispatch) => { - const translate = useTranslate(); try { await mapsApi.delete(mapID); dispatch(deleteMap(mapID)); diff --git a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts index 022cb053e..2a9b304a0 100644 --- a/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts +++ b/src/client/app/redux/middleware/unauthorizedAccesMiddleware.ts @@ -4,7 +4,7 @@ import { isAsyncThunkAction, isRejected } from '@reduxjs/toolkit'; import { showErrorNotification } from '../../utils/notifications'; -import { useTranslate } from '../componentHooks'; +import translate from '../../utils/translate'; import { AppListener } from '../listenerMiddleware'; import { authApi } from '../api/authApi'; @@ -17,7 +17,6 @@ export const unauthorizedRequestListener = (startListening: AppListener) => { effect: (action: any, { dispatch }): void => { // Look for token failed responses from server const unAuthorizedTokenRequest = (action.payload.status === 401 || action.payload.data?.message === 'Failed to authenticate token.'); - const translate = useTranslate(); if (unAuthorizedTokenRequest) { dispatch(authApi.endpoints.logout.initiate()); showErrorNotification(translate('invalid.token.login')); diff --git a/src/client/app/redux/selectors/adminSelectors.ts b/src/client/app/redux/selectors/adminSelectors.ts index ddc2ceeeb..34422cf68 100644 --- a/src/client/app/redux/selectors/adminSelectors.ts +++ b/src/client/app/redux/selectors/adminSelectors.ts @@ -14,7 +14,7 @@ import { UnitData, UnitType } from '../../types/redux/units'; import { unitsCompatibleWithUnit } from '../../utils/determineCompatibleUnits'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { noUnitTranslated, potentialGraphicUnits } from '../../utils/input'; -import { useTranslate } from '../componentHooks'; +import translate from '../../utils/translate'; import { selectAllUnits, selectUnitDataById } from '../api/unitsApi'; import { selectVisibleMetersAndGroups } from './authVisibilitySelectors'; import { createAppSelector } from './selectors'; @@ -217,7 +217,6 @@ export const selectIsValidConversion = createAppSelector( Cannot mix unit represent TODO Some of these can go away when we make the menus dynamic. */ - const translate = useTranslate(); // The destination cannot be a meter unit. if (destinationId !== -999 && unitDataById[destinationId].typeOfUnit === UnitType.meter) { return [false, translate('conversion.create.destination.meter')]; diff --git a/src/client/app/redux/selectors/lineChartSelectors.ts b/src/client/app/redux/selectors/lineChartSelectors.ts index bdf5e1e6e..d4ca92247 100644 --- a/src/client/app/redux/selectors/lineChartSelectors.ts +++ b/src/client/app/redux/selectors/lineChartSelectors.ts @@ -6,7 +6,7 @@ import * as moment from 'moment'; import { selectShowMinMax } from '../../redux/slices/graphSlice'; import { DataType } from '../../types/Datasources'; import getGraphColor from '../../utils/getGraphColor'; -import { useTranslate } from '../componentHooks'; +import translate from '../../utils/translate'; import { createAppSelector } from './selectors'; import { selectScalingFromEntity, selectNameFromEntity } from './entitySelectors'; import { selectPlotlyMeterDeps, selectPlotlyGroupDeps, selectFromLineReadingsResult } from './plotlyDataSelectors'; @@ -57,7 +57,6 @@ export const selectPlotlyMeterData = selectFromLineReadingsResult( yData.push(readingValue); // All hover have the date, meter name and value. const hoverStart = ` ${timeReading.format('ddd, ll LTS')}
${label}: ${readingValue.toPrecision(6)} ${lineUnitLabel}`; - const translate = useTranslate(); if (showMinMax && reading.max != null) { // We want to show min/max. Note if the data is raw for this meter then all the min/max values are null. // In this case we still push the min/max but plotly will not show them. This is a little extra work diff --git a/src/client/app/redux/thunks/exportThunk.ts b/src/client/app/redux/thunks/exportThunk.ts index 7de8afcc1..06eb6cc75 100644 --- a/src/client/app/redux/thunks/exportThunk.ts +++ b/src/client/app/redux/thunks/exportThunk.ts @@ -20,7 +20,7 @@ import { ConversionData } from '../../types/redux/conversions'; import { ChartTypes, MeterOrGroup } from '../../types/redux/graph'; import graphExport, { downloadRawCSV } from '../../utils/exportData'; import { showErrorNotification } from '../../utils/notifications'; -import { useTranslate } from '../componentHooks'; +import translate from '../../utils/translate'; import { createAppThunk } from './appThunk'; import { selectAnythingFetching } from '../../redux/selectors/apiSelectors'; import { RootState } from '../../store'; @@ -139,7 +139,6 @@ export const exportRawReadings = createAppThunk( const fileSize = (count * 0.082 / 1000); // Decides if the readings should be exported, true if should. let shouldDownload = false; - const translate = useTranslate(); if (fileSize <= adminState.defaultWarningFileSize) { // File sizes that anyone can download without prompting so fine shouldDownload = true; diff --git a/src/client/app/utils/calculateCompare.ts b/src/client/app/utils/calculateCompare.ts index ff9c1b3bb..2c5df83b9 100644 --- a/src/client/app/utils/calculateCompare.ts +++ b/src/client/app/utils/calculateCompare.ts @@ -4,7 +4,7 @@ import { TimeInterval } from '../../../common/TimeInterval'; import * as moment from 'moment'; -import { useTranslate } from '../redux/componentHooks'; +import translate from './translate'; /** * 'Day', 'Week' or 'FourWeeks' @@ -159,7 +159,6 @@ export interface ComparePeriodLabels { * @returns human-readable names for the compare period as {{prev: string, current: string}} */ export function getComparePeriodLabels(comparePeriod: ComparePeriod): ComparePeriodLabels { - const translate = useTranslate(); switch (comparePeriod) { case ComparePeriod.Day: return { prev: translate('yesterday'), current: translate('today') }; @@ -181,7 +180,6 @@ export function getComparePeriodLabels(comparePeriod: ComparePeriod): ComparePer * @returns The label summary */ export function getCompareChangeSummary(change: number, name: string, labels: ComparePeriodLabels): string { - const translate = useTranslate(); if (isNaN(change)) { return `${name} ${translate('has.no.data')}`; } diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index e51fd3f5a..7ec057a4b 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -6,7 +6,7 @@ import { showErrorNotification } from './notifications'; import { logToServer } from '../redux/actions/logs'; import { DataType } from '../types/Datasources'; import { MapMetadata } from '../types/redux/map'; -import { useTranslate } from '../redux/componentHooks'; +import translate from './translate'; /** * Defines a Cartesian Point with x & y @@ -120,7 +120,6 @@ export function isValidGPSInput(input: string): boolean { const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; const result = latitudeConstraint && longitudeConstraint; - const translate = useTranslate(); if (!result) { // TODO It would be nice to return the error and then notify as desired. showErrorNotification(translate('input.gps.range') + input); diff --git a/src/client/app/utils/graphics.ts b/src/client/app/utils/graphics.ts index 5c9eedef4..2a2d45e14 100644 --- a/src/client/app/utils/graphics.ts +++ b/src/client/app/utils/graphics.ts @@ -4,7 +4,7 @@ import { LineGraphRate } from 'types/redux/graph'; import { UnitData, UnitRepresentType } from '../types/redux/units'; -import { useTranslate } from '../redux/componentHooks'; +import translate from './translate'; import { AreaUnitType } from './getAreaUnitConversion'; // Has functions for use with graphics @@ -21,7 +21,6 @@ export function lineUnitLabel(selectUnitState: UnitData, currentSelectedRate: Li selectedAreaUnit: AreaUnitType): { unitLabel: string, needsRateScaling: boolean } { let unitLabel: string = ''; let needsRateScaling = false; - const translate = useTranslate(); // Quantity and flow units have different unit labels. // Look up the type of unit if it is for quantity/flow/raw and decide what to do. // Bar graphics are always quantities. @@ -60,7 +59,6 @@ export function lineUnitLabel(selectUnitState: UnitData, currentSelectedRate: Li * @returns y-axis label */ export function barUnitLabel(selectUnitState: UnitData, areaNormalization: boolean, selectedAreaUnit: AreaUnitType): string { - const translate = useTranslate(); let unitLabel: string = ''; // Quantity and flow units have different unit labels. // Look up the type of unit if it is for quantity/flow (should not be raw) and decide what to do. diff --git a/src/client/app/utils/input.ts b/src/client/app/utils/input.ts index 86c8f610f..d6c67fae4 100644 --- a/src/client/app/utils/input.ts +++ b/src/client/app/utils/input.ts @@ -4,7 +4,7 @@ import { GPSPoint } from './calibration'; import { UnitData, DisplayableType, UnitRepresentType, UnitType, UnitDataById } from '../types/redux/units'; -import { useTranslate } from '../redux/componentHooks'; +import translate from './translate'; import { sortBy } from 'lodash'; /** @@ -89,7 +89,6 @@ export const NoUnit: UnitData = { export function noUnitTranslated(): UnitData { // Untranslated no unit. const unit = NoUnit; - const translate = useTranslate(); // Make the identifier be translated. unit.identifier = translate('unit.none'); return unit; From d0f92cf54d4882e52c5b391c9859b85ed7ff57e2 Mon Sep 17 00:00:00 2001 From: MattMiss Date: Fri, 25 Oct 2024 11:30:14 -0700 Subject: [PATCH 21/34] Added test case CG15 --- .../test/web/readingsCompareGroupFlow.js | 105 +++++++++++++++++- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/src/server/test/web/readingsCompareGroupFlow.js b/src/server/test/web/readingsCompareGroupFlow.js index 8eeb2cf34..fc7ff28b3 100644 --- a/src/server/test/web/readingsCompareGroupFlow.js +++ b/src/server/test/web/readingsCompareGroupFlow.js @@ -7,19 +7,116 @@ See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); +const Unit = require('../../models/Unit'); const { prepareTest, expectCompareToEqualExpected, getUnitId, + GROUP_ID, METER_ID, - unitDatakWh, - conversionDatakWh, - meterDatakWh } = require('../../util/readingsUtils'); + unitDatakWh, + conversionDatakWh + } = require('../../util/readingsUtils'); mocha.describe('readings API', () => { mocha.describe('readings test, test if data returned by API is as expected', () => { mocha.describe('for compare charts', () => { mocha.describe('for groups', () => { - // Add CG15 here + // Test CG15 + mocha.it(' 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & kW as kW ', async () => { + + // unit data + const unitDatakW = [ + { + // u4 + name: 'kW', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: '', + displayable: Unit.displayableType.ALL, + preferredDisplay: true, + note: 'kilowatts' + }, + { + // u5 + name: 'Electric', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.METER, + suffix: '', + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: 'special unit' + }, + ]; + // conversion data + const conversionDatakW = [ + { + // c4 + sourceName: 'Electric', + destinationName: 'kW', + bidirectional: false, + slope: 1, + intercept: 0, + note: 'Electric → kW' + } + ]; + // meter groups + const meterDatakWGroups = [ + { + name: 'meterDatakW', + unit: 'Electric', + defaultGraphicUnit: 'kW', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + }, + { + name: 'meterDatakWOther', + unit: 'Electric', + defaultGraphicUnit: 'kW', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_20_days_75.csv', + deleteFile: false, + readingFrequency: '20 minutes', + id: (METER_ID + 1) + } + ]; + // group data + const groupDatakW = [ + { + id: GROUP_ID, + name: 'meterDatakW + meterDatakWOther', + displayable: true, + note: 'special group', + defaultGraphicUnit: 'kW', + childMeters: ['meterDatakW', 'meterDatakWOther'], + childGroups: [], + } + ] + //load data into database + await prepareTest(unitDatakW, conversionDatakW, meterDatakWGroups, groupDatakW); + //get unit ID since the DB could use any value. + const unitId = await getUnitId('kW'); + const expected = [14017.4841100155, 14605.4957015091]; + //for compare, need the unitID, currentStart, currentEnd, shift + const res = await chai.request(app).get(`/api/compareReadings/groups/${GROUP_ID}`) + .query({ + curr_start: '2022-10-30 00:00:00', + curr_end: '2022-10-31 17:00:00', + shift: 'P7D', + graphicUnitId: unitId, + }); + expectCompareToEqualExpected(res, expected, GROUP_ID); + }); // Add CG16 here }); From 46604802c94a3addc4141ee2d9979eca0484625f Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Sun, 27 Oct 2024 11:44:01 -0700 Subject: [PATCH 22/34] removed comment --- src/server/test/web/readingsBarGroupFlow.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/test/web/readingsBarGroupFlow.js b/src/server/test/web/readingsBarGroupFlow.js index 8ab16d1d5..2a9965740 100644 --- a/src/server/test/web/readingsBarGroupFlow.js +++ b/src/server/test/web/readingsBarGroupFlow.js @@ -112,7 +112,6 @@ mocha.describe('readings API', () => { expectReadingToEqualExpected(res, expected, GROUP_ID); }); - // Add BG16 here mocha.it('BG16: should have daily points for 15 + 20 minute reading intervals and flow units with +-inf start/end time & thing as thing where rate is 36', async () => { const unitDataThing = [ { From 77ed7911f853a3e2c83de24ebd46c4e3450d3baa Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 28 Oct 2024 07:54:28 -0500 Subject: [PATCH 23/34] add missing csv menu translations --- src/client/app/translations/data.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 9c2bed3ca..04be11aa2 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -592,6 +592,8 @@ const LocaleTranslationData = { "create.unit": "Create a Unit\u{26A1}", "create.user": "Créer un utilisateur", "csv": "CSV", + "csvMeters": "CSV Meters\u{26A1}", + "csvReadings": "CSV Readings\u{26A1}", "csv.file": "Fichier CSV:", "csv.file.error": "Le fichier doit être au format CSV ou GZIP (.csv ou .gz). ", "csv.clear.button": "Forme claire", @@ -1096,6 +1098,8 @@ const LocaleTranslationData = { "create.unit": "Crear una unidad", "create.user": "Crear un usuario", "csv": "CSV", + "csvMeters": "CSV Meters\u{26A1}", + "csvReadings": "CSV Readings\u{26A1}", "csv.file": "Archivo CSV:", "csv.file.error": "El archivo debe estar en formato CSV o GZIP (.csv o .gz). ", "csv.clear.button": "Forma clara", From 773a04fbe3361f869c07c9bd7493b7db1a79468e Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Mon, 28 Oct 2024 08:05:46 -0500 Subject: [PATCH 24/34] remove map useTranslate While two functions where in the components directory, they were not in the function component so the React Hooks version of translate should not be used. Before the change it was getting errors and crashing. now it seems fine. --- src/client/app/components/ThreeDComponent.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index 4790be383..b9eac43cf 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -20,7 +20,10 @@ import { UnitDataById } from '../types/redux/units'; import { isValidThreeDInterval, roundTimeIntervalForFetch } from '../utils/dateRangeCompatibility'; import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion'; import { lineUnitLabel } from '../utils/graphics'; +// Both translates are used since some are in the function component where the React Hook is okay +// and some are in other functions where the older method is needed. import { useTranslate } from '../redux/componentHooks'; +import translate from '../utils/translate'; import SpinnerComponent from './SpinnerComponent'; import ThreeDPillComponent from './ThreeDPillComponent'; import Plot from 'react-plotly.js'; @@ -111,7 +114,6 @@ function formatThreeDData( graphState: GraphState, unitDataById: UnitDataById ) { - const translate = useTranslate(); // Initialize Plotly Data const xDataToRender: string[] = []; const yDataToRender: string[] = []; @@ -229,7 +231,6 @@ function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: numbe * @returns plotly layout object. */ function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { - const translate = useTranslate(); // Convert date strings to JavaScript Date objects and then get dataRange const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); const dataMin = Math.min(...dateObjects.map(date => date.getTime())); From 48eb941d6d96c950d30fe1791f755c42e85d6f82 Mon Sep 17 00:00:00 2001 From: MattMiss Date: Mon, 28 Oct 2024 11:22:25 -0700 Subject: [PATCH 25/34] Added CG15 to beginning of test description --- src/server/test/web/readingsCompareGroupFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareGroupFlow.js b/src/server/test/web/readingsCompareGroupFlow.js index fc7ff28b3..34afc4b89 100644 --- a/src/server/test/web/readingsCompareGroupFlow.js +++ b/src/server/test/web/readingsCompareGroupFlow.js @@ -22,7 +22,7 @@ mocha.describe('readings API', () => { mocha.describe('for compare charts', () => { mocha.describe('for groups', () => { // Test CG15 - mocha.it(' 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & kW as kW ', async () => { + mocha.it('CG15: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & kW as kW ', async () => { // unit data const unitDatakW = [ From 02c8898a8b39cfc7cad8e01c93e3ea7af0df454f Mon Sep 17 00:00:00 2001 From: Sebastian Bastida Marin <122469079+developersbm@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:59:34 +0000 Subject: [PATCH 26/34] Completed test case C15 --- .../test/web/readingsCompareMeterFlow.js | 104 +++++++++++++++--- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index c74447212..1356174cc 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -3,26 +3,94 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - This file tests the readings retrieval API compare chart meters. - See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. + This file tests the readings retrieval API compare chart meters. + See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); -const { prepareTest, - expectCompareToEqualExpected, - getUnitId, - METER_ID, - unitDatakWh, - conversionDatakWh, - meterDatakWh } = require('../../util/readingsUtils'); +const { + prepareTest, + expectCompareToEqualExpected, + getUnitId, + METER_ID, + unitDatakWh, + conversionDatakWh, + meterDatakWh +} = require('../../util/readingsUtils'); mocha.describe('readings API', () => { - mocha.describe('readings test, test if data returned by API is as expected', () => { - mocha.describe('for compare charts', () => { - mocha.describe('for meters', () => { - // Add C15 here - - // Add C16 here - }); - }); - }); + mocha.describe('readings test, test if data returned by API is as expected', () => { + mocha.describe('for compare charts', () => { + mocha.describe('for meters', () => { + // Add C15 here + mocha.it('C15: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & kW as kW', async () => { + const unitData = [ + { + // u4 + name: 'kW', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: '', + displayable: Unit.displayableType.ALL, + preferredDisplay: true, + note: 'kilowatts' + }, + { + // u5 + name: 'Electric', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.METER, + suffix: '', + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: 'special unit' + } + ]; + const conversionData = [ + { + // c4 + sourceName: 'Electric', + destinationName: 'kW', + bidirectional: false, + slope: 1, + intercept: 0, + note: 'Electric → kW' + } + ]; + const meterData = [ + { + name: 'Electric kW', + unit: 'Electric', + defaultGraphicUnit: 'kW', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } + ]; + await prepareTest(unitData, conversionData, meterData); + const unitId = await getUnitId('kW'); + const expected = [7962.23097109771, 8230.447588312]; + + const res = await chai.request(app) + .get(`/api/compareReadings/meters/${METER_ID}`) + .query({ + curr_start: '2022-10-30 00:00:00', + curr_end: '2022-10-31 17:00:00', + shift: 'P7D', + graphicUnitId: unitId + }); + + expectCompareToEqualExpected(res, expected); + }); + // Add C16 here + }); + }); + }); }); From 35344d97bd528990854fd2a35425431c044465f1 Mon Sep 17 00:00:00 2001 From: Sebastian Bastida Marin <122469079+developersbm@users.noreply.github.com> Date: Tue, 29 Oct 2024 00:06:51 +0000 Subject: [PATCH 27/34] Completed test case C15 --- src/server/test/web/readingsCompareMeterFlow.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index 1356174cc..fd1dd2a87 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -16,6 +16,7 @@ const { conversionDatakWh, meterDatakWh } = require('../../util/readingsUtils'); +const Unit = require('../../models/Unit'); mocha.describe('readings API', () => { mocha.describe('readings test, test if data returned by API is as expected', () => { From 278b3db418a93b6086a2dd0649b1171c42709bd3 Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 22 Oct 2024 19:05:09 -0700 Subject: [PATCH 28/34] Update expected values to the new expected values in the given test case for C16 --- src/server/test/web/readingsCompareMeterFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index 59296ee88..b0b66645d 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -28,7 +28,7 @@ mocha.describe('readings API', () => { // Get the unit ID since the DB could use any value const unitId = await getUnitId('thing unit'); // Expected was taken from the `curr use, prev use` column for this test case, since this is a compare readings test - const expected = [7962.23097109771, 8230.447588311996]; + const expected = [199055.77427744, 205761.1897078]; // Create a request to the API and save the response // Note: the api paths are located in app.js, but this specific one points to compareReadings.js From a4cda6e085cd2677d60e418596974e1fe03b5f7c Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 29 Oct 2024 03:23:16 -0700 Subject: [PATCH 29/34] Move back thing data definitions to readingsCompareMeterFlow --- .../test/web/readingsCompareMeterFlow.js | 61 +++++++++++++++++-- src/server/util/readingsUtils.js | 61 +------------------ 2 files changed, 58 insertions(+), 64 deletions(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index b0b66645d..ad154579d 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -10,10 +10,7 @@ const { chai, mocha, app } = require('../common'); const { prepareTest, expectCompareToEqualExpected, getUnitId, - METER_ID, - unitDataThing, - conversionDataThing_36, - meterDataThing_36} = require('../../util/readingsUtils'); + METER_ID} = require('../../util/readingsUtils'); mocha.describe('readings API', () => { mocha.describe('readings test, test if data returned by API is as expected', () => { @@ -22,6 +19,62 @@ mocha.describe('readings API', () => { // Add C15 here mocha.it('C16: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & thing as thing where rate is 36', async () => { + // These are the 2D arrays for units and conversions to feed into the database + // For Thing units. + const unitDataThing = [ + { + // u14 + name: 'Thing_36', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 36, + typeOfUnit: Unit.unitType.METER, + suffix: '', + displayable: Unit.displayableType.NONE, + preferredDisplay: false, + note: 'special unit' + }, + { + // u15 + name: 'thing unit', + identifier: '', + unitRepresent: Unit.unitRepresentType.FLOW, + secInRate: 3600, + typeOfUnit: Unit.unitType.UNIT, + suffix: '', + displayable: Unit.displayableType.ALL, + preferredDisplay: false, + note: 'special unit' + } + ]; + + const conversionDataThing_36 = [ + { + // c15 + sourceName: 'Thing_36', + destinationName: 'thing unit', + bidirectional: false, + slope: 1, + intercept: 0, + note: 'Thing_36 → thing unit' + } + ]; + + const meterDataThing_36 = [ + { + name: 'Thing_36 thing unit', + unit: 'Thing_36', + defaultGraphicUnit: 'thing unit', + displayable: true, + gps: undefined, + note: 'special meter', + file: 'test/web/readingsData/readings_ri_15_days_75.csv', + deleteFile: false, + readingFrequency: '15 minutes', + id: METER_ID + } + ] + // Initialize test database with "thing" data await prepareTest(unitDataThing, conversionDataThing_36, meterDataThing_36); diff --git a/src/server/util/readingsUtils.js b/src/server/util/readingsUtils.js index ce4b02ecb..dcd5ac7e4 100644 --- a/src/server/util/readingsUtils.js +++ b/src/server/util/readingsUtils.js @@ -277,62 +277,6 @@ const groupDatakWh = [ } ]; -// These are the 2D arrays for units and conversions to feed into the database -// For Thing units. -const unitDataThing = [ - { - // u14 - name: 'Thing_36', - identifier: '', - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 36, - typeOfUnit: Unit.unitType.METER, - suffix: '', - displayable: Unit.displayableType.NONE, - preferredDisplay: false, - note: 'special unit' - }, - { - // u15 - name: 'thing unit', - identifier: '', - unitRepresent: Unit.unitRepresentType.FLOW, - secInRate: 3600, - typeOfUnit: Unit.unitType.UNIT, - suffix: '', - displayable: Unit.displayableType.ALL, - preferredDisplay: false, - note: 'special unit' - } -]; - -const conversionDataThing_36 = [ - { - // c15 - sourceName: 'Thing_36', - destinationName: 'thing unit', - bidirectional: false, - slope: 1, - intercept: 0, - note: 'Thing_36 → thing unit' - } -]; - -const meterDataThing_36 = [ - { - name: 'Thing_36 thing unit', - unit: 'Thing_36', - defaultGraphicUnit: 'thing unit', - displayable: true, - gps: undefined, - note: 'special meter', - file: 'test/web/readingsData/readings_ri_15_days_75.csv', - deleteFile: false, - readingFrequency: '15 minutes', - id: METER_ID - } -] - module.exports = { prepareTest, parseExpectedCsv, @@ -351,9 +295,6 @@ module.exports = { conversionDatakWh, meterDatakWh, meterDatakWhGroups, - groupDatakWh, - unitDataThing, - conversionDataThing_36, - meterDataThing_36 + groupDatakWh }; // groupDatakWh (meterDatakWhGroups with meterDatakWh, meterDatakWhOther) \ No newline at end of file From 71cd4be054a38710fbae4fc37f1cc35aafdcde9b Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 29 Oct 2024 03:26:33 -0700 Subject: [PATCH 30/34] Remove commented code in readingsUtils.js --- src/server/util/readingsUtils.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/util/readingsUtils.js b/src/server/util/readingsUtils.js index dcd5ac7e4..e1fb387e9 100644 --- a/src/server/util/readingsUtils.js +++ b/src/server/util/readingsUtils.js @@ -296,5 +296,4 @@ module.exports = { meterDatakWh, meterDatakWhGroups, groupDatakWh -}; -// groupDatakWh (meterDatakWhGroups with meterDatakWh, meterDatakWhOther) \ No newline at end of file +}; \ No newline at end of file From d4e95f8568e502eb509d3331f7bf847288663b50 Mon Sep 17 00:00:00 2001 From: Tien Han Date: Tue, 29 Oct 2024 03:59:05 -0700 Subject: [PATCH 31/34] import Unit for test --- src/server/test/web/readingsCompareMeterFlow.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index ad154579d..826b2127f 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -7,6 +7,7 @@ See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); +const Unit = require('../../models/Unit'); const { prepareTest, expectCompareToEqualExpected, getUnitId, From 758c265eec9456bd8783c004e2fa74783dc167f1 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Tue, 29 Oct 2024 10:14:36 -0500 Subject: [PATCH 32/34] update expexted values With the merging of PR #1362, the expected values in the testing design document can now be used. --- src/server/test/web/readingsCompareMeterFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index fd1dd2a87..e4c8985b6 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -77,7 +77,7 @@ mocha.describe('readings API', () => { ]; await prepareTest(unitData, conversionData, meterData); const unitId = await getUnitId('kW'); - const expected = [7962.23097109771, 8230.447588312]; + const expected = [1990.55774277443, 2057.611897078]; const res = await chai.request(app) .get(`/api/compareReadings/meters/${METER_ID}`) From 261f536725f9e210bc0f7833e196a83d9dbaf1ea Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Tue, 29 Oct 2024 10:15:43 -0500 Subject: [PATCH 33/34] have file consistently use tabs --- .../test/web/readingsCompareMeterFlow.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/server/test/web/readingsCompareMeterFlow.js b/src/server/test/web/readingsCompareMeterFlow.js index e4c8985b6..4dbb7d356 100644 --- a/src/server/test/web/readingsCompareMeterFlow.js +++ b/src/server/test/web/readingsCompareMeterFlow.js @@ -3,26 +3,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - This file tests the readings retrieval API compare chart meters. - See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. + This file tests the readings retrieval API compare chart meters. + See: https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md for information. */ const { chai, mocha, app } = require('../common'); const { - prepareTest, - expectCompareToEqualExpected, - getUnitId, - METER_ID, - unitDatakWh, - conversionDatakWh, - meterDatakWh + prepareTest, + expectCompareToEqualExpected, + getUnitId, + METER_ID, + unitDatakWh, + conversionDatakWh, + meterDatakWh } = require('../../util/readingsUtils'); const Unit = require('../../models/Unit'); mocha.describe('readings API', () => { - mocha.describe('readings test, test if data returned by API is as expected', () => { - mocha.describe('for compare charts', () => { - mocha.describe('for meters', () => { - // Add C15 here + mocha.describe('readings test, test if data returned by API is as expected', () => { + mocha.describe('for compare charts', () => { + mocha.describe('for meters', () => { + // Add C15 here mocha.it('C15: 7 day shift end 2022-10-31 17:00:00 for 15 minute reading intervals and flow units & kW as kW', async () => { const unitData = [ { @@ -90,8 +90,8 @@ mocha.describe('readings API', () => { expectCompareToEqualExpected(res, expected); }); - // Add C16 here - }); - }); - }); + // Add C16 here + }); + }); + }); }); From 7d8a9acc8d807b5c7217a247ae7f4898c41ee432 Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Tue, 29 Oct 2024 11:31:54 -0500 Subject: [PATCH 34/34] fix CG15 expected values This is not really part of this PR but needed to get this test to pass. The testing design document had the wrong values for compare group flow so when the new SQL was merged it started to fail. --- src/server/test/web/readingsCompareGroupFlow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/test/web/readingsCompareGroupFlow.js b/src/server/test/web/readingsCompareGroupFlow.js index 34afc4b89..117de1a0b 100644 --- a/src/server/test/web/readingsCompareGroupFlow.js +++ b/src/server/test/web/readingsCompareGroupFlow.js @@ -106,7 +106,7 @@ mocha.describe('readings API', () => { await prepareTest(unitDatakW, conversionDatakW, meterDatakWGroups, groupDatakW); //get unit ID since the DB could use any value. const unitId = await getUnitId('kW'); - const expected = [14017.4841100155, 14605.4957015091]; + const expected = [4008.97545574702, 4182.62793481036]; //for compare, need the unitID, currentStart, currentEnd, shift const res = await chai.request(app).get(`/api/compareReadings/groups/${GROUP_ID}`) .query({