diff --git a/scripts/config.template.js b/scripts/config.template.js index cabc3165ec669ac7323634e921f51a0ce79f28c9..5988a8518c26e209466edbd60a6d6dd19637c901 100644 --- a/scripts/config.template.js +++ b/scripts/config.template.js @@ -8,6 +8,7 @@ const cookie = const startingdate = DateTime.local().plus({ days: -120 }).startOf('day') const endingDate = DateTime.local().plus({ days: -1 }).startOf('day') +/** Elec starting date */ const halfHourStartingdate = DateTime.local().plus({ days: -15 }).startOf('day') module.exports = { diff --git a/src/components/Analysis/AnalysisError.spec.tsx b/src/components/Analysis/AnalysisError.spec.tsx deleted file mode 100644 index 1c8fbc8f087afe197f7844743f6b5de44176edbf..0000000000000000000000000000000000000000 --- a/src/components/Analysis/AnalysisError.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import Button from '@material-ui/core/Button' -import Dialog from '@material-ui/core/Dialog' -import AnalysisErrorModal from 'components/Analysis/AnalysisErrorModal' -import { mount } from 'enzyme' -import React from 'react' -import { Provider } from 'react-redux' -import configureStore from 'redux-mock-store' -import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' -import { profileData } from '../../../tests/__mocks__/profileData.mock' - -jest.mock('cozy-ui/transpiled/react/I18n', () => { - return { - useI18n: jest.fn(() => { - return { - t: (str: string) => str, - } - }), - } -}) -const mockedNavigate = jest.fn() -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useNavigate: () => mockedNavigate, -})) -const mockStore = configureStore([]) - -describe('AnalysisErrorModal component', () => { - it('should be rendered correctly', () => { - const store = mockStore({ - ecolyo: { - profile: profileData, - global: globalStateData, - }, - }) - const wrapper = mount( - <Provider store={store}> - <AnalysisErrorModal /> - </Provider> - ) - expect(wrapper.find(Dialog).exists()).toBeTruthy() - expect(wrapper.find(Button).exists()).toBeTruthy() - }) - it('should redirect to previous page', () => { - const store = mockStore({ - ecolyo: { - profile: profileData, - global: globalStateData, - }, - }) - const wrapper = mount( - <Provider store={store}> - <AnalysisErrorModal /> - </Provider> - ) - wrapper.find('.btn-secondary-positive').first().simulate('click') - }) - it('should redirect to options', () => { - const store = mockStore({ - ecolyo: { - profile: profileData, - global: globalStateData, - }, - }) - const wrapper = mount( - <Provider store={store}> - <AnalysisErrorModal /> - </Provider> - ) - wrapper.find('.btn-highlight').first().simulate('click') - }) -}) diff --git a/src/components/Analysis/AnalysisErrorModal.tsx b/src/components/Analysis/AnalysisErrorModal.tsx deleted file mode 100644 index 0372cd591be4ab90f8e7b5e5461e701e22408190..0000000000000000000000000000000000000000 --- a/src/components/Analysis/AnalysisErrorModal.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import Button from '@material-ui/core/Button' -import Dialog from '@material-ui/core/Dialog' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import React from 'react' -import { useNavigate } from 'react-router-dom' -import './analysisError.scss' - -const AnalysisErrorModal = () => { - const { t } = useI18n() - const navigate = useNavigate() - const goToConsumption = () => { - navigate('/consumption') - } - const goBack = () => { - if (history.length <= 2) { - navigate('/consumption') - } else navigate(-1) - } - return ( - <Dialog - open={true} - onClose={(event, reason): void => { - event && reason !== 'backdropClick' && goBack() - }} - disableEscapeKeyDown - aria-labelledby={'accessibility-title'} - classes={{ - root: 'modal-root', - paper: 'modal-paper', - }} - > - <div id={'accessibility-title'}> - {t('analysis_error_modal.accessibility.window_title')} - </div> - <div className="em-root analysis-error-container"> - <div className="em-content"> - <div className="analysis-error-title text-20-bold"> - {t('analysis_error_modal.title')} - </div> - <div className="analysis-error-message text-16-normal"> - {t('analysis_error_modal.message')} - </div> - <div className="analysis-error-button"> - <Button - aria-label={t( - 'analysis_error_modal.accessibility.button_go_back' - )} - onClick={goBack} - classes={{ - root: 'btn-secondary-positive', - label: 'text-16-normal', - }} - > - {t('analysis_error_modal.go_back')} - </Button> - <Button - aria-label={t( - 'analysis_error_modal.accessibility.button_goto_konnector' - )} - onClick={goToConsumption} - classes={{ - root: 'btn-highlight', - label: 'text-16-bold', - }} - > - {t('analysis_error_modal.go_to_options')} - </Button> - </div> - </div> - </div> - </Dialog> - ) -} - -export default AnalysisErrorModal diff --git a/src/components/Analysis/AnalysisView.tsx b/src/components/Analysis/AnalysisView.tsx index b807f35b6b9204cbb533969191bad2940094c66c..528b6b87706ae8408abd1a34f3972fc61bcfc273 100644 --- a/src/components/Analysis/AnalysisView.tsx +++ b/src/components/Analysis/AnalysisView.tsx @@ -4,15 +4,19 @@ import DateNavigator from 'components/DateNavigator/DateNavigator' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { useClient } from 'cozy-client' +import { TimeStep } from 'enum/timeStep.enum' import { UsageEventType } from 'enum/usageEvent.enum' import { DateTime } from 'luxon' import React, { Dispatch, useCallback, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useLocation } from 'react-router-dom' +import DateChartService from 'services/dateChart.service' import UsageEventService from 'services/usageEvent.service' import { AppActionsTypes, AppStore } from 'store' +import { setAnalysisMonth } from 'store/analysis/analysis.slice' import { toggleAnalysisNotification } from 'store/global/global.actions' import { updateProfile } from 'store/profile/profile.actions' +import { isLastDateReached } from 'utils/date' import './analysisView.scss' const AnalysisView = () => { @@ -29,6 +33,8 @@ const AnalysisView = () => { setHeaderHeight(height) }, []) + const dateChartService = new DateChartService() + // Handle email report comeback const { search } = useLocation() const query = new URLSearchParams(search) @@ -97,6 +103,21 @@ const AnalysisView = () => { client, ]) + const disablePrev = + analysisMonth < + DateTime.local(0, 1, 1).setZone('utc', { + keepLocalTime: true, + }) + + const handleMoveDate = (increment: number) => { + const updatedDate = dateChartService.incrementDate( + TimeStep.MONTH, + analysisMonth, + increment + ) + dispatch(setAnalysisMonth(updatedDate)) + } + return ( <> <CozyBar titleKey={'common.title_analysis'} /> @@ -105,8 +126,13 @@ const AnalysisView = () => { desktopTitleKey={'common.title_analysis'} > <DateNavigator - currentAnalysisDate={analysisMonth} + disableNext={isLastDateReached(analysisMonth, TimeStep.MONTH)} + disablePrev={disablePrev} + handleNextDate={() => handleMoveDate(1)} + handlePrevDate={() => handleMoveDate(-1)} inlineDateDisplay={true} + navigatorDate={analysisMonth.minus({ month: 1 })} + timeStep={TimeStep.MONTH} /> </Header> <Content height={headerHeight}> diff --git a/src/components/Analysis/ComparisonView.tsx b/src/components/Analysis/Comparison/Comparison.tsx similarity index 51% rename from src/components/Analysis/ComparisonView.tsx rename to src/components/Analysis/Comparison/Comparison.tsx index db8032078ca2ccbd234308d955a34579e812cc4f..1a200e9d84710b0397e7cf037929f21a47d165a0 100644 --- a/src/components/Analysis/ComparisonView.tsx +++ b/src/components/Analysis/Comparison/Comparison.tsx @@ -1,18 +1,25 @@ import { Button } from '@material-ui/core' import Loader from 'components/Loader/Loader' -import FluidPerformanceIndicator from 'components/PerformanceIndicator/FluidPerformanceIndicator' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' -import { FluidConfig, PerformanceIndicator } from 'models' +import { PerformanceIndicator } from 'models' import React, { Dispatch, useEffect, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import ConsumptionService from 'services/consumption.service' import { AppActionsTypes, AppStore } from 'store' import { setPeriod } from 'store/analysis/analysis.slice' -import './comparisonView.scss' +import FluidPerformanceIndicator from './FluidPerformanceIndicator' +import './comparison.scss' -const ComparisonView = ({ fluidConfig }: { fluidConfig: FluidConfig[] }) => { +const Comparison = ({ + fluidsWithData, + monthPerformanceIndicators, +}: { + fluidsWithData: FluidType[] + monthPerformanceIndicators: PerformanceIndicator[] +}) => { const { t } = useI18n() const client = useClient() const { @@ -20,9 +27,6 @@ const ComparisonView = ({ fluidConfig }: { fluidConfig: FluidConfig[] }) => { analysis: { period, analysisMonth }, } = useSelector((state: AppStore) => state.ecolyo) const dispatch = useDispatch<Dispatch<AppActionsTypes>>() - const [monthPerformanceIndicators, setMonthPerformanceIndicators] = useState< - PerformanceIndicator[] - >([]) const [yearPerformanceIndicators, setYearPerformanceIndicators] = useState< PerformanceIndicator[] >([]) @@ -31,49 +35,37 @@ const ComparisonView = ({ fluidConfig }: { fluidConfig: FluidConfig[] }) => { () => new ConsumptionService(client), [client] ) - const monthPeriod = useMemo(() => { - return { - startDate: analysisMonth.minus({ month: 1 }).startOf('month'), - endDate: analysisMonth.minus({ month: 1 }).endOf('month'), - } - }, [analysisMonth]) - const previousMonthPeriod = useMemo(() => { - return { - startDate: analysisMonth.minus({ month: 2 }).startOf('month'), - endDate: analysisMonth.minus({ month: 2 }).endOf('month'), - } - }, [analysisMonth]) - const previousYearPeriod = useMemo(() => { + const periods = useMemo(() => { return { - startDate: analysisMonth.minus({ year: 1, month: 1 }).startOf('month'), - endDate: analysisMonth.minus({ year: 1, month: 1 }).endOf('month'), + monthPeriod: { + startDate: analysisMonth.minus({ month: 1 }).startOf('month'), + endDate: analysisMonth.minus({ month: 1 }).endOf('month'), + }, + previousMonthPeriod: { + startDate: analysisMonth.minus({ month: 2 }).startOf('month'), + endDate: analysisMonth.minus({ month: 2 }).endOf('month'), + }, + previousYearPeriod: { + startDate: analysisMonth.minus({ year: 1, month: 1 }).startOf('month'), + endDate: analysisMonth.minus({ year: 1, month: 1 }).endOf('month'), + }, } }, [analysisMonth]) const loaderPlaceholderHeight = - fluidTypes.length * 84 + (fluidTypes.length - 1) * 10 + fluidsWithData.length * 84 + (fluidsWithData.length - 1) * 10 useEffect(() => { let subscribed = true async function populateData() { if (subscribed) { - const fetchedMonthIndicators = - await consumptionService.getPerformanceIndicators( - monthPeriod, - TimeStep.MONTH, - fluidTypes, - previousMonthPeriod - ) const fetchedYearIndicators = await consumptionService.getPerformanceIndicators( - monthPeriod, + periods.monthPeriod, TimeStep.MONTH, - fluidTypes, - previousYearPeriod + fluidsWithData, + periods.previousYearPeriod ) - if (fetchedMonthIndicators) { - setMonthPerformanceIndicators(fetchedMonthIndicators) - } if (fetchedYearIndicators) { setYearPerformanceIndicators(fetchedYearIndicators) } @@ -90,13 +82,13 @@ const ComparisonView = ({ fluidConfig }: { fluidConfig: FluidConfig[] }) => { fluidTypes, analysisMonth, consumptionService, - monthPeriod, - previousMonthPeriod, - previousYearPeriod, + fluidsWithData, + periods.monthPeriod, + periods.previousYearPeriod, ]) return ( - <div className="comparisonView"> + <div className="comparison"> <div className="tabs"> <Button className={period === 'month' ? 'active' : ''} @@ -123,29 +115,43 @@ const ComparisonView = ({ fluidConfig }: { fluidConfig: FluidConfig[] }) => { <Loader /> </div> )} + + {/* Placeholder when no data is found */} + {!isLoading && + fluidsWithData.length === 0 && + [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS].map(fluid => ( + <FluidPerformanceIndicator + key={fluid} + fluidType={fluid} + performanceIndicator={{ + value: 0, + compareValue: 0, + percentageVariation: 0, + }} + comparisonDate={periods.previousMonthPeriod.startDate} + /> + ))} + {!isLoading && - fluidConfig.map( - fluid => - fluidTypes.includes(fluid.fluidTypeId) && ( - <FluidPerformanceIndicator - key={fluid.konnectorConfig.slug} - fluidType={fluid.fluidTypeId} - performanceIndicator={ - period === 'month' - ? monthPerformanceIndicators[fluid.fluidTypeId] - : yearPerformanceIndicators[fluid.fluidTypeId] - } - comparisonDate={ - period === 'month' - ? previousMonthPeriod.startDate - : previousYearPeriod.startDate - } - /> - ) - )} + fluidsWithData.map(fluid => ( + <FluidPerformanceIndicator + key={fluid} + fluidType={fluid} + performanceIndicator={ + period === 'month' + ? monthPerformanceIndicators[fluid] + : yearPerformanceIndicators[fluid] + } + comparisonDate={ + period === 'month' + ? periods.previousMonthPeriod.startDate + : periods.previousYearPeriod.startDate + } + /> + ))} </div> </div> ) } -export default ComparisonView +export default Comparison diff --git a/src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx b/src/components/Analysis/Comparison/FluidPerformanceIndicator.tsx similarity index 100% rename from src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx rename to src/components/Analysis/Comparison/FluidPerformanceIndicator.tsx diff --git a/src/components/Analysis/comparisonView.scss b/src/components/Analysis/Comparison/comparison.scss similarity index 97% rename from src/components/Analysis/comparisonView.scss rename to src/components/Analysis/Comparison/comparison.scss index 12271b7131b329d67853f6b65dc8b1d64dd468c0..ad321b9c06d3afe6598b6c5d800ad3136736caa9 100644 --- a/src/components/Analysis/comparisonView.scss +++ b/src/components/Analysis/Comparison/comparison.scss @@ -1,4 +1,4 @@ -.comparisonView { +.comparison { display: flex; flex-direction: column; .tabs { diff --git a/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss b/src/components/Analysis/Comparison/fluidPerformanceIndicator.scss similarity index 91% rename from src/components/PerformanceIndicator/fluidPerformanceIndicator.scss rename to src/components/Analysis/Comparison/fluidPerformanceIndicator.scss index ec138c814f56d564d163093c16f00fa46265f0e7..6213c8fdc63a80db0122e252487db98e19b5f4fb 100644 --- a/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss +++ b/src/components/Analysis/Comparison/fluidPerformanceIndicator.scss @@ -1,5 +1,5 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; //FluidIndicator .fi-root { @@ -36,6 +36,7 @@ padding: 16px 22px; gap: 1rem; align-items: center; + box-shadow: 0px 4px 16px 0px $black-shadow; .fpi-content { .fpi-value { diff --git a/src/components/Analysis/ElecHalfHourChart.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart.spec.tsx similarity index 91% rename from src/components/Analysis/ElecHalfHourChart.spec.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart.spec.tsx index b9e16b2aad2297cddd3812ace4998e06aff0ecdb..f9700e0570e62fbd7b47fa4e2ba6c00d24819acb 100644 --- a/src/components/Analysis/ElecHalfHourChart.spec.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart.spec.tsx @@ -5,8 +5,8 @@ import React from 'react' import * as reactRedux from 'react-redux' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import { dataLoadArray } from '../../../tests/__mocks__/chartData.mock' -import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' +import { dataLoadArray } from '../../../../tests/__mocks__/chartData.mock' +import { globalStateData } from '../../../../tests/__mocks__/globalStateData.mock' import ElecHalfHourChart from './ElecHalfHourChart' jest.mock('cozy-ui/transpiled/react/I18n', () => { diff --git a/src/components/Analysis/ElecHalfHourChart.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart.tsx similarity index 100% rename from src/components/Analysis/ElecHalfHourChart.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart.tsx diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx similarity index 90% rename from src/components/Analysis/ElecHalfHourMonthlyAnalysis.spec.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx index 2cff5c734e2090bc063c9e31bb3c4ec05352e982..194618aba11fe8413e587081e9c1a25a349f9079 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.spec.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx @@ -8,10 +8,10 @@ import configureStore from 'redux-mock-store' import { mockDataLoadEnedisAnalysis, mockEnedisMonthlyAnalysisArray, -} from '../../../tests/__mocks__/enedisMonthlyAnalysisData.mock' -import { allLastFluidPrices } from '../../../tests/__mocks__/fluidPrice.mock' -import { mockAnalysisState } from '../../../tests/__mocks__/store' -import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' +} from '../../../../tests/__mocks__/enedisMonthlyAnalysisData.mock' +import { allLastFluidPrices } from '../../../../tests/__mocks__/fluidPrice.mock' +import { mockAnalysisState } from '../../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../../tests/__mocks__/testUtils' import ElecHalfHourMonthlyAnalysis from './ElecHalfHourMonthlyAnalysis' jest.mock('cozy-ui/transpiled/react/I18n', () => { @@ -28,10 +28,13 @@ jest.mock('components/Hooks/useExploration', () => { }) jest.mock( - 'components/Analysis/ElecHalfHourChart', + 'components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourChart', () => 'mock-elechalfhourchart' ) -jest.mock('components/Analysis/ElecInfoModal', () => 'mock-elecinfomodal') +jest.mock( + 'components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal', + () => 'mock-elecinfomodal' +) const mockCheckDoctypeEntries = jest.fn() jest.mock('services/consumption.service', () => { diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx similarity index 99% rename from src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx index b6fc4072005f7f60765a9f3401bcf9f8467762a6..2c3fb28c24d8eaf71d0396e994c4b9b173f04119 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx @@ -3,7 +3,6 @@ import LeftArrowIcon from 'assets/icons/ico/left-arrow.svg' import MaxPowerIcon from 'assets/icons/ico/maxPower.svg' import MinIcon from 'assets/icons/ico/minimum.svg' import RightArrowIcon from 'assets/icons/ico/right-arrow.svg' -import ElecHalfHourChart from 'components/Analysis/ElecHalfHourChart' import Loader from 'components/Loader/Loader' import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' @@ -22,6 +21,7 @@ import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData import FluidPricesService from 'services/fluidsPrices.service' import { AppStore } from 'store' import { getNavPicto } from 'utils/picto' +import ElecHalfHourChart from './ElecHalfHourChart' import ElecInfoModal from './ElecInfoModal' import './elecHalfHourMonthlyAnalysis.scss' diff --git a/src/components/Analysis/ElecInfoModal.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx similarity index 100% rename from src/components/Analysis/ElecInfoModal.spec.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx diff --git a/src/components/Analysis/ElecInfoModal.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx similarity index 100% rename from src/components/Analysis/ElecInfoModal.tsx rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx diff --git a/src/components/Analysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap similarity index 100% rename from src/components/Analysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourChart.spec.tsx.snap diff --git a/src/components/Analysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap similarity index 100% rename from src/components/Analysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecHalfHourMonthlyAnalysis.spec.tsx.snap diff --git a/src/components/Analysis/__snapshots__/ElecInfoModal.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap similarity index 100% rename from src/components/Analysis/__snapshots__/ElecInfoModal.spec.tsx.snap rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap diff --git a/src/components/Analysis/elecHalfHourMonthlyAnalysis.scss b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/elecHalfHourMonthlyAnalysis.scss similarity index 97% rename from src/components/Analysis/elecHalfHourMonthlyAnalysis.scss rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/elecHalfHourMonthlyAnalysis.scss index 4ea1ba5dec4ce4995302569192dfcc10ea6741ce..582ffd247a66d746c36f5af83ad4a139fa3f1638 100644 --- a/src/components/Analysis/elecHalfHourMonthlyAnalysis.scss +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/elecHalfHourMonthlyAnalysis.scss @@ -1,4 +1,4 @@ -@import '../../styles/base/color'; +@import 'src/styles/base/color'; .special-elec-container { color: white; diff --git a/src/components/Analysis/elecInfoModal.scss b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/elecInfoModal.scss similarity index 100% rename from src/components/Analysis/elecInfoModal.scss rename to src/components/Analysis/ElecHalfHourMonthlyAnalysis/elecInfoModal.scss diff --git a/src/components/Analysis/MaxConsumptionCard.spec.tsx b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx similarity index 82% rename from src/components/Analysis/MaxConsumptionCard.spec.tsx rename to src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx index a4273b052c65a19f899ac98be22551f7225a1421..c8dc1953a7997ddddf2f17cf6d1c70a6500e1bb4 100644 --- a/src/components/Analysis/MaxConsumptionCard.spec.tsx +++ b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.spec.tsx @@ -4,10 +4,10 @@ import toJson from 'enzyme-to-json' import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import { graphData } from '../../../tests/__mocks__/chartData.mock' -import mockClient from '../../../tests/__mocks__/client' -import { mockAnalysisState } from '../../../tests/__mocks__/store' -import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' +import { graphData } from '../../../../tests/__mocks__/chartData.mock' +import mockClient from '../../../../tests/__mocks__/client' +import { mockAnalysisState } from '../../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../../tests/__mocks__/testUtils' import MaxConsumptionCard from './MaxConsumptionCard' jest.mock('cozy-ui/transpiled/react/I18n', () => { @@ -46,16 +46,15 @@ describe('MaxConsumptionCard component', () => { it('should be rendered correctly', async () => { const store = mockStore({ ecolyo: { - global: { - fluidTypes: [FluidType.ELECTRICITY, FluidType.GAS], - }, analysis: mockAnalysisState, }, }) const wrapper = mount( <Provider store={store}> - <MaxConsumptionCard /> + <MaxConsumptionCard + fluidsWithData={[FluidType.ELECTRICITY, FluidType.GAS]} + /> </Provider> ) await waitForComponentToPaint(wrapper) @@ -65,13 +64,12 @@ describe('MaxConsumptionCard component', () => { it('should be rendered with one fluid and not display arrows', async () => { const store = mockStore({ ecolyo: { - global: { fluidTypes: [FluidType.ELECTRICITY] }, analysis: mockAnalysisState, }, }) const wrapper = mount( <Provider store={store}> - <MaxConsumptionCard /> + <MaxConsumptionCard fluidsWithData={[FluidType.ELECTRICITY]} /> </Provider> ) await waitForComponentToPaint(wrapper) @@ -80,13 +78,14 @@ describe('MaxConsumptionCard component', () => { it('should be rendered with several fluids and click navigate between fluid', async () => { const store = mockStore({ ecolyo: { - global: { fluidTypes: [FluidType.ELECTRICITY, FluidType.GAS] }, analysis: mockAnalysisState, }, }) const wrapper = mount( <Provider store={store}> - <MaxConsumptionCard /> + <MaxConsumptionCard + fluidsWithData={[FluidType.ELECTRICITY, FluidType.GAS]} + /> </Provider> ) await waitForComponentToPaint(wrapper) diff --git a/src/components/Analysis/MaxConsumptionCard.tsx b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.tsx similarity index 79% rename from src/components/Analysis/MaxConsumptionCard.tsx rename to src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.tsx index 3fde2da22acabb35915ab79d8433c9327d37551d..f64e1c96d92ab8d43d71adf25cc1541a760da3b7 100644 --- a/src/components/Analysis/MaxConsumptionCard.tsx +++ b/src/components/Analysis/MaxConsumptionCard/MaxConsumptionCard.tsx @@ -21,16 +21,18 @@ import { AppActionsTypes, AppStore } from 'store' import { setSelectedDate } from 'store/chart/chart.slice' import './maxConsumptionCard.scss' -const MaxConsumptionCard = () => { +const MaxConsumptionCard = ({ + fluidsWithData, +}: { + fluidsWithData: FluidType[] +}) => { const { t } = useI18n() const client = useClient() const dispatch = useDispatch<Dispatch<AppActionsTypes>>() - const { - global: { fluidTypes }, - analysis: { analysisMonth }, - } = useSelector((state: AppStore) => state.ecolyo) - const [index, setIndex] = useState<number>(0) - const [isLoading, setIsLoading] = useState<boolean>(true) + const { analysisMonth } = useSelector( + (state: AppStore) => state.ecolyo.analysis + ) + const [isLoading, setIsLoading] = useState<boolean>(false) const [maxDayData, setMaxDayData] = useState<Dataload | null>(null) const [chartData, setChartData] = useState<Datachart>({ actualData: [], @@ -38,27 +40,33 @@ const MaxConsumptionCard = () => { }) const containerRef = useRef<HTMLDivElement>(null) const { height, width } = useChartResize(containerRef, isLoading, 250, 940) + const [currentFluid, setCurrentFluid] = useState<FluidType | undefined>() + + useEffect(() => { + setCurrentFluid(fluidsWithData[0]) + }, [fluidsWithData]) - const currentFluidType = FluidType[fluidTypes[index]] as + const currentFluidSlug = FluidType[currentFluid || 0] as | 'ELECTRICITY' | 'WATER' | 'GAZ' - const fluidColor = currentFluidType.toLowerCase() const handleFluidChange = (direction: number) => { + if (currentFluid === undefined) return setIsLoading(true) - let newIndex = index + direction - if (newIndex >= fluidTypes.length) { + let newIndex = fluidsWithData.indexOf(currentFluid) + direction + if (newIndex >= fluidsWithData.length) { newIndex = 0 } else if (newIndex < 0) { - newIndex = fluidTypes.length - 1 + newIndex = fluidsWithData.length - 1 } - setIndex(newIndex) + setCurrentFluid(fluidsWithData[newIndex]) } useEffect(() => { let subscribed = true async function getMaxLoadData() { + if (currentFluid === undefined) return setIsLoading(true) const timePeriod: TimePeriod = { startDate: analysisMonth.minus({ month: 1 }).startOf('month'), @@ -68,7 +76,7 @@ const MaxConsumptionCard = () => { const monthlyData = await consumptionService.getGraphData( timePeriod, TimeStep.DAY, - [fluidTypes[index]] + [currentFluid] ) if (monthlyData && monthlyData?.actualData.length > 0) { @@ -89,7 +97,7 @@ const MaxConsumptionCard = () => { return () => { subscribed = false } - }, [analysisMonth, client, fluidTypes, index, dispatch]) + }, [analysisMonth, client, fluidsWithData, currentFluid, dispatch]) const getMaxConsumptionDay = (dataload: Dataload[]) => { let maxIndex = -1 @@ -129,17 +137,17 @@ const MaxConsumptionCard = () => { <StyledIcon icon={GraphIcon} size={38} /> <div className="text-16-normal title">{t('analysis.max_day')}</div> <div className="fluid-navigation"> - {fluidTypes.length > 1 && buttonPrev()} - <div className={`text-20-bold fluid ${fluidColor}`}> - {t(`FLUID.${currentFluidType}.LABEL`)} + {fluidsWithData.length > 1 && buttonPrev()} + <div className={`text-20-bold fluid ${currentFluidSlug.toLowerCase()}`}> + {t(`FLUID.${currentFluidSlug}.LABEL`)} </div> - {fluidTypes.length > 1 && buttonNext()} + {fluidsWithData.length > 1 && buttonNext()} </div> <div className="data-container"> {isLoading && ( <div className="loaderContainer"> - <Loader fluidType={fluidTypes[index]} /> + <Loader fluidType={currentFluid} /> </div> )} {!isLoading && ( @@ -147,7 +155,7 @@ const MaxConsumptionCard = () => { {!maxDayData && ( <p className={`text-20-bold no_data`}>{t('analysis.no_data')}</p> )} - {maxDayData && ( + {maxDayData && currentFluid !== undefined && ( <> <div className="text-24-bold maxDay-date"> {maxDayData.date.setLocale('fr').toFormat('cccc dd LLLL')} @@ -155,14 +163,14 @@ const MaxConsumptionCard = () => { <div> <DataloadSection dataload={maxDayData} - fluidType={fluidTypes[index]} + fluidType={currentFluid} dataloadSectionType={DataloadSectionType.NO_COMPARE} toggleEstimationModal={() => null} /> </div> <BarChart chartData={chartData} - fluidType={fluidTypes[index]} + fluidType={currentFluid} timeStep={TimeStep.DAY} height={height} width={width} diff --git a/src/components/Analysis/__snapshots__/MaxConsumptionCard.spec.tsx.snap b/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap similarity index 99% rename from src/components/Analysis/__snapshots__/MaxConsumptionCard.spec.tsx.snap rename to src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap index 8a146488c5ec22dcd6575e849da9afab08d37e36..1bc6803b2a13f24a1c35bbd34aa16c89292a1b2e 100644 --- a/src/components/Analysis/__snapshots__/MaxConsumptionCard.spec.tsx.snap +++ b/src/components/Analysis/MaxConsumptionCard/__snapshots__/MaxConsumptionCard.spec.tsx.snap @@ -13,7 +13,14 @@ exports[`MaxConsumptionCard component should be rendered correctly 1`] = ` } } > - <MaxConsumptionCard> + <MaxConsumptionCard + fluidsWithData={ + Array [ + 0, + 2, + ] + } + > <div className="max-consumption-container" > diff --git a/src/components/Analysis/maxConsumptionCard.scss b/src/components/Analysis/MaxConsumptionCard/maxConsumptionCard.scss similarity index 97% rename from src/components/Analysis/maxConsumptionCard.scss rename to src/components/Analysis/MaxConsumptionCard/maxConsumptionCard.scss index 66ac47a38804821d41f9026d16341d8fa1a9e6b5..b6df1c622636a132aed514da63148843e0bd2bd4 100644 --- a/src/components/Analysis/maxConsumptionCard.scss +++ b/src/components/Analysis/MaxConsumptionCard/maxConsumptionCard.scss @@ -1,4 +1,4 @@ -@import '../../styles/base/color'; +@import 'src/styles/base/color'; .max-consumption-container { display: flex; diff --git a/src/components/Analysis/MonthlyAnalysis.spec.tsx b/src/components/Analysis/MonthlyAnalysis.spec.tsx index 673cc0132691d514f30ff008be9ca3c5ffd33aa4..f56f98490adc7f60cb87feb795a4a5f5eafa8396 100644 --- a/src/components/Analysis/MonthlyAnalysis.spec.tsx +++ b/src/components/Analysis/MonthlyAnalysis.spec.tsx @@ -1,11 +1,14 @@ import MonthlyAnalysis from 'components/Analysis/MonthlyAnalysis' -import { FluidType } from 'enum/fluid.enum' import { mount } from 'enzyme' +import toJson from 'enzyme-to-json' +import { PerformanceIndicator } from 'models' import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' import configureStore from 'redux-mock-store' +import mockClient from '../../../tests/__mocks__/client' import { mockAnalysisState } from '../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' jest.mock('cozy-ui/transpiled/react/I18n', () => { return { @@ -16,9 +19,44 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) -jest.mock('services/consumption.service') -jest.mock('components/Analysis/ComparisonView', () => 'mock-comparison-view') -jest.mock('components/Analysis/AnalysisConsumption', () => 'mock-analysis') + +jest.mock('cozy-client', () => ({ + useClient: () => mockClient, +})) + +const mockPI: PerformanceIndicator[] = [ + { value: 5, compareValue: 10, percentageVariation: 50 }, + { value: 5, compareValue: 10, percentageVariation: 50 }, +] + +jest.mock('services/consumption.service', () => { + return jest.fn(() => { + return { + getFluidsWithDataForTimePeriod: jest.fn(() => [0, 1, 2]), + getPerformanceIndicators: jest.fn(() => mockPI), + } + }) +}) + +jest.mock('components/Analysis/Comparison/Comparison', () => 'mock-comparison') +jest.mock( + 'components/Analysis/TotalAnalysisChart/TotalAnalysisChart', + () => 'mock-total-analysis' +) +jest.mock( + 'components/Analysis/MaxConsumptionCard/MaxConsumptionCard', + () => 'mock-max-consumption' +) +jest.mock( + 'components/Analysis/ProfileComparator/ProfileComparator', + () => 'mock-analysis' +) +jest.mock( + 'components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis', + () => 'mock-half-hour-analysis' +) + +window.scrollTo = jest.fn() const mockStore = configureStore([]) @@ -26,7 +64,6 @@ describe('MonthlyAnalysis component', () => { it('should be rendered correctly', async () => { const store = mockStore({ ecolyo: { - global: { fluidTypes: [FluidType.ELECTRICITY, FluidType.GAS] }, analysis: mockAnalysisState, }, }) @@ -39,7 +76,8 @@ describe('MonthlyAnalysis component', () => { /> </Provider> </BrowserRouter> - ).getElement() - expect(wrapper).toMatchSnapshot() + ) + await waitForComponentToPaint(wrapper) + expect(toJson(wrapper)).toMatchSnapshot() }) }) diff --git a/src/components/Analysis/MonthlyAnalysis.tsx b/src/components/Analysis/MonthlyAnalysis.tsx index 6c8ff0f11940e6ede604cb82217e248c03313c10..7a0cb57b78fc41347d1c26fc8d3d8a150220da01 100644 --- a/src/components/Analysis/MonthlyAnalysis.tsx +++ b/src/components/Analysis/MonthlyAnalysis.tsx @@ -2,19 +2,17 @@ import Loader from 'components/Loader/Loader' import { useClient } from 'cozy-client' import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' -import { PerformanceIndicator } from 'models' -import React, { useEffect, useState } from 'react' +import { PerformanceIndicator, TimePeriod } from 'models' +import React, { useEffect, useMemo, useState } from 'react' import { useSelector } from 'react-redux' import ConsumptionService from 'services/consumption.service' -import ConfigService from 'services/fluidConfig.service' import PerformanceIndicatorService from 'services/performanceIndicator.service' import { AppStore } from 'store' -import AnalysisConsumption from './AnalysisConsumption' -import AnalysisErrorModal from './AnalysisErrorModal' -import ComparisonView from './ComparisonView' -import ElecHalfHourMonthlyAnalysis from './ElecHalfHourMonthlyAnalysis' -import MaxConsumptionCard from './MaxConsumptionCard' -import TotalAnalysisChart from './TotalAnalysisChart' +import Comparison from './Comparison/Comparison' +import ElecHalfHourMonthlyAnalysis from './ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis' +import MaxConsumptionCard from './MaxConsumptionCard/MaxConsumptionCard' +import ProfileComparator from './ProfileComparator/ProfileComparator' +import TotalAnalysisChart from './TotalAnalysisChart/TotalAnalysisChart' import './monthlyanalysis.scss' interface MonthlyAnalysisProps { @@ -27,10 +25,21 @@ const MonthlyAnalysis = ({ scrollPosition, }: MonthlyAnalysisProps) => { const client = useClient() - const { - analysis: { analysisMonth }, - global: { fluidTypes }, - } = useSelector((state: AppStore) => state.ecolyo) + const { analysisMonth } = useSelector( + (state: AppStore) => state.ecolyo.analysis + ) + + const consumptionService = useMemo( + () => new ConsumptionService(client), + [client] + ) + const performanceIndicatorService = useMemo( + () => new PerformanceIndicatorService(), + [] + ) + + const [loadAnalysis, setLoadAnalysis] = useState<boolean>(true) + const [fluidsWithData, setFluidsWithData] = useState<FluidType[]>([]) const [performanceIndicators, setPerformanceIndicators] = useState< PerformanceIndicator[] >([]) @@ -40,56 +49,62 @@ const MonthlyAnalysis = ({ compareValue: 0, percentageVariation: 0, }) - const [loadAnalysis, setLoadAnalysis] = useState<boolean>(true) - const configService = new ConfigService() - const fluidConfig = configService.getFluidConfig() useEffect(() => { let subscribed = true - async function populateData() { + + const populateData = async () => { setLoadAnalysis(true) - const consumptionService = new ConsumptionService(client) - const performanceIndicatorService = new PerformanceIndicatorService() - const periods = { - timePeriod: { - startDate: analysisMonth.minus({ month: 1 }).startOf('month'), - endDate: analysisMonth.minus({ month: 1 }).endOf('month'), - }, - comparisonTimePeriod: { - startDate: analysisMonth.minus({ month: 2 }).startOf('month'), - endDate: analysisMonth.minus({ month: 2 }).endOf('month'), - }, + + const timePeriod: TimePeriod = { + startDate: analysisMonth.minus({ month: 1 }).startOf('month'), + endDate: analysisMonth.minus({ month: 1 }).endOf('month'), + } + const comparisonTimePeriod: TimePeriod = { + startDate: analysisMonth.minus({ month: 2 }).startOf('month'), + endDate: analysisMonth.minus({ month: 2 }).endOf('month'), } + const resultFluids = + await consumptionService.getFluidsWithDataForTimePeriod( + [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS], + timePeriod + ) + const fetchedPerformanceIndicators = await consumptionService.getPerformanceIndicators( - periods.timePeriod, + timePeriod, TimeStep.MONTH, - fluidTypes, - periods.comparisonTimePeriod + resultFluids, + comparisonTimePeriod ) - if (subscribed) { - if (fetchedPerformanceIndicators) { - setPerformanceIndicators(fetchedPerformanceIndicators) - setLoadAnalysis(false) - const hasValidPI = fetchedPerformanceIndicators.some(pi => pi?.value) - if (hasValidPI) setLoadAnalysis(true) - setAggregatedPerformanceIndicators( - performanceIndicatorService.aggregatePerformanceIndicators( - fetchedPerformanceIndicators - ) - ) - } + if (fetchedPerformanceIndicators) { + setPerformanceIndicators(fetchedPerformanceIndicators) setLoadAnalysis(false) + setAggregatedPerformanceIndicators( + performanceIndicatorService.aggregatePerformanceIndicators( + fetchedPerformanceIndicators + ) + ) } + setFluidsWithData(resultFluids) + setLoadAnalysis(false) + } + + if (subscribed) { + populateData() } - populateData() return () => { saveLastScrollPosition() subscribed = false } - }, [client, fluidTypes, analysisMonth, saveLastScrollPosition]) + }, [ + analysisMonth, + consumptionService, + performanceIndicatorService, + saveLastScrollPosition, + ]) useEffect(() => { if (!loadAnalysis) { @@ -102,51 +117,45 @@ const MonthlyAnalysis = ({ return ( <> {loadAnalysis && ( - <div className="analysis-container-spinner" aria-busy="true"> + <div className="analysis-container-spinner"> <Loader /> </div> )} {!loadAnalysis && ( - <div className="analysis-root black"> - {fluidTypes.length >= 1 ? ( - <> - <div className="analysis-content"> - <ComparisonView fluidConfig={fluidConfig} /> - </div> - <div className="analysis-content"> - <div className="card rich-card"> - <TotalAnalysisChart fluidTypes={fluidTypes} /> - </div> - </div> - <div className="analysis-content"> - <div className="card rich-card"> - <MaxConsumptionCard /> - </div> - </div> - <div className="analysis-content"> - <div className="card rich-card"> - <AnalysisConsumption - aggregatedPerformanceIndicator={ - aggregatedPerformanceIndicators - } - performanceIndicators={performanceIndicators} - /> - </div> + <div className="analysis-root"> + <div className="analysis-content"> + <Comparison + fluidsWithData={fluidsWithData} + monthPerformanceIndicators={performanceIndicators} + /> + </div> + <div className="analysis-content"> + <div className="card rich-card"> + <TotalAnalysisChart fluidsWithData={fluidsWithData} /> + </div> + </div> + <div className="analysis-content"> + <div className="card rich-card"> + <MaxConsumptionCard fluidsWithData={fluidsWithData} /> + </div> + </div> + <div className="analysis-content"> + <div className="card rich-card"> + <ProfileComparator + aggregatedPerformanceIndicator={aggregatedPerformanceIndicators} + fluidsWithData={fluidsWithData} + performanceIndicators={performanceIndicators} + /> + </div> + </div> + {fluidsWithData.includes(FluidType.ELECTRICITY) && ( + <div className="analysis-content"> + <div className="card"> + <ElecHalfHourMonthlyAnalysis + perfIndicator={performanceIndicators[FluidType.ELECTRICITY]} + /> </div> - {fluidTypes.includes(FluidType.ELECTRICITY) && ( - <div className="analysis-content"> - <div className="card"> - <ElecHalfHourMonthlyAnalysis - perfIndicator={ - performanceIndicators[FluidType.ELECTRICITY] - } - /> - </div> - </div> - )} - </> - ) : ( - <AnalysisErrorModal /> + </div> )} </div> )} diff --git a/src/components/Analysis/PieChart.spec.tsx b/src/components/Analysis/PieChart.spec.tsx deleted file mode 100644 index 130cf415b532f5325ac4bd2b7dcdf7765fe2f154..0000000000000000000000000000000000000000 --- a/src/components/Analysis/PieChart.spec.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { DataloadState } from 'enum/dataload.enum' -import { mount } from 'enzyme' -import toJson from 'enzyme-to-json' -import { DateTime } from 'luxon' -import { DataloadValueDetail } from 'models' -import React from 'react' -import { Provider } from 'react-redux' -import configureStore from 'redux-mock-store' -import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' -import PieChart from './PieChart' - -jest.mock('cozy-ui/transpiled/react/I18n', () => { - return { - useI18n: jest.fn(() => { - return { - t: (str: string) => str, - } - }), - } -}) - -const mockGetAllLastPrices = jest.fn() -jest.mock('services/fluidsPrices.service', () => { - return jest.fn(() => { - return { - getAllLastPrices: mockGetAllLastPrices, - } - }) -}) - -const mockStore = configureStore([]) - -describe('PieChart component', () => { - const mockDataloadValueDetailArray: DataloadValueDetail[] = [ - { value: 10, state: DataloadState.VALID }, - { value: 20, state: DataloadState.VALID }, - { value: 30, state: DataloadState.VALID }, - ] - it('should be rendered correctly', () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - }, - }) - const wrapper = mount( - <Provider store={store}> - <PieChart - width={300} - height={300} - outerRadius={300} - innerRadius={300} - currentAnalysisDate={DateTime.fromISO('2021-07-01T00:00:00.000Z', { - zone: 'utc', - })} - totalValue={60} - dataloadValueDetailArray={mockDataloadValueDetailArray} - /> - </Provider> - ) - expect(toJson(wrapper)).toMatchSnapshot() - }) - it('should open estimation modal', () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - }, - }) - const wrapper = mount( - <Provider store={store}> - <PieChart - width={300} - height={300} - outerRadius={300} - innerRadius={300} - currentAnalysisDate={DateTime.fromISO('2021-07-01T00:00:00.000Z', { - zone: 'utc', - })} - totalValue={60} - dataloadValueDetailArray={mockDataloadValueDetailArray} - /> - </Provider> - ) - expect(wrapper.find('.estimation-text').simulate('click')) - }) -}) diff --git a/src/components/Analysis/AnalysisConsumption.spec.tsx b/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx similarity index 81% rename from src/components/Analysis/AnalysisConsumption.spec.tsx rename to src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx index 0e19f2b8e709232f442aba2b6aed2625c2b177f5..b07d924062942ceaee79035535c24cbd8f3a94f4 100644 --- a/src/components/Analysis/AnalysisConsumption.spec.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparator.spec.tsx @@ -1,21 +1,21 @@ /* eslint-disable react/display-name */ import { Accordion } from '@material-ui/core' import Button from '@material-ui/core/Button' -import AnalysisConsumption from 'components/Analysis/AnalysisConsumption' import { FluidType } from 'enum/fluid.enum' import { mount } from 'enzyme' import { PerformanceIndicator } from 'models' import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' -import { profileData } from '../../../tests/__mocks__/profileData.mock' +import { globalStateData } from '../../../../tests/__mocks__/globalStateData.mock' +import { profileData } from '../../../../tests/__mocks__/profileData.mock' import { mockMonthlyForecastJanuaryTestProfile1, profileTypeData, -} from '../../../tests/__mocks__/profileType.mock' -import { mockAnalysisState } from '../../../tests/__mocks__/store' -import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' +} from '../../../../tests/__mocks__/profileType.mock' +import { mockAnalysisState } from '../../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../../tests/__mocks__/testUtils' +import ProfileComparator from './ProfileComparator' jest.mock('cozy-ui/transpiled/react/I18n', () => { return { @@ -51,9 +51,10 @@ jest.mock('services/profileTypeEntity.service', () => { }) }) -jest.mock('components/Analysis/AnalysisConsumptionRow', () => () => ( - <div id="analysisconsumptionrow" /> -)) +jest.mock( + 'components/Analysis/ProfileComparator/ProfileComparatorRow', + () => () => <div id="profilecomparatorrow" /> +) const modifiedProfile = { ...profileData, isProfileTypeCompleted: true } const mockStore = configureStore([]) @@ -66,6 +67,7 @@ const store = mockStore({ }, }) +const allFluids = [FluidType.ELECTRICITY, FluidType.GAS, FluidType.MULTIFLUID] const performanceIndicator = { compareValue: 160.42797399999998, percentageVariation: 0.026592126632478563, @@ -93,15 +95,16 @@ describe('AnalysisConsumption component', () => { it('should be rendered correctly', async () => { const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={performanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) await waitForComponentToPaint(wrapper) expect(wrapper.find(Accordion).exists()).toBeTruthy() - expect(wrapper.find('#analysisconsumptionrow').length).toBe(4) + expect(wrapper.find('#profilecomparatorrow').length).toBe(4) }) it('should be rendered correctly with no profil set', async () => { @@ -112,9 +115,10 @@ describe('AnalysisConsumption component', () => { } const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={mockAggregatedPerformanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) @@ -130,29 +134,31 @@ describe('AnalysisConsumption component', () => { } const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={mockAggregatedPerformanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) await waitForComponentToPaint(wrapper) expect(wrapper.find(Accordion).exists()).toBeTruthy() - expect(wrapper.find('#analysisconsumptionrow').length).toBe(4) + expect(wrapper.find('#profilecomparatorrow').length).toBe(4) }) it('should be rendered correctly without fluid', async () => { const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={performanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) await waitForComponentToPaint(wrapper) expect( - wrapper.find('#analysisconsumptionrow').first().parent().prop('fluid') + wrapper.find('#profilecomparatorrow').first().parent().prop('fluid') ).toBe(FluidType.MULTIFLUID) }) @@ -175,9 +181,10 @@ describe('AnalysisConsumption component', () => { ) const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={performanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) @@ -186,7 +193,7 @@ describe('AnalysisConsumption component', () => { profileData.monthlyAnalysisDate.month - 1 ) expect( - wrapper.find('#analysisconsumptionrow').first().parent().prop('fluid') + wrapper.find('#profilecomparatorrow').first().parent().prop('fluid') ).toBe(FluidType.MULTIFLUID) }) @@ -203,9 +210,10 @@ describe('AnalysisConsumption component', () => { ) const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={performanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) @@ -214,16 +222,17 @@ describe('AnalysisConsumption component', () => { profileData.monthlyAnalysisDate.month - 1 ) expect( - wrapper.find('#analysisconsumptionrow').first().parent().prop('fluid') + wrapper.find('#profilecomparatorrow').first().parent().prop('fluid') ).toBe(FluidType.MULTIFLUID) }) it('should redirect to profileType form when click on mui button', async () => { const wrapper = mount( <Provider store={store}> - <AnalysisConsumption + <ProfileComparator aggregatedPerformanceIndicator={performanceIndicator} performanceIndicators={performanceIndicators} + fluidsWithData={allFluids} /> </Provider> ) diff --git a/src/components/Analysis/AnalysisConsumption.tsx b/src/components/Analysis/ProfileComparator/ProfileComparator.tsx similarity index 89% rename from src/components/Analysis/AnalysisConsumption.tsx rename to src/components/Analysis/ProfileComparator/ProfileComparator.tsx index 80c8c93bf400a58bf11af6527525aba834d9dda7..88249ad39e662f99030710aaf0cc07831a6cd289 100644 --- a/src/components/Analysis/AnalysisConsumption.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparator.tsx @@ -22,25 +22,26 @@ import { useNavigate } from 'react-router-dom' import ProfileTypeService from 'services/profileType.service' import ProfileTypeEntityService from 'services/profileTypeEntity.service' import { AppStore } from 'store' -import AnalysisConsumptionRow from './AnalysisConsumptionRow' -import './analysisConsumption.scss' +import ProfileComparatorRow from './ProfileComparatorRow' +import './profileComparator.scss' -interface AnalysisConsumptionProps { +interface ProfileComparatorProps { aggregatedPerformanceIndicator: PerformanceIndicator + fluidsWithData: FluidType[] performanceIndicators: PerformanceIndicator[] } -const AnalysisConsumption = ({ +const ProfileComparator = ({ aggregatedPerformanceIndicator, + fluidsWithData, performanceIndicators, -}: AnalysisConsumptionProps) => { +}: ProfileComparatorProps) => { const { t } = useI18n() const navigate = useNavigate() const client: Client = useClient() const userPriceConsumption: number = aggregatedPerformanceIndicator.value || 0 const { analysis: { analysisMonth }, - global: { fluidTypes }, profile, } = useSelector((state: AppStore) => state.ecolyo) const [homePriceConsumption, setHomePriceConsumption] = useState<number>(0) @@ -63,41 +64,37 @@ const AnalysisConsumption = ({ } } - // Disconnected + empty fluids to show in AnalysisConsumptionRow + /** Disconnected + empty fluids to show in ProfileComparatorRow */ const disconnectedFluidTypes: FluidType[] = [ FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS, - ].filter(fluidType => !fluidTypes.includes(fluidType)) + ].filter(fluidType => !fluidsWithData.includes(fluidType)) const emptyFluidTypes: FluidType[] = [] for (let i = 0; i < performanceIndicators.length; i++) { - if (!performanceIndicators[i]?.value && fluidTypes[i]) { - emptyFluidTypes.push(fluidTypes[i]) + if (!performanceIndicators[i]?.value && fluidsWithData[i]) { + emptyFluidTypes.push(fluidsWithData[i]) } } const getTotalValueWithConnectedFluids = useCallback( (monthlyForecast: MonthlyForecast) => { - if (fluidTypes.length === 3) { + if (fluidsWithData.length === 3) { setHomePriceConsumption(monthlyForecast.totalValue) } else { let totalPrice = 0 - fluidTypes.forEach(fluid => { + fluidsWithData.forEach(fluid => { if (monthlyForecast.fluidForecast[fluid].value) totalPrice += monthlyForecast.fluidForecast[fluid].value }) setHomePriceConsumption(totalPrice) } }, - [fluidTypes] + [fluidsWithData] ) - const goToForm = () => { - navigate('/profileType') - } - useEffect(() => { let subscribed = true async function loadAverageConsumption() { @@ -144,7 +141,7 @@ const AnalysisConsumption = ({ </div> <Button aria-label={t('analysis.accessibility.button_go_to_profil')} - onClick={goToForm} + onClick={() => navigate('/profileType')} classes={{ root: 'btn-highlight', label: 'text-18-bold', @@ -174,20 +171,20 @@ const AnalysisConsumption = ({ <div className={`average-title`}>{t(`analysis.comparison`)}</div> </div> <div className="consumption-price"> - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.MULTIFLUID} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} performanceValue={null} forecast={forecast} - connected={fluidTypes.length > 0} + connected={fluidsWithData.length > 0} noData={false} /> </div> - {fluidTypes.map( + {fluidsWithData.map( fluid => Boolean(performanceIndicators[fluid]?.value) && ( - <AnalysisConsumptionRow + <ProfileComparatorRow key={fluid} fluid={fluid} userPriceConsumption={userPriceConsumption} @@ -199,9 +196,9 @@ const AnalysisConsumption = ({ /> ) )} - {fluidTypes.length < 3 && <hr className="consumption-sep" />} + {fluidsWithData.length < 3 && <hr className="consumption-sep" />} {disconnectedFluidTypes.map(fluid => ( - <AnalysisConsumptionRow + <ProfileComparatorRow key={fluid} fluid={fluid} userPriceConsumption={userPriceConsumption} @@ -213,7 +210,7 @@ const AnalysisConsumption = ({ /> ))} {emptyFluidTypes.map(fluid => ( - <AnalysisConsumptionRow + <ProfileComparatorRow key={fluid} fluid={fluid} userPriceConsumption={userPriceConsumption} @@ -273,7 +270,7 @@ const AnalysisConsumption = ({ {profile.isProfileTypeCompleted && ( <Button aria-label={t('analysis.accessibility.button_go_to_profil')} - onClick={goToForm} + onClick={() => navigate('/profileType')} classes={{ root: 'btn-secondary-negative', label: 'text-16-normal', @@ -292,4 +289,4 @@ const AnalysisConsumption = ({ ) } -export default AnalysisConsumption +export default ProfileComparator diff --git a/src/components/Analysis/AnalysisConsumptionRow.spec.tsx b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx similarity index 95% rename from src/components/Analysis/AnalysisConsumptionRow.spec.tsx rename to src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx index b147d31cf440dafc9f9cd73c09e4a28cd448649f..6691932a7227630d155936ed0803aaa66de9b903 100644 --- a/src/components/Analysis/AnalysisConsumptionRow.spec.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.spec.tsx @@ -2,8 +2,8 @@ import { FluidType } from 'enum/fluid.enum' import { mount } from 'enzyme' import { MonthlyForecast } from 'models' import React from 'react' -import { mockMonthlyForecastJanuaryTestProfile1 } from '../../../tests/__mocks__/profileType.mock' -import AnalysisConsumptionRow from './AnalysisConsumptionRow' +import { mockMonthlyForecastJanuaryTestProfile1 } from '../../../../tests/__mocks__/profileType.mock' +import ProfileComparatorRow from './ProfileComparatorRow' jest.mock('cozy-ui/transpiled/react/I18n', () => { return { @@ -26,7 +26,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for Multifluid and at least fluid connected', async () => { const mockPerformanceValue: number | null = null const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.MULTIFLUID} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} @@ -47,7 +47,7 @@ describe('AnalysisConsumptionRow component', () => { const mockPerformanceValue: number | null = null const mockConnected = false const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.MULTIFLUID} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} @@ -66,7 +66,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for singleFluid connected for average', async () => { const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} @@ -90,7 +90,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for singleFluid not connected', async () => { const mockConnected = false const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} @@ -112,7 +112,7 @@ describe('AnalysisConsumptionRow component', () => { it('should be rendered correctly for singleFluid with none performance value', async () => { const mockPerformanceValue: number | null = null const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} @@ -146,7 +146,7 @@ describe('AnalysisConsumptionRow component', () => { ], } const wrapper = mount( - <AnalysisConsumptionRow + <ProfileComparatorRow fluid={FluidType.ELECTRICITY} userPriceConsumption={userPriceConsumption} homePriceConsumption={homePriceConsumption} diff --git a/src/components/Analysis/AnalysisConsumptionRow.tsx b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx similarity index 96% rename from src/components/Analysis/AnalysisConsumptionRow.tsx rename to src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx index 451ffab1b3c804f7d2a60ca3730a231cb4175761..b0ae0c5fcbc2afb62387b52870f598ef87229411 100644 --- a/src/components/Analysis/AnalysisConsumptionRow.tsx +++ b/src/components/Analysis/ProfileComparator/ProfileComparatorRow.tsx @@ -8,9 +8,9 @@ import React from 'react' import ConverterService from 'services/converter.service' import { getPicto } from 'utils/picto' import { formatNumberValues } from 'utils/utils' -import './analysisConsumptionRow.scss' +import './profileComparatorRow.scss' -interface AnalysisConsumptionRowProps { +interface ProfileComparatorRowProps { fluid: FluidType userPriceConsumption: number homePriceConsumption: number @@ -20,7 +20,7 @@ interface AnalysisConsumptionRowProps { noData: boolean } -const AnalysisConsumptionRow = ({ +const ProfileComparatorRow = ({ fluid, userPriceConsumption, homePriceConsumption, @@ -28,7 +28,7 @@ const AnalysisConsumptionRow = ({ forecast, connected, noData, -}: AnalysisConsumptionRowProps) => { +}: ProfileComparatorRowProps) => { const { t } = useI18n() const converterService: ConverterService = new ConverterService() const maxPriceConsumption: number = Math.max( @@ -168,4 +168,4 @@ const AnalysisConsumptionRow = ({ ) } -export default AnalysisConsumptionRow +export default ProfileComparatorRow diff --git a/src/components/Analysis/analysisConsumption.scss b/src/components/Analysis/ProfileComparator/profileComparator.scss similarity index 89% rename from src/components/Analysis/analysisConsumption.scss rename to src/components/Analysis/ProfileComparator/profileComparator.scss index 510244040a68e994aa9c95f1512a3a9b9016ac4f..20ef9a412749d387400b9e04b5c42f1a8d59922e 100644 --- a/src/components/Analysis/analysisConsumption.scss +++ b/src/components/Analysis/ProfileComparator/profileComparator.scss @@ -1,5 +1,5 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; .analysis-graph { margin-top: 1.5rem; @@ -23,6 +23,8 @@ div.expansion-panel-root { border: solid 2px $blue-grey; color: $blue-grey; + margin-bottom: 0 !important; + box-shadow: 0px 4px 16px 0px $black-shadow; } .accordion-title { font-weight: bold; diff --git a/src/components/Analysis/analysisConsumptionRow.scss b/src/components/Analysis/ProfileComparator/profileComparatorRow.scss similarity index 95% rename from src/components/Analysis/analysisConsumptionRow.scss rename to src/components/Analysis/ProfileComparator/profileComparatorRow.scss index 5d0171f1f6468965c2e8394e11f0548bbcc808a1..cc9b0faa3b43a9414d4f9fa1716c300b36862012 100644 --- a/src/components/Analysis/analysisConsumptionRow.scss +++ b/src/components/Analysis/ProfileComparator/profileComparatorRow.scss @@ -1,5 +1,5 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; .consumption-multifluid, .consumption-electricity, diff --git a/src/components/Analysis/TotalAnalysisChart.tsx b/src/components/Analysis/TotalAnalysisChart.tsx deleted file mode 100644 index 1166bbc8c9a25d014691ddec1e664168244b33e4..0000000000000000000000000000000000000000 --- a/src/components/Analysis/TotalAnalysisChart.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useClient } from 'cozy-client' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import Icon from 'cozy-ui/transpiled/react/Icon' -import { FluidType } from 'enum/fluid.enum' -import { TimeStep } from 'enum/timeStep.enum' -import { DataloadValueDetail, TimePeriod } from 'models' -import React, { useEffect, useState } from 'react' -import { useSelector } from 'react-redux' -import ConsumptionDataManager from 'services/consumption.service' -import { AppStore } from 'store' -import { getNavPicto } from 'utils/picto' -import { formatNumberValues } from 'utils/utils' -import PieChart from './PieChart' -import './analysisView.scss' -import './totalAnalysisChart.scss' - -const TotalAnalysisChart = ({ fluidTypes }: { fluidTypes: FluidType[] }) => { - const { analysisMonth } = useSelector( - (state: AppStore) => state.ecolyo.analysis - ) - const [dataLoadValueDetailArray, setDataLoadValueDetailArray] = useState< - DataloadValueDetail[] | null - >(null) - const [totalLoadValue, setTotalLoadValue] = useState<number>(0) - const client = useClient() - const { t } = useI18n() - const arcWidth = 30 - const radius = Math.min(375, innerWidth - 100) - const outerRadius = radius / 2 - const innerRadius = outerRadius - arcWidth - - useEffect(() => { - let subscribed = true - async function getTotalData() { - const timePeriod: TimePeriod = { - startDate: analysisMonth.minus({ month: 1 }).startOf('month'), - endDate: analysisMonth.minus({ month: 1 }).endOf('month'), - } - const consumptionService = new ConsumptionDataManager(client) - const monthTotalData = await consumptionService.getGraphData( - timePeriod, - TimeStep.MONTH, - fluidTypes, - undefined, - undefined, - true - ) - if (monthTotalData?.actualData) { - setDataLoadValueDetailArray(monthTotalData.actualData[0].valueDetail) - setTotalLoadValue(monthTotalData.actualData[0].value) - } - } - if (subscribed) { - getTotalData() - } - return () => { - subscribed = false - } - }, [analysisMonth, client, fluidTypes]) - - return ( - <div - className="totalAnalysis-container" - style={{ - minHeight: radius + 100, - }} - > - <div className="text-24-normal title">{t('analysis_pie.total')}</div> - {dataLoadValueDetailArray && ( - <PieChart - dataloadValueDetailArray={dataLoadValueDetailArray} - totalValue={totalLoadValue} - width={radius} - height={radius} - innerRadius={innerRadius} - outerRadius={outerRadius} - currentAnalysisDate={analysisMonth} - /> - )} - {dataLoadValueDetailArray && fluidTypes.length > 1 && ( - <div className="total-card-container"> - {dataLoadValueDetailArray.map((dataload, index) => { - return ( - <div key={index} className="total-card"> - <div className="text-18-bold fluidconso"> - {dataload.value !== -1 - ? `${formatNumberValues(dataload.value)} €` - : '--- €'} - </div> - <Icon - className="euro-fluid-icon" - icon={getNavPicto(index, true, true)} - size={38} - /> - <div className="text-16-normal"> - {t('FLUID.' + FluidType[index] + '.LABEL')} - </div> - </div> - ) - })} - </div> - )} - </div> - ) -} - -export default TotalAnalysisChart diff --git a/src/components/Analysis/TotalAnalysisChart/PieChart.spec.tsx b/src/components/Analysis/TotalAnalysisChart/PieChart.spec.tsx new file mode 100644 index 0000000000000000000000000000000000000000..58a58e09bcca94969ad5455642f331905766672e --- /dev/null +++ b/src/components/Analysis/TotalAnalysisChart/PieChart.spec.tsx @@ -0,0 +1,37 @@ +import { DataloadState } from 'enum/dataload.enum' +import { mount } from 'enzyme' +import toJson from 'enzyme-to-json' +import { DataloadValueDetail } from 'models' +import React from 'react' +import PieChart from './PieChart' + +jest.mock('cozy-ui/transpiled/react/I18n', () => { + return { + useI18n: jest.fn(() => { + return { + t: (str: string) => str, + } + }), + } +}) + +describe('PieChart component', () => { + const mockDataloadValueDetailArray: DataloadValueDetail[] = [ + { value: 10, state: DataloadState.VALID }, + { value: 20, state: DataloadState.VALID }, + { value: 30, state: DataloadState.VALID }, + ] + it('should be rendered correctly', () => { + const wrapper = mount( + <PieChart + radius={300} + outerRadius={300} + innerRadius={300} + dataloadValueDetailArray={mockDataloadValueDetailArray} + > + child with value and text to render + </PieChart> + ) + expect(toJson(wrapper)).toMatchSnapshot() + }) +}) diff --git a/src/components/Analysis/PieChart.tsx b/src/components/Analysis/TotalAnalysisChart/PieChart.tsx similarity index 56% rename from src/components/Analysis/PieChart.tsx rename to src/components/Analysis/TotalAnalysisChart/PieChart.tsx index 5a215454d830a7bd54e8a22dd65b6172260ff7ee..ad96a96122221cf037ac4e0ab48d0bb088e6e7cb 100644 --- a/src/components/Analysis/PieChart.tsx +++ b/src/components/Analysis/TotalAnalysisChart/PieChart.tsx @@ -1,39 +1,26 @@ -import EstimatedConsumptionModal from 'components/ConsumptionVisualizer/EstimatedConsumptionModal' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' import * as d3 from 'd3' -import { DateTime } from 'luxon' import { DataloadValueDetail } from 'models' -import React, { useCallback, useEffect, useRef, useState } from 'react' -import { formatNumberValues, getMonthNameWithPrep } from 'utils/utils' +import React, { useEffect, useRef } from 'react' import './totalAnalysisChart.scss' interface PieProps { innerRadius: number outerRadius: number dataloadValueDetailArray: DataloadValueDetail[] - width: number - height: number - totalValue: number - currentAnalysisDate: DateTime + radius: number + children: React.ReactNode } const PieChart = ({ innerRadius, outerRadius, dataloadValueDetailArray, - width, - height, - totalValue, - currentAnalysisDate, + radius, + children, }: PieProps) => { const ref = useRef(null) - const { t } = useI18n() const createPie = d3.pie().sort(null) const arcWidth = outerRadius - innerRadius - const [openEstimationModal, setOpenEstimationModal] = useState<boolean>(false) - const toggleEstimationModal = useCallback(() => { - setOpenEstimationModal(prev => !prev) - }, []) useEffect(() => { const dataloadArray: number[] = dataloadValueDetailArray.map( @@ -70,11 +57,11 @@ const PieChart = ({ <div className="pie-container" style={{ - width: width, - height: height, + width: radius, + height: radius, }} > - <svg width={width} height={height}> + <svg width={radius} height={radius}> <defs> <filter id="glow" height="300%" width="300%" x="-75%" y="-75%"> <feGaussianBlur stdDeviation="10" result="coloredBlur" /> @@ -89,41 +76,23 @@ const PieChart = ({ <div className="pie-center" style={{ - width: width - arcWidth, - height: height - arcWidth, + width: radius - arcWidth, + height: radius - arcWidth, top: arcWidth / 2, left: arcWidth - arcWidth / 2, }} > - <div className="text-36-bold"> - {formatNumberValues(totalValue)} - <span className="euro-unit">{`${t('FLUID.MULTIFLUID.UNIT')}`}</span> - </div> - <div className="text-16-normal date"> - {t('analysis_pie.month') + - getMonthNameWithPrep(currentAnalysisDate.minus({ month: 1 }))} - </div> - <div - className="text-14-normal estimation-text" - onClick={toggleEstimationModal} - > - <span className="estimated">{t('analysis_pie.estimation')}</span> - <span className="estimated">{t('analysis_pie.estimation2')}</span> - </div> + {children} <div className="circle" style={{ - width: width - arcWidth * 2, - height: height - arcWidth * 2, + width: radius - arcWidth * 2, + height: radius - arcWidth * 2, top: arcWidth / 2, left: arcWidth - arcWidth / 2, }} /> </div> - <EstimatedConsumptionModal - open={openEstimationModal} - handleCloseClick={toggleEstimationModal} - /> </div> ) } diff --git a/src/components/Analysis/TotalAnalysisChart.spec.tsx b/src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.spec.tsx similarity index 68% rename from src/components/Analysis/TotalAnalysisChart.spec.tsx rename to src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.spec.tsx index bff43f5a94771ea8312207b0fc5a8cec6e91d2d2..00cd51eee151f6d558da489a2a578771db864646 100644 --- a/src/components/Analysis/TotalAnalysisChart.spec.tsx +++ b/src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.spec.tsx @@ -7,9 +7,9 @@ import { Datachart } from 'models' import React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' -import { graphMonthData } from '../../../tests/__mocks__/chartData.mock' -import { mockAnalysisState } from '../../../tests/__mocks__/store' -import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' +import { graphMonthData } from '../../../../tests/__mocks__/chartData.mock' +import { mockAnalysisState } from '../../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../../tests/__mocks__/testUtils' import TotalAnalysisChart from './TotalAnalysisChart' jest.mock('cozy-ui/transpiled/react/I18n', () => { @@ -21,15 +21,20 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) -const mockgetGraphData = jest.fn() +const mockGetGraphData = jest.fn() jest.mock('services/consumption.service', () => { - return jest.fn(() => { - return { - getGraphData: mockgetGraphData, - } - }) + return jest.fn(() => ({ + getGraphData: mockGetGraphData, + })) }) -jest.mock('components/Analysis/PieChart', () => 'mock-piechart') +jest.mock( + 'components/Analysis/TotalAnalysisChart/PieChart', + () => 'mock-piechart' +) +jest.mock( + 'components/ConsumptionVisualizer/EstimatedConsumptionModal', + () => 'mock-estimatedmodal' +) const mockStore = configureStore([]) const store = mockStore({ @@ -38,20 +43,25 @@ const store = mockStore({ }, }) describe('TotalAnalysisChart component', () => { - it('should be rendered correctly', () => { + it('should be rendered correctly', async () => { const wrapper = mount( <Provider store={store}> - <TotalAnalysisChart fluidTypes={[FluidType.ELECTRICITY]} /> + <TotalAnalysisChart fluidsWithData={[FluidType.ELECTRICITY]} /> </Provider> ) + await waitForComponentToPaint(wrapper) expect(toJson(wrapper)).toMatchSnapshot() }) it('should render several fluids and display month data', async () => { - mockgetGraphData.mockResolvedValueOnce(graphMonthData) + mockGetGraphData.mockResolvedValue(graphMonthData) const wrapper = mount( <Provider store={store}> <TotalAnalysisChart - fluidTypes={[FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS]} + fluidsWithData={[ + FluidType.ELECTRICITY, + FluidType.WATER, + FluidType.GAS, + ]} /> </Provider> ) @@ -72,11 +82,11 @@ describe('TotalAnalysisChart component', () => { ], comparisonData: null, } - mockgetGraphData.mockResolvedValueOnce(emptyData) + mockGetGraphData.mockResolvedValue(emptyData) const wrapper = mount( <Provider store={store}> <TotalAnalysisChart - fluidTypes={[FluidType.ELECTRICITY, FluidType.WATER]} + fluidsWithData={[FluidType.ELECTRICITY, FluidType.WATER]} /> </Provider> ) @@ -97,10 +107,10 @@ describe('TotalAnalysisChart component', () => { ], comparisonData: null, } - mockgetGraphData.mockResolvedValueOnce(emptyData) + mockGetGraphData.mockResolvedValue(emptyData) const wrapper = mount( <Provider store={store}> - <TotalAnalysisChart fluidTypes={[FluidType.ELECTRICITY]} /> + <TotalAnalysisChart fluidsWithData={[FluidType.ELECTRICITY]} /> </Provider> ) await waitForComponentToPaint(wrapper) diff --git a/src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.tsx b/src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1f0e646ee821fdbc5a0f0beaa2c289e7e7f38e0e --- /dev/null +++ b/src/components/Analysis/TotalAnalysisChart/TotalAnalysisChart.tsx @@ -0,0 +1,156 @@ +import EstimatedConsumptionModal from 'components/ConsumptionVisualizer/EstimatedConsumptionModal' +import { useClient } from 'cozy-client' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' +import Icon from 'cozy-ui/transpiled/react/Icon' +import { FluidType } from 'enum/fluid.enum' +import { TimeStep } from 'enum/timeStep.enum' +import { DataloadValueDetail, TimePeriod } from 'models' +import React, { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' +import ConsumptionDataManager from 'services/consumption.service' +import { AppStore } from 'store' +import { getNavPicto } from 'utils/picto' +import { formatNumberValues, getMonthNameWithPrep } from 'utils/utils' +import PieChart from './PieChart' +import './totalAnalysisChart.scss' + +const TotalAnalysisChart = ({ + fluidsWithData, +}: { + fluidsWithData: FluidType[] +}) => { + const { analysisMonth } = useSelector( + (state: AppStore) => state.ecolyo.analysis + ) + const [dataLoadValueDetailArray, setDataLoadValueDetailArray] = useState< + DataloadValueDetail[] | null + >(null) + const [totalLoadValue, setTotalLoadValue] = useState<number>(0) + const client = useClient() + const { t } = useI18n() + const [openEstimationModal, setOpenEstimationModal] = useState<boolean>(false) + const arcWidth = 30 + const radius = Math.min(375, innerWidth - 100) + const outerRadius = radius / 2 + const innerRadius = outerRadius - arcWidth + + useEffect(() => { + let subscribed = true + async function getTotalData() { + const timePeriod: TimePeriod = { + startDate: analysisMonth.minus({ month: 1 }).startOf('month'), + endDate: analysisMonth.minus({ month: 1 }).endOf('month'), + } + const consumptionService = new ConsumptionDataManager(client) + const monthTotalData = await consumptionService.getGraphData( + timePeriod, + TimeStep.MONTH, + fluidsWithData, + undefined, + undefined, + true + ) + if (monthTotalData?.actualData) { + setDataLoadValueDetailArray(monthTotalData.actualData[0].valueDetail) + setTotalLoadValue(monthTotalData.actualData[0].value) + } + } + if (subscribed) { + getTotalData() + } + return () => { + subscribed = false + } + }, [analysisMonth, client, fluidsWithData]) + + const emptyPieChart = () => ( + <PieChart + dataloadValueDetailArray={[]} + radius={radius} + innerRadius={innerRadius} + outerRadius={outerRadius} + > + <div className="text-36-bold"> + {formatNumberValues(0)} + <span className="euro-unit">{`${t('FLUID.MULTIFLUID.UNIT')}`}</span> + </div> + <div className={`text-20-bold no_data`}>{t('analysis.no_data')}</div> + </PieChart> + ) + + return ( + <> + <div + className="totalAnalysis-container" + style={{ + minHeight: radius + 100, + }} + > + <div className="text-24-normal title">{t('analysis_pie.total')}</div> + + {!dataLoadValueDetailArray ? ( + emptyPieChart() + ) : ( + <> + <PieChart + dataloadValueDetailArray={dataLoadValueDetailArray} + radius={radius} + innerRadius={innerRadius} + outerRadius={outerRadius} + > + <div className="text-36-bold"> + {formatNumberValues(totalLoadValue)} + <span className="euro-unit">{`${t( + 'FLUID.MULTIFLUID.UNIT' + )}`}</span> + </div> + <div className="text-16-normal date"> + {t('analysis_pie.month') + + getMonthNameWithPrep(analysisMonth.minus({ month: 1 }))} + </div> + <div + className="text-14-normal estimation-text" + onClick={() => setOpenEstimationModal(true)} + > + <span + className="estimated" + dangerouslySetInnerHTML={{ + __html: t('analysis_pie.estimation'), + }} + /> + </div> + </PieChart> + <EstimatedConsumptionModal + open={openEstimationModal} + handleCloseClick={() => setOpenEstimationModal(false)} + /> + </> + )} + + {dataLoadValueDetailArray && fluidsWithData.length > 1 && ( + <div className="total-card-container"> + {dataLoadValueDetailArray.map((dataload, index) => ( + <div key={index} className="total-card"> + <div className="text-18-bold fluidconso"> + {dataload.value !== -1 + ? `${formatNumberValues(dataload.value)} €` + : '--- €'} + </div> + <Icon + className="euro-fluid-icon" + icon={getNavPicto(index, true, true)} + size={38} + /> + <div className="text-16-normal"> + {t('FLUID.' + FluidType[index] + '.LABEL')} + </div> + </div> + ))} + </div> + )} + </div> + </> + ) +} + +export default TotalAnalysisChart diff --git a/src/components/Analysis/TotalAnalysisChart/__snapshots__/PieChart.spec.tsx.snap b/src/components/Analysis/TotalAnalysisChart/__snapshots__/PieChart.spec.tsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..03b1ba56bc4c61e0cb75e8ac3c6e4ce7d50fc623 --- /dev/null +++ b/src/components/Analysis/TotalAnalysisChart/__snapshots__/PieChart.spec.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PieChart component should be rendered correctly 1`] = ` +<PieChart + dataloadValueDetailArray={ + Array [ + Object { + "state": "VALID", + "value": 10, + }, + Object { + "state": "VALID", + "value": 20, + }, + Object { + "state": "VALID", + "value": 30, + }, + ] + } + innerRadius={300} + outerRadius={300} + radius={300} +> + <div + className="pie-container" + style={ + Object { + "height": 300, + "width": 300, + } + } + > + <svg + height={300} + width={300} + > + <defs> + <filter + height="300%" + id="glow" + width="300%" + x="-75%" + y="-75%" + > + <feGaussianBlur + result="coloredBlur" + stdDeviation="10" + /> + <feMerge> + <feMergeNode + in="coloredBlur" + /> + <feMergeNode + in="SourceGraphic" + /> + </feMerge> + </filter> + </defs> + <g + transform="translate(300 300)" + /> + </svg> + <div + className="pie-center" + style={ + Object { + "height": 300, + "left": 0, + "top": 0, + "width": 300, + } + } + > + child with value and text to render + <div + className="circle" + style={ + Object { + "height": 300, + "left": 0, + "top": 0, + "width": 300, + } + } + /> + </div> + </div> +</PieChart> +`; diff --git a/src/components/Analysis/__snapshots__/TotalAnalysisChart.spec.tsx.snap b/src/components/Analysis/TotalAnalysisChart/__snapshots__/TotalAnalysisChart.spec.tsx.snap similarity index 58% rename from src/components/Analysis/__snapshots__/TotalAnalysisChart.spec.tsx.snap rename to src/components/Analysis/TotalAnalysisChart/__snapshots__/TotalAnalysisChart.spec.tsx.snap index c5f2907e27ed682ca5092e5687f7db37e7b2d7b5..9daddac9757bf7b5981c9b335677645cebeb6137 100644 --- a/src/components/Analysis/__snapshots__/TotalAnalysisChart.spec.tsx.snap +++ b/src/components/Analysis/TotalAnalysisChart/__snapshots__/TotalAnalysisChart.spec.tsx.snap @@ -14,7 +14,7 @@ exports[`TotalAnalysisChart component should be rendered correctly 1`] = ` } > <TotalAnalysisChart - fluidTypes={ + fluidsWithData={ Array [ 0, ] @@ -33,6 +33,28 @@ exports[`TotalAnalysisChart component should be rendered correctly 1`] = ` > analysis_pie.total </div> + <mock-piechart + dataloadValueDetailArray={Array []} + innerRadius={157.5} + outerRadius={187.5} + radius={375} + > + <div + className="text-36-bold" + > + 0,00 + <span + className="euro-unit" + > + FLUID.MULTIFLUID.UNIT + </span> + </div> + <div + className="text-20-bold no_data" + > + analysis.no_data + </div> + </mock-piechart> </div> </TotalAnalysisChart> </Provider> diff --git a/src/components/Analysis/totalAnalysisChart.scss b/src/components/Analysis/TotalAnalysisChart/totalAnalysisChart.scss similarity index 91% rename from src/components/Analysis/totalAnalysisChart.scss rename to src/components/Analysis/TotalAnalysisChart/totalAnalysisChart.scss index 348f80301afc486e3072d34f954603d51a093bb5..26467906ea808e9aa0deff6b0b9643e66d67cae3 100644 --- a/src/components/Analysis/totalAnalysisChart.scss +++ b/src/components/Analysis/TotalAnalysisChart/totalAnalysisChart.scss @@ -1,6 +1,6 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; -@import '../../styles/base/z-index'; +@import 'src/styles/base/color'; +@import 'src/styles/base/breakpoint'; +@import 'src/styles/base/z-index'; .totalAnalysis-container { display: flex; @@ -12,6 +12,10 @@ color: $grey-bright; margin-bottom: 1.5rem; } + .no_data { + color: $grey-bright; + margin-top: 1rem; + } .pie-container { text-align: center; position: relative; diff --git a/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap b/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap index 58f132c9cc13dd48fdcb95e671b7b7eeffc0fada..17cc19794a6cfb406f4512984552e0d5b7444798 100644 --- a/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap +++ b/src/components/Analysis/__snapshots__/MonthlyAnalysis.spec.tsx.snap @@ -2,22 +2,174 @@ exports[`MonthlyAnalysis component should be rendered correctly 1`] = ` <BrowserRouter> - <Provider - store={ + <Router + location={ Object { - "clearActions": [Function], - "dispatch": [Function], - "getActions": [Function], - "getState": [Function], - "replaceReducer": [Function], - "subscribe": [Function], + "hash": "", + "key": "default", + "pathname": "/", + "search": "", + "state": null, + } + } + navigationType="POP" + navigator={ + Object { + "action": "POP", + "createHref": [Function], + "encodeLocation": [Function], + "go": [Function], + "listen": [Function], + "location": Object { + "hash": "", + "key": "default", + "pathname": "/", + "search": "", + "state": null, + }, + "push": [Function], + "replace": [Function], } } > - <MonthlyAnalysis - saveLastScrollPosition={[MockFunction]} - scrollPosition={0} - /> - </Provider> + <Provider + store={ + Object { + "clearActions": [Function], + "dispatch": [Function], + "getActions": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + } + } + > + <MonthlyAnalysis + saveLastScrollPosition={[MockFunction]} + scrollPosition={0} + > + <div + className="analysis-root" + > + <div + className="analysis-content" + > + <mock-comparison + fluidsWithData={ + Array [ + 0, + 1, + 2, + ] + } + monthPerformanceIndicators={ + Array [ + Object { + "compareValue": 10, + "percentageVariation": 50, + "value": 5, + }, + Object { + "compareValue": 10, + "percentageVariation": 50, + "value": 5, + }, + ] + } + /> + </div> + <div + className="analysis-content" + > + <div + className="card rich-card" + > + <mock-total-analysis + fluidsWithData={ + Array [ + 0, + 1, + 2, + ] + } + /> + </div> + </div> + <div + className="analysis-content" + > + <div + className="card rich-card" + > + <mock-max-consumption + fluidsWithData={ + Array [ + 0, + 1, + 2, + ] + } + /> + </div> + </div> + <div + className="analysis-content" + > + <div + className="card rich-card" + > + <mock-analysis + aggregatedPerformanceIndicator={ + Object { + "compareValue": 1.7718999999999998, + "percentageVariation": -0.5, + "value": 0.8859499999999999, + } + } + fluidsWithData={ + Array [ + 0, + 1, + 2, + ] + } + performanceIndicators={ + Array [ + Object { + "compareValue": 10, + "percentageVariation": 50, + "value": 5, + }, + Object { + "compareValue": 10, + "percentageVariation": 50, + "value": 5, + }, + ] + } + /> + </div> + </div> + <div + className="analysis-content" + > + <div + className="card" + > + <mock-half-hour-analysis + perfIndicator={ + Object { + "compareValue": 10, + "percentageVariation": 50, + "value": 5, + } + } + /> + </div> + </div> + </div> + </MonthlyAnalysis> + </Provider> + </Router> </BrowserRouter> `; diff --git a/src/components/Analysis/__snapshots__/PieChart.spec.tsx.snap b/src/components/Analysis/__snapshots__/PieChart.spec.tsx.snap deleted file mode 100644 index 068b2c5beea6874e586213b5186fc2958ef45d50..0000000000000000000000000000000000000000 --- a/src/components/Analysis/__snapshots__/PieChart.spec.tsx.snap +++ /dev/null @@ -1,507 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PieChart component should be rendered correctly 1`] = ` -<Provider - store={ - Object { - "clearActions": [Function], - "dispatch": [Function], - "getActions": [Function], - "getState": [Function], - "replaceReducer": [Function], - "subscribe": [Function], - } - } -> - <PieChart - currentAnalysisDate={"2021-07-01T00:00:00.000Z"} - dataloadValueDetailArray={ - Array [ - Object { - "state": "VALID", - "value": 10, - }, - Object { - "state": "VALID", - "value": 20, - }, - Object { - "state": "VALID", - "value": 30, - }, - ] - } - height={300} - innerRadius={300} - outerRadius={300} - totalValue={60} - width={300} - > - <div - className="pie-container" - style={ - Object { - "height": 300, - "width": 300, - } - } - > - <svg - height={300} - width={300} - > - <defs> - <filter - height="300%" - id="glow" - width="300%" - x="-75%" - y="-75%" - > - <feGaussianBlur - result="coloredBlur" - stdDeviation="10" - /> - <feMerge> - <feMergeNode - in="coloredBlur" - /> - <feMergeNode - in="SourceGraphic" - /> - </feMerge> - </filter> - </defs> - <g - transform="translate(300 300)" - /> - </svg> - <div - className="pie-center" - style={ - Object { - "height": 300, - "left": 0, - "top": 0, - "width": 300, - } - } - > - <div - className="text-36-bold" - > - 60,00 - <span - className="euro-unit" - > - FLUID.MULTIFLUID.UNIT - </span> - </div> - <div - className="text-16-normal date" - > - analysis_pie.monthde juin - </div> - <div - className="text-14-normal estimation-text" - onClick={[Function]} - > - <span - className="estimated" - > - analysis_pie.estimation - </span> - <span - className="estimated" - > - analysis_pie.estimation2 - </span> - </div> - <div - className="circle" - style={ - Object { - "height": 300, - "left": 0, - "top": 0, - "width": 300, - } - } - /> - </div> - <EstimatedConsumptionModal - handleCloseClick={[Function]} - open={false} - > - <WithStyles(ForwardRef(Dialog)) - aria-labelledby="accessibility-title" - classes={ - Object { - "paper": "modal-paper", - "root": "modal-root", - } - } - onClose={[Function]} - open={false} - > - <ForwardRef(Dialog) - aria-labelledby="accessibility-title" - classes={ - Object { - "container": "MuiDialog-container", - "paper": "MuiDialog-paper modal-paper", - "paperFullScreen": "MuiDialog-paperFullScreen", - "paperFullWidth": "MuiDialog-paperFullWidth", - "paperScrollBody": "MuiDialog-paperScrollBody", - "paperScrollPaper": "MuiDialog-paperScrollPaper", - "paperWidthFalse": "MuiDialog-paperWidthFalse", - "paperWidthLg": "MuiDialog-paperWidthLg", - "paperWidthMd": "MuiDialog-paperWidthMd", - "paperWidthSm": "MuiDialog-paperWidthSm", - "paperWidthXl": "MuiDialog-paperWidthXl", - "paperWidthXs": "MuiDialog-paperWidthXs", - "root": "MuiDialog-root modal-root", - "scrollBody": "MuiDialog-scrollBody", - "scrollPaper": "MuiDialog-scrollPaper", - } - } - onClose={[Function]} - open={false} - > - <ForwardRef(Modal) - BackdropComponent={ - Object { - "$$typeof": Symbol(react.forward_ref), - "Naked": Object { - "$$typeof": Symbol(react.forward_ref), - "propTypes": Object { - "children": [Function], - "className": [Function], - "classes": [Function], - "invisible": [Function], - "open": [Function], - "transitionDuration": [Function], - }, - "render": [Function], - }, - "displayName": "WithStyles(ForwardRef(Backdrop))", - "options": Object { - "defaultTheme": Object { - "breakpoints": Object { - "between": [Function], - "down": [Function], - "keys": Array [ - "xs", - "sm", - "md", - "lg", - "xl", - ], - "only": [Function], - "up": [Function], - "values": Object { - "lg": 1280, - "md": 960, - "sm": 600, - "xl": 1920, - "xs": 0, - }, - "width": [Function], - }, - "direction": "ltr", - "mixins": Object { - "gutters": [Function], - "toolbar": Object { - "@media (min-width:0px) and (orientation: landscape)": Object { - "minHeight": 48, - }, - "@media (min-width:600px)": Object { - "minHeight": 64, - }, - "minHeight": 56, - }, - }, - "overrides": Object {}, - "palette": Object { - "action": Object { - "activatedOpacity": 0.12, - "active": "rgba(0, 0, 0, 0.54)", - "disabled": "rgba(0, 0, 0, 0.26)", - "disabledBackground": "rgba(0, 0, 0, 0.12)", - "disabledOpacity": 0.38, - "focus": "rgba(0, 0, 0, 0.12)", - "focusOpacity": 0.12, - "hover": "rgba(0, 0, 0, 0.04)", - "hoverOpacity": 0.04, - "selected": "rgba(0, 0, 0, 0.08)", - "selectedOpacity": 0.08, - }, - "augmentColor": [Function], - "background": Object { - "default": "#fafafa", - "paper": "#fff", - }, - "common": Object { - "black": "#000", - "white": "#fff", - }, - "contrastThreshold": 3, - "divider": "rgba(0, 0, 0, 0.12)", - "error": Object { - "contrastText": "#fff", - "dark": "#d32f2f", - "light": "#e57373", - "main": "#f44336", - }, - "getContrastText": [Function], - "grey": Object { - "100": "#f5f5f5", - "200": "#eeeeee", - "300": "#e0e0e0", - "400": "#bdbdbd", - "50": "#fafafa", - "500": "#9e9e9e", - "600": "#757575", - "700": "#616161", - "800": "#424242", - "900": "#212121", - "A100": "#d5d5d5", - "A200": "#aaaaaa", - "A400": "#303030", - "A700": "#616161", - }, - "info": Object { - "contrastText": "#fff", - "dark": "#1976d2", - "light": "#64b5f6", - "main": "#2196f3", - }, - "primary": Object { - "contrastText": "#fff", - "dark": "#303f9f", - "light": "#7986cb", - "main": "#3f51b5", - }, - "secondary": Object { - "contrastText": "#fff", - "dark": "#c51162", - "light": "#ff4081", - "main": "#f50057", - }, - "success": Object { - "contrastText": "rgba(0, 0, 0, 0.87)", - "dark": "#388e3c", - "light": "#81c784", - "main": "#4caf50", - }, - "text": Object { - "disabled": "rgba(0, 0, 0, 0.38)", - "hint": "rgba(0, 0, 0, 0.38)", - "primary": "rgba(0, 0, 0, 0.87)", - "secondary": "rgba(0, 0, 0, 0.54)", - }, - "tonalOffset": 0.2, - "type": "light", - "warning": Object { - "contrastText": "rgba(0, 0, 0, 0.87)", - "dark": "#f57c00", - "light": "#ffb74d", - "main": "#ff9800", - }, - }, - "props": Object {}, - "shadows": Array [ - "none", - "0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)", - "0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)", - "0px 3px 3px -2px rgba(0,0,0,0.2),0px 3px 4px 0px rgba(0,0,0,0.14),0px 1px 8px 0px rgba(0,0,0,0.12)", - "0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)", - "0px 3px 5px -1px rgba(0,0,0,0.2),0px 5px 8px 0px rgba(0,0,0,0.14),0px 1px 14px 0px rgba(0,0,0,0.12)", - "0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)", - "0px 4px 5px -2px rgba(0,0,0,0.2),0px 7px 10px 1px rgba(0,0,0,0.14),0px 2px 16px 1px rgba(0,0,0,0.12)", - "0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)", - "0px 5px 6px -3px rgba(0,0,0,0.2),0px 9px 12px 1px rgba(0,0,0,0.14),0px 3px 16px 2px rgba(0,0,0,0.12)", - "0px 6px 6px -3px rgba(0,0,0,0.2),0px 10px 14px 1px rgba(0,0,0,0.14),0px 4px 18px 3px rgba(0,0,0,0.12)", - "0px 6px 7px -4px rgba(0,0,0,0.2),0px 11px 15px 1px rgba(0,0,0,0.14),0px 4px 20px 3px rgba(0,0,0,0.12)", - "0px 7px 8px -4px rgba(0,0,0,0.2),0px 12px 17px 2px rgba(0,0,0,0.14),0px 5px 22px 4px rgba(0,0,0,0.12)", - "0px 7px 8px -4px rgba(0,0,0,0.2),0px 13px 19px 2px rgba(0,0,0,0.14),0px 5px 24px 4px rgba(0,0,0,0.12)", - "0px 7px 9px -4px rgba(0,0,0,0.2),0px 14px 21px 2px rgba(0,0,0,0.14),0px 5px 26px 4px rgba(0,0,0,0.12)", - "0px 8px 9px -5px rgba(0,0,0,0.2),0px 15px 22px 2px rgba(0,0,0,0.14),0px 6px 28px 5px rgba(0,0,0,0.12)", - "0px 8px 10px -5px rgba(0,0,0,0.2),0px 16px 24px 2px rgba(0,0,0,0.14),0px 6px 30px 5px rgba(0,0,0,0.12)", - "0px 8px 11px -5px rgba(0,0,0,0.2),0px 17px 26px 2px rgba(0,0,0,0.14),0px 6px 32px 5px rgba(0,0,0,0.12)", - "0px 9px 11px -5px rgba(0,0,0,0.2),0px 18px 28px 2px rgba(0,0,0,0.14),0px 7px 34px 6px rgba(0,0,0,0.12)", - "0px 9px 12px -6px rgba(0,0,0,0.2),0px 19px 29px 2px rgba(0,0,0,0.14),0px 7px 36px 6px rgba(0,0,0,0.12)", - "0px 10px 13px -6px rgba(0,0,0,0.2),0px 20px 31px 3px rgba(0,0,0,0.14),0px 8px 38px 7px rgba(0,0,0,0.12)", - "0px 10px 13px -6px rgba(0,0,0,0.2),0px 21px 33px 3px rgba(0,0,0,0.14),0px 8px 40px 7px rgba(0,0,0,0.12)", - "0px 10px 14px -6px rgba(0,0,0,0.2),0px 22px 35px 3px rgba(0,0,0,0.14),0px 8px 42px 7px rgba(0,0,0,0.12)", - "0px 11px 14px -7px rgba(0,0,0,0.2),0px 23px 36px 3px rgba(0,0,0,0.14),0px 9px 44px 8px rgba(0,0,0,0.12)", - "0px 11px 15px -7px rgba(0,0,0,0.2),0px 24px 38px 3px rgba(0,0,0,0.14),0px 9px 46px 8px rgba(0,0,0,0.12)", - ], - "shape": Object { - "borderRadius": 4, - }, - "spacing": [Function], - "transitions": Object { - "create": [Function], - "duration": Object { - "complex": 375, - "enteringScreen": 225, - "leavingScreen": 195, - "short": 250, - "shorter": 200, - "shortest": 150, - "standard": 300, - }, - "easing": Object { - "easeIn": "cubic-bezier(0.4, 0, 1, 1)", - "easeInOut": "cubic-bezier(0.4, 0, 0.2, 1)", - "easeOut": "cubic-bezier(0.0, 0, 0.2, 1)", - "sharp": "cubic-bezier(0.4, 0, 0.6, 1)", - }, - "getAutoHeightDuration": [Function], - }, - "typography": Object { - "body1": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "1rem", - "fontWeight": 400, - "letterSpacing": "0.00938em", - "lineHeight": 1.5, - }, - "body2": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "0.875rem", - "fontWeight": 400, - "letterSpacing": "0.01071em", - "lineHeight": 1.43, - }, - "button": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "0.875rem", - "fontWeight": 500, - "letterSpacing": "0.02857em", - "lineHeight": 1.75, - "textTransform": "uppercase", - }, - "caption": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "0.75rem", - "fontWeight": 400, - "letterSpacing": "0.03333em", - "lineHeight": 1.66, - }, - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": 14, - "fontWeightBold": 700, - "fontWeightLight": 300, - "fontWeightMedium": 500, - "fontWeightRegular": 400, - "h1": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "6rem", - "fontWeight": 300, - "letterSpacing": "-0.01562em", - "lineHeight": 1.167, - }, - "h2": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "3.75rem", - "fontWeight": 300, - "letterSpacing": "-0.00833em", - "lineHeight": 1.2, - }, - "h3": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "3rem", - "fontWeight": 400, - "letterSpacing": "0em", - "lineHeight": 1.167, - }, - "h4": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "2.125rem", - "fontWeight": 400, - "letterSpacing": "0.00735em", - "lineHeight": 1.235, - }, - "h5": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "1.5rem", - "fontWeight": 400, - "letterSpacing": "0em", - "lineHeight": 1.334, - }, - "h6": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "1.25rem", - "fontWeight": 500, - "letterSpacing": "0.0075em", - "lineHeight": 1.6, - }, - "htmlFontSize": 16, - "overline": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "0.75rem", - "fontWeight": 400, - "letterSpacing": "0.08333em", - "lineHeight": 2.66, - "textTransform": "uppercase", - }, - "pxToRem": [Function], - "round": [Function], - "subtitle1": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "1rem", - "fontWeight": 400, - "letterSpacing": "0.00938em", - "lineHeight": 1.75, - }, - "subtitle2": Object { - "fontFamily": "\\"Roboto\\", \\"Helvetica\\", \\"Arial\\", sans-serif", - "fontSize": "0.875rem", - "fontWeight": 500, - "letterSpacing": "0.00714em", - "lineHeight": 1.57, - }, - }, - "zIndex": Object { - "appBar": 1100, - "drawer": 1200, - "mobileStepper": 1000, - "modal": 1300, - "snackbar": 1400, - "speedDial": 1050, - "tooltip": 1500, - }, - }, - "name": "MuiBackdrop", - }, - "propTypes": Object { - "classes": [Function], - "innerRef": [Function], - }, - "render": [Function], - "useStyles": [Function], - } - } - BackdropProps={ - Object { - "transitionDuration": Object { - "enter": 225, - "exit": 195, - }, - } - } - className="MuiDialog-root modal-root" - closeAfterTransition={true} - disableEscapeKeyDown={false} - onClose={[Function]} - open={false} - /> - </ForwardRef(Dialog)> - </WithStyles(ForwardRef(Dialog))> - </EstimatedConsumptionModal> - </div> - </PieChart> -</Provider> -`; diff --git a/src/components/Analysis/analysisError.scss b/src/components/Analysis/analysisError.scss deleted file mode 100644 index 20fe6495e9c43389f0307b1071a18210eba3eabd..0000000000000000000000000000000000000000 --- a/src/components/Analysis/analysisError.scss +++ /dev/null @@ -1,56 +0,0 @@ -@import '../../styles/base/color'; -@import '../../styles/base/breakpoint'; - -.analysis-root.black { - .modal-overlay { - .modal-close-button { - display: none; - } - } -} - -.analysis-error-container { - border-radius: 4px; - margin-bottom: 1rem; - color: $grey-bright; - text-align: center; - .analysis-error-title { - color: $gold-shadow; - margin-bottom: 2rem; - } - .analysis-error-button { - display: flex; - justify-content: space-between; - gap: 1rem; - margin-top: 2rem; - button { - margin: 0; - &.btn-highlight, - &.btn-secondary-positive { - width: 45%; - margin-bottom: 0; - } - &.btn-secondary-positive { - padding: 0.5rem 1rem; - } - &.btn-highlight { - padding: 0.25rem 0.5rem; - } - } - @media #{$large-phone} { - flex-direction: column-reverse; - button { - &.btn-highlight, - &.btn-secondary-positive { - margin-bottom: 0; - width: 100%; - height: 45px; - } - } - } - } -} - -#accessibility-title { - display: none; -} diff --git a/src/components/Analysis/monthlyanalysis.scss b/src/components/Analysis/monthlyanalysis.scss index 7f775f6408d871a7f952fc55c9cc53b8ce7e1b78..fe7df2a081cb3a2f80bc7be7951b2024d3f310e6 100644 --- a/src/components/Analysis/monthlyanalysis.scss +++ b/src/components/Analysis/monthlyanalysis.scss @@ -5,15 +5,9 @@ display: flex; flex-direction: column; align-items: center; - justify-content: space-around; padding: 1rem 1rem 1.5rem; gap: 1rem; - &.black { - background: var(--darkLight2); - } - @media #{$large-phone} { - margin-bottom: 0; - } + .analysis-content { width: 45.75rem; diff --git a/src/components/DateNavigator/DateNavigator.spec.tsx b/src/components/DateNavigator/DateNavigator.spec.tsx index 60020958c4661d0fc0bbf2df6835ac022838dc78..741a4f4a40ee2611ea6025a842027eb693d07b0c 100644 --- a/src/components/DateNavigator/DateNavigator.spec.tsx +++ b/src/components/DateNavigator/DateNavigator.spec.tsx @@ -1,13 +1,9 @@ import { IconButton } from '@material-ui/core' +import { TimeStep } from 'enum/timeStep.enum' import { mount } from 'enzyme' import toJson from 'enzyme-to-json' import { DateTime } from 'luxon' import React from 'react' -import * as reactRedux from 'react-redux' -import { Provider } from 'react-redux' -import configureStore from 'redux-mock-store' -import { globalStateData } from '../../../tests/__mocks__/globalStateData.mock' -import { mockInitialChartState } from '../../../tests/__mocks__/store' import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' import DateNavigator from './DateNavigator' @@ -20,99 +16,101 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) -const mockStore = configureStore([]) jest.mock( 'components/DateNavigator/DateNavigatorFormat', () => 'mock-date-navigator-format' ) -const mockUseDispatch = jest.spyOn(reactRedux, 'useDispatch') - +const mockedDate = DateTime.local(2021, 7, 1) + .setZone('utc', { + keepLocalTime: true, + }) + .startOf('day') describe('DateNavigator component', () => { - const mockedDate = DateTime.local(2021, 7, 1) - .setZone('utc', { - keepLocalTime: true, - }) - .startOf('day') + const mockHandleNextDate = jest.fn() + const mockHandlePrevDate = jest.fn() + beforeEach(() => { + jest.clearAllMocks() + }) it('should be rendered correctly', async () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - chart: mockInitialChartState, - }, - }) const wrapper = mount( - <Provider store={store}> - <DateNavigator currentAnalysisDate={mockedDate} /> - </Provider> + <DateNavigator + disableNext={false} + disablePrev={false} + handleNextDate={mockHandleNextDate} + handlePrevDate={mockHandlePrevDate} + navigatorDate={mockedDate} + timeStep={TimeStep.MONTH} + /> ) await waitForComponentToPaint(wrapper) expect(toJson(wrapper)).toMatchSnapshot() }) - it('should click on left arrow and change date', async () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - chart: mockInitialChartState, - }, - }) - const wrapper = mount( - <Provider store={store}> - <DateNavigator currentAnalysisDate={mockedDate} /> - </Provider> - ) - await waitForComponentToPaint(wrapper) - wrapper.find(IconButton).first().simulate('click') - expect(mockUseDispatch).toHaveBeenCalledTimes(2) - }) - it('should click on right arrow and change date', async () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - chart: mockInitialChartState, - }, - }) - const wrapper = mount( - <Provider store={store}> - <DateNavigator currentAnalysisDate={mockedDate} /> - </Provider> - ) - await waitForComponentToPaint(wrapper) - wrapper.find(IconButton).at(1).simulate('click') - expect(mockUseDispatch).toHaveBeenCalledTimes(3) - }) - it('should be rendered without analysis date and change to previous index', async () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - chart: mockInitialChartState, - }, + describe('should test navigation functions', () => { + describe('should test previous arrow', () => { + it('should call handlePrevDate', async () => { + const wrapper = mount( + <DateNavigator + disableNext={false} + disablePrev={false} + handleNextDate={mockHandleNextDate} + handlePrevDate={mockHandlePrevDate} + navigatorDate={mockedDate} + timeStep={TimeStep.MONTH} + /> + ) + wrapper.find(IconButton).first().simulate('click') + expect(mockHandlePrevDate).toHaveBeenCalledTimes(1) + }) + + it('should NOT call handlePrevDate if disablePrev is true', async () => { + const wrapper = mount( + <DateNavigator + disableNext={false} + disablePrev={true} + handleNextDate={mockHandleNextDate} + handlePrevDate={mockHandlePrevDate} + navigatorDate={mockedDate} + timeStep={TimeStep.MONTH} + /> + ) + wrapper.find(IconButton).first().simulate('click') + expect(mockHandlePrevDate).toHaveBeenCalledTimes(0) + }) }) - const wrapper = mount( - <Provider store={store}> - <DateNavigator /> - </Provider> - ) - await waitForComponentToPaint(wrapper) - wrapper.find(IconButton).at(0).simulate('click') - expect(mockUseDispatch).toHaveBeenCalledTimes(4) - }) - it('should be rendered without analysis date and change to next index', async () => { - const store = mockStore({ - ecolyo: { - global: globalStateData, - chart: mockInitialChartState, - }, + + describe('should test next arrow', () => { + it('should call mockHandleNextDate', async () => { + const wrapper = mount( + <DateNavigator + disableNext={false} + disablePrev={false} + handleNextDate={mockHandleNextDate} + handlePrevDate={mockHandlePrevDate} + navigatorDate={mockedDate} + timeStep={TimeStep.MONTH} + /> + ) + wrapper.find(IconButton).at(1).simulate('click') + expect(mockHandleNextDate).toHaveBeenCalledTimes(1) + }) + + it('should NOT call mockHandleNextDate if disableNext is true', async () => { + const wrapper = mount( + <DateNavigator + disableNext={true} + disablePrev={false} + handleNextDate={mockHandleNextDate} + handlePrevDate={mockHandlePrevDate} + navigatorDate={mockedDate} + timeStep={TimeStep.MONTH} + /> + ) + wrapper.find(IconButton).at(1).simulate('click') + expect(mockHandleNextDate).toHaveBeenCalledTimes(0) + }) }) - const wrapper = mount( - <Provider store={store}> - <DateNavigator /> - </Provider> - ) - await waitForComponentToPaint(wrapper) - wrapper.find(IconButton).at(1).simulate('click') - expect(mockUseDispatch).toHaveBeenCalledTimes(5) }) }) diff --git a/src/components/DateNavigator/DateNavigator.tsx b/src/components/DateNavigator/DateNavigator.tsx index 97a2a39ec089523dd957dae2b4eed0c79503aff8..0d207cbe0e2640f52448b84495370eeaf7a0b15c 100644 --- a/src/components/DateNavigator/DateNavigator.tsx +++ b/src/components/DateNavigator/DateNavigator.tsx @@ -5,135 +5,59 @@ import classNames from 'classnames' import DateNavigatorFormat from 'components/DateNavigator/DateNavigatorFormat' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import Icon from 'cozy-ui/transpiled/react/Icon' -import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' import { DateTime } from 'luxon' -import React, { Dispatch } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import DateChartService from 'services/dateChart.service' -import { AppActionsTypes, AppStore } from 'store' -import { setAnalysisMonth } from 'store/analysis/analysis.slice' -import { setCurrentIndex, setSelectedDate } from 'store/chart/chart.slice' -import { isLastDateReached } from 'utils/date' -import { isKonnectorActive } from 'utils/utils' +import React from 'react' import './datenavigator.scss' interface DateNavigatorProps { - /** If a value is given, Navigator is for Analysis view */ - currentAnalysisDate?: DateTime + disableNext: boolean + disablePrev: boolean + handleNextDate: () => void + handlePrevDate: () => void inlineDateDisplay?: boolean + navigatorDate: DateTime + timeStep: TimeStep } const DateNavigator = ({ - currentAnalysisDate, + disableNext, + disablePrev, + handleNextDate, + handlePrevDate, inlineDateDisplay = false, + navigatorDate, + timeStep, }: DateNavigatorProps) => { const { t } = useI18n() - const dispatch = useDispatch<Dispatch<AppActionsTypes>>() - const { - chart: { currentTimeStep, selectedDate, currentIndex }, - global: { fluidStatus }, - } = useSelector((state: AppStore) => state.ecolyo) - - const disablePrev = - selectedDate < - DateTime.local(0, 1, 1).setZone('utc', { - keepLocalTime: true, - }) - const disableNext: boolean = currentAnalysisDate - ? isLastDateReached(currentAnalysisDate, TimeStep.MONTH) - : isLastDateReached(selectedDate, currentTimeStep) - - const dateChartService = new DateChartService() - - /** Handle date navigation from ConsumptionView and Analysis view, not ideal though */ - const handleClickMove = (increment: number) => { - if (!currentAnalysisDate) { - const updatedDate: DateTime = dateChartService.incrementDate( - currentTimeStep, - selectedDate, - increment - ) - const updatedIndex: number = dateChartService.defineDateIndex( - currentTimeStep, - updatedDate - ) - dispatch(setSelectedDate(updatedDate)) - dispatch(setCurrentIndex(updatedIndex)) - } else { - const updatedDate: DateTime = dateChartService.incrementDate( - TimeStep.MONTH, - currentAnalysisDate, - increment - ) - dispatch(setAnalysisMonth(updatedDate)) - } - } - - const handleChangePrevIndex = () => { - if (!disablePrev && isKonnectorActive(fluidStatus, FluidType.MULTIFLUID)) { - const increment: number = - dateChartService.defineIncrementForPreviousIndex( - currentTimeStep, - selectedDate, - currentIndex - ) - if (currentAnalysisDate) { - handleClickMove(-1) - } else handleClickMove(increment) - } - } - const handleChangeNextIndex = () => { - if (!disableNext && isKonnectorActive(fluidStatus, FluidType.MULTIFLUID)) { - const increment: number = dateChartService.defineIncrementForNextIndex( - currentTimeStep, - selectedDate, - currentIndex - ) - if (currentAnalysisDate) { - handleClickMove(1) - } else handleClickMove(increment) - } - } return ( <div className="date-navigator"> - <div> - <IconButton - aria-label={t('consumption.accessibility.button_previous_value')} - className={classNames('date-navigator-button', { - ['disable']: - disablePrev || - !isKonnectorActive(fluidStatus, FluidType.MULTIFLUID), - })} - onClick={() => handleChangePrevIndex()} - > - <Icon icon={LeftArrowIcon} size={16} /> - </IconButton> - </div> + <IconButton + disabled={disablePrev} + onClick={handlePrevDate} + className={classNames('date-navigator-button', { + ['disable']: disablePrev, + })} + aria-label={t('consumption.accessibility.button_previous_value')} + > + <Icon icon={LeftArrowIcon} size={16} /> + </IconButton> <DateNavigatorFormat - timeStep={currentAnalysisDate ? TimeStep.MONTH : currentTimeStep} - date={ - currentAnalysisDate - ? currentAnalysisDate.minus({ month: 1 }) - : selectedDate - } + timeStep={timeStep} + date={navigatorDate} inline={inlineDateDisplay} /> - - <div> - <IconButton - aria-label={t('consumption.accessibility.button_next_value')} - className={classNames('date-navigator-button', { - ['disable']: - disableNext || - !isKonnectorActive(fluidStatus, FluidType.MULTIFLUID), - })} - onClick={() => handleChangeNextIndex()} - > - <Icon icon={RightArrowIcon} size={16} /> - </IconButton> - </div> + <IconButton + disabled={disableNext} + onClick={handleNextDate} + className={classNames('date-navigator-button', { + ['disable']: disableNext, + })} + aria-label={t('consumption.accessibility.button_next_value')} + > + <Icon icon={RightArrowIcon} size={16} /> + </IconButton> </div> ) } diff --git a/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap b/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap index 387873be42031c45326461c64db8e7ccba2da098..8daf2089343eeaee62c17f6c24f86038176a0d71 100644 --- a/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap +++ b/src/components/DateNavigator/__snapshots__/DateNavigator.spec.tsx.snap @@ -1,284 +1,276 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DateNavigator component should be rendered correctly 1`] = ` -<Provider - store={ - Object { - "clearActions": [Function], - "dispatch": [Function], - "getActions": [Function], - "getState": [Function], - "replaceReducer": [Function], - "subscribe": [Function], - } - } +<DateNavigator + disableNext={false} + disablePrev={false} + handleNextDate={[MockFunction]} + handlePrevDate={[MockFunction]} + navigatorDate={"2021-07-01T00:00:00.000Z"} + timeStep={40} > - <DateNavigator - currentAnalysisDate={"2021-07-01T00:00:00.000Z"} + <div + className="date-navigator" > - <div - className="date-navigator" + <WithStyles(ForwardRef(IconButton)) + aria-label="consumption.accessibility.button_previous_value" + className="date-navigator-button" + disabled={false} + onClick={[MockFunction]} > - <div> - <WithStyles(ForwardRef(IconButton)) + <ForwardRef(IconButton) + aria-label="consumption.accessibility.button_previous_value" + className="date-navigator-button" + classes={ + Object { + "colorInherit": "MuiIconButton-colorInherit", + "colorPrimary": "MuiIconButton-colorPrimary", + "colorSecondary": "MuiIconButton-colorSecondary", + "disabled": "Mui-disabled", + "edgeEnd": "MuiIconButton-edgeEnd", + "edgeStart": "MuiIconButton-edgeStart", + "label": "MuiIconButton-label", + "root": "MuiIconButton-root", + "sizeSmall": "MuiIconButton-sizeSmall", + } + } + disabled={false} + onClick={[MockFunction]} + > + <WithStyles(ForwardRef(ButtonBase)) aria-label="consumption.accessibility.button_previous_value" - className="date-navigator-button disable" - onClick={[Function]} + centerRipple={true} + className="MuiIconButton-root date-navigator-button" + disabled={false} + focusRipple={true} + onClick={[MockFunction]} > - <ForwardRef(IconButton) + <ForwardRef(ButtonBase) aria-label="consumption.accessibility.button_previous_value" - className="date-navigator-button disable" + centerRipple={true} + className="MuiIconButton-root date-navigator-button" classes={ Object { - "colorInherit": "MuiIconButton-colorInherit", - "colorPrimary": "MuiIconButton-colorPrimary", - "colorSecondary": "MuiIconButton-colorSecondary", "disabled": "Mui-disabled", - "edgeEnd": "MuiIconButton-edgeEnd", - "edgeStart": "MuiIconButton-edgeStart", - "label": "MuiIconButton-label", - "root": "MuiIconButton-root", - "sizeSmall": "MuiIconButton-sizeSmall", + "focusVisible": "Mui-focusVisible", + "root": "MuiButtonBase-root", } } - onClick={[Function]} + disabled={false} + focusRipple={true} + onClick={[MockFunction]} > - <WithStyles(ForwardRef(ButtonBase)) + <button aria-label="consumption.accessibility.button_previous_value" - centerRipple={true} - className="MuiIconButton-root date-navigator-button disable" + className="MuiButtonBase-root MuiIconButton-root date-navigator-button" disabled={false} - focusRipple={true} - onClick={[Function]} + onBlur={[Function]} + onClick={[MockFunction]} + onDragLeave={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyUp={[Function]} + onMouseDown={[Function]} + onMouseLeave={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + tabIndex={0} + type="button" > - <ForwardRef(ButtonBase) - aria-label="consumption.accessibility.button_previous_value" - centerRipple={true} - className="MuiIconButton-root date-navigator-button disable" - classes={ - Object { - "disabled": "Mui-disabled", - "focusVisible": "Mui-focusVisible", - "root": "MuiButtonBase-root", - } - } - disabled={false} - focusRipple={true} - onClick={[Function]} + <span + className="MuiIconButton-label" > - <button - aria-label="consumption.accessibility.button_previous_value" - className="MuiButtonBase-root MuiIconButton-root date-navigator-button disable" - disabled={false} - onBlur={[Function]} - onClick={[Function]} - onDragLeave={[Function]} - onFocus={[Function]} - onKeyDown={[Function]} - onKeyUp={[Function]} - onMouseDown={[Function]} - onMouseLeave={[Function]} - onMouseUp={[Function]} - onTouchEnd={[Function]} - onTouchMove={[Function]} - onTouchStart={[Function]} - tabIndex={0} - type="button" + <Icon + icon="test-file-stub" + size={16} + spin={false} > - <span - className="MuiIconButton-label" + <Component + className="styles__icon___23x3R" + height={16} + style={Object {}} + width={16} > - <Icon - icon="test-file-stub" - size={16} - spin={false} + <svg + className="styles__icon___23x3R" + height={16} + style={Object {}} + width={16} > - <Component - className="styles__icon___23x3R" - height={16} - style={Object {}} - width={16} - > - <svg - className="styles__icon___23x3R" - height={16} - style={Object {}} - width={16} - > - <use - xlinkHref="#test-file-stub" - /> - </svg> - </Component> - </Icon> - </span> - <WithStyles(memo) - center={true} + <use + xlinkHref="#test-file-stub" + /> + </svg> + </Component> + </Icon> + </span> + <WithStyles(memo) + center={true} + > + <ForwardRef(TouchRipple) + center={true} + classes={ + Object { + "child": "MuiTouchRipple-child", + "childLeaving": "MuiTouchRipple-childLeaving", + "childPulsate": "MuiTouchRipple-childPulsate", + "ripple": "MuiTouchRipple-ripple", + "ripplePulsate": "MuiTouchRipple-ripplePulsate", + "rippleVisible": "MuiTouchRipple-rippleVisible", + "root": "MuiTouchRipple-root", + } + } + > + <span + className="MuiTouchRipple-root" > - <ForwardRef(TouchRipple) - center={true} - classes={ - Object { - "child": "MuiTouchRipple-child", - "childLeaving": "MuiTouchRipple-childLeaving", - "childPulsate": "MuiTouchRipple-childPulsate", - "ripple": "MuiTouchRipple-ripple", - "ripplePulsate": "MuiTouchRipple-ripplePulsate", - "rippleVisible": "MuiTouchRipple-rippleVisible", - "root": "MuiTouchRipple-root", - } - } - > - <span - className="MuiTouchRipple-root" - > - <TransitionGroup - childFactory={[Function]} - component={null} - exit={true} - /> - </span> - </ForwardRef(TouchRipple)> - </WithStyles(memo)> - </button> - </ForwardRef(ButtonBase)> - </WithStyles(ForwardRef(ButtonBase))> - </ForwardRef(IconButton)> - </WithStyles(ForwardRef(IconButton))> - </div> - <mock-date-navigator-format - date={"2021-06-01T00:00:00.000Z"} - inline={false} - timeStep={40} - /> - <div> - <WithStyles(ForwardRef(IconButton)) + <TransitionGroup + childFactory={[Function]} + component={null} + exit={true} + /> + </span> + </ForwardRef(TouchRipple)> + </WithStyles(memo)> + </button> + </ForwardRef(ButtonBase)> + </WithStyles(ForwardRef(ButtonBase))> + </ForwardRef(IconButton)> + </WithStyles(ForwardRef(IconButton))> + <mock-date-navigator-format + date={"2021-07-01T00:00:00.000Z"} + inline={false} + timeStep={40} + /> + <WithStyles(ForwardRef(IconButton)) + aria-label="consumption.accessibility.button_next_value" + className="date-navigator-button" + disabled={false} + onClick={[MockFunction]} + > + <ForwardRef(IconButton) + aria-label="consumption.accessibility.button_next_value" + className="date-navigator-button" + classes={ + Object { + "colorInherit": "MuiIconButton-colorInherit", + "colorPrimary": "MuiIconButton-colorPrimary", + "colorSecondary": "MuiIconButton-colorSecondary", + "disabled": "Mui-disabled", + "edgeEnd": "MuiIconButton-edgeEnd", + "edgeStart": "MuiIconButton-edgeStart", + "label": "MuiIconButton-label", + "root": "MuiIconButton-root", + "sizeSmall": "MuiIconButton-sizeSmall", + } + } + disabled={false} + onClick={[MockFunction]} + > + <WithStyles(ForwardRef(ButtonBase)) aria-label="consumption.accessibility.button_next_value" - className="date-navigator-button disable" - onClick={[Function]} + centerRipple={true} + className="MuiIconButton-root date-navigator-button" + disabled={false} + focusRipple={true} + onClick={[MockFunction]} > - <ForwardRef(IconButton) + <ForwardRef(ButtonBase) aria-label="consumption.accessibility.button_next_value" - className="date-navigator-button disable" + centerRipple={true} + className="MuiIconButton-root date-navigator-button" classes={ Object { - "colorInherit": "MuiIconButton-colorInherit", - "colorPrimary": "MuiIconButton-colorPrimary", - "colorSecondary": "MuiIconButton-colorSecondary", "disabled": "Mui-disabled", - "edgeEnd": "MuiIconButton-edgeEnd", - "edgeStart": "MuiIconButton-edgeStart", - "label": "MuiIconButton-label", - "root": "MuiIconButton-root", - "sizeSmall": "MuiIconButton-sizeSmall", + "focusVisible": "Mui-focusVisible", + "root": "MuiButtonBase-root", } } - onClick={[Function]} + disabled={false} + focusRipple={true} + onClick={[MockFunction]} > - <WithStyles(ForwardRef(ButtonBase)) + <button aria-label="consumption.accessibility.button_next_value" - centerRipple={true} - className="MuiIconButton-root date-navigator-button disable" + className="MuiButtonBase-root MuiIconButton-root date-navigator-button" disabled={false} - focusRipple={true} - onClick={[Function]} + onBlur={[Function]} + onClick={[MockFunction]} + onDragLeave={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyUp={[Function]} + onMouseDown={[Function]} + onMouseLeave={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + tabIndex={0} + type="button" > - <ForwardRef(ButtonBase) - aria-label="consumption.accessibility.button_next_value" - centerRipple={true} - className="MuiIconButton-root date-navigator-button disable" - classes={ - Object { - "disabled": "Mui-disabled", - "focusVisible": "Mui-focusVisible", - "root": "MuiButtonBase-root", - } - } - disabled={false} - focusRipple={true} - onClick={[Function]} + <span + className="MuiIconButton-label" > - <button - aria-label="consumption.accessibility.button_next_value" - className="MuiButtonBase-root MuiIconButton-root date-navigator-button disable" - disabled={false} - onBlur={[Function]} - onClick={[Function]} - onDragLeave={[Function]} - onFocus={[Function]} - onKeyDown={[Function]} - onKeyUp={[Function]} - onMouseDown={[Function]} - onMouseLeave={[Function]} - onMouseUp={[Function]} - onTouchEnd={[Function]} - onTouchMove={[Function]} - onTouchStart={[Function]} - tabIndex={0} - type="button" + <Icon + icon="test-file-stub" + size={16} + spin={false} > - <span - className="MuiIconButton-label" + <Component + className="styles__icon___23x3R" + height={16} + style={Object {}} + width={16} > - <Icon - icon="test-file-stub" - size={16} - spin={false} + <svg + className="styles__icon___23x3R" + height={16} + style={Object {}} + width={16} > - <Component - className="styles__icon___23x3R" - height={16} - style={Object {}} - width={16} - > - <svg - className="styles__icon___23x3R" - height={16} - style={Object {}} - width={16} - > - <use - xlinkHref="#test-file-stub" - /> - </svg> - </Component> - </Icon> - </span> - <WithStyles(memo) - center={true} + <use + xlinkHref="#test-file-stub" + /> + </svg> + </Component> + </Icon> + </span> + <WithStyles(memo) + center={true} + > + <ForwardRef(TouchRipple) + center={true} + classes={ + Object { + "child": "MuiTouchRipple-child", + "childLeaving": "MuiTouchRipple-childLeaving", + "childPulsate": "MuiTouchRipple-childPulsate", + "ripple": "MuiTouchRipple-ripple", + "ripplePulsate": "MuiTouchRipple-ripplePulsate", + "rippleVisible": "MuiTouchRipple-rippleVisible", + "root": "MuiTouchRipple-root", + } + } + > + <span + className="MuiTouchRipple-root" > - <ForwardRef(TouchRipple) - center={true} - classes={ - Object { - "child": "MuiTouchRipple-child", - "childLeaving": "MuiTouchRipple-childLeaving", - "childPulsate": "MuiTouchRipple-childPulsate", - "ripple": "MuiTouchRipple-ripple", - "ripplePulsate": "MuiTouchRipple-ripplePulsate", - "rippleVisible": "MuiTouchRipple-rippleVisible", - "root": "MuiTouchRipple-root", - } - } - > - <span - className="MuiTouchRipple-root" - > - <TransitionGroup - childFactory={[Function]} - component={null} - exit={true} - /> - </span> - </ForwardRef(TouchRipple)> - </WithStyles(memo)> - </button> - </ForwardRef(ButtonBase)> - </WithStyles(ForwardRef(ButtonBase))> - </ForwardRef(IconButton)> - </WithStyles(ForwardRef(IconButton))> - </div> - </div> - </DateNavigator> -</Provider> + <TransitionGroup + childFactory={[Function]} + component={null} + exit={true} + /> + </span> + </ForwardRef(TouchRipple)> + </WithStyles(memo)> + </button> + </ForwardRef(ButtonBase)> + </WithStyles(ForwardRef(ButtonBase))> + </ForwardRef(IconButton)> + </WithStyles(ForwardRef(IconButton))> + </div> +</DateNavigator> `; diff --git a/src/components/Home/ConsumptionView.spec.tsx b/src/components/Home/ConsumptionView.spec.tsx index 1fe128cadf0d202e2d308e2c282741e79a2fc053..86a4f844794bf824ec77f1a7c89ce9794de3616b 100644 --- a/src/components/Home/ConsumptionView.spec.tsx +++ b/src/components/Home/ConsumptionView.spec.tsx @@ -2,6 +2,7 @@ import Loader from 'components/Loader/Loader' import { FluidState, FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' import { mount } from 'enzyme' +import { DateTime } from 'luxon' import { FluidStatus } from 'models' import React from 'react' import * as reactRedux from 'react-redux' @@ -73,6 +74,10 @@ const mockedPartnersIssueModal = { grdf: false, } +const mockSelectedDate = DateTime.local().endOf('minute').setZone('utc', { + keepLocalTime: true, +}) + describe('ConsumptionView component', () => { const store = createMockEcolyoStore() beforeEach(() => { @@ -88,8 +93,9 @@ describe('ConsumptionView component', () => { useSelectorSpy.mockReturnValue({ chart: { currentTimeStep: TimeStep.WEEK, - loading: true, + loading: false, showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: mockFluidStatus, @@ -119,6 +125,7 @@ describe('ConsumptionView component', () => { currentTimeStep: TimeStep.WEEK, loading: true, showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: mockFluidStatus, @@ -142,6 +149,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.HALF_AN_HOUR, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: mockInitialEcolyoState.global.fluidStatus, @@ -164,6 +173,7 @@ describe('ConsumptionView component', () => { currentTimeStep: TimeStep.WEEK, loading: true, showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: [], @@ -189,6 +199,7 @@ describe('ConsumptionView component', () => { currentTimeStep: TimeStep.WEEK, loading: true, showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -214,6 +225,7 @@ describe('ConsumptionView component', () => { currentTimeStep: TimeStep.WEEK, loading: true, showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -239,6 +251,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.WEEK, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -263,6 +277,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.WEEK, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -286,6 +302,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.WEEK, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -310,6 +328,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.WEEK, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, @@ -337,6 +357,8 @@ describe('ConsumptionView component', () => { chart: { currentTimeStep: TimeStep.WEEK, loading: true, + showOfflineData: true, + selectedDate: mockSelectedDate, }, global: { fluidStatus: updatedStatus, diff --git a/src/components/Home/ConsumptionView.tsx b/src/components/Home/ConsumptionView.tsx index d6d6373561ad2b59f0bf53ef7f4bbbca26d398bd..f823dbdf2100188ecb1ffa58acf755c296d60af3 100644 --- a/src/components/Home/ConsumptionView.tsx +++ b/src/components/Home/ConsumptionView.tsx @@ -15,14 +15,22 @@ import PartnerIssueModal from 'components/PartnerIssue/PartnerIssueModal' import { useClient } from 'cozy-client' import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' +import { DateTime } from 'luxon' import React, { Dispatch, useCallback, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' +import DateChartService from 'services/dateChart.service' import ProfileService from 'services/profile.service' import { AppActionsTypes, AppStore } from 'store' -import { setCurrentTimeStep, setShowOfflineData } from 'store/chart/chart.slice' +import { + setCurrentIndex, + setCurrentTimeStep, + setSelectedDate, + setShowOfflineData, +} from 'store/chart/chart.slice' import { showReleaseNotes } from 'store/global/global.actions' import { openPartnersModal, setCustomPopup } from 'store/modal/modal.slice' +import { isLastDateReached } from 'utils/date' import { getKonnectorUpdateError, getTodayDate, @@ -37,11 +45,19 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { const dispatch = useDispatch<Dispatch<AppActionsTypes>>() const isMulti = fluidType === FluidType.MULTIFLUID const { - chart: { currentTimeStep, loading, showOfflineData }, + chart: { + currentTimeStep, + loading, + showOfflineData, + selectedDate, + currentIndex, + }, global: { fluidStatus, releaseNotes }, modal: { partnersIssueModal, customPopupModal }, } = useSelector((state: AppStore) => state.ecolyo) + const dateChartService = new DateChartService() + const [openReleaseNoteModal, setOpenReleaseNoteModal] = useState<boolean>( releaseNotes.show ) @@ -167,6 +183,40 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { } }, [fluidStatus]) + const disablePrev = + selectedDate < + DateTime.local(0, 1, 1).setZone('utc', { + keepLocalTime: true, + }) && !isKonnectorActive(fluidStatus, FluidType.MULTIFLUID) + + const getIncrement = (next: boolean) => + next + ? dateChartService.defineIncrementForNextIndex( + currentTimeStep, + selectedDate, + currentIndex + ) + : dateChartService.defineIncrementForPreviousIndex( + currentTimeStep, + selectedDate, + currentIndex + ) + + const handleClickMove = (next: boolean) => { + const increment = getIncrement(next) + const updatedDate = dateChartService.incrementDate( + currentTimeStep, + selectedDate, + increment + ) + const updatedIndex = dateChartService.defineDateIndex( + currentTimeStep, + updatedDate + ) + dispatch(setSelectedDate(updatedDate)) + dispatch(setCurrentIndex(updatedIndex)) + } + return ( <> <CozyBar titleKey={'common.title_consumption'} /> @@ -174,7 +224,14 @@ const ConsumptionView = ({ fluidType }: { fluidType: FluidType }) => { setHeaderHeight={defineHeaderHeight} desktopTitleKey={'common.title_consumption'} > - <DateNavigator /> + <DateNavigator + disableNext={isLastDateReached(selectedDate, currentTimeStep)} + disablePrev={disablePrev} + handleNextDate={() => handleClickMove(true)} + handlePrevDate={() => handleClickMove(false)} + navigatorDate={selectedDate} + timeStep={currentTimeStep} + /> </Header> <Content height={headerHeight}> <FluidButtons activeFluid={fluidType} key={updateKey} /> diff --git a/src/components/Options/ExportData/ExportData.spec.tsx b/src/components/Options/ExportData/ExportData.spec.tsx index ef4cf3cf380dee33473741923879b06f45d33c6b..e4f7b336f3f4b1e54011f3451543f0fdf94cc54f 100644 --- a/src/components/Options/ExportData/ExportData.spec.tsx +++ b/src/components/Options/ExportData/ExportData.spec.tsx @@ -14,16 +14,11 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) -const mockGetExportableFluids = jest.fn(() => { - return [] -}) jest.mock('services/consumption.service', () => { return jest.fn(() => { return { - fetchAllFirstDateData: jest.fn(() => { - return [null, null, null] - }), - getExportableFluids: mockGetExportableFluids, + fetchAllFirstDateData: jest.fn(() => [null, null, null]), + getFluidsWithData: jest.fn(() => []), } }) }) diff --git a/src/components/Options/ExportData/ExportData.tsx b/src/components/Options/ExportData/ExportData.tsx index 253a24355d001e2aa329fcd96bb618c83037a714..512d627d2406297e1a6d3b1a06603898cc7e3e18 100644 --- a/src/components/Options/ExportData/ExportData.tsx +++ b/src/components/Options/ExportData/ExportData.tsx @@ -55,11 +55,10 @@ const ExportData = () => { useEffect(() => { let subscribed = true const getExportableFluids = async () => { - const exportableFluidsData: FluidType[] = - await consumptionService.getExportableFluids( - [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS], - TimeStep.MONTH - ) + const exportableFluidsData = await consumptionService.getFluidsWithData( + [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS], + TimeStep.MONTH + ) setExportableFluids(exportableFluidsData) setAnswer(exportableFluidsData) subscribed = false diff --git a/src/locales/fr.json b/src/locales/fr.json index 823bb73b4d6e3d4008b14ccf4055e3086061ae97..5d0f18ff7655c963bf86420f45775254a76480f3 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -103,22 +103,10 @@ }, "no_data": "Pas de données" }, - "analysis_error_modal": { - "title": "Aucune analyse", - "message": "Pour profiter d’une analyse de vos consommations, connectez au moins un de vos compteurs.", - "go_to_options": "Je connecte mes compteurs", - "go_back": "Retour", - "accessibility": { - "window_title": "Fenêtre d'erreur", - "button_go_back": "Retour", - "button_goto_konnector": "Aller aux connecteurs" - } - }, "analysis_pie": { "total": "Conso totale", "month": "Au mois\u00a0", - "estimation": "Comment sont estimés", - "estimation2": "les prix\u00a0?" + "estimation": "Comment sont estimés<br>les prix\u00a0?" }, "special_elec": { "title": "Spécial Électricité", diff --git a/src/services/consumption.service.spec.ts b/src/services/consumption.service.spec.ts index 9c14ac5fa72003fd59bde4673c000cfe14cd97ed..b848e4c06ea52f1be608af23042543bfff83ea44 100644 --- a/src/services/consumption.service.spec.ts +++ b/src/services/consumption.service.spec.ts @@ -403,7 +403,7 @@ describe('Consumption service', () => { }) }) - describe('getExportableFluids method', () => { + describe('getFluidsWithData method', () => { it('should return the array of fluidTypes that have entries', async () => { const fluidTypes: FluidType[] = [ FluidType.ELECTRICITY, @@ -413,7 +413,7 @@ describe('Consumption service', () => { mockGetEntries.mockResolvedValueOnce({ data: [1] }) mockGetEntries.mockResolvedValueOnce({ data: [] }) mockGetEntries.mockResolvedValueOnce({ data: [1] }) - const result = await consumptionDataManager.getExportableFluids( + const result = await consumptionDataManager.getFluidsWithData( fluidTypes, TimeStep.MONTH ) @@ -581,4 +581,32 @@ describe('Consumption service', () => { expect(result.length).toEqual(4) }) }) + + describe('getFluidsWithDataForTimePeriod', () => { + const allFluids = [FluidType.ELECTRICITY, FluidType.WATER, FluidType.GAS] + const timePeriods: TimePeriod = { + startDate: DateTime.local(2023, 5, 1), + endDate: DateTime.local(2023, 6, 1), + } + it('should return 2 fluids', async () => { + mockFetchFluidData.mockResolvedValueOnce(null) + mockFetchFluidData.mockResolvedValueOnce(mockFetchDataActual) + mockFetchFluidData.mockResolvedValueOnce(mockFetchDataActual) + const fluidsWithData = + await consumptionDataManager.getFluidsWithDataForTimePeriod( + allFluids, + timePeriods + ) + expect(fluidsWithData).toEqual([FluidType.WATER, FluidType.GAS]) + }) + it('should return no fluids', async () => { + mockFetchFluidData.mockResolvedValue(null) + const fluidsWithData = + await consumptionDataManager.getFluidsWithDataForTimePeriod( + allFluids, + timePeriods + ) + expect(fluidsWithData).toEqual([]) + }) + }) }) diff --git a/src/services/consumption.service.ts b/src/services/consumption.service.ts index 48fb3f060614ce28957d46b43270676d3fbbdcdc..7fce6981f526172c8d54cb4111e84aace3b8da42 100644 --- a/src/services/consumption.service.ts +++ b/src/services/consumption.service.ts @@ -331,17 +331,40 @@ export default class ConsumptionDataManager { return result } - public async getExportableFluids( + /** + * Check that fluidTypes contains data over a timestep. + * @returns an array of FluidType that contains data + */ + public async getFluidsWithData( fluidTypes: FluidType[], timeStep: TimeStep ): Promise<FluidType[]> { - const exportableFluids: FluidType[] = [] + const fluidsWithData: FluidType[] = [] for (const fluidType of fluidTypes) { if (await this.checkDoctypeEntries(fluidType, timeStep)) { - exportableFluids.push(fluidType) + fluidsWithData.push(fluidType) + } + } + return fluidsWithData + } + + public async getFluidsWithDataForTimePeriod( + fluidTypes: FluidType[], + timePeriod: TimePeriod, + timeStep = TimeStep.MONTH + ): Promise<FluidType[]> { + const fluidsWithData: FluidType[] = [] + for (const fluidType of fluidTypes) { + const data = await this._queryRunnerService.fetchFluidData( + timePeriod, + timeStep, + fluidType + ) + if (data?.length) { + fluidsWithData.push(fluidType) } } - return exportableFluids + return fluidsWithData } public async fetchAllFirstDateData( diff --git a/src/styles/base/_color.scss b/src/styles/base/_color.scss index ed06067df8c0d8dd47eb15042d6a4eda5546a9ea..bc7fa395af58468ba90d67a3ed1cd9e1bf304b4e 100644 --- a/src/styles/base/_color.scss +++ b/src/styles/base/_color.scss @@ -11,6 +11,7 @@ $dark-background: radial-gradient( #1b1c22 100% ); $bottom-bar-grey: #32343d; +$black-shadow: #0000008c; /** RED **/ $red-primary: #d25959; diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 4d079cef9976410cea493ff017194d90fce3a086..94634375044e4bf66b712020034a796805a442ca 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -10,6 +10,6 @@ background: $grey-linear-gradient-background-hover; } &.rich-card { - padding: 24px 16px; + padding: 16px; } }