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/assets/icons/email/pwa_android.gif b/src/assets/icons/email/pwa_android.gif new file mode 100644 index 0000000000000000000000000000000000000000..35c61ebbbc6f273d6e1b36353d2e11bb52d80939 Binary files /dev/null and b/src/assets/icons/email/pwa_android.gif differ diff --git a/src/assets/icons/email/pwa_ios.gif b/src/assets/icons/email/pwa_ios.gif new file mode 100644 index 0000000000000000000000000000000000000000..8dcdd7bc86c6d0110f130a4a40bab26fe4b42b5b Binary files /dev/null and b/src/assets/icons/email/pwa_ios.gif differ diff --git a/src/assets/icons/visu/duelResult/challengeWon.svg b/src/assets/icons/visu/duelResult/challengeWon.svg new file mode 100644 index 0000000000000000000000000000000000000000..aecc4cb6ca4749922b4fc1b358030ad939d5de8b --- /dev/null +++ b/src/assets/icons/visu/duelResult/challengeWon.svg @@ -0,0 +1,12 @@ +<svg width="320" height="321" viewBox="0 0 320 321" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g opacity="0.6" filter="url(#filter0_f_6_18534)"> +<path d="M137.316 4.00668L159.647 150.867L160.871 2.32359L161.064 150.872L184.413 4.16994L162.464 151.088L207.417 9.50447L163.817 151.51L229.37 18.208L165.091 152.129L249.779 30.0862L166.259 152.931L268.191 44.8736L167.295 153.899L284.193 62.24L168.174 155.01L297.428 81.7974L168.878 156.239L307.6 103.109L169.391 157.56L314.482 125.698L169.702 158.942L317.921 149.061L169.803 160.355L317.839 172.676L169.692 161.768L314.239 196.015L169.372 163.148L307.2 218.556L168.85 164.465L296.88 239.796L168.137 165.69L283.51 259.262L167.25 166.795L267.388 276.517L166.208 167.755L248.874 291.176L165.034 168.549L228.383 302.912L163.756 169.159L206.371 311.464L162.4 169.572L183.33 316.639L160.999 169.779L159.775 318.322L159.582 169.774L136.233 316.475L158.181 169.558L113.229 311.141L156.829 169.135L91.2763 302.437L155.555 168.516L70.8664 290.559L154.387 167.714L52.4548 275.772L153.351 166.747L36.4528 258.405L152.472 165.636L23.2179 238.848L151.768 164.406L13.0457 217.536L151.254 163.085L6.1634 194.947L150.944 161.703L2.72477 171.584L150.843 160.29L2.80662 147.969L150.954 158.877L6.40713 124.631L151.274 157.497L13.4459 102.089L151.796 156.18L23.7656 80.8488L152.509 154.955L37.1358 61.3837L153.396 153.851L53.2578 44.1286L154.438 152.89L71.7714 29.4692L155.611 152.096L92.2632 17.7328L156.89 151.486L114.275 9.18161L158.246 151.073L137.316 4.00668Z" fill="#FFD951"/> +</g> +<defs> +<filter id="filter0_f_6_18534" x="0.724731" y="0.323486" width="319.196" height="319.998" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_6_18534"/> +</filter> +</defs> +</svg> 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/Challenge/ChallengeView.tsx b/src/components/Challenge/ChallengeView.tsx index 5f00edbfd69ad12c5f183e2fa3cf82434253a0c3..929a63f11206f57b9b0449af5e8c163076dc39ff 100644 --- a/src/components/Challenge/ChallengeView.tsx +++ b/src/components/Challenge/ChallengeView.tsx @@ -77,13 +77,6 @@ const ChallengeView: React.FC = () => { [cardWidth] ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleClickOrTouchStart = (e: any) => { - if (e.nativeEvent instanceof TouchEvent) - setTouchStart(e.targetTouches[0].clientX) - if (e.nativeEvent instanceof MouseEvent) setTouchStart(e.clientX) - } - const handleClickOrTouchEnd = () => { // if the swipe is too small and can be taken for a touch if (touchStart && touchEnd) { @@ -101,13 +94,6 @@ const ChallengeView: React.FC = () => { resetValues() } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleClickOrTouchMove = (e: any) => { - if (e.nativeEvent instanceof TouchEvent) - setTouchEnd(e.targetTouches[0].clientX) - if (e.nativeEvent instanceof MouseEvent) setTouchEnd(e.clientX) - } - useEffect(() => { userChallengeList.forEach((challenge: UserChallenge, i: number) => { if ( @@ -152,11 +138,11 @@ const ChallengeView: React.FC = () => { <div className="challengeSlider" onClick={resetValues} - onTouchStart={handleClickOrTouchStart} - onTouchMove={handleClickOrTouchMove} + onTouchStart={event => setTouchStart(event.targetTouches[0].clientX)} + onTouchMove={event => setTouchEnd(event.targetTouches[0].clientX)} onTouchEnd={handleClickOrTouchEnd} - onMouseDown={handleClickOrTouchStart} - onMouseMove={handleClickOrTouchMove} + onMouseDown={event => setTouchStart(event.clientX)} + onMouseMove={event => setTouchEnd(event.clientX)} onMouseUp={handleClickOrTouchEnd} > <div diff --git a/src/components/Challenge/challengeView.scss b/src/components/Challenge/challengeView.scss index 694fcbcad650b5d18b0279daf8a3148b0098b995..b0cc2a8fab8d77ed19e2060f891d2840ea20abef 100644 --- a/src/components/Challenge/challengeView.scss +++ b/src/components/Challenge/challengeView.scss @@ -22,6 +22,7 @@ } .cardContent { margin: auto; + cursor: pointer; .title { font-weight: 400; text-align: center; 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/CommonKit/Card/StyledEcogestureCard.tsx b/src/components/CommonKit/Card/StyledEcogestureCard.tsx index 08fd623762bc340a6e9056a7cd8754570f03967f..dea5dd8310b6fb4091061c9daee31eff9581b9a8 100644 --- a/src/components/CommonKit/Card/StyledEcogestureCard.tsx +++ b/src/components/CommonKit/Card/StyledEcogestureCard.tsx @@ -12,7 +12,6 @@ const CardBase = withStyles({ boxSizing: 'border-box', boxShadow: '0px 4px 16px black', borderRadius: '4px', - margin: '10px 0px 10px 0px', padding: '0.5rem 1rem', minHeight: '72px', }, 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/Duel/DuelResultModal.tsx b/src/components/Duel/DuelResultModal.tsx index 0c99d1a3a71b9b90ec7e288a64ce212e139ac7e0..7d83254a60e93f9c37f13e936f44806215f37bef 100644 --- a/src/components/Duel/DuelResultModal.tsx +++ b/src/components/Duel/DuelResultModal.tsx @@ -1,5 +1,6 @@ import Button from '@material-ui/core/Button' import Dialog from '@material-ui/core/Dialog' +import challengeWon from 'assets/icons/visu/duelResult/challengeWon.svg' import defaultIcon from 'assets/icons/visu/duelResult/default.svg' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import Icon from 'cozy-ui/transpiled/react/Icon' @@ -60,11 +61,16 @@ const DuelResultModal: React.FC<DuelResultModalProps> = ({ {t('duel_result_modal.accessibility.window_title')} </div> <div className="duel-result-modal-root "> - <Icon - className="imgResult" - icon={win ? winIcon : lossIcon} - size={208} - /> + <div className="imgResultContainer"> + {win && ( + <Icon className="challengeWon" icon={challengeWon} size={300} /> + )} + <Icon + className="imgResult" + icon={win ? winIcon : lossIcon} + size={180} + /> + </div> <div className="text-28-normal-uppercase title"> {win ? t('duel_result_modal.sucess.title') diff --git a/src/components/Duel/duelResultModal.scss b/src/components/Duel/duelResultModal.scss index 672c1629a1b48c68b4ef18ab3ddaf264075447fc..ba90fa755ea6d27f55b8bdf887ca4ded86bb2154 100644 --- a/src/components/Duel/duelResultModal.scss +++ b/src/components/Duel/duelResultModal.scss @@ -9,3 +9,18 @@ #accessibility-title { display: none; } + +.imgResultContainer { + position: relative; + height: 300px; + width: 100%; + .challengeWon { + position: absolute; + transform: translateX(-50%); + } + + .imgResult { + position: absolute; + transform: translate(-50%, 32%); + } +} 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..93414138c8a1dab9e9f54198721afcc67fbde1df 100644 --- a/src/components/Ecogesture/EcogestureList.tsx +++ b/src/components/Ecogesture/EcogestureList.tsx @@ -45,16 +45,15 @@ const EcogestureList: React.FC<EcogestureListProps> = ({ setAnchorEl(null) } - const filterEcogesture = (_ecogestures: Ecogesture[]) => { - const filtered = _ecogestures + const filterEcogesture = (ecogestures: Ecogesture[]) => { + const filtered = ecogestures .filter(ecogesture => Usage[ecogesture.usage] === activeFilter) .map(ecogesture => ( - <div key={ecogesture.id} className="ecogesture-list-item"> - <EcogestureCard - ecogesture={ecogesture} - selectionCompleted={selectionViewed === selectionTotal} - /> - </div> + <EcogestureCard + key={ecogesture.id} + ecogesture={ecogesture} + selectionCompleted={selectionViewed === selectionTotal} + /> )) if (filtered.length > 0) { return filtered @@ -72,6 +71,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 +163,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,38 +184,24 @@ 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> - </button> + <Button + onClick={handleReinitClick} + classes={{ + root: 'btn-secondary-negative', + label: 'text-16-normal', + }} + > + {t('ecogesture.reinit')} + </Button> )} </div> </div> diff --git a/src/components/Ecogesture/__snapshots__/EcogestureList.spec.tsx.snap b/src/components/Ecogesture/__snapshots__/EcogestureList.spec.tsx.snap index 426846c3562143ea6d12b4ecfd43f1c3bbabb057..a534041ec392820102f3f47f100e0e5bb46e0671 100644 --- a/src/components/Ecogesture/__snapshots__/EcogestureList.spec.tsx.snap +++ b/src/components/Ecogesture/__snapshots__/EcogestureList.spec.tsx.snap @@ -1953,14 +1953,132 @@ exports[`EcogesturesList component should be rendered correctly 1`] = ` key="ECOGESTURE0013" selectionCompleted={true} /> - <button - className="reinit-button" + <WithStyles(ForwardRef(Button)) + classes={ + Object { + "label": "text-16-normal", + "root": "btn-secondary-negative", + } + } onClick={[MockFunction]} > - <span> - ecogesture.reinit - </span> - </button> + <ForwardRef(Button) + classes={ + Object { + "colorInherit": "MuiButton-colorInherit", + "contained": "MuiButton-contained", + "containedPrimary": "MuiButton-containedPrimary", + "containedSecondary": "MuiButton-containedSecondary", + "containedSizeLarge": "MuiButton-containedSizeLarge", + "containedSizeSmall": "MuiButton-containedSizeSmall", + "disableElevation": "MuiButton-disableElevation", + "disabled": "Mui-disabled", + "endIcon": "MuiButton-endIcon", + "focusVisible": "Mui-focusVisible", + "fullWidth": "MuiButton-fullWidth", + "iconSizeLarge": "MuiButton-iconSizeLarge", + "iconSizeMedium": "MuiButton-iconSizeMedium", + "iconSizeSmall": "MuiButton-iconSizeSmall", + "label": "MuiButton-label text-16-normal", + "outlined": "MuiButton-outlined", + "outlinedPrimary": "MuiButton-outlinedPrimary", + "outlinedSecondary": "MuiButton-outlinedSecondary", + "outlinedSizeLarge": "MuiButton-outlinedSizeLarge", + "outlinedSizeSmall": "MuiButton-outlinedSizeSmall", + "root": "MuiButton-root btn-secondary-negative", + "sizeLarge": "MuiButton-sizeLarge", + "sizeSmall": "MuiButton-sizeSmall", + "startIcon": "MuiButton-startIcon", + "text": "MuiButton-text", + "textPrimary": "MuiButton-textPrimary", + "textSecondary": "MuiButton-textSecondary", + "textSizeLarge": "MuiButton-textSizeLarge", + "textSizeSmall": "MuiButton-textSizeSmall", + } + } + onClick={[MockFunction]} + > + <WithStyles(ForwardRef(ButtonBase)) + className="MuiButton-root btn-secondary-negative MuiButton-text" + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[MockFunction]} + type="button" + > + <ForwardRef(ButtonBase) + className="MuiButton-root btn-secondary-negative MuiButton-text" + classes={ + Object { + "disabled": "Mui-disabled", + "focusVisible": "Mui-focusVisible", + "root": "MuiButtonBase-root", + } + } + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[MockFunction]} + type="button" + > + <button + className="MuiButtonBase-root MuiButton-root btn-secondary-negative MuiButton-text" + disabled={false} + onBlur={[Function]} + onClick={[MockFunction]} + onDragLeave={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyUp={[Function]} + onMouseDown={[Function]} + onMouseLeave={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + tabIndex={0} + type="button" + > + <span + className="MuiButton-label text-16-normal" + > + ecogesture.reinit + </span> + <WithStyles(memo) + center={false} + > + <ForwardRef(TouchRipple) + center={false} + classes={ + Object { + "child": "MuiTouchRipple-child", + "childLeaving": "MuiTouchRipple-childLeaving", + "childPulsate": "MuiTouchRipple-childPulsate", + "ripple": "MuiTouchRipple-ripple", + "ripplePulsate": "MuiTouchRipple-ripplePulsate", + "rippleVisible": "MuiTouchRipple-rippleVisible", + "root": "MuiTouchRipple-root", + } + } + > + <span + className="MuiTouchRipple-root" + > + <TransitionGroup + childFactory={[Function]} + component={null} + exit={true} + /> + </span> + </ForwardRef(TouchRipple)> + </WithStyles(memo)> + </button> + </ForwardRef(ButtonBase)> + </WithStyles(ForwardRef(ButtonBase))> + </ForwardRef(Button)> + </WithStyles(ForwardRef(Button))> </div> </div> </EcogestureList> diff --git a/src/components/Ecogesture/ecogestureList.scss b/src/components/Ecogesture/ecogestureList.scss index 58f38c24f8d9340a9262a223760fbc501f7122e3..4b0beefe46363f1cf8146537d4c52f4d26ebb634 100644 --- a/src/components/Ecogesture/ecogestureList.scss +++ b/src/components/Ecogesture/ecogestureList.scss @@ -8,20 +8,11 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; padding: 1rem 1.5rem 2.5rem 1.5rem; + gap: 1rem; .efficiency-button-content { max-width: 52rem; - width: 97%; - display: flex; - justify-content: space-between; - align-items: center; - @media #{$tablet} { - width: 97%; - } - @media #{$large-phone} { - width: 97%; - } + width: 100%; .selection { display: flex; align-items: center; @@ -101,10 +92,15 @@ } .ecogesture-content { display: flex; + justify-content: center; flex-wrap: wrap; max-width: 53rem; animation: appear 600ms ease; width: 100%; + gap: 1rem; + @media #{$tablet} { + gap: 0.5rem; + } @keyframes appear { from { @@ -124,10 +120,12 @@ color: $grey-bright; } .ecogesture-list-item { - width: 48%; + box-sizing: border-box; height: 8rem; - margin: 1% 1%; animation: appear 600ms ease; + display: flex; + flex: 1; + flex-basis: 45%; } .ecogesture-list-item > button { height: 100%; @@ -186,17 +184,3 @@ div.filter-menu { margin-left: auto; min-width: 0; } -.reinit-button { - background: transparent; - border: 1px solid $white-light; - border-radius: 2px; - margin: 20px 6px; - padding: 3px; - width: 100%; - cursor: pointer; - span { - color: $white; - display: inline-block; - max-width: 200px; - } -} 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/EcogestureSelection/ecogestureSelectionDetail.scss b/src/components/EcogestureSelection/ecogestureSelectionDetail.scss index c680eaa3788c672a2073a55a949642a0b89861f0..7f860e0385f0fe9b0b55f00a37b17c486349ae82 100644 --- a/src/components/EcogestureSelection/ecogestureSelectionDetail.scss +++ b/src/components/EcogestureSelection/ecogestureSelectionDetail.scss @@ -2,20 +2,12 @@ @import '../../styles/base/breakpoint'; .eg-selection-detail-container { - position: relative; min-height: inherit; - margin: auto; display: flex; flex-direction: column; - justify-content: center; - align-items: center; text-align: center; color: $grey-bright; - max-width: 45.75rem; - @media #{$large-phone} { - margin: 0 1rem; - max-width: unset; - } + padding: 0 1.5rem; .content { display: flex; gap: 0.5rem; 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/components/WelcomeModal/WelcomeModal.tsx b/src/components/WelcomeModal/WelcomeModal.tsx index 3636741e92360d3c206b92a65c280f9ed6984e9e..15151c49c856b9431d9cb66d1db7a2d94d0c483b 100644 --- a/src/components/WelcomeModal/WelcomeModal.tsx +++ b/src/components/WelcomeModal/WelcomeModal.tsx @@ -48,6 +48,8 @@ const WelcomeModal = ({ open }: WelcomeModalProps) => { dotImageUrl: baseUrl + '/assets/dot.png', starImageUrl: baseUrl + '/assets/star.png', shareImageUrl: baseUrl + '/assets/share.png', + pwaAndroidUrl: baseUrl + '/assets/pwa_android.gif', + pwaIosUrl: baseUrl + '/assets/pwa_ios.gif', }) const mailData = { mode: 'noreply', 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/notifications/welcome.hbs b/src/notifications/welcome.hbs index e708651b3d8338450f1e36fcb4a33313626c83e1..8977171b4db6b7f42a076cc7ed2161a24636f4b4 100644 --- a/src/notifications/welcome.hbs +++ b/src/notifications/welcome.hbs @@ -55,6 +55,7 @@ <mj-text color="white" font-size="18px"> 3 - Nommez la page et appuyez sur "Ajouter". Un raccourci vers la page web est apparu sur l'écran d'accueil de votre smartphone. </mj-text> + <mj-image src={{pwaAndroidUrl}} width="250px" align="center"></mj-image> <mj-social align="left" icon-size="30px" mode="horizontal" font-weight="600" font-size="18px" padding-top="30px"> <mj-social-element color="#F1C017" src={{appleImageUrl}} name="ecolyo" padding="0 24px 0 0"> @@ -74,6 +75,7 @@ <mj-text color="#A0A0A0" font-weight="400" font-style="italic" font-size="18px"> Attention cette manipulation ne fonctionne que si vous avez ouvert le lien dans Safari. </mj-text> + <mj-image src={{pwaIosUrl}} width="250px" align="center"></mj-image> </mj-column> </mj-section> 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 ''