diff --git a/Dockerfile b/Dockerfile index 21dcf6be9eae54cdbe3986814f95d1563d28fb94..23abf5b04f68f2bbd74a521628371cb747b927d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:10.15.0 as builder +FROM node:10.15.0 AS builder ARG BUILD_CMD # Add application files diff --git a/src/components/Action/ActionOnGoing.tsx b/src/components/Action/ActionOnGoing.tsx index 44b7181c45e1c3b1e8d299d3bfc23094b80ddc22..ac6d3701ba2c51cb70a7dbf66422be73dfb3ea57 100644 --- a/src/components/Action/ActionOnGoing.tsx +++ b/src/components/Action/ActionOnGoing.tsx @@ -23,39 +23,38 @@ const ActionOnGoing: React.FC<ActionOnGoingProps> = ({ }, [setOpenEcogestureModal]) const setGradient = useCallback(() => { - if (userAction.startDate && userAction.ecogesture) { - const circle = 360 - const durationInDays: number = userAction.ecogesture.actionDuration - const ratio: number = circle / durationInDays - const progressionInDays = -Math.round( - userAction.startDate.diffNow('days').days - ) - const progress = ratio * progressionInDays - if (progress === 0) { - return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(110deg, #58ffff 50%, transparent 50%)` - } else if (progress === circle) { - return `linear-gradient(90deg, #58ffff 50%, #58ffff 50%)` - } else if (progress === circle / 2) { - return `linear-gradient(90deg, #121212 50%, #58ffff 50%)` - } else if (progress > circle / 2) { - if (durationInDays / 3 === 1) { - return `linear-gradient(${ - progress / 2 - }deg, transparent 50%, #58ffff 50%), - + if (!userAction.startDate || !userAction.ecogesture) return null + + const circle = 360 + const durationInDays = userAction.ecogesture.actionDuration + const ratio = circle / durationInDays + const progressionInDays = -Math.round( + userAction.startDate.diffNow('days').days + ) + const progress = ratio * progressionInDays + if (progress === 0) { + return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(110deg, #58ffff 50%, transparent 50%)` + } else if (progress === circle) { + return `linear-gradient(90deg, #58ffff 50%, #58ffff 50%)` + } else if (progress === circle / 2) { + return `linear-gradient(90deg, #121212 50%, #58ffff 50%)` + } else if (progress > circle / 2) { + if (durationInDays / 3 === 1) { + return `linear-gradient(${ + progress / 2 + }deg, transparent 50%, #58ffff 50%), linear-gradient(90deg, transparent 50%, #58ffff 50%)` - } else { - return `linear-gradient(90deg, transparent 50%, #58ffff 50%), + } else { + return `linear-gradient(90deg, transparent 50%, #58ffff 50%), linear-gradient(180deg, transparent 50%, #58ffff 50%)` - } - } else if (progress < circle / 2) { - if (durationInDays / 3 === 1) { - return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(240deg, #58ffff 50%, transparent 50%)` - } else { - return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(${ - progress * 2 - }deg, #58ffff 50%, transparent 50%)` - } + } + } else if (progress < circle / 2) { + if (durationInDays / 3 === 1) { + return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(240deg, #58ffff 50%, transparent 50%)` + } else { + return `linear-gradient(90deg, #121212 50%,transparent 50%), linear-gradient(${ + progress * 2 + }deg, #58ffff 50%, transparent 50%)` } } }, [userAction.startDate, userAction.ecogesture]) diff --git a/src/components/Analysis/AnalysisView.tsx b/src/components/Analysis/AnalysisView.tsx index 55dd0fcc29038ebaa1fb8b5549cfb6a6f107d9a1..110099f61ae6a1f5a64328e6e15d286ba815ebea 100644 --- a/src/components/Analysis/AnalysisView.tsx +++ b/src/components/Analysis/AnalysisView.tsx @@ -36,14 +36,13 @@ const AnalysisView: React.FC = () => { const query = new URLSearchParams(search) const paramToken = query.get('token') - // Scroll handling const app = document.querySelector('.app-content') const [scrollPosition, setScrollPosition] = useState(0) + /** Scroll handling for switching months and staying on the same scroll position */ const saveLastScrollPosition = useCallback(() => { if (app) { - const position = - app.scrollTop > window.pageYOffset ? app.scrollTop : window.pageYOffset + const position = Math.max(app.scrollTop, window.scrollY) setScrollPosition(position) } }, [app]) diff --git a/src/components/Analysis/ElecHalfHourChart.tsx b/src/components/Analysis/ElecHalfHourChart.tsx index f24bc9e81a17d8a907a1a98305094e6fd79c81d0..576423c4131c9bb016ef06ab882c983c6f623b90 100644 --- a/src/components/Analysis/ElecHalfHourChart.tsx +++ b/src/components/Analysis/ElecHalfHourChart.tsx @@ -1,12 +1,13 @@ import AxisBottom from 'components/Charts/AxisBottom' import AxisRight from 'components/Charts/AxisRight' import Bar from 'components/Charts/Bar' +import { useChartResize } from 'components/Hooks/useChartResize' import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale' import { FluidType } from 'enum/fluid.enum' import { TimeStep } from 'enum/timeStep.enum' import { DateTime } from 'luxon' import { Dataload } from 'models' -import React, { useEffect, useRef, useState } from 'react' +import React, { useRef } from 'react' import './elecHalfHourMonthlyAnalysis.scss' interface ElecHalfHourChartProps { @@ -15,22 +16,18 @@ interface ElecHalfHourChartProps { } const ElecHalfHourChart = ({ dataLoad, isWeekend }: ElecHalfHourChartProps) => { - const [width, setWidth] = useState<number>(0) - const [height, setHeight] = useState<number>(0) const chartContainer = useRef<HTMLDivElement>(null) + const { height, width } = useChartResize(chartContainer, 170, 940) const marginLeft = 10 const marginRight = 60 const marginTop = 0 const marginBottom = 30 - const getContentWidth = () => { - return width - marginLeft - marginRight - } + const getContentWidth = () => width - marginLeft - marginRight + const getContentHeight = () => height - marginTop - marginBottom - const getContentHeight = () => { - return height - marginTop - marginBottom - } const getMaxLoad = () => { - return dataLoad ? Math.max(...dataLoad.map((d: Dataload) => d.value)) : 0 + if (!dataLoad || dataLoad.length === 0) return 0 + return Math.max(...dataLoad.map(({ value }) => value)) } const xScale: ScaleBand<string> = scaleBand() @@ -46,28 +43,6 @@ const ElecHalfHourChart = ({ dataLoad, isWeekend }: ElecHalfHourChartProps) => { .domain([0, getMaxLoad()]) .range([getContentHeight(), 0]) - useEffect(() => { - function handleResize() { - const maxWidth = 940 - const maxHeight = 170 - const _width = chartContainer.current - ? chartContainer.current.offsetWidth > maxWidth - ? maxWidth - : chartContainer.current.offsetWidth - : 400 - setWidth(_width) - const _height = chartContainer.current - ? chartContainer.current.offsetHeight > maxHeight - ? maxHeight - : chartContainer.current.offsetHeight - : 200 - setHeight(_height) - } - handleResize() - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, []) - return ( <div className="graph-elec-half-hour" ref={chartContainer}> <svg width={width} height={height}> @@ -80,25 +55,23 @@ const ElecHalfHourChart = ({ dataLoad, isWeekend }: ElecHalfHourChartProps) => { isAnalysis={true} /> <g transform={`translate(${10},${0})`}> - {dataLoad.map((value, index) => { - return ( - <Bar - key={index} - index={index} - dataload={value} - compareDataload={null} - fluidType={FluidType.ELECTRICITY} - timeStep={TimeStep.HALF_AN_HOUR} - showCompare={false} - xScale={xScale} - yScale={yScale} - height={getContentHeight()} - isSwitching={false} - isDuel={false} - weekdays={isWeekend ? 'weekend' : 'week'} - /> - ) - })} + {dataLoad.map((value, index) => ( + <Bar + key={index} + index={index} + dataload={value} + compareDataload={null} + fluidType={FluidType.ELECTRICITY} + timeStep={TimeStep.HALF_AN_HOUR} + showCompare={false} + xScale={xScale} + yScale={yScale} + height={getContentHeight()} + isSwitching={false} + isDuel={false} + weekdays={isWeekend ? 'weekend' : 'week'} + /> + ))} </g> <AxisBottom data={dataLoad} diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx index 625f7a4f106fd009eea99914a91d020448ea81e8..f053445d7ba81e1aa62751773716f4faf97c4822 100644 --- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx +++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis.tsx @@ -21,8 +21,8 @@ import ConsumptionService from 'services/consumption.service' import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData.service' import FluidPricesService from 'services/fluidsPrices.service' import { getNavPicto } from 'utils/picto' -import './elecHalfHourMonthlyAnalysis.scss' import ElecInfoModal from './ElecInfoModal' +import './elecHalfHourMonthlyAnalysis.scss' interface ElecHalfHourMonthlyAnalysisProps { analysisDate: DateTime @@ -34,19 +34,19 @@ const ElecHalfHourMonthlyAnalysis: React.FC< > = ({ analysisDate, perfIndicator }: ElecHalfHourMonthlyAnalysisProps) => { const { t } = useI18n() const client = useClient() - const [isWeekend, setisWeekend] = useState<boolean>(true) - const [isHalfHourActivated, setisHalfHourActivated] = useState<boolean>(true) - const [isLoading, setisLoading] = useState<boolean>(true) + const [isWeekend, setIsWeekend] = useState<boolean>(true) + const [isHalfHourActivated, setIsHalfHourActivated] = useState<boolean>(true) + const [isLoading, setIsLoading] = useState<boolean>(true) const [monthDataloads, setMonthDataloads] = useState<AggregatedEnedisMonthlyDataloads>() - const [enedisAnalysisValues, setenedisAnalysisValues] = + const [enedisAnalysisValues, setEnedisAnalysisValues] = useState<EnedisMonthlyAnalysisData>() const [facturePercentage, setFacturePercentage] = useState<number>() const [elecPrice, setElecPrice] = useState<FluidPrice>() const [openInfoModal, setOpenInfoModal] = useState<boolean>(false) const handleChangeWeek = useCallback(() => { - setisWeekend(prev => !prev) + setIsWeekend(prev => !prev) }, []) const toggleOpenModal = useCallback(() => { @@ -78,38 +78,36 @@ const ElecHalfHourMonthlyAnalysis: React.FC< useEffect(() => { let subscribed = true - async function getEnedisAnalysisData() { + async function fetchEnedisAnalysisData() { const cs = new ConsumptionService(client) - const activateHalfHourLoad = await cs.checkDoctypeEntries( + const isHalfHourLoadActivated = await cs.checkDoctypeEntries( FluidType.ELECTRICITY, TimeStep.HALF_AN_HOUR ) - if (subscribed) { - if (activateHalfHourLoad) { - const emas = new EnedisMonthlyAnalysisDataService(client) - const aggegatedDate = analysisDate.minus({ month: 1 }) - const data: EnedisMonthlyAnalysisData[] = - await emas.getEnedisMonthlyAnalysisByDate( - aggegatedDate.year, - aggegatedDate.month - ) - if (subscribed && data?.length) { - const aggregatedData = emas.aggregateValuesToDataLoad(data[0]) - setenedisAnalysisValues(data[0]) - setMonthDataloads(aggregatedData) - if (data[0].minimumLoad && perfIndicator.value && subscribed) { - const percentage = - (data[0].minimumLoad / perfIndicator.value) * 100 - setFacturePercentage(percentage) - } + if (!subscribed) return + if (isHalfHourLoadActivated) { + const emas = new EnedisMonthlyAnalysisDataService(client) + const aggregatedDate = analysisDate.minus({ month: 1 }) + const data = await emas.getEnedisMonthlyAnalysisByDate( + aggregatedDate.year, + aggregatedDate.month + ) + if (data?.length) { + const aggregatedData = emas.aggregateValuesToDataLoad(data[0]) + setEnedisAnalysisValues(data[0]) + setMonthDataloads(aggregatedData) + if (data[0].minimumLoad && perfIndicator.value) { + const percentage = (data[0].minimumLoad / perfIndicator.value) * 100 + setFacturePercentage(percentage) } - } else { - setisHalfHourActivated(false) } - setisLoading(false) + } else { + setIsHalfHourActivated(false) } + + setIsLoading(false) } - getEnedisAnalysisData() + fetchEnedisAnalysisData() return () => { subscribed = false diff --git a/src/components/Analysis/MonthlyAnalysis.tsx b/src/components/Analysis/MonthlyAnalysis.tsx index da0110f451c03eb8298f492e630dc230b74b7d12..728d50ed0a548a3d6c3738b82dc2ea67624095c4 100644 --- a/src/components/Analysis/MonthlyAnalysis.tsx +++ b/src/components/Analysis/MonthlyAnalysis.tsx @@ -15,8 +15,8 @@ import AnalysisConsumption from './AnalysisConsumption' import AnalysisErrorModal from './AnalysisErrorModal' import ElecHalfHourMonthlyAnalysis from './ElecHalfHourMonthlyAnalysis' import MaxConsumptionCard from './MaxConsumptionCard' -import './monthlyanalysis.scss' import TotalAnalysisChart from './TotalAnalysisChart' +import './monthlyanalysis.scss' interface MonthlyAnalysisProps { analysisDate: DateTime @@ -74,11 +74,10 @@ const MonthlyAnalysis: React.FC<MonthlyAnalysisProps> = ({ if (fetchedPerformanceIndicators) { setPerformanceIndicators(fetchedPerformanceIndicators) setLoadAnalysis(false) - for (let i = 0; i < fetchedPerformanceIndicators.length; i++) { - if (fetchedPerformanceIndicators[i]?.value) { - setLoadAnalysis(true) - } - } + const hasValidPI = fetchedPerformanceIndicators.some( + pi => pi?.value + ) + if (hasValidPI) setLoadAnalysis(true) setAggregatedPerformanceIndicators( performanceIndicatorService.aggregatePerformanceIndicators( diff --git a/src/components/Challenge/ChallengeCardLocked.spec.tsx b/src/components/Challenge/ChallengeCardLocked.spec.tsx index a3d3f4d67acf4f9a1c9f117be6444c18ceb95e43..e1a908384465a0664bf0cbaeaea20359df89cb31 100644 --- a/src/components/Challenge/ChallengeCardLocked.spec.tsx +++ b/src/components/Challenge/ChallengeCardLocked.spec.tsx @@ -12,7 +12,7 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => { }), } }) -// TODO fis MUI theme error + describe('ChallengeCardLocked component', () => { it('should be rendered correctly', () => { const component = shallow( diff --git a/src/components/Challenge/ChallengeCardOnGoing.tsx b/src/components/Challenge/ChallengeCardOnGoing.tsx index 6fd07442333c2af42f1dfdbea61e479f6ee55560..314d7cf52dc33ec0be9048f7c7454edf808e8935 100644 --- a/src/components/Challenge/ChallengeCardOnGoing.tsx +++ b/src/components/Challenge/ChallengeCardOnGoing.tsx @@ -3,7 +3,6 @@ import circleChecked from 'assets/icons/visu/challenge/circleChecked.svg' import circleUnchecked from 'assets/icons/visu/challenge/circleUnchecked.svg' import circleStar from 'assets/icons/visu/duel/circleStar.svg' import defaultIcon from 'assets/icons/visu/duel/default.svg' -import duelLocked from 'assets/icons/visu/duel/locked.svg' import classNames from 'classnames' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import Loader from 'components/Loader/Loader' @@ -22,9 +21,9 @@ import ChallengeService from 'services/challenge.service' import { AppActionsTypes, AppStore } from 'store' import { updateUserChallengeList } from 'store/challenge/challenge.actions' import { getChallengeTitleWithLineReturn, importIconById } from 'utils/utils' -import './challengeCardOnGoing.scss' import ChallengeNoFluidModal from './ChallengeNoFluidModal' import StarsContainer from './StarsContainer' +import './challengeCardOnGoing.scss' interface ChallengeCardOnGoingProps { userChallenge: UserChallenge @@ -44,10 +43,16 @@ const ChallengeCardOnGoing: React.FC<ChallengeCardOnGoingProps> = ({ challenge: { currentDataload }, global: { fluidTypes, fluidStatus }, } = useSelector((state: AppStore) => state.ecolyo) + const { + progress: { actionProgress, explorationProgress, quizProgress }, + target, + duel, + } = userChallenge const toggleNoFluidModal = useCallback(() => { setIsOneFluidUp(prev => !prev) }, []) + const goDuel = async () => { setIsLoading(true) // Check if at least one fluid is up @@ -69,6 +74,7 @@ const ChallengeCardOnGoing: React.FC<ChallengeCardOnGoingProps> = ({ toggleNoFluidModal() } } + const goQuiz = async () => { if (userChallenge.quiz.state !== UserQuizState.ONGOING) { const challengeService = new ChallengeService(client) @@ -80,10 +86,12 @@ const ChallengeCardOnGoing: React.FC<ChallengeCardOnGoingProps> = ({ } if (userChallenge.progress.quizProgress !== 5) navigate('/challenges/quiz') } + const goExploration = () => { if (userChallenge.progress.explorationProgress !== 5) navigate('/challenges/exploration') } + const goAction = () => { if (userChallenge.progress.actionProgress !== 5) navigate('/challenges/action') @@ -144,142 +152,167 @@ const ChallengeCardOnGoing: React.FC<ChallengeCardOnGoingProps> = ({ } }, [client, currentDataload, userChallenge, dispatch]) - return ( - <div className="cardContent onGoing"> - <div className="titleBlock"> - <span className="challengeTitle"> - {getChallengeTitleWithLineReturn(userChallenge.id)} - </span> + const quizButton = () => ( + <button + title={t('challenge.card.ongoing.quiz')} + tabIndex={userChallenge.progress.quizProgress === 5 ? -1 : 0} + className={classNames('smallCard', { + ['finished']: userChallenge.progress.quizProgress === 5, + })} + onClick={goQuiz} + > + <StyledIcon + className="cardIcon" + icon={ + userChallenge.progress.quizProgress === 5 + ? circleChecked + : circleUnchecked + } + size={25} + /> + <div className="content"> + <span>{t('challenge.card.ongoing.quiz')}</span> + <StarsContainer result={userChallenge.progress.quizProgress} /> </div> - <button - title={t('challenge.card.ongoing.quiz')} - tabIndex={userChallenge.progress.quizProgress === 5 ? -1 : 0} - className={classNames('smallCard', { - ['finished']: userChallenge.progress.quizProgress === 5, - })} - onClick={goQuiz} - > - <StyledIcon - className="cardIcon" - icon={ - userChallenge.progress.quizProgress === 5 - ? circleChecked - : circleUnchecked - } - size={25} - /> - <div className="content"> - <span>{t('challenge.card.ongoing.quiz')}</span> - <StarsContainer result={userChallenge.progress.quizProgress} /> - </div> - </button> - <button - title={t('challenge.card.ongoing.exploration')} - tabIndex={userChallenge.progress.explorationProgress === 5 ? -1 : 0} - className={classNames('smallCard explorationCard', { - ['finished']: userChallenge.progress.explorationProgress === 5, - })} - onClick={goExploration} - > - <StyledIcon - className="cardIcon" - icon={ - userChallenge.progress.explorationProgress === 5 - ? circleChecked - : circleUnchecked - } - size={25} - /> - {userChallenge.exploration.state === - UserExplorationState.NOTIFICATION && ( - <div className="notifChallenge">1</div> - )} - <div className="content"> - <span>{t('challenge.card.ongoing.exploration')}</span> - <StarsContainer result={userChallenge.progress.explorationProgress} /> - </div> - </button> - <button - title={t('challenge.card.ongoing.action')} - tabIndex={userChallenge.progress.actionProgress === 5 ? -1 : 0} - className={classNames('smallCard actionCard', { - ['finished']: userChallenge.progress.actionProgress === 5, - })} - onClick={goAction} - > - <StyledIcon - className="cardIcon" - icon={ - userChallenge.progress.actionProgress === 5 - ? circleChecked - : circleUnchecked - } - size={25} - /> - {userChallenge.action.state === UserActionState.NOTIFICATION && ( - <div className="notifChallenge">1</div> - )} - <div className="content"> - <span>{t('challenge.card.ongoing.action')}</span> - <StarsContainer result={userChallenge.progress.actionProgress} /> - </div> - </button> - {(userChallenge.progress.actionProgress + - userChallenge.progress.explorationProgress + - userChallenge.progress.quizProgress >= - userChallenge.target && - userChallenge.duel.state === UserDuelState.UNLOCKED) || - userChallenge.duel.state === UserDuelState.NO_REF_PERIOD_VALID ? ( - <button className="smallCard goDuel" onClick={goDuel}> - {isLoading ? ( - <div className="spinner-container"> - <Loader color="black" /> - </div> - ) : ( - <> - {t('challenge.card.ongoing.duel')} - <StyledIcon - className="challengeminIcon" - icon={challengeIcon} - size={60} - /> - </> - )} - </button> - ) : userChallenge.duel.state === UserDuelState.ONGOING && !isDone ? ( - <div className={'smallCard duelCard active'} onClick={goDuel}> - <div className="finalDuel"> - <span>{t('challenge.card.ongoing.duel')}</span> - <p className="starCount"> - <span className="blueNumber">{`${userChallenge.duel.userConsumption} € `}</span> - <span>{` / ${userChallenge.duel.threshold} €`}</span> - </p> - </div> - <StyledIcon className="circleStar" icon={challengeIcon} size={60} /> + </button> + ) + + const explorationButton = () => ( + <button + title={t('challenge.card.ongoing.exploration')} + tabIndex={userChallenge.progress.explorationProgress === 5 ? -1 : 0} + className={classNames('smallCard explorationCard', { + ['finished']: userChallenge.progress.explorationProgress === 5, + })} + onClick={goExploration} + > + <StyledIcon + className="cardIcon" + icon={ + userChallenge.progress.explorationProgress === 5 + ? circleChecked + : circleUnchecked + } + size={25} + /> + {userChallenge.exploration.state === + UserExplorationState.NOTIFICATION && ( + <div className="notifChallenge">1</div> + )} + <div className="content"> + <span>{t('challenge.card.ongoing.exploration')}</span> + <StarsContainer result={userChallenge.progress.explorationProgress} /> + </div> + </button> + ) + + const actionButton = () => ( + <button + title={t('challenge.card.ongoing.action')} + tabIndex={userChallenge.progress.actionProgress === 5 ? -1 : 0} + className={classNames('smallCard actionCard', { + ['finished']: userChallenge.progress.actionProgress === 5, + })} + onClick={goAction} + > + <StyledIcon + className="cardIcon" + icon={ + userChallenge.progress.actionProgress === 5 + ? circleChecked + : circleUnchecked + } + size={25} + /> + {userChallenge.action.state === UserActionState.NOTIFICATION && ( + <div className="notifChallenge">1</div> + )} + <div className="content"> + <span>{t('challenge.card.ongoing.action')}</span> + <StarsContainer result={userChallenge.progress.actionProgress} /> + </div> + </button> + ) + + const duelButton = ( + <button className="smallCard goDuel" onClick={goDuel}> + {isLoading ? ( + <div className="spinner-container"> + <Loader color="black" /> </div> - ) : userChallenge.duel.state === UserDuelState.ONGOING && isDone ? ( - <div className={'smallCard duelCard active'} onClick={goDuel}> + ) : ( + <> + {t('challenge.card.ongoing.duel')} + <StyledIcon + className="challengeminIcon" + icon={challengeIcon} + size={60} + /> + </> + )} + </button> + ) + + const duelCard = (content: JSX.Element, extraClassName = '') => ( + <div className={`smallCard duelCard ${extraClassName}`} onClick={goDuel}> + {content} + <StyledIcon className="circleStar" icon={challengeIcon} size={60} /> + </div> + ) + + const duelContainer = () => { + if ( + duel.state === UserDuelState.NO_REF_PERIOD_VALID || + (actionProgress + explorationProgress + quizProgress >= target && + duel.state === UserDuelState.UNLOCKED) + ) { + return duelButton + } else if (duel.state === UserDuelState.ONGOING && !isDone) { + return duelCard( + <div className="finalDuel"> + <span>{t('challenge.card.ongoing.duel')}</span> + <p className="starCount"> + <span className="blueNumber">{`${duel.userConsumption} € `}</span> + <span>{` / ${duel.threshold} €`}</span> + </p> + </div>, + 'active' + ) + } else if (duel.state === UserDuelState.ONGOING && isDone) { + return duelCard( + <> <div className="finalDuel result"> <span>{t('challenge.card.ongoing.result')}</span> <span>{t('challenge.card.ongoing.duelDone')}</span> </div> - <StyledIcon className="duelLocked" icon={challengeIcon} size={60} /> <div className="notifChallenge">1</div> - </div> - ) : ( - <div className={'smallCard duelCard'}> - <p className="starCount"> - <StyledIcon icon={circleStar} size={30} /> - <span className="blueNumber">{`${ - userChallenge.progress.quizProgress + - userChallenge.progress.explorationProgress + - userChallenge.progress.actionProgress - } `}</span> - <span>{` / ${userChallenge.target}`}</span> - </p> - <StyledIcon className="duelLocked" icon={duelLocked} size={60} /> - </div> - )} + </>, + 'active' + ) + } else { + return duelCard( + <p className="starCount"> + <StyledIcon icon={circleStar} size={30} /> + <span className="blueNumber"> + {quizProgress + explorationProgress + actionProgress} + </span> + <span>{` / ${target}`}</span> + </p> + ) + } + } + + return ( + <div className="cardContent onGoing"> + <div className="titleBlock"> + <span className="challengeTitle"> + {getChallengeTitleWithLineReturn(userChallenge.id)} + </span> + </div> + {quizButton()} + {explorationButton()} + {actionButton()} + {duelContainer()} <ChallengeNoFluidModal open={!isOneFluidUp} handleCloseClick={toggleNoFluidModal} diff --git a/src/components/Challenge/ChallengeView.spec.tsx b/src/components/Challenge/ChallengeView.spec.tsx index 69768b00c7885b39864b855b745c867689018fe5..1410759a78c71acb190803b725d7442d14ba91d3 100644 --- a/src/components/Challenge/ChallengeView.spec.tsx +++ b/src/components/Challenge/ChallengeView.spec.tsx @@ -23,14 +23,10 @@ jest.mock('components/Header/Header', () => 'mock-header') jest.mock('components/Content/Content', () => 'mock-content') jest.mock('components/Challenge/ChallengeCard', () => 'mock-challengecard') -// TODO unused ? -const useSelectorSpy = jest.spyOn(reactRedux, 'useSelector') - describe('ChallengeView component', () => { const store = createMockEcolyoStore() beforeEach(() => { store.clearActions() - useSelectorSpy.mockClear() }) it('should be rendered correctly', () => { diff --git a/src/components/Charts/AxisBottom.tsx b/src/components/Charts/AxisBottom.tsx index 4ad15f3dc452e010bc3c62f4926141ef49146797..a34c45c90762665f3b0dfa789ec891e81784a938 100644 --- a/src/components/Charts/AxisBottom.tsx +++ b/src/components/Charts/AxisBottom.tsx @@ -52,10 +52,10 @@ function TextAxis({ </tspan> </text> ) - case TimeStep.DAY: - return ( - <text y="10" dy="0.71em" transform={`translate(${width})`}> - {displayAllDays ? ( + case TimeStep.DAY: { + const renderText = () => { + if (displayAllDays) { + return ( <> <tspan className={style} x="0" textAnchor="middle"> {dataload.date.toLocaleString({ weekday: 'narrow' })} @@ -64,7 +64,9 @@ function TextAxis({ {dataload.date.toLocaleString({ day: 'numeric' })} </tspan> </> - ) : dataload.date.weekday === 1 ? ( + ) + } else if (dataload.date.weekday === 1) { + return ( <> <tspan className={style} x="0" textAnchor="middle"> {capitalize( @@ -77,9 +79,17 @@ function TextAxis({ {dataload.date.toLocaleString({ day: 'numeric' })} </tspan> </> - ) : null} + ) + } + return null + } + return ( + <text y="10" dy="0.71em" transform={`translate(${width})`}> + {renderText()} </text> ) + } + case TimeStep.WEEK: return ( <text y="10" dy="0.71em" transform={`translate(${width})`}> diff --git a/src/components/Charts/Bar.tsx b/src/components/Charts/Bar.tsx index 06ff85fdb1968737121662015cb7bb0cbd3e6e4f..cff62d808a3f8a4202478845fd89689bdddaca28 100644 --- a/src/components/Charts/Bar.tsx +++ b/src/components/Charts/Bar.tsx @@ -90,39 +90,47 @@ const Bar = ({ dataload.date ) - const barClass = clicked - ? `bar-${fluidStyle} ${weekdays} selected bounce-${ - browser && browser.name !== 'edge' ? '2' : '3' - } delay` - : isSelectedDate - ? animationEnded - ? `bar-${fluidStyle} ${weekdays} selected` - : `bar-${fluidStyle} ${weekdays} selected bounce-${ - browser && browser.name !== 'edge' ? '1' : '3' - } delay--${index % 13}` - : animationEnded - ? `bar-${fluidStyle} ${weekdays}` - : `bar-${fluidStyle} ${weekdays} bounce-${ - browser && browser.name !== 'edge' ? '1' : '3' - } delay--${index % 13}` + const delayIndex = index % 13 + const edgeBrowser = browser && browser.name !== 'edge' + const selected = isSelectedDate ? ' selected' : '' + const bounce = edgeBrowser ? '1' : '3' - const barBackgroundClass = isSelectedDate + const getBarClass = () => { + const bounceDelay = ` bounce-${bounce} delay--${delayIndex}` + const fluidWeekdays = `bar-${fluidStyle} ${weekdays}` + + if (clicked) { + return `${fluidWeekdays}${selected} bounce-2 delay` + } else if (animationEnded) { + return `${fluidWeekdays}${selected}` + } + return `${fluidWeekdays}${bounceDelay}${selected}` + } + + const getCompareBarClass = () => { + const bounceValue = clicked ? (edgeBrowser ? '2' : '3') : bounce + const bounceDelay = ` bounce-${bounceValue} delay--${ + clicked ? 0 : delayIndex + }` + const fluidStyleClass = `bar-compare-${fluidStyle}` - const compareBarClass = clicked - ? `bar-compare-${fluidStyle} selected bounce-${ - browser && browser.name !== 'edge' ? '2' : '3' - } delay--0` - : isSelectedDate - ? compareAnimationEnded - ? `bar-compare-${fluidStyle} selected` - : `bar-compare-${fluidStyle} selected bounce-${ - browser && browser.name !== 'edge' ? '1' : '3' - } delay--${index % 13}` - : compareAnimationEnded - ? `bar-compare-${fluidStyle} ` - : `bar-compare-${fluidStyle} bounce-${ - browser && browser.name !== 'edge' ? '1' : '3' - } delay--${index % 13}` + if (clicked) { + return `${fluidStyleClass} ${selected}${bounceDelay}` + } + if (isSelectedDate) { + return compareAnimationEnded + ? `${fluidStyleClass} ${selected}` + : `${fluidStyleClass} ${selected}${bounceDelay}` + } + return compareAnimationEnded + ? fluidStyleClass + : `${fluidStyleClass} ${bounceDelay}` + } + + const compareBarClass = getCompareBarClass() + const barClass = getBarClass() + + const barBackgroundClass = isSelectedDate const getBandWidth = (): number => { return showCompare ? xScale.bandwidth() / 2 : xScale.bandwidth() diff --git a/src/components/Charts/UncomingBar.tsx b/src/components/Charts/UncomingBar.tsx index 9e11a1b3af7dfba3954ebae2ce6267db42c0d186..9a8ae0f69fe023a36ffc6103aba489f7e59d17b6 100644 --- a/src/components/Charts/UncomingBar.tsx +++ b/src/components/Charts/UncomingBar.tsx @@ -28,30 +28,30 @@ const UncomingBar = ({ setAnimationEnded(true) } - const barClass = animationEnded - ? `bar-UNCOMING ` - : `bar-UNCOMING bounce-${browser?.name !== 'edge' ? '1' : '3'} delay--${ - index % 13 - }` + const barClass = `bar-UNCOMING ${ + animationEnded + ? '' + : `bounce-${browser?.name !== 'edge' ? '1' : '3'} delay--${index % 13}` + }` const getBandWidth = (): number => { return xScale.bandwidth() } const topRoundedRectDashedLine = ( - _x: number, - _y: number, - _width: number, - _height: number + x: number, + y: number, + width: number, + height: number ): string => { - const radius = _height > 4 ? 4 : _height / 4 + const radius = height > 4 ? 4 : height / 4 return ( 'M' + - _x + + x + ',' + - (_y + radius + (_height - radius)) + + (y + radius + (height - radius)) + 'v-' + - (_height - radius) + + (height - radius) + ' a' + radius + ',' + @@ -61,7 +61,7 @@ const UncomingBar = ({ ',' + -radius + 'h' + - (_width - 2 * radius) + + (width - 2 * radius) + 'a' + radius + ',' + @@ -71,13 +71,13 @@ const UncomingBar = ({ ',' + radius + 'v' + - (_height - radius) + (height - radius) ) } return ( <g> - {height > 0 ? ( + {height > 0 && ( <g transform={`translate(${xScale( dataload.date.toLocaleString(DateTime.DATETIME_SHORT) @@ -92,8 +92,8 @@ const UncomingBar = ({ fill="#E0E0E0" /> </g> - ) : null} - {height > 0 && dataload.value && dataload.value >= 0 ? ( + )} + {height > 0 && dataload.value >= 0 && ( <g transform={`translate(${xScale( dataload.date.toLocaleString(DateTime.DATETIME_SHORT) @@ -113,7 +113,7 @@ const UncomingBar = ({ className={barClass} /> </g> - ) : null} + )} </g> ) } diff --git a/src/components/Connection/ConnectionResult.tsx b/src/components/Connection/ConnectionResult.tsx index f41fa87fe37227f2d43b89aff24cbc34e51f1185..9d24e0a0946ccad8efcef0b85eeed225753ea61e 100644 --- a/src/components/Connection/ConnectionResult.tsx +++ b/src/components/Connection/ConnectionResult.tsx @@ -22,12 +22,12 @@ import TriggerService from 'services/triggers.service' import { AppActionsTypes } from 'store' import { setShouldRefreshConsent, - updatedFluidConnection, updateSgeStore, + updatedFluidConnection, } from 'store/global/global.actions' import { getKonnectorUpdateError } from 'utils/utils' -import './connectionResult.scss' import DeleteGRDFAccountModal from './DeleteGRDFAccountModal' +import './connectionResult.scss' interface ConnectionResultProps { fluidStatus: FluidStatus @@ -182,6 +182,57 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ konnectorError === KonnectorUpdate.ERROR_CONSENT_FORM_GAS || konnectorError === KonnectorUpdate.ERROR_UPDATE_OAUTH + /** + * Get Konnector state, possible values: + * * partner maintenance + * * error state + * * outdated + * * last update date + */ + const getConnectionStatus = () => { + // First check if there is partner error from backoffice + if (fluidStatus.maintenance) { + return ( + <div className="connection-caption text-16-normal"> + <div className="text-16-normal"> + <div className="connection-caption"> + {t('konnector_form.wait_end_issue')} + </div> + </div> + </div> + ) + } + // Else check if konnector is in error state + if (status === 'errored') { + return ( + <DisplayKonnectorErrorState + konnectorError={konnectorError} + consentRelatedError={consentError} + lastExecutionDate={lastExecutionDate} + fluidConcerned={getFluidTypeTranslation(fluidType)} + /> + ) + } + // Else check if data is outdated + if (outDatedDataDays) { + return ( + <DisplayDataOutdated + fluidStatus={fluidStatus} + fluidType={fluidType} + lastExecutionDate={lastExecutionDate} + hasUpdatedToday={hasUpdatedToday()} + /> + ) + } + // Default to displaying the last update date + return ( + <DisplayLastUpdateDate + fluidType={fluidType} + lastExecutionDate={lastExecutionDate} + /> + ) + } + return ( <div className="connection-update-result"> <div @@ -191,37 +242,7 @@ const ConnectionResult: React.FC<ConnectionResultProps> = ({ : '' } > - {fluidStatus.maintenance ? ( - // First check if there is partner error from backoffice - <div className="connection-caption text-16-normal"> - <div className="text-16-normal"> - <div className="connection-caption"> - {t('konnector_form.wait_end_issue')} - </div> - </div> - </div> - ) : status === 'errored' ? ( - // Else check if konnector is in error state - <DisplayKonnectorErrorState - konnectorError={konnectorError} - consentRelatedError={consentError} - lastExecutionDate={lastExecutionDate} - fluidConcerned={getFluidTypeTranslation(fluidType)} - /> - ) : outDatedDataDays ? ( - // Else check if data is outdated - <DisplayDataOutdated - fluidStatus={fluidStatus} - fluidType={fluidType} - lastExecutionDate={lastExecutionDate} - hasUpdatedToday={hasUpdatedToday()} - /> - ) : ( - <DisplayLastUpdateDate - fluidType={fluidType} - lastExecutionDate={lastExecutionDate} - /> - )} + {getConnectionStatus()} </div> <div className="inline-buttons"> {!consentError && ( diff --git a/src/components/Connection/FormLogin.tsx b/src/components/Connection/FormLogin.tsx index cf25a9a3e620c40f79c09e1e41a8cba49d27c749..68434000785ce984f0c2e2c1fc499c531ba4dd6f 100644 --- a/src/components/Connection/FormLogin.tsx +++ b/src/components/Connection/FormLogin.tsx @@ -38,7 +38,7 @@ const FormLogin: React.FC<FormLoginProps> = ({ ) const changeLogin = (value: string) => { - if ((/[0-9]/.test(value) && value.length <= 7) || value === '') { + if ((/\d/.test(value) && value.length <= 7) || value === '') { setError('') setLogin(value) } diff --git a/src/components/Connection/SGEConnect/SgeConnectView.tsx b/src/components/Connection/SGEConnect/SgeConnectView.tsx index 8a78148b0ff6eec6a04f93c754b8104d4935d016..fbde3f636945b73f067a423f7da8551ab78f0525 100644 --- a/src/components/Connection/SGEConnect/SgeConnectView.tsx +++ b/src/components/Connection/SGEConnect/SgeConnectView.tsx @@ -100,10 +100,11 @@ const SgeConnectView: React.FC = () => { value: string | boolean | number, maxLength?: number ) => { + // TODO duplicate with Form login input if ( !maxLength || value === '' || - (/[0-9]/.test(value.toString()) && value.toString().length <= maxLength) + (/\d/.test(value.toString()) && value.toString().length <= maxLength) ) { const updatedState = { ...currentSgeState, diff --git a/src/components/Duel/DuelOngoing.tsx b/src/components/Duel/DuelOngoing.tsx index 80ad04c31c9cd32b24b0c834dc588f85e1a15389..56de0d8ce5a2826d3cb80f34598daf92d53aa408 100644 --- a/src/components/Duel/DuelOngoing.tsx +++ b/src/components/Duel/DuelOngoing.tsx @@ -5,6 +5,7 @@ import StyledIcon from 'components/CommonKit/Icon/StyledIcon' import DuelChart from 'components/Duel/DuelChart' import DuelResultModal from 'components/Duel/DuelResultModal' import LastDuelModal from 'components/Duel/lastDuelModal' +import { useChartResize } from 'components/Hooks/useChartResize' import { Client, useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { UsageEventType } from 'enum/usageEvent.enum' @@ -54,10 +55,9 @@ const DuelOngoing: React.FC<DuelOngoingProps> = ({ const [resultModal, setResultModal] = useState<boolean>(false) const [winChallenge, setWinChallenge] = useState<boolean>(false) const [isLastDuel, setIsLastDuel] = useState<boolean>(false) - const [width, setWidth] = useState<number>(0) - const [height, setHeight] = useState<number>(0) const [finishedDataLoad, setFinishedDataLoad] = useState<Dataload[]>() const chartContainer = useRef<HTMLDivElement>(null) + const { height, width } = useChartResize(chartContainer) const challengeService = useMemo(() => new ChallengeService(client), [client]) const duel: UserDuel = userChallenge.duel @@ -114,28 +114,6 @@ const DuelOngoing: React.FC<DuelOngoingProps> = ({ navigate('/challenges') }, [navigate]) - useEffect(() => { - function handleResize() { - const maxWidth = 940 - const maxHeight = 300 - const _width = chartContainer.current - ? chartContainer.current.offsetWidth > maxWidth - ? maxWidth - : chartContainer.current.offsetWidth - : 400 - setWidth(_width) - const _height = chartContainer.current - ? chartContainer.current.offsetHeight > maxHeight - ? maxHeight - : chartContainer.current.offsetHeight - : 300 - setHeight(_height) - } - handleResize() - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, []) - useEffect(() => { let subscribed = true async function setChallengeResult() { diff --git a/src/components/Ecogesture/EcogestureEmptyList.spec.tsx b/src/components/Ecogesture/EcogestureEmptyList.spec.tsx index 09570fb09f8d25b80396bb03951eddc7384433c1..8f609d629c3b62ba524decca7241f2095893852b 100644 --- a/src/components/Ecogesture/EcogestureEmptyList.spec.tsx +++ b/src/components/Ecogesture/EcogestureEmptyList.spec.tsx @@ -25,7 +25,6 @@ const mockStore = configureStore([]) const mockedNavigate = jest.fn() const mockChangeTab = jest.fn() const mockHandleClick = jest.fn() - describe('EcogestureEmptyList component', () => { it('should be rendered correctly', () => { const store = mockStore({ diff --git a/src/components/Ecogesture/EcogestureEmptyList.tsx b/src/components/Ecogesture/EcogestureEmptyList.tsx index 521cf35fd068c437da07b0ed2be69cd6ea6ba983..2aad11c27ec0d0b558ecac1c6782d24d4a216a71 100644 --- a/src/components/Ecogesture/EcogestureEmptyList.tsx +++ b/src/components/Ecogesture/EcogestureEmptyList.tsx @@ -21,6 +21,8 @@ const EcogestureEmptyList: React.FC<EcogestureEmptyListProps> = ({ }: EcogestureEmptyListProps) => { const { t } = useI18n() const navigate = useNavigate() + const objOrDoing = isObjective ? 'obj' : 'doing' + const isDone = isSelectionDone ? '_done' : '' return ( <div className="ec-empty-container"> <div className="ec-empty-content"> @@ -30,14 +32,10 @@ const EcogestureEmptyList: React.FC<EcogestureEmptyListProps> = ({ size={150} /> <div className="text-16-normal text"> - {isObjective - ? t(`ecogesture.emptyList.obj1${isSelectionDone ? '_done' : ''}`) - : t(`ecogesture.emptyList.doing1${isSelectionDone ? '_done' : ''}`)} + {t(`ecogesture.emptyList.${objOrDoing}1${isDone}`)} </div> <div className="text-16-normal text"> - {isObjective - ? t(`ecogesture.emptyList.obj2${isSelectionDone ? '_done' : ''}`) - : t(`ecogesture.emptyList.doing2${isSelectionDone ? '_done' : ''}`)} + {t(`ecogesture.emptyList.${objOrDoing}2${isDone}`)} </div> <div className="btn-container"> <Button diff --git a/src/components/Ecogesture/EcogestureList.tsx b/src/components/Ecogesture/EcogestureList.tsx index 5b652f69e4d0430a4d3d9cf5bd5c71bb676e369f..a2035e73f6079609a5c798bccbf985a9ea9a672d 100644 --- a/src/components/Ecogesture/EcogestureList.tsx +++ b/src/components/Ecogesture/EcogestureList.tsx @@ -72,6 +72,33 @@ const EcogestureList: React.FC<EcogestureListProps> = ({ } } + const renderEcogestureContent = () => { + if (list.length > 0) { + if (activeFilter === Usage[Usage.ALL]) { + return list.map(ecogesture => ( + <EcogestureCard + key={ecogesture.id} + ecogesture={ecogesture} + selectionCompleted={selectionViewed === selectionTotal} + /> + )) + } else { + return filterEcogesture(list) + } + } else if (!displaySelection) { + return ( + <div className="ec-filter-error"> + <div className="text-20-normal"> + {t('ecogesture.no_ecogesture_filter.text1')} + </div> + <div className="text-16-italic"> + {t('ecogesture.no_ecogesture_filter.text2')} + </div> + </div> + ) + } + } + return ( <div className="ecogesture-root"> <div className="efficiency-button-content"> @@ -137,8 +164,8 @@ const EcogestureList: React.FC<EcogestureListProps> = ({ variant="menu" MenuListProps={{ className: 'filter-menu-list' }} > - {Object.values(Usage).map((usage, key) => { - return ( + {Object.values(Usage).map( + (usage, key) => typeof usage !== 'number' && ( <MenuItem classes={{ @@ -158,34 +185,14 @@ const EcogestureList: React.FC<EcogestureListProps> = ({ )} </MenuItem> ) - ) - })} + )} </Menu> </div> </div> )} </div> <div className="ecogesture-content"> - {list.length > 0 && activeFilter === Usage[Usage.ALL] - ? list.map(ecogesture => ( - <EcogestureCard - key={ecogesture.id} - ecogesture={ecogesture} - selectionCompleted={selectionViewed === selectionTotal} - /> - )) - : list.length > 0 && activeFilter !== Usage[Usage.ALL] - ? filterEcogesture(list) - : !displaySelection && ( - <div className="ec-filter-error"> - <div className="text-20-normal"> - {t('ecogesture.no_ecogesture_filter.text1')} - </div> - <div className="text-16-italic"> - {t('ecogesture.no_ecogesture_filter.text2')} - </div> - </div> - )} + {renderEcogestureContent()} {!displaySelection && handleReinitClick && ( <button className="reinit-button" onClick={handleReinitClick}> <span>{t('ecogesture.reinit')}</span> diff --git a/src/components/EcogestureSelection/EcogestureSelection.tsx b/src/components/EcogestureSelection/EcogestureSelection.tsx index 3250748c75c143b4b54a1fd2aae658c9f1614499..090a33ffd5914d14452262acd03a9ff43630d6fc 100644 --- a/src/components/EcogestureSelection/EcogestureSelection.tsx +++ b/src/components/EcogestureSelection/EcogestureSelection.tsx @@ -121,6 +121,27 @@ const EcogestureSelection: React.FC = () => { ) } + const renderEcogestureSelection = () => { + if (indexEcogesture <= ecogestureList.length - 1) { + return ( + <EcogestureSelectionDetail + ecogesture={ecogestureList[indexEcogesture]} + validate={validateChoice} + title={getTitle()} + /> + ) + } else if (totalAvailable > totalViewed + ecogestureList.length) { + return ( + <EcogestureSelectionRestart + listLength={ecogestureList.length} + restart={restartSelection} + /> + ) + } else { + return <EcogestureSelectionEnd /> + } + } + return ( <> <CozyBar @@ -139,22 +160,7 @@ const EcogestureSelection: React.FC = () => { : ''} </div> </Header> - <Content height={headerHeight}> - {indexEcogesture <= ecogestureList.length - 1 ? ( - <EcogestureSelectionDetail - ecogesture={ecogestureList[indexEcogesture]} - validate={validateChoice} - title={getTitle()} - /> - ) : totalAvailable > totalViewed + ecogestureList.length ? ( - <EcogestureSelectionRestart - listLength={ecogestureList.length} - restart={restartSelection} - /> - ) : ( - <EcogestureSelectionEnd /> - )} - </Content> + <Content height={headerHeight}>{renderEcogestureSelection()}</Content> {openEcogestureSelectionModal && ( <EcogestureSelectionModal open={openEcogestureSelectionModal} diff --git a/src/components/FluidChart/FluidChartSwipe.tsx b/src/components/FluidChart/FluidChartSwipe.tsx index 269979d0c6b36a2dd797f63bfcb8cf2d75d262a2..00f87e72468b2848597a4bafe5f78af2fbdef272 100644 --- a/src/components/FluidChart/FluidChartSwipe.tsx +++ b/src/components/FluidChart/FluidChartSwipe.tsx @@ -1,4 +1,5 @@ import FluidChartSlide from 'components/FluidChart/FluidChartSlide' +import { useChartResize } from 'components/Hooks/useChartResize' import { FluidType } from 'enum/fluid.enum' import { DateTime } from 'luxon' import React, { Dispatch, useEffect, useRef, useState } from 'react' @@ -28,8 +29,6 @@ const FluidChartSwipe: React.FC<FluidChartSwipeProps> = ({ (state: AppStore) => state.ecolyo.chart ) const swipe = useRef<HTMLDivElement>(null) - const [width, setWidth] = useState(0) - const [height, setHeight] = useState(0) const [isSwitching, setIsSwitching] = useState(false) const handleChangeIndex = (index: number) => { @@ -61,29 +60,7 @@ const FluidChartSwipe: React.FC<FluidChartSwipeProps> = ({ dispatch(setCurrentIndex(updatedIndex)) } - useEffect(() => { - function handleResize() { - if (!loading) { - const maxWidth = 940 - const maxHeight = 300 - const _width = swipe.current - ? swipe.current.offsetWidth > maxWidth - ? maxWidth - : swipe.current.offsetWidth - : 400 - setWidth(_width) - const _height = swipe.current - ? swipe.current.offsetHeight > maxHeight - ? maxHeight - : swipe.current.offsetHeight - : 300 - setHeight(_height) - } - } - handleResize() - window.addEventListener('resize', handleResize) - return () => window.removeEventListener('resize', handleResize) - }, [loading]) + const { height, width } = useChartResize(swipe, 300, 940, loading) useEffect(() => { function initIndex() { @@ -97,6 +74,19 @@ const FluidChartSwipe: React.FC<FluidChartSwipeProps> = ({ initIndex() }, [dispatch, currentTimeStep, selectedDate]) + const slideRenderer = (key: number, index: number) => ( + <FluidChartSlide + key={key} + index={index} + fluidType={fluidType} + showCompare={showCompare} + width={width} + height={height} + isSwitching={isSwitching} + setActive={setActive} + /> + ) + return ( <div className={'fluidchartswipe-root'} ref={swipe}> <VirtualizeSwipeableViews @@ -104,18 +94,7 @@ const FluidChartSwipe: React.FC<FluidChartSwipeProps> = ({ overscanSlideAfter={1} overscanSlideBefore={1} onChangeIndex={handleChangeIndex} - slideRenderer={({ key, index }) => ( - <FluidChartSlide - key={key} - index={index} - fluidType={fluidType} - showCompare={showCompare} - width={width} - height={height} - isSwitching={isSwitching} - setActive={setActive} - /> - )} + slideRenderer={({ key, index }) => slideRenderer(key, index)} enableMouseEvents onSwitching={!isSwitching ? () => setIsSwitching(true) : null} onTransitionEnd={() => { diff --git a/src/components/Hooks/useChartResize.tsx b/src/components/Hooks/useChartResize.tsx new file mode 100644 index 0000000000000000000000000000000000000000..74176aa12ba9d8d8760a34906b2b17423507a8d3 --- /dev/null +++ b/src/components/Hooks/useChartResize.tsx @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react' + +export const useChartResize = ( + ref: React.RefObject<HTMLDivElement>, + maxHeight = 300, + maxWidth = 940, + loading = false +) => { + const [width, setWidth] = useState(0) + const [height, setHeight] = useState(0) + + useEffect(() => { + function handleResize() { + if (!loading) { + const chartContainerWidth = ref?.current?.offsetWidth ?? 400 + const chartContainerHeight = ref?.current?.offsetHeight ?? 200 + const width = Math.min(chartContainerWidth, maxWidth) + const height = Math.min(chartContainerHeight, maxHeight) + setWidth(width) + setHeight(height) + } + } + + handleResize() + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, [loading]) + + return { width, height } +} diff --git a/src/components/Loader/Loader.tsx b/src/components/Loader/Loader.tsx index 8f8928b0559b1553d1cf70b3892fd4b123c8992a..9646685b1cc53556b6b53741c89b3378380d3992 100644 --- a/src/components/Loader/Loader.tsx +++ b/src/components/Loader/Loader.tsx @@ -17,9 +17,6 @@ const Loader = ({ color = 'gold', fluidType }: color) => { let variant = color switch (fluidType) { - case FluidType.MULTIFLUID: - variant = 'gold' - break case FluidType.ELECTRICITY: variant = 'elec' break diff --git a/src/components/ProfileType/ProfileTypeView.tsx b/src/components/ProfileType/ProfileTypeView.tsx index 0b255ca9c7baaa5287f894851ff3b32d5e2823d8..5fb0f3b2f6cec6fada99365f8df9ecee08d8154d 100644 --- a/src/components/ProfileType/ProfileTypeView.tsx +++ b/src/components/ProfileType/ProfileTypeView.tsx @@ -2,6 +2,7 @@ import Content from 'components/Content/Content' import EcogestureFormEquipment from 'components/EcogestureForm/EcogestureFormEquipment' import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' +import Loader from 'components/Loader/Loader' import ProfileTypeFinished from 'components/ProfileType/ProfileTypeFinished' import ProfileTypeFormMultiChoice from 'components/ProfileType/ProfileTypeFormMultiChoice' import ProfileTypeFormNumber from 'components/ProfileType/ProfileTypeFormNumber' @@ -219,10 +220,14 @@ const ProfileTypeView: React.FC = () => { /> <Content height={headerHeight}> <div className={'profile-type-container'}> - {isLoading ? null : step !== ProfileTypeStepForm.END ? ( - selectForm() - ) : ( - <ProfileTypeFinished profileType={profileType} /> + {isLoading && <Loader />} + {!isLoading && ( + <> + {step !== ProfileTypeStepForm.END && selectForm()} + {step === ProfileTypeStepForm.END && ( + <ProfileTypeFinished profileType={profileType} /> + )} + </> )} </div> </Content> diff --git a/src/components/TotalConsumption/TotalConsumption.tsx b/src/components/TotalConsumption/TotalConsumption.tsx index 7d033074176a49e9aa8001a1a15153be6feac296..d2f628e605f8d71568c777e7642c2331fcdd5827 100644 --- a/src/components/TotalConsumption/TotalConsumption.tsx +++ b/src/components/TotalConsumption/TotalConsumption.tsx @@ -53,19 +53,23 @@ const TotalConsumption: React.FC<TotalConsumptionProps> = ({ } }) - const displayedValue = + let displayedValue + if ( total <= 0 || (!activateHalfHourLoad && currentTimeStep === TimeStep.HALF_AN_HOUR && fluidType === FluidType.ELECTRICITY) - ? '-----' - : fluidType === FluidType.MULTIFLUID - ? formatNumberValues(total).toString() - : totalPrice <= 0 - ? formatNumberValues( - converterService.LoadToEuro(total, fluidType) - ).toString() - : formatNumberValues(totalPrice).toString() + ) { + displayedValue = '-----' + } else if (fluidType === FluidType.MULTIFLUID) { + displayedValue = formatNumberValues(total).toString() + } else if (totalPrice <= 0) { + displayedValue = formatNumberValues( + converterService.LoadToEuro(total, fluidType) + ).toString() + } else { + displayedValue = formatNumberValues(totalPrice).toString() + } setTotalValue(displayedValue) } diff --git a/src/migrations/migration.spec.ts b/src/migrations/migration.spec.ts index 25ef2b9aabddf6ec4267c8d568cc080390e2eeb7..1aa4f518578d95d682ee35f1f79631d7d5833e35 100644 --- a/src/migrations/migration.spec.ts +++ b/src/migrations/migration.spec.ts @@ -210,7 +210,6 @@ describe('migration create', () => { return [] }, } - const runSpy = jest.spyOn(migrationTestCreate, 'run') const mockCreationDoctypeSchema: QueryResult<Schema[]> = { data: [{ _id: 'abc', version: 0 }], diff --git a/src/services/challenge.service.ts b/src/services/challenge.service.ts index 012741fb6b44e8c916d83f5ec208335ad39770a0..1822ae431e32ad085a782bbcd471e3b16428e653 100644 --- a/src/services/challenge.service.ts +++ b/src/services/challenge.service.ts @@ -212,7 +212,7 @@ export default class ChallengeService { fluidStatus: FluidStatus[] ): Promise<UserChallenge | undefined> { let userChallenge: UserChallenge | null = null - // Check if it's a conditionnal exploration + // Check if it's a conditional exploration if (exploration.fluid_condition.length > 0) { const isConditionVerified = await this.isExplorationConditionVerified( exploration, @@ -371,7 +371,7 @@ export default class ChallengeService { const quizService = new QuizService(this._client) const explorationService = new ExplorationService(this._client) let buildList: UserChallenge[] = [] - // Case UserChallengList is empty + // Case UserChallengeList is empty if (challengeEntityList.length > 0 && userChallengeList.length === 0) { for (const challenge of challengeEntityList) { const relationEntities = await this.getRelationEntities(challenge) @@ -474,8 +474,8 @@ export default class ChallengeService { try { const challengeEntity = await this.getAllChallengeEntities() if (!challengeEntity) return true - for (let index = 0; index < challengeEntity.length; index++) { - await this._client.destroy(challengeEntity[index]) + for (const entity of challengeEntity) { + await this._client.destroy(entity) } return true } catch (error) { diff --git a/src/services/consumption.service.spec.ts b/src/services/consumption.service.spec.ts index e57b614a04c5659650811615a96a8a1b56f2425d..9c14ac5fa72003fd59bde4673c000cfe14cd97ed 100644 --- a/src/services/consumption.service.spec.ts +++ b/src/services/consumption.service.spec.ts @@ -154,7 +154,7 @@ describe('Consumption service', () => { FluidType.GAS, ] - for (const fluidType of fluidTypes) { + for (let i = 0; i < fluidTypes.length; i++) { mockFetchFluidData.mockResolvedValueOnce(mockFetchDataActual) mockFetchFluidData.mockResolvedValueOnce(mockFetchDataComparison) } @@ -311,7 +311,7 @@ describe('Consumption service', () => { FluidType.GAS, ] - for (const fluidtype of fluidTypes) { + for (let i = 0; i < fluidTypes.length; i++) { mockFetchFluidData.mockResolvedValueOnce(mockFetchDataActual) mockFetchFluidData.mockResolvedValueOnce(mockFetchDataComparison) } diff --git a/src/services/duel.service.ts b/src/services/duel.service.ts index e4ef323b7b15d0e49a80529a0ead45ff269fdc74..63e308e4ca42d94e4a769603e14ee699d86eef1e 100644 --- a/src/services/duel.service.ts +++ b/src/services/duel.service.ts @@ -148,10 +148,10 @@ export default class DuelService { */ public async deleteAllDuelEntities(): Promise<boolean> { try { - const dueles = await this.getAllDuelEntities() - if (!dueles) return true - for (let index = 0; index < dueles.length; index++) { - await this._client.destroy(dueles[index]) + const duels = await this.getAllDuelEntities() + if (!duels) return true + for (const duel of duels) { + await this._client.destroy(duel) } return true } catch (error) { diff --git a/src/services/exploration.service.ts b/src/services/exploration.service.ts index 466dc382553606eaf85f62436abaf68067041d7d..8a4db83a644ba04a881fe6fe91a7d6bc66e5dddb 100644 --- a/src/services/exploration.service.ts +++ b/src/services/exploration.service.ts @@ -54,8 +54,8 @@ export default class ExplorationService { public async deleteAllExplorationEntities(): Promise<boolean> { const explorations = await this.getAllExplorationEntities() if (explorations) { - for (let index = 0; index < explorations.length; index++) { - await this._client.destroy(explorations[index]) + for (const exploration of explorations) { + await this._client.destroy(exploration) } } return true diff --git a/src/services/performanceIndicator.service.ts b/src/services/performanceIndicator.service.ts index c459f00c6e93103fc26a43dc64135654aef122e5..4a6e8d4a2d3a3a8804d223abca1ee2b23268c85c 100644 --- a/src/services/performanceIndicator.service.ts +++ b/src/services/performanceIndicator.service.ts @@ -20,7 +20,7 @@ export default class PerformanceIndicatorService { ? converterService.LoadToEuro( performanceIndicator.value, fluidType, - performanceIndicator ? performanceIndicator.price : null + performanceIndicator?.price || null ) : 0 } @@ -38,7 +38,7 @@ export default class PerformanceIndicatorService { ? converterService.LoadToEuro( performanceIndicator.compareValue, fluidType, - performanceIndicator ? performanceIndicator.price : null + performanceIndicator?.price || null ) : 0 } @@ -55,7 +55,7 @@ export default class PerformanceIndicatorService { ? converterService.LoadToEuro( performanceIndicator.value, fluidType, - performanceIndicator ? performanceIndicator.price : null + performanceIndicator?.price || null ) : 0 } @@ -70,7 +70,7 @@ export default class PerformanceIndicatorService { ? converterService.LoadToEuro( performanceIndicator.compareValue, fluidType, - performanceIndicator ? performanceIndicator.price : null + performanceIndicator?.price || null ) : 0 } diff --git a/src/services/profileEcogestureForm.service.ts b/src/services/profileEcogestureForm.service.ts index 6717b877190c2f5eb83cdcb90ccfbfb250907175..9cbe8fa36e381787e695ceda5023642c680533be 100644 --- a/src/services/profileEcogestureForm.service.ts +++ b/src/services/profileEcogestureForm.service.ts @@ -65,12 +65,6 @@ export default class ProfileEcogestureFormService { */ static getAnswerForStep(step: EcogestureStepForm): ProfileEcogestureAnswer { switch (step) { - case EcogestureStepForm.HEATING_TYPE: - return { - type: ProfileEcogestureAnswerType.SINGLE_CHOICE, - attribute: 'heating', - choices: Object.values(IndividualOrCollective), - } case EcogestureStepForm.WARMING_FLUID: return { type: ProfileEcogestureAnswerType.SINGLE_CHOICE, @@ -94,6 +88,7 @@ export default class ProfileEcogestureFormService { attribute: 'equipments', choices: Object.keys(EquipmentType), } + case EcogestureStepForm.HEATING_TYPE: default: return { type: ProfileEcogestureAnswerType.SINGLE_CHOICE, diff --git a/src/services/profileType.service.ts b/src/services/profileType.service.ts index bcfa6b5cd2a9256eede572a262236d3a8e327978..f911b87f894a6ae38c19496dff57d6ab13bd4ef1 100644 --- a/src/services/profileType.service.ts +++ b/src/services/profileType.service.ts @@ -383,34 +383,43 @@ export default class ProfileTypeService { const hotWaterFluid = this.profileType.hotWaterFluid const cookingFluid = this.profileType.cookingFluid - const detailsMonthlyForecast = { - heatingConsumption: - this.profileType.heating === IndividualOrCollective.COLLECTIVE - ? null - : warmingFluid !== null && - (fluidType as number) === (warmingFluid as number) - ? await this.getMonthHeating(month) - : null, - ecsConsumption: - this.profileType.heating === IndividualOrCollective.COLLECTIVE - ? null - : hotWaterFluid !== null && - (fluidType as number) === (hotWaterFluid as number) - ? this.getMonthEcs(month) - : null, - cookingConsumption: - fluidType === cookingFluid - ? this.getMonthCookingConsumption(month) - : null, - electricSpecificConsumption: - fluidType === FluidType.ELECTRICITY - ? this.getMonthElectricSpecificConsumption(month) - : null, - coldWaterConsumption: - fluidType === FluidType.WATER - ? this.getMonthColdWaterConsumption(month) - : null, + const detailsMonthlyForecast: DetailsMonthlyForecast = { + heatingConsumption: null, + ecsConsumption: null, + cookingConsumption: null, + electricSpecificConsumption: null, + coldWaterConsumption: null, } + + if (this.profileType.heating !== IndividualOrCollective.COLLECTIVE) { + if ( + warmingFluid !== null && + (fluidType as number) === (warmingFluid as number) + ) { + detailsMonthlyForecast.heatingConsumption = await this.getMonthHeating( + month + ) + } + if ( + hotWaterFluid !== null && + (fluidType as number) === (hotWaterFluid as number) + ) { + detailsMonthlyForecast.ecsConsumption = this.getMonthEcs(month) + } + } + if (fluidType === cookingFluid) { + detailsMonthlyForecast.cookingConsumption = + this.getMonthCookingConsumption(month) + } + if (fluidType === FluidType.ELECTRICITY) { + detailsMonthlyForecast.electricSpecificConsumption = + this.getMonthElectricSpecificConsumption(month) + } + if (fluidType === FluidType.WATER) { + detailsMonthlyForecast.coldWaterConsumption = + this.getMonthColdWaterConsumption(month) + } + return detailsMonthlyForecast } diff --git a/src/services/profileTypeEntity.service.ts b/src/services/profileTypeEntity.service.ts index 361d60fa00ca35818b1db34aec9528b6edc7b673..bcfa84e2d85a4ba6429c9a3b1ffd4acbc4bf5644 100644 --- a/src/services/profileTypeEntity.service.ts +++ b/src/services/profileTypeEntity.service.ts @@ -157,8 +157,8 @@ export default class ProfileTypeEntityService { profileTypes: ProfileType[] ): Promise<boolean> { try { - for (let index = 0; index < profileTypes.length; index++) { - await this._client.destroy(profileTypes[index]) + for (const profileType of profileTypes) { + await this._client.destroy(profileType) } return true } catch (error) { diff --git a/src/targets/services/enedisHalfHourMonthlyAnalysis.ts b/src/targets/services/enedisHalfHourMonthlyAnalysis.ts index b9bd297ff9be608d0831734d9872ac383275e50c..4309efcf7607835ca7a31ca0749ce04cc2a3d11e 100644 --- a/src/targets/services/enedisHalfHourMonthlyAnalysis.ts +++ b/src/targets/services/enedisHalfHourMonthlyAnalysis.ts @@ -301,7 +301,6 @@ const syncEnedisMonthlyAnalysisDataDoctype = async ({ 'info', 'Enedis Minute is not activated or there is no data yet in this doctype' ) - return } } diff --git a/src/utils/picto.ts b/src/utils/picto.ts index a5c2b78cfd9be3e1cae4e3ecdfb51fec480b77c5..54058b3d5dd0e91ec0cc317d5a44a21845ac08a7 100644 --- a/src/utils/picto.ts +++ b/src/utils/picto.ts @@ -115,13 +115,13 @@ export function getNavPicto( * @param blackLogo boolean - define the color of the logo (black or white) */ export function getPartnerPicto(slug: string, blackLogo = true) { - const fluidconfig = new ConfigService().getFluidConfig() + const fluidConfig = new ConfigService().getFluidConfig() switch (slug) { - case fluidconfig[FluidType.ELECTRICITY].konnectorConfig.slug: + case fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug: return blackLogo ? iconEnedisLogo : iconEnedisWhiteLogo - case fluidconfig[FluidType.WATER].konnectorConfig.slug: + case fluidConfig[FluidType.WATER].konnectorConfig.slug: return blackLogo ? iconEglLogo : iconEglWhiteLogo - case fluidconfig[FluidType.GAS].konnectorConfig.slug: + case fluidConfig[FluidType.GAS].konnectorConfig.slug: return blackLogo ? iconGrdfLogo : iconGrdfWhiteLogo default: return ''