diff --git a/src/components/Analysis/ComparisonView.tsx b/src/components/Analysis/ComparisonView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..17d80b45a620754979610383c629c6408ac5b059 --- /dev/null +++ b/src/components/Analysis/ComparisonView.tsx @@ -0,0 +1,160 @@ +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 { TimeStep } from 'enum/timeStep.enum' +import { DateTime } from 'luxon' +import { FluidConfig, 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' + +interface ComparisonViewProps { + analysisDate: DateTime + fluidConfig: FluidConfig[] +} + +const ComparisonView: React.FC<ComparisonViewProps> = ({ + analysisDate, + fluidConfig, +}) => { + const { t } = useI18n() + const client = useClient() + const { + global: { fluidTypes }, + analysis: { period }, + } = useSelector((state: AppStore) => state.ecolyo) + const dispatch = useDispatch<Dispatch<AppActionsTypes>>() + const [monthPerformanceIndicators, setMonthPerformanceIndicators] = useState< + PerformanceIndicator[] + >([]) + const [yearPerformanceIndicators, setYearPerformanceIndicators] = useState< + PerformanceIndicator[] + >([]) + const [isLoading, setIsLoading] = useState<boolean>(true) + const consumptionService = useMemo( + () => new ConsumptionService(client), + [client] + ) + const monthPeriod = useMemo(() => { + return { + startDate: analysisDate.minus({ month: 1 }).startOf('month'), + endDate: analysisDate.minus({ month: 1 }).endOf('month'), + } + }, [analysisDate]) + const previousMonthPeriod = useMemo(() => { + return { + startDate: analysisDate.minus({ month: 2 }).startOf('month'), + endDate: analysisDate.minus({ month: 2 }).endOf('month'), + } + }, [analysisDate]) + const previousYearPeriod = useMemo(() => { + return { + startDate: analysisDate.minus({ year: 1, month: 1 }).startOf('month'), + endDate: analysisDate.minus({ year: 1, month: 1 }).endOf('month'), + } + }, [analysisDate]) + + const loaderPlaceholderHeight = + fluidTypes.length * 84 + (fluidTypes.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, + TimeStep.MONTH, + fluidTypes, + previousYearPeriod + ) + if (fetchedMonthIndicators) { + setMonthPerformanceIndicators(fetchedMonthIndicators) + } + if (fetchedYearIndicators) { + setYearPerformanceIndicators(fetchedYearIndicators) + } + setIsLoading(false) + } + } + populateData() + + return () => { + subscribed = false + } + }, [ + client, + fluidTypes, + analysisDate, + consumptionService, + monthPeriod, + previousMonthPeriod, + previousYearPeriod, + ]) + + return ( + <div className="comparisonView"> + <div className="tabs"> + <Button + className={period === 'month' ? 'active' : ''} + onClick={() => dispatch(setPeriod('month'))} + > + {t('analysis.compare.month_tab')} + </Button> + <Button + className={period === 'year' ? 'active' : ''} + onClick={() => dispatch(setPeriod('year'))} + > + {t('analysis.compare.year_tab')} + </Button> + </div> + <div className="performanceIndicators"> + {isLoading && ( + <div + style={{ + height: `${loaderPlaceholderHeight}px`, + display: 'flex', + justifyContent: 'center', + }} + > + <Loader /> + </div> + )} + {!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 + } + /> + ) + )} + </div> + </div> + ) +} + +export default ComparisonView diff --git a/src/components/Analysis/MonthlyAnalysis.spec.tsx b/src/components/Analysis/MonthlyAnalysis.spec.tsx index ea7ede3c010c4eb3cd9a501bceb4b6c03706195f..3e3322e346673996db7f429b7b2fe69600cd0470 100644 --- a/src/components/Analysis/MonthlyAnalysis.spec.tsx +++ b/src/components/Analysis/MonthlyAnalysis.spec.tsx @@ -18,10 +18,7 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { } }) jest.mock('services/consumption.service') -jest.mock( - 'components/PerformanceIndicator/FluidPerformanceIndicator', - () => 'mock-fluid-performance-indicator' -) +jest.mock('components/Analysis/ComparisonView', () => 'mock-comparison-view') const mockStore = configureStore([]) diff --git a/src/components/Analysis/MonthlyAnalysis.tsx b/src/components/Analysis/MonthlyAnalysis.tsx index 728d50ed0a548a3d6c3738b82dc2ea67624095c4..0f5ef9a4c1b0f47acfecd8a0273dddce97ade3ec 100644 --- a/src/components/Analysis/MonthlyAnalysis.tsx +++ b/src/components/Analysis/MonthlyAnalysis.tsx @@ -1,5 +1,4 @@ import Loader from 'components/Loader/Loader' -import FluidPerformanceIndicator from 'components/PerformanceIndicator/FluidPerformanceIndicator' import { useClient } from 'cozy-client' import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' @@ -13,6 +12,7 @@ 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' @@ -41,7 +41,7 @@ const MonthlyAnalysis: React.FC<MonthlyAnalysisProps> = ({ compareValue: 0, percentageVariation: 0, }) - const [isLoaded, setIsLoaded] = useState<boolean>(false) + const [isLoading, setIsLoading] = useState<boolean>(true) const configService = new ConfigService() const fluidConfig = configService.getFluidConfig() const timeStep = TimeStep.MONTH @@ -49,44 +49,40 @@ const MonthlyAnalysis: React.FC<MonthlyAnalysisProps> = ({ useEffect(() => { let subscribed = true async function populateData() { + setIsLoading(true) + const consumptionService = new ConsumptionService(client) + const performanceIndicatorService = new PerformanceIndicatorService() + const periods = { + timePeriod: { + startDate: analysisDate.minus({ month: 1 }).startOf('month'), + endDate: analysisDate.minus({ month: 1 }).endOf('month'), + }, + comparisonTimePeriod: { + startDate: analysisDate.minus({ month: 2 }).startOf('month'), + endDate: analysisDate.minus({ month: 2 }).endOf('month'), + }, + } + const fetchedPerformanceIndicators = + await consumptionService.getPerformanceIndicators( + periods.timePeriod, + timeStep, + fluidTypes, + periods.comparisonTimePeriod + ) if (subscribed) { - setIsLoaded(false) - const consumptionService = new ConsumptionService(client) - const performanceIndicatorService = new PerformanceIndicatorService() - const periods = { - timePeriod: { - startDate: analysisDate.minus({ month: 1 }).startOf('month'), - endDate: analysisDate.minus({ month: 1 }).endOf('month'), - }, - comparisonTimePeriod: { - startDate: analysisDate.minus({ month: 2 }).startOf('month'), - endDate: analysisDate.minus({ month: 2 }).endOf('month'), - }, - } - const fetchedPerformanceIndicators = - await consumptionService.getPerformanceIndicators( - periods.timePeriod, - timeStep, - fluidTypes, - periods.comparisonTimePeriod - ) - if (subscribed) { - if (fetchedPerformanceIndicators) { - setPerformanceIndicators(fetchedPerformanceIndicators) - setLoadAnalysis(false) - const hasValidPI = fetchedPerformanceIndicators.some( - pi => pi?.value - ) - if (hasValidPI) setLoadAnalysis(true) + if (fetchedPerformanceIndicators) { + setPerformanceIndicators(fetchedPerformanceIndicators) + setLoadAnalysis(false) + const hasValidPI = fetchedPerformanceIndicators.some(pi => pi?.value) + if (hasValidPI) setLoadAnalysis(true) - setAggregatedPerformanceIndicators( - performanceIndicatorService.aggregatePerformanceIndicators( - fetchedPerformanceIndicators - ) + setAggregatedPerformanceIndicators( + performanceIndicatorService.aggregatePerformanceIndicators( + fetchedPerformanceIndicators ) - } - setIsLoaded(true) + ) } + setIsLoading(false) } } populateData() @@ -98,42 +94,29 @@ const MonthlyAnalysis: React.FC<MonthlyAnalysisProps> = ({ }, [client, timeStep, fluidTypes, analysisDate, saveLastScrollPosition]) useEffect(() => { - if (isLoaded) { + if (!isLoading) { const app = document.querySelector('.app-content') window.scrollTo(0, scrollPosition) app?.scrollTo(0, scrollPosition) } - }, [isLoaded, scrollPosition]) + }, [isLoading, scrollPosition]) return ( <> - {!isLoaded && ( + {isLoading && ( <div className="analysis-container-spinner" aria-busy="true"> <Loader /> </div> )} - {isLoaded && ( + {!isLoading && ( <div className="analysis-root black"> {fluidTypes.length >= 1 ? ( <> <div className="analysis-content"> - <div> - {fluidConfig.map( - fluid => - fluidTypes.includes(fluid.fluidTypeId) && ( - <FluidPerformanceIndicator - key={fluid.konnectorConfig.slug} - fluidType={fluid.fluidTypeId} - performanceIndicator={ - performanceIndicators[fluid.fluidTypeId] - } - date={analysisDate - .minus({ month: 1 }) - .startOf('month')} - /> - ) - )} - </div> + <ComparisonView + analysisDate={analysisDate} + fluidConfig={fluidConfig} + /> </div> {loadAnalysis && ( diff --git a/src/components/Analysis/comparisonView.scss b/src/components/Analysis/comparisonView.scss new file mode 100644 index 0000000000000000000000000000000000000000..12271b7131b329d67853f6b65dc8b1d64dd468c0 --- /dev/null +++ b/src/components/Analysis/comparisonView.scss @@ -0,0 +1,33 @@ +.comparisonView { + display: flex; + flex-direction: column; + .tabs { + display: flex; + flex-direction: row; + gap: 8px; + button { + flex-grow: 1; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)), + linear-gradient(180deg, #323339 0%, #25262b 100%); + border-radius: 4px 4px 0px 0px; + color: #ffffff; + text-transform: none; + font-size: 0.75rem; + line-height: 1.3; + padding: 0.5rem 0; + box-shadow: 6px 0px 12px rgba(0, 0, 0, 0.3); + &.active { + background: #5e5e5e; + } + } + } + .performanceIndicators { + display: flex; + flex-direction: column; + gap: 10px; + background: linear-gradient(180deg, #323339 0%, #25262b 100%); + border: 1px solid #5e5e5e; + border-radius: 0px 0px 4px 4px; + padding: 1rem; + } +} diff --git a/src/components/Analysis/monthlyanalysis.scss b/src/components/Analysis/monthlyanalysis.scss index 63a26ff9dc3254d077c3fd4369785914bb54d95c..7f775f6408d871a7f952fc55c9cc53b8ce7e1b78 100644 --- a/src/components/Analysis/monthlyanalysis.scss +++ b/src/components/Analysis/monthlyanalysis.scss @@ -5,8 +5,9 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; - padding: 0 1.5rem 2rem; + justify-content: space-around; + padding: 1rem 1rem 1.5rem; + gap: 1rem; &.black { background: var(--darkLight2); } @@ -19,12 +20,6 @@ @media #{$large-phone} { width: 100%; } - .analysis-header { - margin-top: 1.5rem; - margin-bottom: 1.25rem; - color: $grey-bright; - font-size: 1rem; - } } .status-header { display: grid; diff --git a/src/components/Options/GCU/gcuLink.scss b/src/components/Options/GCU/gcuLink.scss index 42e9c1500da97f3a40f4cfe27d61c6d834fe42c4..f3e6e9d033be3e910a17ee9a719113f1da65df6f 100644 --- a/src/components/Options/GCU/gcuLink.scss +++ b/src/components/Options/GCU/gcuLink.scss @@ -8,6 +8,7 @@ justify-content: center; color: $white; padding: 0 1.5rem 0; + margin-top: 0.5rem; .gcu-link-header { margin-bottom: 1.25rem; } diff --git a/src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx b/src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx index 9d27584ff4298a7bdacc6e230789253eed619658..998cb8fb79e2d4353bfd5cfb230b8a25e5be3e58 100644 --- a/src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx +++ b/src/components/PerformanceIndicator/FluidPerformanceIndicator.tsx @@ -5,85 +5,72 @@ import { DateTime } from 'luxon' import { PerformanceIndicator } from 'models' import React from 'react' import { getPicto } from 'utils/picto' -import { formatNumberValues, getPreviousMonthName } from 'utils/utils' +import { formatNumberValues, getMonthName } from 'utils/utils' import './fluidPerformanceIndicator.scss' interface FluidPerformanceIndicatorProps { performanceIndicator: PerformanceIndicator fluidType: FluidType - date: DateTime -} - -const percentSwitch = (percentage: number) => { - if (percentage > 0) { - return `+${formatNumberValues(percentage * 100)} %` - } else if (percentage < 0) { - return `-${formatNumberValues(Math.abs(percentage) * 100)} %` - } else { - return `${formatNumberValues(percentage * 100)} %` - } + comparisonDate: DateTime } const FluidPerformanceIndicator: React.FC<FluidPerformanceIndicatorProps> = ({ performanceIndicator, fluidType, - date, + comparisonDate, }: FluidPerformanceIndicatorProps) => { const { t } = useI18n() const iconType = getPicto(fluidType) - let displayedValue = '----' - if (performanceIndicator?.value) { - displayedValue = formatNumberValues(performanceIndicator.value).toString() + const displayedValue = performanceIndicator?.value + ? formatNumberValues(performanceIndicator.value).toString() + : '' + const positive = + performanceIndicator?.percentageVariation && + performanceIndicator?.percentageVariation > 0 + const percentSwitch = (percentage: number) => { + if (positive) { + return `+${formatNumberValues(percentage * 100)} %` + } else { + return `-${formatNumberValues(Math.abs(percentage) * 100)} %` + } } + return ( - <div className="card"> - <div className="fpi"> - <div className="fpi-left"> - <div className="fpi-content"> - <StyledIcon - className="fpi-content-icon" - icon={iconType} - size={50} - /> - <div className="fpi-content-perf"> - {!performanceIndicator.value ? ( - <div className="fpi-content-perf-no-data card-result"> - <span>{t('performance_indicator.fpi.no_data')}</span> - </div> - ) : ( - <div className="fpi-content-perf-result card-result"> - <span>{displayedValue}</span> - <span className="card-indicator"> - {t('FLUID.' + FluidType[fluidType] + '.UNIT')} - </span> - <span className={`euro-value month`}> - {/* condition pas de comparaison possible */} - <span - className={`${ - performanceIndicator?.percentageVariation && - performanceIndicator?.percentageVariation >= 0 - ? 'positive' - : 'negative' - }`} - > - {performanceIndicator?.percentageVariation !== null ? ( - percentSwitch(performanceIndicator.percentageVariation) - ) : ( - <> - <span className="no-comparison"> - {t('performance_indicator.fpi.no_comparison')} - </span> - </> - )} - </span> - {performanceIndicator.percentageVariation !== null && - `/ ${getPreviousMonthName(date.minus({ month: 1 }))}`} - </span> - </div> - )} - </div> + <div className="fpi"> + <StyledIcon icon={iconType} size={50} /> + <div className="fpi-content"> + {!displayedValue && ( + <div className="fpi-content-no-data"> + <span>{t('performance_indicator.fpi.no_data')}</span> </div> - </div> + )} + {displayedValue && ( + <> + <div className="fpi-value"> + <span className="fpi-load">{displayedValue}</span> + <span className="fpi-unit"> + {t('FLUID.' + FluidType[fluidType] + '.UNIT')} + </span> + </div> + {performanceIndicator?.percentageVariation === null && ( + <span className="fpi-no-comparison"> + {t('performance_indicator.fpi.no_comparison')} + </span> + )} + {performanceIndicator?.percentageVariation !== null && ( + <div className={`fpi-comparison`}> + <span + className={`percent ${positive ? 'positive' : 'negative'}`} + > + {percentSwitch(performanceIndicator.percentageVariation)} + </span> + <span className="fpi-comparison-date"> + {` / ${getMonthName(comparisonDate)} ${comparisonDate.year}`} + </span> + </div> + )} + </> + )} </div> </div> ) diff --git a/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx b/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx deleted file mode 100644 index afe099db8f59ab06d7cd33c6408b475254949971..0000000000000000000000000000000000000000 --- a/src/components/PerformanceIndicator/PerformanceIndicatorContent.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import PileIcon from 'assets/icons/ico/coins.svg' -import ErrorIndicatorIcon from 'assets/icons/visu/indicator/error.svg' -import GreenIndicatorIcon from 'assets/icons/visu/indicator/green.svg' -import GreyIndicatorIcon from 'assets/icons/visu/indicator/grey.svg' -import RedIndicatorIcon from 'assets/icons/visu/indicator/red.svg' -import StyledIcon from 'components/CommonKit/Icon/StyledIcon' -import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { FluidType } from 'enum/fluid.enum' -import { TimeStep } from 'enum/timeStep.enum' -import { DateTime } from 'luxon' -import { PerformanceIndicator } from 'models' -import React from 'react' -import { convertDateToMonthString } from 'utils/date' -import { formatNumberValues } from 'utils/utils' -import './fluidPerformanceIndicator.scss' - -interface PerformanceIndicatorContentProps { - performanceIndicator: PerformanceIndicator - timeStep: TimeStep - fluidLackOfData?: Array<FluidType> - analysisDate?: DateTime -} - -const PerformanceIndicatorContent: React.FC< - PerformanceIndicatorContentProps -> = ({ - performanceIndicator, - fluidLackOfData = [], - analysisDate, -}: PerformanceIndicatorContentProps) => { - const { t } = useI18n() - let displayedValue: string - if (performanceIndicator?.value) - displayedValue = formatNumberValues(performanceIndicator.value).toString() - else displayedValue = '-----' - - let errorInPerf = false - if (!performanceIndicator?.value) { - errorInPerf = true - } - - let perf: number | null = null - let diffInEuro: number | null = null - if (performanceIndicator?.value && performanceIndicator.compareValue) { - perf = - 100 * (performanceIndicator.value / performanceIndicator.compareValue - 1) - diffInEuro = performanceIndicator.value - performanceIndicator.compareValue - } - const perfString = perf ? formatNumberValues(perf) : '' - const diffString = diffInEuro ? formatNumberValues(diffInEuro) : '' - let perfStatus = ['error', ErrorIndicatorIcon] - if (perf === null && !errorInPerf) perfStatus = ['nodata', ErrorIndicatorIcon] - else if (perf === null && errorInPerf) - perfStatus = ['error', ErrorIndicatorIcon] - else if (perf && perf === 0) perfStatus = ['zero', GreyIndicatorIcon] - else if (perf && perf > 0) perfStatus = ['positive', RedIndicatorIcon] - else if (perf && perf < 0) perfStatus = ['negative', GreenIndicatorIcon] - return ( - <div className="fpi"> - <div className="fpi-left"> - <div className="fpi-content"> - <div className="fpi-content-perf"> - <div className="fpi-content-perf-result card-result"> - <div className="icon-line"> - <StyledIcon - className="fpi-content-icon perf-icon" - icon={PileIcon} - size={35} - /> - <div> - <span className="euro-value">{displayedValue}</span> - <span className="card-indicator"> €</span> - </div> - </div> - </div> - {perfStatus[0] === 'positive' || - perfStatus[0] === 'negative' || - perfStatus[0] === 'zero' ? ( - <div className="fpi-content-perf-indicator bilan-card card-text"> - <div className="icon-line"> - <StyledIcon - className="fpi-content-icon perf-icon" - icon={perfStatus[1]} - size={35} - /> - <div className="evolution-text"> - {t('performance_indicator.bilan.text1')} - {analysisDate && - convertDateToMonthString( - analysisDate.plus({ month: -2 }) - ).substring(3)}{' '} - : - <span - className={`fpi-content-perf-indicator-kpi ${perfStatus[0]} card-text-bold`} - > - {perfStatus[0] === 'positive' ? ' + ' : ' '} - {perfString}% - </span> - <br /> - <span> - {t('performance_indicator.bilan.text2')} - <span className="diff-value">{diffString} €</span> - </span> - </div> - </div> - </div> - ) : ( - <div className="fpi-content-perf-indicator card-text error"> - <StyledIcon - className="fpi-content-icon perf-icon" - icon={perfStatus[1]} - size={35} - /> - - {fluidLackOfData.length !== 0 ? ( - <div> - <div> - {' '} - {t('performance_indicator.error_no_compare_no_data')}{' '} - </div> - <div> - {fluidLackOfData.map(fluidType => { - return ( - <div key={fluidType} className="fluid-enum"> - {' '} - - {t( - 'FLUID.' + FluidType[fluidType] + '.NAME' - )}{' '} - </div> - ) - })} - </div> - </div> - ) : ( - <div> - <div> {t('performance_indicator.error_no_compare')} </div> - <div> - {' '} - {t('performance_indicator.error_no_compare_reason')}{' '} - </div> - </div> - )} - </div> - )} - </div> - </div> - </div> - </div> - ) -} - -export default PerformanceIndicatorContent diff --git a/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss b/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss index 72f6553496f661e08875d46096cf9c61fec892af..ec138c814f56d564d163093c16f00fa46265f0e7 100644 --- a/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss +++ b/src/components/PerformanceIndicator/fluidPerformanceIndicator.scss @@ -1,5 +1,5 @@ -@import 'src/styles/base/color'; -@import 'src/styles/base/breakpoint'; +@import '../../styles/base/color'; +@import '../../styles/base/breakpoint'; //FluidIndicator .fi-root { @@ -28,138 +28,49 @@ } //FluidPerformanceIndicator -.fpi-link { - // color: transparent; -} -.details-title { - color: white; - display: block; - margin-bottom: 1rem; - margin-top: 1rem; -} .fpi { display: flex; flex-direction: row; - margin: 0.25rem 0.25rem; - width: 100%; - .fpi-left { - flex: 1; - display: flex; - flex-direction: column; - .fpi-title { - align-content: flex-start; - margin-bottom: 0.5rem; - } - .fpi-content { - display: flex; - flex-direction: row; - // justify-content: center; - &:first-child() { - margin-bottom: 0.75rem; + border: 1px solid $grey-dark; + border-radius: 4px; + padding: 16px 22px; + gap: 1rem; + align-items: center; + + .fpi-content { + .fpi-value { + .fpi-load { + font-size: 1.75rem; + font-weight: 900; + margin-right: 4px; + color: $white; } - .fluid-enum { - font-weight: bold; - margin-left: 1rem; + .fpi-unit { + font-size: 1.125rem; + color: $grey-bright; } - .icon-line { - display: flex; - align-items: center; - margin-bottom: 0.25rem; - .euro-value { - font-size: 2.2rem; - font-weight: 900; - margin-right: 0.2rem; + } + .fpi-comparison { + .percent { + font-weight: 700; + &.positive { + color: $red-primary; } - .evolution-text { - color: $soft-grey; - .fpi-content-perf-indicator-kpi { - &.positive { - color: $red-primary !important; - } - &.negative { - color: $green !important; - } - } - .diff-value { - color: white; - font-weight: 700; - } + &.negative { + color: $green; } } - .bilan-card { - margin-bottom: 1rem; - min-height: 3.563rem; - } - .error { - display: flex; - gap: 1rem; - margin-bottom: 1rem; - min-height: 3.563rem; - } - .fpi-content-icon { - margin: 0.5rem 0; - } - .perf-icon { - margin-right: 0.8rem; - align-self: start; - } - .fpi-content-perf:not(:first-child) { - margin: 0 0 0 1rem; - align-self: center; - .fpi-content-perf-result { - color: $grey-bright; - & span { - display: inline-block; - padding-right: 0.25rem; - } - .positive { - color: $red-primary !important; - } - .negative { - color: $green !important; - } - .month { - color: $soft-grey !important; - } - .euro-value { - font-size: 1.125rem; - display: block; - font-weight: 400; - } - .ELECTRICITY-color { - color: $elec-color; - } - .GAS-color { - color: $gas-color; - } - .WATER-color { - color: $water-color; - } - .no-comparison { - color: $soft-grey; - } - } - .fpi-content-perf-no-data { - color: $grey-bright; - & span { - padding-right: 0.25rem; - font-size: 1.1rem; - display: block; - font-weight: 400; - } - } + .fpi-comparison-date { + color: $soft-grey; } } - .fpi-footer { - margin-top: 0.5rem; + .fpi-no-comparison { + font-size: 0.875rem; color: $soft-grey; } + .fpi-content-no-data { + color: $grey-bright; + } } } -.flex-center { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} diff --git a/src/components/PerformanceIndicator/performanceIndicatorContent.scss b/src/components/PerformanceIndicator/performanceIndicatorContent.scss deleted file mode 100644 index 70a2d267d23e5f96c2051fab08ae8c71f75e32d6..0000000000000000000000000000000000000000 --- a/src/components/PerformanceIndicator/performanceIndicatorContent.scss +++ /dev/null @@ -1,3 +0,0 @@ -.performance-indicator { - margin-bottom: 2rem; -} diff --git a/src/locales/fr.json b/src/locales/fr.json index 34d999bcfab46ecd2926751debad7f1d90945002..97df02600e7ea82424f2e1376113ea61c8e24b8e 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -93,11 +93,13 @@ "not_connected": "Non connecté", "no_data_2": "Données non disponibles", "accessibility": { - "button_go_to_profil": "Aller à la page de profil" + "button_go_to_profil": "Détailler mon profil" }, "max_day": "Jour où vous avez le plus consommé", "compare": { - "title": "Comparateur" + "title": "Comparateur", + "month_tab": "Comparer au mois dernier", + "year_tab": "Comparer à l'année dernière" }, "no_data": "Pas de données" }, @@ -445,7 +447,6 @@ }, "accessibility": { "window_title": "Fenêtre d'information'", - "button_go_to_profil": "Aller à la page de profil", "button_close": "Fermer la fenêtre", "button_selection": "Aller à la page de sélection" }, diff --git a/src/models/analysis.model.ts b/src/models/analysis.model.ts index 19faa7431bbe8801cacd84edae7ac100663fa733..ce7da6eecdcaacc133bd4f868140ad0015f5388c 100644 --- a/src/models/analysis.model.ts +++ b/src/models/analysis.model.ts @@ -5,3 +5,7 @@ export interface AnalysisAttributes { haveSeenLastAnalysis: boolean monthlyAnalysisDate: DateTime } + +export interface AnalysisState { + period: 'month' | 'year' +} diff --git a/src/store/analysis/analysis.slice.spec.ts b/src/store/analysis/analysis.slice.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1799dd98bcc8836d95192884cf74ab22a9512ae --- /dev/null +++ b/src/store/analysis/analysis.slice.spec.ts @@ -0,0 +1,24 @@ +import { mockInitialAnalysisState } from '../../../tests/__mocks__/store' +import { analysisSlice, setPeriod } from './analysis.slice' + +describe('analysis reducer', () => { + it('should return the initial state', () => { + const initialState = analysisSlice.reducer(undefined, { + type: undefined, + }) + expect(initialState).toEqual(mockInitialAnalysisState) + }) + + describe('setPeriod', () => { + it('should handle setPeriod with payload', () => { + const state = analysisSlice.reducer( + mockInitialAnalysisState, + setPeriod('year') + ) + expect(state).toEqual({ + ...mockInitialAnalysisState, + period: 'year', + }) + }) + }) +}) diff --git a/src/store/analysis/analysis.slice.ts b/src/store/analysis/analysis.slice.ts new file mode 100644 index 0000000000000000000000000000000000000000..ffd088e0084c94d1a31b558d9e330006c0706ea8 --- /dev/null +++ b/src/store/analysis/analysis.slice.ts @@ -0,0 +1,21 @@ +import { PayloadAction, createSlice } from '@reduxjs/toolkit' +import { AnalysisState } from 'models' + +const initialState: AnalysisState = { + period: 'month', +} + +type SetPeriodAction = PayloadAction<'month' | 'year'> +export type AnalysisActionTypes = SetPeriodAction + +export const analysisSlice = createSlice({ + name: 'analysis', + initialState, + reducers: { + setPeriod: (state, action: SetPeriodAction) => { + state.period = action.payload + }, + }, +}) + +export const { setPeriod } = analysisSlice.actions diff --git a/src/store/index.ts b/src/store/index.ts index 0f372642928a24fd1e6a6f136d015c39bf68cb5a..86778be0e4cf7153d83df50b67212d27d578cc22 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/react' import { Client } from 'cozy-client' import { + AnalysisState, ChallengeState, GlobalState, ModalState, @@ -19,6 +20,7 @@ import { import { composeWithDevTools } from 'redux-devtools-extension' import thunkMiddleware from 'redux-thunk' import { globalReducer } from 'store/global/global.reducer' +import { AnalysisActionTypes, analysisSlice } from './analysis/analysis.slice' import { ChallengeActionTypes } from './challenge/challenge.actions' import { challengeReducer } from './challenge/challenge.reducer' import { ChartActionTypes } from './chart/chart.actions' @@ -33,6 +35,7 @@ import { ProfileTypeActionTypes } from './profileType/profileType.actions' import { profileTypeReducer } from './profileType/profileType.reducer' export interface EcolyoState { + analysis: AnalysisState challenge: ChallengeState chart: ChartState global: GlobalState @@ -45,6 +48,7 @@ export interface EcolyoState { export const defaultAction = { type: null, payload: undefined } const ecolyoReducer = combineReducers({ + analysis: analysisSlice.reducer, challenge: challengeReducer, chart: chartReducer, global: globalReducer, @@ -61,6 +65,7 @@ export interface AppStore { // todo refactor types ? export type AppActionsTypes = + | AnalysisActionTypes | ChallengeActionTypes | ChartActionTypes | GlobalActionTypes diff --git a/src/styles/base/_typography.scss b/src/styles/base/_typography.scss index ed8a7ac92e3477d60ddab1c93b00455ad3281ffa..a0a743fb893995f6081059ed136c91c9bd35de1f 100644 --- a/src/styles/base/_typography.scss +++ b/src/styles/base/_typography.scss @@ -93,14 +93,6 @@ p { line-height: 120%; color: $grey-bright; } -.card-result { - font-family: $text-font; - font-style: normal; - font-weight: 900; - font-size: 1.75rem; - line-height: 120%; - color: $grey-bright; -} .card-indicator { font-family: $text-font; font-style: normal; diff --git a/src/styles/components/_card.scss b/src/styles/components/_card.scss index 5f37c3c5dc07455a2eae4387f044200bf829819d..4d079cef9976410cea493ff017194d90fce3a086 100644 --- a/src/styles/components/_card.scss +++ b/src/styles/components/_card.scss @@ -5,7 +5,6 @@ box-sizing: border-box; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.75); border-radius: 4px; - margin-top: 1rem; padding: 16px; &:hover { background: $grey-linear-gradient-background-hover; diff --git a/src/targets/services/consumptionAlert.ts b/src/targets/services/consumptionAlert.ts index 350fa89eb4dcdf36bc5f8345e7a3d84ba8382657..49d49ae03ebc767b89bbdaf36f3ebfd65adaf425 100644 --- a/src/targets/services/consumptionAlert.ts +++ b/src/targets/services/consumptionAlert.ts @@ -8,7 +8,7 @@ import ConsumptionService from 'services/consumption.service' import EnvironmentService from 'services/environment.service' import MailService from 'services/mail.service' import ProfileService from 'services/profile.service' -import { getPreviousMonthName } from 'utils/utils' +import { getMonthName } from 'utils/utils' import { runService } from './service' const consumptionLimit = require('notifications/consumptionLimit.hbs') @@ -86,7 +86,7 @@ const consumptionAlert = async ({ client }: ConsumptionAlertProps) => { clientUrl: `${appLink}/#/consumption/water`, unsubscribeUrl: `${appLink}/#/options`, userLimit: userProfil.waterDailyConsumptionLimit, - limitDate: `${alertDay.day} ${getPreviousMonthName(alertDay)}`, + limitDate: `${alertDay.day} ${getMonthName(alertDay)}`, consumption: lastDayValue, }) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4386dde74532ec779566ec7e83160797b1a96cab..aa81be0849f4563a172b3a56d97fe81ae29b36b0 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -134,7 +134,7 @@ export const importIconById = async (id: string, pathType: string) => { * @param date - DateTime * @returns month in french */ -export const getPreviousMonthName = (date: DateTime) => { +export const getMonthName = (date: DateTime) => { const monthNames = [ 'janvier', 'février', diff --git a/tests/__mocks__/store.ts b/tests/__mocks__/store.ts index 7ef4e9327ce072d9432cbd93db6e41646be51ede..ce4757c75bd9e136dcd653ca5d51f8e69ec3dccb 100644 --- a/tests/__mocks__/store.ts +++ b/tests/__mocks__/store.ts @@ -17,6 +17,7 @@ import { ScreenType } from 'enum/screen.enum' import { TimeStep } from 'enum/timeStep.enum' import { DateTime } from 'luxon' import { + AnalysisState, ChallengeState, ChartState, FluidStatus, @@ -294,20 +295,26 @@ export const mockInitialChallengeState: ChallengeState = { currentDataload: [], } +export const mockInitialAnalysisState: AnalysisState = { + period: 'month', +} + export const mockInitialEcolyoState: EcolyoState = { + analysis: mockInitialAnalysisState, + challenge: mockInitialChallengeState, + chart: mockInitialChartState, global: mockInitialGlobalState, + modal: mockInitialModalState, profile: mockInitialProfileState, profileType: mockInitialProfileTypeState, profileEcogesture: mockProfileEcogesture, - chart: mockInitialChartState, - modal: mockInitialModalState, - challenge: mockInitialChallengeState, } const middlewares = [thunkMiddleware.withExtraArgument({ mockClient })] const mockStore = configureStore< { ecolyo: { + analysis: AnalysisState challenge: ChallengeState chart: ChartState global: GlobalState