Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • build
  • build-dev
  • build-test
  • dev
  • lint/testing-libraby-plugin
  • master
  • renovate/copy-webpack-plugin-13.x
  • renovate/couchdb-3.x
  • renovate/cozy-client-49.x
  • renovate/cozy-device-helper-3.x
  • renovate/cozy-flags-4.x
  • renovate/cozy-harvest-lib-32.x
  • renovate/cozy-harvest-lib-9.x
  • renovate/cozy-realtime-5.x
  • renovate/cozy-scripts-8.x
  • renovate/devdependencies-(non-major)
  • renovate/eslint-9.x
  • renovate/eslint-config-prettier-10.x
  • renovate/eslint-plugin-testing-library-7.x
  • renovate/major-react-monorepo
  • renovate/major-react-router-monorepo
  • renovate/major-typescript-eslint-monorepo
  • renovate/react-19.x
  • renovate/react-dom-19.x
  • renovate/react-inspector-6.x
  • renovate/sass-loader-16.x
  • 0.1.0
  • 0.1.1
  • 0.1.2
  • 0.1.6
  • 0.1.7
  • 1.0.2
  • 1.0.3
  • 1.0.5
  • 1.0.6
  • 1.0.7
  • 1.0.8
  • 1.0.9
  • 1.1.0
  • 1.2.0
  • 1.2.1
  • 1.2.4-beta.1
  • V1.11.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.12.0
  • v1.12.1
  • v1.2.0
  • v1.2.1
  • v1.2.2
  • v1.2.3
  • v1.2.4
  • v1.2.4-beta.1
  • v1.3.0
  • v1.4.0
  • v1.4.1
  • v1.4.2
  • v1.4.3
  • v1.4.4
  • v1.5.0
  • v1.5.1
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.6.3
  • v1.6.4
  • v1.6.5
  • v1.7.0
  • v1.7.1
  • v1.7.2
  • v1.7.3
  • v1.7.4
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.9.0
  • v1.9.1
  • v1.9.2
  • v1.9.3
  • v1.9.4
  • v2.0.0
  • v2.0.1
  • v2.0.2
  • v2.1.0
  • v2.1.1
  • v2.2.0
  • v2.2.1
  • v2.2.2
  • v2.3.0
  • v2.3.1
  • v2.4.0
  • v2.5.0
  • v2.5.1
  • v2.6.0
  • v2.7.0
  • v2.7.1
  • v2.7.2
  • v2.8.0
  • v3.0.0
  • v3.1.0
  • v3.1.1
103 results

Target

Select target project
  • web-et-numerique/factory/llle_project/ecolyo
1 result
Select Git revision
  • build
  • build-dev
  • build-test
  • dev
  • lint/testing-libraby-plugin
  • master
  • renovate/copy-webpack-plugin-13.x
  • renovate/couchdb-3.x
  • renovate/cozy-client-49.x
  • renovate/cozy-device-helper-3.x
  • renovate/cozy-flags-4.x
  • renovate/cozy-harvest-lib-32.x
  • renovate/cozy-harvest-lib-9.x
  • renovate/cozy-realtime-5.x
  • renovate/cozy-scripts-8.x
  • renovate/devdependencies-(non-major)
  • renovate/eslint-9.x
  • renovate/eslint-config-prettier-10.x
  • renovate/eslint-plugin-testing-library-7.x
  • renovate/major-react-monorepo
  • renovate/major-react-router-monorepo
  • renovate/major-typescript-eslint-monorepo
  • renovate/react-19.x
  • renovate/react-dom-19.x
  • renovate/react-inspector-6.x
  • renovate/sass-loader-16.x
  • 0.1.0
  • 0.1.1
  • 0.1.2
  • 0.1.6
  • 0.1.7
  • 1.0.2
  • 1.0.3
  • 1.0.5
  • 1.0.6
  • 1.0.7
  • 1.0.8
  • 1.0.9
  • 1.1.0
  • 1.2.0
  • 1.2.1
  • 1.2.4-beta.1
  • V1.11.0
  • v1.10.0
  • v1.10.1
  • v1.10.2
  • v1.12.0
  • v1.12.1
  • v1.2.0
  • v1.2.1
  • v1.2.2
  • v1.2.3
  • v1.2.4
  • v1.2.4-beta.1
  • v1.3.0
  • v1.4.0
  • v1.4.1
  • v1.4.2
  • v1.4.3
  • v1.4.4
  • v1.5.0
  • v1.5.1
  • v1.6.0
  • v1.6.1
  • v1.6.2
  • v1.6.3
  • v1.6.4
  • v1.6.5
  • v1.7.0
  • v1.7.1
  • v1.7.2
  • v1.7.3
  • v1.7.4
  • v1.8.0
  • v1.8.1
  • v1.8.2
  • v1.8.3
  • v1.9.0
  • v1.9.1
  • v1.9.2
  • v1.9.3
  • v1.9.4
  • v2.0.0
  • v2.0.1
  • v2.0.2
  • v2.1.0
  • v2.1.1
  • v2.2.0
  • v2.2.1
  • v2.2.2
  • v2.3.0
  • v2.3.1
  • v2.4.0
  • v2.5.0
  • v2.5.1
  • v2.6.0
  • v2.7.0
  • v2.7.1
  • v2.7.2
  • v2.8.0
  • v3.0.0
  • v3.1.0
  • v3.1.1
103 results
Show changes
Showing
with 641 additions and 444 deletions
......@@ -21,17 +21,17 @@ const DataloadSection = ({
toggleEstimationModal,
}: DataloadSectionProps) => {
const { t } = useI18n()
const isLeft = dataloadSectionType === DataloadSectionType.LEFT
const isRight = dataloadSectionType === DataloadSectionType.RIGHT
const noCompare = dataloadSectionType === DataloadSectionType.NO_COMPARE
const isMulti = fluidType === FluidType.MULTIFLUID
const fluidName = getFluidName(fluidType)
if (
dataload.value === -1 &&
dataloadSectionType === DataloadSectionType.LEFT
) {
if (dataload.value === -1 && isLeft) {
return (
<div className="dataloadvisualizer-section dataloadvisualizer-section-left-novalue">
<div
className={`dataloadvisualizer-novalue ${FluidType[
fluidType
].toLowerCase()}-compare text-20-normal`}
className={`dataloadvisualizer-novalue ${fluidName}-compare text-20-normal`}
>
{t('consumption_visualizer.no_data')}
</div>
......@@ -39,12 +39,6 @@ const DataloadSection = ({
)
}
const isLeft = dataloadSectionType === DataloadSectionType.LEFT
const isRight = dataloadSectionType === DataloadSectionType.RIGHT
const noCompare = dataloadSectionType === DataloadSectionType.NO_COMPARE
const isMulti = fluidType === FluidType.MULTIFLUID
const fluidName = getFluidName(fluidType)
return (
<div
className={classNames('dataloadvisualizer-section', {
......
......@@ -3,7 +3,7 @@ import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { DataloadSectionType, FluidType } from 'enums'
import { Dataload } from 'models'
import React from 'react'
import { formatNumberValues } from 'utils/utils'
import { formatNumberValues, getFluidName } from 'utils/utils'
interface DataloadSectionValueProps {
dataload: Dataload
......@@ -19,13 +19,14 @@ const DataloadSectionValue = ({
toggleEstimationModal,
}: DataloadSectionValueProps) => {
const { t } = useI18n()
const FLUIDNAME = getFluidName(fluidType).toUpperCase()
if (fluidType === FluidType.MULTIFLUID) {
return (
<>
{formatNumberValues(dataload.value)}
<div className="text-18-normal euroUnit">
{t(`FLUID.${FluidType[fluidType]}.UNIT`)}
{t(`FLUID.${FLUIDNAME}.UNIT`)}
</div>
{dataloadSectionType === DataloadSectionType.NO_COMPARE && (
<Button
......@@ -43,27 +44,21 @@ const DataloadSectionValue = ({
)
}
const formattedValue = formatNumberValues(
dataload.value,
FluidType[fluidType],
true
)
const formattedValue = formatNumberValues(dataload.value, FLUIDNAME, true)
return (
<>
{Number(formattedValue) >= 1000 ? (
<>
{formatNumberValues(dataload.value, FluidType[fluidType])}
{formatNumberValues(dataload.value, FLUIDNAME)}
<span className="text-18-normal">
{t(`FLUID.${FluidType[fluidType]}.MEGAUNIT`)}
{t(`FLUID.${FLUIDNAME}.MEGAUNIT`)}
</span>
</>
) : (
<>
{formatNumberValues(dataload.value)}
<span className="text-18-normal">
{t(`FLUID.${FluidType[fluidType]}.UNIT`)}
</span>
<span className="text-18-normal">{t(`FLUID.${FLUIDNAME}.UNIT`)}</span>
</>
)}
</>
......
import { Button } from '@material-ui/core'
import { useMoveToLatestDate } from 'components/Hooks/useMoveToDate'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { DataloadState, FluidType } from 'enums'
import { DateTime } from 'luxon'
import { Dataload } from 'models'
import React, { useCallback, useState } from 'react'
import DateChartService from 'services/dateChart.service'
import { setCurrentIndex, setSelectedDate } from 'store/chart/chart.slice'
import { useAppDispatch, useAppSelector } from 'store/hooks'
import NoDataModal from './NoDataModal'
import './infoDataConsumptionVisualizer.scss'
......@@ -22,26 +20,13 @@ const InfoDataConsumptionVisualizer = ({
lastDataDate,
}: InfoDataConsumptionVisualizerProps) => {
const { t } = useI18n()
const dispatch = useAppDispatch()
const { currentTimeStep } = useAppSelector(state => state.ecolyo.chart)
const [openNoDataModal, setOpenNoDataModal] = useState<boolean>(false)
const { moveToLatestDate } = useMoveToLatestDate(lastDataDate)
const toggleNoDataModal = useCallback(() => {
setOpenNoDataModal(prev => !prev)
}, [])
const moveToDate = () => {
if (lastDataDate) {
const dateChartService = new DateChartService()
const updatedIndex = dateChartService.defineDateIndex(
currentTimeStep,
lastDataDate
)
dispatch(setSelectedDate(lastDataDate))
dispatch(setCurrentIndex(updatedIndex))
}
}
if (!dataload) {
return <></>
}
......@@ -60,7 +45,7 @@ const InfoDataConsumptionVisualizer = ({
? 'last_valid_data_multi'
: 'last_available_data'
return (
<Button className="btnText" onClick={moveToDate}>
<Button className="btnText" onClick={moveToLatestDate}>
{t(`consumption_visualizer.${key}`, {
date: lastDate,
})}
......
import { render, screen, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import EcogestureTabsView from 'components/Ecogesture/EcogestureTabsView'
import { Ecogesture } from 'models'
import React from 'react'
import { Provider } from 'react-redux'
import * as profileActions from 'store/profile/profile.slice'
import { mockedEcogesturesData } from 'tests/__mocks__/ecogesturesData.mock'
import { mockDoingEcogestures } from 'tests/__mocks__/ecogesturesData.mock'
import { createMockEcolyoStore } from 'tests/__mocks__/store'
const mockInitEcogesture = jest.fn().mockResolvedValue(mockedEcogesturesData)
const mockInitEcogesture = jest
.fn<
Promise<{
ecogestureHash: string
ecogestureList: Ecogesture[]
}>,
[]
>()
.mockResolvedValue({
ecogestureHash: 'hash',
ecogestureList: mockDoingEcogestures,
})
const mockGetEcogesture = jest.fn().mockResolvedValue([])
jest.mock('services/ecogesture.service', () => {
return jest.fn(() => ({
getEcogestureListByProfile: jest.fn().mockResolvedValue([]),
getEcogestureListByProfile: mockGetEcogesture,
initEcogesture: mockInitEcogesture,
}))
})
......@@ -44,7 +57,7 @@ describe('EcogestureView component', () => {
jest.clearAllMocks()
})
it('should be rendered correctly', async () => {
it('should be rendered correctly with 3 clickable tabs', async () => {
const { container } = render(
<Provider store={store}>
<EcogestureTabsView />
......@@ -72,29 +85,12 @@ describe('EcogestureView component', () => {
})
it('should render empty list', async () => {
mockInitEcogesture.mockResolvedValueOnce([])
const { container } = render(
<Provider store={store}>
<EcogestureTabsView />
</Provider>
)
await waitFor(() => null, { container })
expect(
container.getElementsByClassName('ec-empty-container').length
).toBeTruthy()
})
it('should change tab', async () => {
const { container } = render(
<Provider store={store}>
<EcogestureTabsView />
</Provider>
)
await waitFor(() => null, { container })
await userEvent.click(screen.getAllByRole('button')[1])
await waitFor(() => null, { container })
expect(
container.getElementsByClassName('ec-empty-container').length
).toBeTruthy()
expect(container).toMatchSnapshot()
})
})
......@@ -8,7 +8,7 @@ import { useClient } from 'cozy-client'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { EcogestureTab } from 'enums'
import { Ecogesture } from 'models'
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import EcogestureService from 'services/ecogesture.service'
import { useAppDispatch, useAppSelector } from 'store/hooks'
......@@ -65,6 +65,11 @@ const EcogestureTabsView = () => {
const [openEcogestureReinitModal, setOpenEcogestureReinitModal] =
useState<boolean>(false)
const ecogestureService = useMemo(
() => new EcogestureService(client),
[client]
)
const handleReinitClick = useCallback(() => {
setOpenEcogestureReinitModal(true)
}, [])
......@@ -83,14 +88,13 @@ const EcogestureTabsView = () => {
const handleLaunchReinit = useCallback(async () => {
setOpenEcogestureReinitModal(false)
setIsLoading(true)
const ecogestureService = new EcogestureService(client)
const reset = await ecogestureService.reinitAllEcogestures()
if (reset) {
setOpenEcogestureReinitModal(false)
setIsLoading(false)
navigate('/ecogesture-form?modal=true')
}
}, [client, navigate])
}, [ecogestureService, navigate])
const handleCloseEcogestureReinitModal = useCallback(() => {
setOpenEcogestureReinitModal(false)
......@@ -136,6 +140,9 @@ const EcogestureTabsView = () => {
async function loadEcogestures() {
const ecogestureService = new EcogestureService(client)
const currentProfile = profile.isProfileTypeCompleted
? profileType
: profileEcogesture
const { ecogestureList, ecogestureHash } =
await ecogestureService.initEcogesture(profile.ecogestureHash)
......@@ -144,10 +151,11 @@ const EcogestureTabsView = () => {
}
const availableList =
await ecogestureService.getEcogestureListByProfile(profileEcogesture)
await ecogestureService.getEcogestureListByProfile(currentProfile)
const filteredList = availableList.filter(
ecogesture => ecogesture.viewedInSelection === false
)
if (subscribed && ecogestureList) {
const doing = ecogestureList.filter(
ecogesture => ecogesture.doing === true
......@@ -163,11 +171,19 @@ const EcogestureTabsView = () => {
}
setIsLoading(false)
}
loadEcogestures()
return () => {
subscribed = false
}
}, [client, profileEcogesture, profileType, dispatch, profile.ecogestureHash])
}, [
client,
profileEcogesture,
profileType,
dispatch,
profile.ecogestureHash,
profile.isProfileTypeCompleted,
])
return (
<>
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EcogestureView component should be rendered correctly 1`] = `
exports[`EcogestureView component should be rendered correctly with 3 clickable tabs 1`] = `
<div
aria-hidden="true"
>
......@@ -56,7 +56,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = `
>
ecogesture.title_tab_1
<br />
(0)
(2)
</span>
<span
class="MuiTouchRipple-root"
......@@ -76,7 +76,7 @@ exports[`EcogestureView component should be rendered correctly 1`] = `
>
ecogesture.title_tab_2
<br />
(0)
(3)
</span>
<span
class="MuiTouchRipple-root"
......@@ -221,7 +221,247 @@ exports[`EcogestureView component should be rendered correctly 1`] = `
hidden=""
id="simple-tabpanel-2"
role="tabpanel"
/>
>
<mock-ecogesturelist
displayselection="false"
list="[object Object],[object Object],[object Object]"
selectiontotal="0"
selectionviewed="0"
/>
</div>
</mock-content>
</div>
`;
exports[`EcogestureView component should render empty list 1`] = `
<div
aria-hidden="true"
>
<mock-cozybar
titlekey="common.title_ecogestures"
/>
<mock-header
desktoptitlekey="common.title_ecogestures"
>
<div
class="MuiTabs-root ecogestures-tabs"
>
<div
class="MuiTabs-scroller MuiTabs-fixed"
style="overflow: hidden;"
>
<div
aria-label="ecogestures-tabs"
class="MuiTabs-flexContainer MuiTabs-centered"
role="tablist"
>
<button
aria-controls="simple-tabpanel-0"
aria-selected="true"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit single-tab active Mui-selected"
id="simple-tab-0"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper"
>
ecogesture.title_tab_0
<br />
(0)
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="simple-tabpanel-1"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit single-tab"
id="simple-tab-1"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper"
>
ecogesture.title_tab_1
<br />
(2)
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<button
aria-controls="simple-tabpanel-2"
aria-selected="false"
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit single-tab"
id="simple-tab-2"
role="tab"
tabindex="0"
type="button"
>
<span
class="MuiTab-wrapper"
>
ecogesture.title_tab_2
<br />
(3)
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
<span
class="PrivateTabIndicator-root-9 PrivateTabIndicator-colorSecondary-11 indicator-tab"
style="left: 0px; width: 0px;"
/>
</div>
</div>
</mock-header>
<mock-content>
<div
aria-labelledby="simple-tab-0"
id="simple-tabpanel-0"
role="tabpanel"
>
<div
class="ec-empty-container"
>
<div
class="ec-empty-content"
>
<svg
aria-hidden="true"
class="icon-big styles__icon___23x3R"
height="150"
width="150"
>
<use
xlink:href="#test-file-stub"
/>
</svg>
<div
class="text-16-normal"
>
ecogesture.emptyList.obj1
</div>
<div
class="text-16-normal"
>
ecogesture.emptyList.obj2
</div>
<div
class="buttons"
>
<button
aria-label="ecogesture.emptyList.btn1"
class="MuiButtonBase-root MuiButton-root MuiButton-text btnSecondary"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
ecogesture.emptyList.btn1
</span>
</button>
<button
aria-label="ecogesture.emptyList.btn2"
class="MuiButtonBase-root MuiButton-root MuiButton-text btnPrimary"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
ecogesture.emptyList.btn2
</span>
</button>
</div>
</div>
</div>
</div>
<div
aria-labelledby="simple-tab-1"
hidden=""
id="simple-tabpanel-1"
role="tabpanel"
>
<div
class="ec-empty-container"
>
<div
class="ec-empty-content"
>
<svg
aria-hidden="true"
class="icon-big styles__icon___23x3R"
height="150"
width="150"
>
<use
xlink:href="#test-file-stub"
/>
</svg>
<div
class="text-16-normal"
>
ecogesture.emptyList.doing1
</div>
<div
class="text-16-normal"
>
ecogesture.emptyList.doing2
</div>
<div
class="buttons"
>
<button
aria-label="ecogesture.emptyList.btn1"
class="MuiButtonBase-root MuiButton-root MuiButton-text btnSecondary"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
ecogesture.emptyList.btn1
</span>
</button>
<button
aria-label="ecogesture.emptyList.btn2"
class="MuiButtonBase-root MuiButton-root MuiButton-text btnPrimary"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
ecogesture.emptyList.btn2
</span>
</button>
</div>
</div>
</div>
</div>
<div
aria-labelledby="simple-tab-2"
hidden=""
id="simple-tabpanel-2"
role="tabpanel"
>
<mock-ecogesturelist
displayselection="false"
list="[object Object],[object Object],[object Object]"
selectiontotal="0"
selectionviewed="0"
/>
</div>
</mock-content>
</div>
`;
......@@ -306,6 +306,25 @@ exports[`EcogestureFormEquipment component should be rendered correctly 1`] = `
class="MuiTouchRipple-root"
/>
</button>
<button
aria-label="ecogesture_profile.equipments.accessible_label"
class="MuiButtonBase-root MuiIconButton-root checkbox-equipment"
style="border-radius: 5px;"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label"
>
<mock-equipment-icon
equipment="GARDEN"
ischecked="false"
/>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
......
......@@ -19,7 +19,11 @@ const EcogestureSelectionView = () => {
const { t } = useI18n()
const client = useClient()
const navigate = useNavigate()
const { profileEcogesture } = useAppSelector(state => state.ecolyo)
const {
profileType,
profileEcogesture,
profile: { isProfileTypeCompleted },
} = useAppSelector(state => state.ecolyo)
const [isLoading, setIsLoading] = useState(true)
const [indexEcogesture, setIndexEcogesture] = useState<number>(0)
const [ecogestureList, setEcogestureList] = useState<Ecogesture[]>([])
......@@ -51,9 +55,11 @@ const EcogestureSelectionView = () => {
doing: doing,
viewedInSelection: true,
})
const updatedList = ecogestureList
updatedList[indexEcogesture] = updatedEcogesture
setEcogestureList(updatedList)
setEcogestureList(prevList => {
const updatedList = [...prevList]
updatedList[indexEcogesture] = updatedEcogesture
return updatedList
})
setIndexEcogesture(prev => prev + 1)
},
[ecogestureList, ecogestureService, indexEcogesture]
......@@ -61,8 +67,9 @@ const EcogestureSelectionView = () => {
const restartSelection = useCallback(async () => {
setIsLoading(true)
const profile = isProfileTypeCompleted ? profileType : profileEcogesture
const availableList =
await ecogestureService.getEcogestureListByProfile(profileEcogesture)
await ecogestureService.getEcogestureListByProfile(profile)
const filteredList = availableList.filter(
ecogesture => ecogesture.viewedInSelection === false
)
......@@ -71,20 +78,26 @@ const EcogestureSelectionView = () => {
setEcogestureList(slicedFilteredList)
setIndexEcogesture(0)
setIsLoading(false)
}, [ecogestureService, profileEcogesture])
}, [
ecogestureService,
isProfileTypeCompleted,
profileEcogesture,
profileType,
])
useEffect(() => {
let subscribed = true
async function getFilteredList() {
const profile = isProfileTypeCompleted ? profileType : profileEcogesture
const availableList =
await ecogestureService.getEcogestureListByProfile(profileEcogesture)
await ecogestureService.getEcogestureListByProfile(profile)
const filteredList = availableList.filter(
ecogesture => ecogesture.viewedInSelection === false
)
const slicedFilteredList = filteredList.slice(0, 10)
if (subscribed) {
if (
availableList.length === filteredList.length &&
availableList.length === slicedFilteredList.length &&
slicedFilteredList.length > 0
) {
setOpenEcogestureSelectionModal(true)
......@@ -96,11 +109,17 @@ const EcogestureSelectionView = () => {
setIsLoading(false)
}
}
getFilteredList()
return () => {
subscribed = false
}
}, [ecogestureService, profileEcogesture])
}, [
ecogestureService,
profileType,
profileEcogesture,
isProfileTypeCompleted,
])
const renderEcogestureSelection = () => {
if (indexEcogesture <= ecogestureList.length - 1) {
......
......@@ -3,7 +3,6 @@ import userEvent from '@testing-library/user-event'
import FeedbackModal from 'components/Feedback/FeedbackModal'
import React from 'react'
import { Provider } from 'react-redux'
import * as storeHooks from 'store/hooks'
import { createMockEcolyoStore } from 'tests/__mocks__/store'
// Value coming from jest.config
......@@ -17,8 +16,6 @@ jest.mock('services/environment.service', () => {
jest.mock('components/Hooks/useExploration', () => () => ['', jest.fn()])
const mockAppDispatch = jest.spyOn(storeHooks, 'useAppDispatch')
describe('FeedbackModal component', () => {
const store = createMockEcolyoStore({ modal: { isFeedbacksOpen: true } })
beforeEach(() => {
......@@ -30,40 +27,19 @@ describe('FeedbackModal component', () => {
<FeedbackModal />
</Provider>
)
expect(screen.getByRole('dialog')).toBeInTheDocument()
expect(baseElement).toMatchSnapshot()
})
describe('FeedbackModal functionalities', () => {
it('should close modal with the "x" button', async () => {
render(
<Provider store={store}>
<FeedbackModal />
</Provider>
)
await userEvent.click(screen.getAllByRole('button')[0])
expect(mockAppDispatch).toHaveBeenCalledTimes(1)
})
it('should close modal with the "later" button', async () => {
render(
<Provider store={store}>
<FeedbackModal />
</Provider>
)
await userEvent.click(screen.getAllByRole('button')[1])
expect(mockAppDispatch).toHaveBeenCalledTimes(1)
})
it('should open the SAU link', async () => {
global.open = jest.fn()
render(
<Provider store={store}>
<FeedbackModal />
</Provider>
)
await userEvent.click(screen.getAllByRole('button')[2])
expect(window.open).toHaveBeenCalledTimes(1)
expect(global.open).toHaveBeenCalledWith(`${__SAU_LINK__}?version=0.0.0`)
})
it('should open the SAU link', async () => {
global.open = jest.fn()
render(
<Provider store={store}>
<FeedbackModal />
</Provider>
)
await userEvent.click(screen.getAllByRole('button')[2])
expect(window.open).toHaveBeenCalledTimes(1)
expect(global.open).toHaveBeenCalledWith(`${__SAU_LINK__}?version=0.0.0`)
})
})
......@@ -3,6 +3,7 @@ import LegendComparisonIcon from 'assets/icons/ico/legendComparison.svg'
import StyledIcon from 'components/CommonKit/Icon/StyledIcon'
import StyledSwitch from 'components/CommonKit/Switch/StyledSwitch'
import useExploration from 'components/Hooks/useExploration'
import { useMoveToLatestDate } from 'components/Hooks/useMoveToDate'
import { useClient } from 'cozy-client'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { FluidType, TimeStep, UserExplorationID } from 'enums'
......@@ -10,13 +11,7 @@ import { DateTime } from 'luxon'
import React, { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import ConsumptionService from 'services/consumption.service'
import DateChartService from 'services/dateChart.service'
import {
setCurrentIndex,
setSelectedDate,
setShowCompare,
setShowOfflineData,
} from 'store/chart/chart.slice'
import { setShowCompare, setShowOfflineData } from 'store/chart/chart.slice'
import { useAppDispatch, useAppSelector } from 'store/hooks'
import { getFluidName, getKonnectorSlug, isKonnectorActive } from 'utils/utils'
import FluidChartSwipe from './FluidChartSwipe'
......@@ -37,6 +32,9 @@ const FluidChart = ({ fluidType }: { fluidType: FluidType }) => {
const currentFluidStatus = fluidStatus[fluidType]
const isFluidConnected = isKonnectorActive(fluidStatus, fluidType)
const { moveToLatestDate } = useMoveToLatestDate(
currentFluidStatus?.lastDataDate
)
const [, setValidExploration] = useExploration()
const [containsHalfHourData, setContainsHalfHourData] =
useState<boolean>(false)
......@@ -103,19 +101,6 @@ const FluidChart = ({ fluidType }: { fluidType: FluidType }) => {
[lowercaseFluidType, lowercaseTimeStep, t]
)
// TODO if we keep this, use the same existing function
const moveToDate = () => {
if (currentFluidStatus?.lastDataDate) {
const dateChartService = new DateChartService()
const updatedIndex = dateChartService.defineDateIndex(
currentTimeStep,
currentFluidStatus.lastDataDate
)
dispatch(setSelectedDate(currentFluidStatus.lastDataDate))
dispatch(setCurrentIndex(updatedIndex))
}
}
const toggleModalConnection = () => {
switch (fluidType) {
case FluidType.ELECTRICITY:
......@@ -132,7 +117,7 @@ const FluidChart = ({ fluidType }: { fluidType: FluidType }) => {
const LastDataValid = fluidType !== FluidType.MULTIFLUID && (
<div className="lastValidData">
<Button className="btnText" onClick={moveToDate}>
<Button className="btnText" onClick={moveToLatestDate}>
{t('consumption_visualizer.last_valid_data', {
date: currentFluidStatus?.lastDataDate?.toFormat('dd/MM/yy') ?? '-',
})}
......
......@@ -70,15 +70,15 @@ const FluidChartSlide = ({
),
])
const consumptionService = new ConsumptionService(client)
const fluidTypeArray: FluidType[] =
fluidType === FluidType.MULTIFLUID ? fluidTypes : [fluidType]
const isMulti = fluidType === FluidType.MULTIFLUID
const fluidTypeArray: FluidType[] = isMulti ? fluidTypes : [fluidType]
const graphData = await consumptionService.getGraphData(
timePeriod,
currentTimeStep,
fluidTypeArray,
fluidStatus,
compareTimePeriod,
fluidType === FluidType.MULTIFLUID
isMulti
)
if (subscribed && graphData && graphData?.actualData.length > 0) {
setChartData(graphData)
......
......@@ -60,7 +60,6 @@ const formatAuthData = ({
}
const useKonnectorAuth = (
// not needed ?
fluidType: FluidType,
options: {
eglAuthData?: AccountEGLData
......
import { DateTime } from 'luxon'
import DateChartService from 'services/dateChart.service'
import { setCurrentIndex, setSelectedDate } from 'store/chart/chart.slice'
import { useAppDispatch, useAppSelector } from 'store/hooks'
export const useMoveToLatestDate = (lastDataDate: DateTime | null) => {
const dispatch = useAppDispatch()
const { currentTimeStep } = useAppSelector(state => state.ecolyo.chart)
const moveToLatestDate = () => {
if (!lastDataDate) return
const dateChartService = new DateChartService()
const updatedIndex = dateChartService.defineDateIndex(
currentTimeStep,
lastDataDate
)
dispatch(setSelectedDate(lastDataDate))
dispatch(setCurrentIndex(updatedIndex))
}
return { moveToLatestDate }
}
......@@ -2,7 +2,6 @@ import Button from '@material-ui/core/Button'
import warningDark from 'assets/icons/ico/warning-dark.svg'
import warningWhite from 'assets/icons/ico/warning-white.svg'
import StyledIcon from 'components/CommonKit/Icon/StyledIcon'
import Loader from 'components/Loader/Loader'
import { useClient } from 'cozy-client'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { FluidType, KonnectorUpdate } from 'enums'
......@@ -38,7 +37,6 @@ const ConnectionResult = ({
const account: Account | null = currentFluidStatus.connection.account
const [deleting, setDeleting] = useState<boolean>(false)
const [updating, setUpdating] = useState<boolean>(false)
const [lastExecutionDate, setLastExecutionDate] = useState<string | DateTime>(
'-'
)
......@@ -47,7 +45,6 @@ const ConnectionResult = ({
const [outDatedDataDays, setOutDatedDataDays] = useState<number | null>(null)
const updateKonnector = async () => {
setUpdating(true)
setStatus('')
setLastExecutionDate('-')
setKonnectorError('')
......@@ -62,7 +59,6 @@ const ConnectionResult = ({
fluidConnection: updatedConnection,
})
)
setUpdating(false)
}
const deleteAccountsAndTriggers = useCallback(async () => {
......@@ -110,37 +106,35 @@ const ConnectionResult = ({
}
}, [lastExecutionDate])
const handleRefreshConsent = useCallback(
(fluidType: FluidType) => {
if (fluidType == FluidType.ELECTRICITY) {
const accountData = currentFluidStatus.connection.account
?.auth as AccountSgeData
// store the previous account data since the onDelete will remove account from DB
dispatch(
updateSgeStore({
currentStep: 0,
firstName: accountData.firstname,
lastName: accountData.lastname,
pdl: parseInt(accountData.pointId),
address: accountData.address,
zipCode: parseInt(accountData.postalCode),
city: accountData.city,
dataConsent: true,
pdlConfirm: true,
shouldLaunchAccount: true,
})
)
dispatch(setShouldRefreshConsent(true))
} else {
deleteAccountsAndTriggers()
}
},
[
deleteAccountsAndTriggers,
dispatch,
currentFluidStatus.connection.account?.auth,
]
)
const handleRefreshConsent = useCallback(() => {
if (fluidType == FluidType.ELECTRICITY) {
const accountData = currentFluidStatus.connection.account
?.auth as AccountSgeData
// store the previous account data since the onDelete will remove account from DB
dispatch(
updateSgeStore({
currentStep: 0,
firstName: accountData.firstname,
lastName: accountData.lastname,
pdl: parseInt(accountData.pointId),
address: accountData.address,
zipCode: parseInt(accountData.postalCode),
city: accountData.city,
dataConsent: true,
pdlConfirm: true,
shouldLaunchAccount: true,
})
)
dispatch(setShouldRefreshConsent(true))
} else {
deleteAccountsAndTriggers()
}
}, [
fluidType,
currentFluidStatus.connection.account?.auth,
dispatch,
deleteAccountsAndTriggers,
])
useEffect(() => {
if (currentFluidStatus.connection.triggerState?.last_success) {
......@@ -162,9 +156,8 @@ const ConnectionResult = ({
)
)
}
if (isOutdated()) {
setOutDatedDataDays(isOutdated())
}
const outdated = isOutdated()
if (outdated) setOutDatedDataDays(outdated)
}, [currentFluidStatus.connection.triggerState, isOutdated])
const getFluidTypeTranslation = (fluidType: FluidType) => {
......@@ -193,15 +186,12 @@ const ConnectionResult = ({
// First check if there is partner error from backoffice
if (currentFluidStatus.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 className="connection-caption">
{t('konnector_form.wait_end_issue')}
</div>
)
}
// Else check if konnector is in error state
if (status === 'errored') {
return (
......@@ -233,50 +223,40 @@ const ConnectionResult = ({
)
}
const getErrorClassName = (): string => {
if (
status === 'errored' &&
!hasUpdatedToday() &&
!currentFluidStatus.maintenance
) {
return 'connection-update-errored'
}
return ''
}
return (
<div className="connection-update-result">
<div
className={
status === 'errored' &&
!hasUpdatedToday() &&
!currentFluidStatus.maintenance
? 'connection-update-errored'
: ''
}
>
{getConnectionStatus()}
</div>
<div className={getErrorClassName()}>{getConnectionStatus()}</div>
<div className="inline-buttons">
{!consentError && (
<Button
aria-label={t('konnector_form.accessibility.button_disconnect')}
onClick={deleteAccountsAndTriggers}
disabled={updating || deleting}
disabled={deleting}
className="btnSecondary"
>
{deleting
? t('konnector_form.loading')
: t('konnector_form.button_disconnect')}
{t(`konnector_form.${deleting ? 'loading' : 'button_disconnect'}`)}
</Button>
)}
<Button
aria-label={t('konnector_form.accessibility.button_update')}
onClick={
consentError
? () => handleRefreshConsent(fluidType)
: updateKonnector
}
disabled={updating || deleting}
onClick={consentError ? handleRefreshConsent : updateKonnector}
disabled={deleting}
className="btnPrimary"
>
{updating && <Loader color="black" />}
{!updating && (
<div>
{consentError
? t('konnector_form.button_oauth_reload')
: t('konnector_form.button_update')}
</div>
)}
{consentError
? t('konnector_form.button_oauth_reload')
: t('konnector_form.button_update')}
</Button>
</div>
</div>
......
......@@ -23,7 +23,7 @@ describe('KonnectorModal component', () => {
error={null}
fluidType={FluidType.ELECTRICITY}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
......@@ -41,7 +41,7 @@ describe('KonnectorModal component', () => {
error={null}
fluidType={FluidType.ELECTRICITY}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
......@@ -61,7 +61,7 @@ describe('KonnectorModal component', () => {
error={null}
fluidType={FluidType.ELECTRICITY}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
......@@ -80,18 +80,18 @@ describe('KonnectorModal component', () => {
error={KonnectorError.LOGIN_FAILED}
fluidType={FluidType.ELECTRICITY}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
</Provider>
)
expect(
baseElement.getElementsByClassName('kce-picto-txt')[0]
baseElement.getElementsByClassName('headerError')[0]
).toBeInTheDocument()
})
it('should render unknown error', async () => {
const { baseElement } = render(
render(
<Provider store={store}>
<KonnectorModal
open={true}
......@@ -100,15 +100,13 @@ describe('KonnectorModal component', () => {
error={null}
fluidType={FluidType.ELECTRICITY}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
</Provider>
)
expect(
baseElement.getElementsByClassName('err-data-2')[0]
).toBeInTheDocument()
expect(screen.getByText('konnector_modal.error_data_2')).toBeInTheDocument()
})
it('should render update error', async () => {
const { baseElement } = render(
......@@ -120,7 +118,7 @@ describe('KonnectorModal component', () => {
error={null}
fluidType={FluidType.WATER}
handleCloseClick={mockHandleCloseClick}
isLogging={false}
isVerifyingIdentity={false}
account={null}
handleAccountDeletion={jest.fn()}
/>
......
......@@ -17,6 +17,7 @@ import { FluidType, KonnectorError } from 'enums'
import { shuffle } from 'lodash'
import { Account } from 'models'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { getFluidName } from 'utils/utils'
import KonnectorModalFooter from './KonnectorModalFooter'
import './konnectorModal.scss'
......@@ -24,7 +25,7 @@ interface KonnectorModalProps {
open: boolean
isUpdating: boolean
/** Only used for SGE when searching for user identity */
isLogging: boolean
isVerifyingIdentity: boolean
state: string | null
error: KonnectorError | null
fluidType: FluidType
......@@ -36,7 +37,7 @@ interface KonnectorModalProps {
const KonnectorModal = ({
open,
isUpdating,
isLogging,
isVerifyingIdentity,
state,
error,
fluidType,
......@@ -45,19 +46,21 @@ const KonnectorModal = ({
account,
}: KonnectorModalProps) => {
const { t } = useI18n()
const fluidName: string = FluidType[fluidType]
const fluidName = getFluidName(fluidType)
const [index, setIndex] = useState<number>(0)
/** Only used for enedis to see common errors */
const [showCommonErrors, setShowCommonErrors] = useState(false)
const shuffledWaitingTexts = useMemo(() => {
if (fluidType) {
return shuffle(connectionWaitingText)
} else {
return connectionWaitingText
}
return connectionWaitingText
}, [fluidType])
const firstConnectionWaitingTexts = firstConnectionWaitingText.concat(
...shuffledWaitingTexts
)
const [showCommonErrors, setShowCommonErrors] = useState(false)
const getUpdatingText = useCallback(() => {
return (
......@@ -80,7 +83,7 @@ const KonnectorModal = ({
const getConnectionText = useCallback(() => {
return (
<div className="kmodal-waiting-text text-18-italic">
{isLogging ? (
{isVerifyingIdentity ? (
<p className="text-18-white">{t('konnector_modal.logging_txt')}</p>
) : (
firstConnectionWaitingTexts.map((text, idx) => (
......@@ -97,29 +100,29 @@ const KonnectorModal = ({
)}
</div>
)
}, [firstConnectionWaitingTexts, index, isLogging, t])
}, [firstConnectionWaitingTexts, index, isVerifyingIdentity, t])
/** Returns connection success contents, depending on the fluid and update status */
const connectionSuccessContent = () => (
<div className="konnector-config">
<Icon icon={successIcon} size={48} />
<div className="kcs-picto-txt text-20-bold">
<div className="headerSuccess text-20-bold">
{t(`konnector_modal.success_${isUpdating ? 'update_' : ''}txt`)}
</div>
<b>
{t(
`konnector_modal.success_data_${
isUpdating ? 'update_' : ''
}${fluidName.toLowerCase()}`
}${fluidName}`
)}
</b>
<p
style={{ fontWeight: 400 }}
<div
className="light"
dangerouslySetInnerHTML={{
__html: t(
`konnector_modal.success_data_additional_${
isUpdating ? 'update_' : ''
}${fluidName.toLowerCase()}`
}${fluidName}`
),
}}
/>
......@@ -156,11 +159,13 @@ const KonnectorModal = ({
{t('konnector_modal.accessibility.window_title')}
</div>
<div className="kmodal-content">
{open && !state ? (
{/* NEITHER ERROR NOR SUCCESS => LOADING */}
{!state ? (
<>
<Loader fluidType={fluidType} />
{!isLogging && (
<div className="kmodal-content-text kmodal-content-text-center text-16-normal">
{!isVerifyingIdentity && (
<div className="kmodal-content-text text-16-normal">
{/* TODO remove kc-wait */}
<div className="kc-wait text-16-bold">
{t(
`konnector_modal.loading_data${isUpdating ? '_update' : ''}`
......@@ -173,6 +178,7 @@ const KonnectorModal = ({
</>
) : (
<>
{/* ERROR OR SUCCESS */}
<div className="kmodal-info">
{state === ERROR_EVENT && (
<>
......@@ -180,35 +186,34 @@ const KonnectorModal = ({
// LOGIN FAILED FOR ENEDIS AND EGL
<div className="konnector-config">
<Icon icon={errorIcon} size={48} />
<div className="kce-picto-txt text-20-bold">
<div className="headerError text-20-bold">
{t('konnector_modal.error_txt')}
</div>
<div>
{t(
`konnector_modal.error_credentials_${
isUpdating ? 'update_' : ''
}${fluidName.toLowerCase()}`
}${fluidName}`
)}
</div>
{fluidType === FluidType.ELECTRICITY && !isUpdating && (
<div className="elec-fail">
{t(
`konnector_modal.error_credentials_${fluidName.toLowerCase()}_2`
`konnector_modal.error_credentials_${fluidName}_2`
)}
</div>
)}
{/* Show common errors */}
{/* Show common errors for enedis */}
{fluidType === FluidType.ELECTRICITY && (
<>
{!showCommonErrors && (
{!showCommonErrors ? (
<Button
className="btnText commonErrors"
className="btnText"
onClick={() => setShowCommonErrors(true)}
>
{t('konnector_modal.show_common_error')}
</Button>
)}
{showCommonErrors && (
) : (
<div
className="commonErrorsList"
dangerouslySetInnerHTML={{
......@@ -226,7 +231,7 @@ const KonnectorModal = ({
isUpdating &&
fluidType === FluidType.ELECTRICITY && (
// MISMATCH UPDATE ERROR ENEDIS
<div className="kce-picto-txt konnector-config mismatch">
<div className="headerError konnector-config mismatch">
<Icon icon={EnedisIcon} width={120} height={80} />
<div className="title text-20-bold">
{t('konnector_modal.mismatch.title')}
......@@ -242,44 +247,40 @@ const KonnectorModal = ({
</div>
</div>
)}
{error === KonnectorError.CHALLENGE_ASKED &&
{error ===
KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED &&
fluidType === FluidType.GAS && (
// CONSENT FORM ERROR GRDF
<div className="konnector-config">
<Icon icon={errorIcon} size={48} />
<div className="kce-picto-txt text-20-bold">
<div className="headerError text-20-bold">
{t('konnector_modal.error_txt')}
</div>
<div className="title text-20-bold">
<div className="title text-16-bold">
{t('konnector_modal.error_consent_form_gas_title')}
</div>
<div className="err-data-2">
{t('konnector_modal.error_consent_form_gas_content')}
</div>
<div className="err-data-2">
{t(
'konnector_modal.error_consent_form_gas_content_2'
)}
<div className="light text-14-regular">
{t('konnector_modal.error_consent_form_gas_report')}
</div>
</div>
)}
{error !== KonnectorError.LOGIN_FAILED &&
error !== KonnectorError.TERMS_VERSION_MISMATCH &&
error !== KonnectorError.CHALLENGE_ASKED && (
error !==
KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED && (
// DEFAULT CASE
<div className="konnector-config">
<Icon icon={errorIcon} size={48} />
<div className="kce-picto-txt text-20-bold">
<div className="headerError text-20-bold">
{t('konnector_modal.error_txt')}
</div>
<div>
<div className="text-15-bold">
{t(
`konnector_modal.error_data_${
isUpdating ? 'update_' : ''
}${fluidName.toLowerCase()}`
}${fluidName}`
)}
</div>
<div className="err-data-2">
<div className="text-14-regular">
{t('konnector_modal.error_data_2')}
</div>
</div>
......
import Button from '@material-ui/core/Button'
import { useClient } from 'cozy-client'
import {
ERROR_EVENT,
SUCCESS_EVENT,
} from 'cozy-harvest-lib/dist/models/flowEvents'
import { SUCCESS_EVENT } from 'cozy-harvest-lib/dist/models/flowEvents'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'
import { KonnectorError } from 'enums'
import { Account } from 'models'
......@@ -12,6 +9,8 @@ import { useNavigate } from 'react-router-dom'
import AccountService from 'services/account.service'
import './konnectorModal.scss'
declare let __SAU_ISSUE_DIRECT_LINK__: string
interface KonnectorModalFooterProps {
state: string | null
error: KonnectorError | null
......@@ -32,6 +31,7 @@ const KonnectorModalFooter = ({
const { t } = useI18n()
const client = useClient()
const navigate = useNavigate()
const handleSGELoginRetry = useCallback(() => {
handleCloseClick(state === SUCCESS_EVENT)
navigate('/connect/electricity')
......@@ -46,73 +46,87 @@ const KonnectorModalFooter = ({
}
}, [account, client, handleAccountDeletion, navigate])
const defaultButton = (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_validate')}</div>
</Button>
)
const errorButtons = () => {
switch (error) {
case KonnectorError.USER_ACTION_NEEDED:
// INSEE CODE ERROR ENEDIS
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_understood')}</div>
</Button>
)
case KonnectorError.LOGIN_FAILED:
case KonnectorError.CHALLENGE_ASKED:
// INCOMPLETE CONSENT FORM - GRDF
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_try_again')}</div>
</Button>
)
case KonnectorError.TERMS_VERSION_MISMATCH:
return (
<div className="buttons">
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnSecondary"
>
<div>{t('konnector_modal.button_later')}</div>
</Button>
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={
!isUpdating ? handleSGELoginRetry : handleResetSGEAccount
}
className="btnPrimary"
>
<div>
{!isUpdating
? t('konnector_modal.button_check_info')
: t('konnector_modal.button_go')}
</div>
</Button>
if (error === KonnectorError.USER_ACTION_NEEDED) {
// INSEE CODE ERROR ENEDIS
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_understood')}</div>
</Button>
)
} else if (error === KonnectorError.LOGIN_FAILED) {
// INCOMPLETE CONSENT FORM - GRDF // what is this comment ?
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_try_again')}</div>
</Button>
)
} else if (error === KonnectorError.CHALLENGE_ASKED) {
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_come_back_later')}</div>
</Button>
)
} else if (error === KonnectorError.TERMS_VERSION_MISMATCH) {
return (
<div className="buttons">
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnSecondary"
>
<div>{t('konnector_modal.button_later')}</div>
</Button>
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={!isUpdating ? handleSGELoginRetry : handleResetSGEAccount}
className="btnPrimary"
>
<div>
{!isUpdating
? t('konnector_modal.button_check_info')
: t('konnector_modal.button_go')}
</div>
)
default:
// DEFAULT FOOTER BUTTONS
return defaultButton
}
</Button>
</div>
)
} else if (error === KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED) {
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => {
window.open(
`${__SAU_ISSUE_DIRECT_LINK__}?version=${client.appMetadata.version}`
)
handleCloseClick(state === SUCCESS_EVENT)
}}
className="btnPrimary"
>
<div>{t('konnector_modal.button_contact')}</div>
</Button>
)
} else {
return (
<Button
aria-label={t('konnector_modal.accessibility.button_close')}
onClick={() => handleCloseClick(state === SUCCESS_EVENT)}
className="btnPrimary"
>
<div>{t('konnector_modal.button_validate')}</div>
</Button>
)
}
return <>{state === ERROR_EVENT ? errorButtons() : defaultButton}</>
}
export default KonnectorModalFooter
......@@ -12,7 +12,6 @@ import OfflinePicto from 'assets/icons/visu/offline-param.svg'
import classNames from 'classnames'
import StyledIcon from 'components/CommonKit/Icon/StyledIcon'
import Connection from 'components/Connection/Connection'
import { GrdfWaitConsent } from 'components/Connection/GRDFConnect/GrdfWaitConsent'
import KonnectorModal from 'components/Konnector/KonnectorModal'
import { useClient } from 'cozy-client'
import { isKonnectorRunning } from 'cozy-harvest-lib/dist/helpers/triggers'
......@@ -54,27 +53,24 @@ import {
updateFluidConnection,
} from 'store/global/global.slice'
import { useAppDispatch, useAppSelector } from 'store/hooks'
import logApp from 'utils/logger'
import { getParamPicto } from 'utils/picto'
import { getKonnectorSlug } from 'utils/utils'
import ConnectionNotFound from './ConnectionNotFound/ConnectionNotFound'
import ConnectionResult from './ConnectionResult/ConnectionResult'
import './konnectorViewerCard.scss'
interface KonnectorViewerCardProps {
showOfflineData: boolean
fluidType: FluidType
}
const KonnectorViewerCard = ({
showOfflineData,
fluidType,
}: KonnectorViewerCardProps) => {
}: {
fluidType: Exclude<FluidType, FluidType.MULTIFLUID>
}) => {
const { t } = useI18n()
const client = useClient()
const navigate = useNavigate()
const dispatch = useAppDispatch()
const {
chart: { showConnectionDetails },
chart: { showConnectionDetails, showOfflineData },
challenge: { currentChallenge },
global: { fluidStatus, shouldRefreshConsent, partnersInfo },
} = useAppSelector(state => state.ecolyo)
......@@ -83,26 +79,27 @@ const KonnectorViewerCard = ({
const fluidState = currentFluidStatus.status
const { konnector, account, trigger } = currentFluidStatus.connection
const currentFluidName = FluidType[currentFluidStatus.fluidType].toLowerCase()
const [openModal, setOpenModal] = useState(false)
const [isUpdating, setIsUpdating] = useState(false)
const [isLogging, setIsLogging] = useState(
const [isVerifyingIdentity, setIsVerifyingIdentity] = useState(
fluidType === FluidType.ELECTRICITY
)
const [konnectorErrorDescription, setKonnectorErrorDescription] =
useState<KonnectorError | null>(null)
const [konnectorState, setKonnectorState] = useState<string | null>(null)
const [isOutdatedData, setIsOutdatedData] = useState<number | null>(null)
const isWaitingForConsent =
fluidType === FluidType.GAS &&
currentFluidStatus.status === FluidState.CHALLENGE_ASKED
const fluidService = useMemo(() => new FluidService(client), [client])
const partnersInfoService = useMemo(
() => new PartnersInfoService(client),
[client]
)
const lastDataDate =
fluidType !== FluidType.MULTIFLUID && currentFluidStatus.lastDataDate
? currentFluidStatus.lastDataDate.toLocaleString() + fluidType
: fluidType
const iconType = getParamPicto(currentFluidStatus.fluidType)
const toggleAccordion = () => {
......@@ -112,19 +109,19 @@ const KonnectorViewerCard = ({
const updateGlobalFluidStatus = useCallback(async (): Promise<
FluidStatus[]
> => {
const _updatedFluidStatus = await fluidService.getFluidStatus()
const updatedFluidStatus = await fluidService.getFluidStatus()
const refDate = DateTime.fromISO('0001-01-01')
let _lastDataDate = DateTime.fromISO('0001-01-01')
for (const fluid of _updatedFluidStatus) {
if (fluid?.lastDataDate && fluid?.lastDataDate > _lastDataDate) {
_lastDataDate = fluid.lastDataDate
let lastDataDate = DateTime.fromISO('0001-01-01')
for (const fluid of updatedFluidStatus) {
if (fluid?.lastDataDate && fluid?.lastDataDate > lastDataDate) {
lastDataDate = fluid.lastDataDate
}
}
if (_lastDataDate > refDate) {
dispatch(setSelectedDate(_lastDataDate))
if (lastDataDate > refDate) {
dispatch(setSelectedDate(lastDataDate))
}
return _updatedFluidStatus
return updatedFluidStatus
}, [dispatch, fluidService])
const refreshChallengeState = useCallback(async () => {
......@@ -173,9 +170,10 @@ const KonnectorViewerCard = ({
const isGlobalLoginFailed =
konnectorErrorDescription === KonnectorError.LOGIN_FAILED ||
konnectorErrorDescription === KonnectorError.UNKNOWN_ERROR ||
konnectorErrorDescription === KonnectorError.CHALLENGE_ASKED ||
konnectorErrorDescription === KonnectorError.CRITICAL ||
konnectorErrorDescription === KonnectorError.MISSING_SECRET
konnectorErrorDescription === KonnectorError.MISSING_SECRET ||
konnectorErrorDescription ===
KonnectorError.USER_ACTION_NEEDED_ACCOUNT_REMOVED
// CASE FOR ENEDIS CODE INSEE ERROR
const isEnedisCodeInseeError =
......@@ -189,25 +187,21 @@ const KonnectorViewerCard = ({
(isGlobalLoginFailed || isEnedisCodeInseeError)
if (shouldDeleteAccount) {
logApp('info', `shouldDeleteAccount`)
// KEEP LAST LOGIN FOR EPGL
let lastEpglLogin = ''
if (
fluidSlug === FluidSlugType.WATER &&
currentFluidStatus.connection.account?.auth
) {
const auth = currentFluidStatus.connection.account
.auth as AccountEGLData
lastEpglLogin = auth.login
const lastEpglLogin = auth.login
dispatch(setLastEpglLogin(lastEpglLogin))
}
// DELETE ACCOUNT
const accountService = new AccountService(client)
await accountService.deleteAccount(account)
await handleAccountDeletion()
// RESTORE LAST KNOWN CREDENTIALS
if (lastEpglLogin) {
dispatch(setLastEpglLogin(lastEpglLogin))
}
} else {
const updatedFluidStatus =
await fluidService.getFluidStatus(partnersInfo)
......@@ -249,12 +243,6 @@ const KonnectorViewerCard = ({
}, [dispatch, fluidType, navigate])
const getConnectionCard = useCallback(() => {
if (
fluidType === FluidType.GAS &&
fluidState === FluidState.CHALLENGE_ASKED
) {
return <GrdfWaitConsent />
}
if (showOfflineData && !account) {
return (
<Button className="btnPrimary" onClick={toggleModalConnection}>
......@@ -262,35 +250,32 @@ const KonnectorViewerCard = ({
</Button>
)
}
if (fluidState === FluidState.KONNECTOR_NOT_FOUND && !isUpdating) {
return <ConnectionNotFound konnectorSlug={fluidSlug} />
}
// Handle login failed for EGL
else if (
if (
(fluidType === FluidType.WATER &&
fluidState === FluidState.ERROR_LOGIN_FAILED) ||
fluidState === FluidState.LOGIN_FAILED) ||
(account && currentFluidStatus.status !== FluidState.NOT_CONNECTED)
) {
return (
<ConnectionResult
handleAccountDeletion={handleAccountDeletion}
fluidType={fluidType}
key={lastDataDate}
/>
)
} else {
return <Connection fluidType={currentFluidStatus.fluidType} />
}
}, [
account,
currentFluidStatus.fluidType,
currentFluidStatus.status,
fluidSlug,
fluidState,
fluidType,
handleAccountDeletion,
isUpdating,
lastDataDate,
showOfflineData,
t,
toggleModalConnection,
......@@ -325,59 +310,34 @@ const KonnectorViewerCard = ({
]
)
const getIconForStatus = (
status: FluidState,
maintenance: boolean,
connection: FluidConnection,
outdatedData: number | null
) => {
if (maintenance) {
return (
<StyledIcon
icon={PartnersIssueNotif}
size={24}
className="konnector-state-picto"
/>
)
}
/** Display konnector icon & its smaller status icon in upper left corner */
const displayKonnectorIcon = useCallback(() => {
const { maintenance, status, connection } = currentFluidStatus
let statusIcon = null
if (
(status === FluidState.ERROR ||
status === FluidState.ERROR_LOGIN_FAILED) &&
if (maintenance) {
statusIcon = PartnersIssueNotif
} else if (
(status === FluidState.ERROR || status === FluidState.LOGIN_FAILED) &&
connection.account
) {
return (
<StyledIcon
icon={ErrorNotif}
size={24}
className="konnector-state-picto"
/>
)
statusIcon = ErrorNotif
} else if (isOutdatedData && isOutdatedData > 0) {
statusIcon = WarningNotif
}
if (outdatedData && outdatedData > 0) {
return (
<StyledIcon
icon={WarningNotif}
size={24}
className="konnector-state-picto"
/>
)
}
}
const displayKonnectorIcon = useCallback(() => {
return (
<div className="konnector-icon">
<Icon
icon={currentFluidStatus.connection.account ? iconType : OfflinePicto}
size={49}
/>
{getIconForStatus(
currentFluidStatus.status,
currentFluidStatus.maintenance,
currentFluidStatus.connection,
isOutdatedData
{statusIcon && (
<StyledIcon
icon={statusIcon}
size={24}
className="konnector-state-picto"
/>
)}
</div>
)
......@@ -452,7 +412,7 @@ const KonnectorViewerCard = ({
callbackResponse(ERROR_EVENT)
})
connectionFlow.jobWatcher.on(LOGIN_SUCCESS_EVENT, () => {
setIsLogging(false)
setIsVerifyingIdentity(false)
})
connectionFlow.jobWatcher.on(SUCCESS_EVENT, () => {
callbackResponse(SUCCESS_EVENT)
......@@ -486,21 +446,18 @@ const KonnectorViewerCard = ({
return (
<div className="konnector-section-root">
{!showOfflineData && (
<AccordionDetails>{getConnectionCard()}</AccordionDetails>
)}
{showOfflineData && (
{!showOfflineData && <Connection fluidType={fluidType} />}
{showOfflineData && !isWaitingForConsent && (
<Accordion
expanded={showConnectionDetails}
onChange={toggleAccordion}
classes={{
root: `expansion-panel-root ${
!currentFluidStatus.maintenance &&
(currentFluidStatus.status === FluidState.ERROR ||
currentFluidStatus.status === FluidState.ERROR_LOGIN_FAILED)
? 'red-border'
: ''
}`,
root: classNames('expansion-panel-root', {
['red-border']:
!currentFluidStatus.maintenance &&
(currentFluidStatus.status === FluidState.ERROR ||
currentFluidStatus.status === FluidState.LOGIN_FAILED),
}),
}}
>
<AccordionSummary
......@@ -538,10 +495,10 @@ const KonnectorViewerCard = ({
<KonnectorModal
open={openModal}
isUpdating={isUpdating}
isLogging={isLogging}
isVerifyingIdentity={isVerifyingIdentity}
state={konnectorState}
error={konnectorErrorDescription}
fluidType={currentFluidStatus.fluidType}
fluidType={fluidType}
handleCloseClick={handleConnectionEnd}
handleAccountDeletion={handleAccountDeletion}
account={account}
......
......@@ -11,8 +11,9 @@ import './konnectorViewerCard.scss'
const KonnectorViewerList = () => {
const { t } = useI18n()
const { fluidStatus } = useAppSelector(state => state.ecolyo.global)
const navigate = useNavigate()
const { fluidStatus } = useAppSelector(state => state.ecolyo.global)
const goToFluid = (fluidType: FluidType) => {
navigate(`/consumption/${getFluidName(fluidType)}`)
}
......@@ -29,14 +30,14 @@ const KonnectorViewerList = () => {
>
<Icon icon={getAddPicto(fluidStatusItem.fluidType)} size={36} />
<div
className={`konnector-title text-18-bold ${FluidType[
className={`konnector-title text-18-bold ${getFluidName(
fluidStatusItem.fluidType
].toLowerCase()}`}
)}`}
>
{t(
`konnector_options.label_connect_to_${FluidType[
`konnector_options.label_connect_to_${getFluidName(
fluidStatusItem.fluidType
].toLowerCase()}`
)}`
)}
</div>
</StyledCard>
......
......@@ -62,7 +62,7 @@ exports[`KonnectorModal component should be rendered correctly 1`] = `
</div>
</div>
<div
class="kmodal-content-text kmodal-content-text-center text-16-normal"
class="kmodal-content-text text-16-normal"
>
<div
class="kc-wait text-16-bold"
......