diff --git a/backend/datasources/dummy.py b/backend/datasources/dummy.py
index 959c4a76..f1b39af7 100644
--- a/backend/datasources/dummy.py
+++ b/backend/datasources/dummy.py
@@ -209,7 +209,9 @@ def dummy_installation_details(username: str, contract_number: str) -> Installat
code=contract_number,
error=f"{contract_number} (Dummy error)",
))
- return InstallationDetailsResult(**ns.load('frontend/src/data/dummyinstallationdetail.yaml'))
+ details = InstallationDetailsResult(**ns.load('frontend/src/data/dummyinstallationdetail.yaml'))
+ details.installation_details.contract_number = contract_number
+ return details
invoice_pdf_exceptions = {
e.__name__: e
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 66f184d0..7ad6a5d0 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -26,6 +26,7 @@ import InstallationsPage from './pages/InstallationsPage'
import DetailInstallationPage from './pages/DetailInstallationPage'
import InvoicesPage from './pages/InvoicesPage'
import ProductionPage from './pages/ProductionPage'
+import { InstallationContextProvider } from './components/InstallationProvider'
const routes = [
{
@@ -73,11 +74,21 @@ const routes = [
},
{
path: '/installation',
- element: ,
- },
- {
- path: '/installation/:contract_number',
- element: ,
+ element: (
+
+
+
+ ),
+ children: [
+ {
+ path: '',
+ element: ,
+ },
+ {
+ path: ':contract_number',
+ element: ,
+ },
+ ],
},
{
path: '/invoices',
diff --git a/frontend/src/components/BreakPointIndicator.jsx b/frontend/src/components/BreakPointIndicator.jsx
index 6bbd6051..0e3d118f 100644
--- a/frontend/src/components/BreakPointIndicator.jsx
+++ b/frontend/src/components/BreakPointIndicator.jsx
@@ -16,7 +16,7 @@ export default function BreakPointIndicator() {
minWidth: '4rem',
padding: '0.5rem 0.9rem',
borderRadius: '5px',
- alignText: 'center',
+ textAlign: 'center',
color: 'black',
backgroundColor: {
xs: '#d99a',
diff --git a/frontend/src/components/InstallationProvider.jsx b/frontend/src/components/InstallationProvider.jsx
new file mode 100644
index 00000000..d6602319
--- /dev/null
+++ b/frontend/src/components/InstallationProvider.jsx
@@ -0,0 +1,48 @@
+import React, { useEffect, useState, useMemo } from 'react'
+import PropTypes from 'prop-types'
+import { useAuth } from './AuthProvider'
+import ovapi from '../services/ovapi'
+
+const InstallationContext = React.createContext()
+
+const InstallationContextProvider = ({ children }) => {
+ const { currentUser } = useAuth()
+ const [installations, setInstallations] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+ const memoizedCurrentUser = useMemo(() => currentUser, [currentUser])
+
+ useEffect(() => {
+ const getInstallations = async () => {
+ try {
+ const installationsData = await ovapi.installations(memoizedCurrentUser)
+ setInstallations(installationsData)
+ } catch (error) {
+ setError(error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ getInstallations()
+ }, [memoizedCurrentUser])
+ getInstallations()
+ }, [memoizedCurrentUser])
+
+ const contextValue = useMemo(
+ () => ({ installations, loading, error }),
+ [installations, loading, error],
+ )
+
+ return installations !== null ? (
+
+ {children}
+
+ ) : null
+}
+
+InstallationContextProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+export { InstallationContext, InstallationContextProvider }
diff --git a/frontend/src/components/NavigationButtons.jsx b/frontend/src/components/NavigationButtons.jsx
index fc6e3e3e..30e2a428 100644
--- a/frontend/src/components/NavigationButtons.jsx
+++ b/frontend/src/components/NavigationButtons.jsx
@@ -19,10 +19,12 @@ const StyledButton = styled(Button)(({ theme }) => ({
const NavigationButtons = (props) => {
const { toBefore, toNext, toReturn, returnIcon } = props
+
return (
{
}}
>
-
-
-
-
-
-
+ {toBefore && (
+
+
+
+ )}
+ {toNext && (
+
+
+
+ )}
{returnIcon}
diff --git a/frontend/src/pages/DetailInstallationPage/detailInstallationData.js b/frontend/src/pages/DetailInstallationPage/detailInstallationData.js
index 6cb42549..809c948f 100644
--- a/frontend/src/pages/DetailInstallationPage/detailInstallationData.js
+++ b/frontend/src/pages/DetailInstallationPage/detailInstallationData.js
@@ -26,6 +26,7 @@ export const contractFields = [
'discharge_date',
'status',
]
+
export function transformInstallationDetails(data) {
const t = i18n.t
const productionTecnologyOptions = {
@@ -39,7 +40,7 @@ export function transformInstallationDetails(data) {
technology: format.enumeration(data.technology, productionTecnologyOptions),
}
}
-export default function transformContractDetails(contract) {
+function transformContractDetails(contract) {
const t = i18n.t
const billingModeOptions = {
index: t('CONTRACT_DETAIL.BILLING_MODE_INDEX'),
@@ -88,3 +89,26 @@ export default function transformContractDetails(contract) {
return contract
}
+
+function computeNavigationInfo(installations, currentInstallationContractNumber) {
+ if (installations.length < 2) {
+ return {}
+ }
+
+ // Find the index of the current installation
+ const currentIndex = installations.findIndex(
+ (installation) => installation.contract_number === currentInstallationContractNumber,
+ )
+
+ // Determine the index of the previous and next installations
+ const previousIndex = currentIndex > 0 ? currentIndex - 1 : installations.length - 1
+ const nextIndex = currentIndex < installations.length - 1 ? currentIndex + 1 : 0
+
+ // Extract the contract numbers for the previous and next installations
+ const before = installations[previousIndex].contract_number
+ const next = installations[nextIndex].contract_number
+
+ return { before, next }
+}
+
+export { transformContractDetails, computeNavigationInfo }
diff --git a/frontend/src/pages/DetailInstallationPage/detailInstallationData.test.js b/frontend/src/pages/DetailInstallationPage/detailInstallationData.test.js
index d72d7080..69065d06 100644
--- a/frontend/src/pages/DetailInstallationPage/detailInstallationData.test.js
+++ b/frontend/src/pages/DetailInstallationPage/detailInstallationData.test.js
@@ -1,8 +1,10 @@
import { describe, expect, it } from 'vitest'
import i18n from '../../i18n/i18n'
-import transformContractDetails, {
+import {
+ transformContractDetails,
transformInstallationDetails,
+ computeNavigationInfo,
} from './detailInstallationData'
describe('transformContractDetails', () => {
@@ -138,3 +140,77 @@ describe('transformInstallationDetails', () => {
expect(result['rated_power']).toEqual('11 kW')
})
})
+
+describe('computeNavigationInfo', () => {
+ it('Returns empty when installations length is smaller than 2', () => {
+ const installations = [
+ {
+ contract_number: 'a_contract_number',
+ installation_name: 'an_installation_name',
+ },
+ ]
+ const currentInstallationContractNumber = 'a_contract_number'
+
+ const result = computeNavigationInfo(installations, currentInstallationContractNumber)
+
+ expect(result).toEqual({})
+ })
+
+ describe('Having the installation 2 elements', () => {
+ it('Returns the not current element as before and next navigation values', () => {
+ const installations = [
+ {
+ contract_number: 'a_contract_number',
+ installation_name: 'an_installation_name',
+ },
+ {
+ contract_number: 'another_contract_number',
+ installation_name: 'another_installation_name',
+ },
+ ]
+ const currentInstallationContractNumber = 'a_contract_number'
+
+ const result = computeNavigationInfo(
+ installations,
+ currentInstallationContractNumber,
+ )
+
+ const expected_result = {
+ before: 'another_contract_number',
+ next: 'another_contract_number',
+ }
+ expect(result).toEqual(expected_result)
+ })
+ })
+
+ describe('Having the installation more than 2 elements', () => {
+ it('Returns the not current element as before and next navigation values', () => {
+ const installations = [
+ {
+ contract_number: 'a_contract_number',
+ installation_name: 'an_installation_name',
+ },
+ {
+ contract_number: 'another_contract_number',
+ installation_name: 'another_installation_name',
+ },
+ {
+ contract_number: 'yet_another_contract_number',
+ installation_name: 'yet_another_installation_name',
+ },
+ ]
+ const currentInstallationContractNumber = 'another_contract_number'
+
+ const result = computeNavigationInfo(
+ installations,
+ currentInstallationContractNumber,
+ )
+
+ const expected_result = {
+ before: 'a_contract_number',
+ next: 'yet_another_contract_number',
+ }
+ expect(result).toEqual(expected_result)
+ })
+ })
+})
diff --git a/frontend/src/pages/DetailInstallationPage/index.jsx b/frontend/src/pages/DetailInstallationPage/index.jsx
index c95806de..b4eacb82 100644
--- a/frontend/src/pages/DetailInstallationPage/index.jsx
+++ b/frontend/src/pages/DetailInstallationPage/index.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useContext, useMemo } from 'react'
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Container from '@mui/material/Container'
@@ -12,36 +12,52 @@ import ErrorSplash from '../../components/ErrorSplash'
import NavigationButtons from '../../components/NavigationButtons'
import { contractFields } from './detailInstallationData'
import { installationFields } from './detailInstallationData'
-import transformContractDetails, {
+import {
+ transformContractDetails,
transformInstallationDetails,
+ computeNavigationInfo,
} from './detailInstallationData'
+import { InstallationContext } from '../../components/InstallationProvider'
-export default function DetailInstallationPage(params) {
+export default function DetailInstallationPage() {
const { contract_number } = useParams()
const { t } = useTranslation()
const [installationDetail, setInstallationDetail] = useState(undefined)
const [contractDetail, setContractDetail] = useState(undefined)
const [error, setError] = useState(false)
+ const { installations } = useContext(InstallationContext)
+ const memoizedInstallations = useMemo(() => installations, [installations])
+ const navigationInfo = computeNavigationInfo(
+ memoizedInstallations,
+ installationDetail?.contract_number,
+ )
+ const navigationBeforeUrl = navigationInfo.before
+ ? `/installation/${navigationInfo.before}`
+ : undefined
+ const navigationNextUrl = navigationInfo.next
+ ? `/installation/${navigationInfo.next}`
+ : undefined
useEffect(() => {
getDetailInstallation()
- }, [contract_number])
+ }, [contract_number, memoizedInstallations])
async function getDetailInstallation() {
setError(false)
setInstallationDetail(undefined)
setContractDetail(undefined)
- var result
try {
- result = await ovapi.installationDetails(contract_number)
- } catch (e) {
- setError(e)
- return
+ const result = await ovapi.installationDetails(contract_number)
+ if (!result) {
+ setError(true)
+ return
+ }
+ setInstallationDetail(result?.installation_details)
+ const contractData = transformContractDetails(result?.contract_details)
+ setContractDetail(contractData)
+ } catch (error) {
+ setError(true)
}
- const installationData = transformInstallationDetails(result?.installation_details)
- setInstallationDetail(installationData)
- const contractData = transformContractDetails(result?.contract_details)
- setContractDetail(contractData)
}
return !error && (!installationDetail || !contractDetail) ? (
@@ -50,23 +66,21 @@ export default function DetailInstallationPage(params) {
{t('INSTALLATION_DETAIL.DETAILS_TITLE')}
+ }
+ />
{error ? (
) : (
<>
- {/* TODO: get the before and after user installations */}
- }
- />
installations, [installations])
- React.useEffect(() => {
- getInstallations()
- }, [currentUser])
-
- async function getInstallations() {
- beLoading(true)
- setRows([])
- setError(false)
- try {
- setRows(await ovapi.installations(currentUser))
- } catch (error) {
- setError(error)
+ useEffect(() => {
+ setPageLoading(true)
+ if (installations) {
+ setPageLoading(false)
}
- beLoading(false)
- }
+ }, [installations])
const columns = [
{
@@ -54,27 +41,28 @@ export default function InstallationsPage(params) {
numeric: false,
},
]
+
const actions = []
const selectionActions = []
+
const itemActions = [
{
title: t('INSTALLATIONS.TOOLTIP_DETAILS'),
- view: (contract) => {
- return (
- }
- >
- {t('INSTALLATIONS.BUTTON_DETAILS')}
-
- )
- },
+ view: (contract) => (
+ }
+ >
+ {t('INSTALLATIONS.BUTTON_DETAILS')}
+
+ ),
},
]
- return isLoading ? (
+
+ return pageLoading ? (
) : (
@@ -85,21 +73,21 @@ export default function InstallationsPage(params) {
getInstallations()}
+ backlink="/installation"
backtext={t('INSTALLATIONS.RELOAD')}
/>
) : (
@@ -111,7 +99,7 @@ export default function InstallationsPage(params) {
}
- >
+ />
)}
)
diff --git a/frontend/src/services/messages.js b/frontend/src/services/messages.js
index c9c7fd5e..340454cd 100644
--- a/frontend/src/services/messages.js
+++ b/frontend/src/services/messages.js
@@ -25,7 +25,7 @@ IMPORTANT: `subscribe()` returns a function to unsubscribe. Use it.
const _subscribers = new Set()
function _notify(message, level, extra) {
- _subscribers.forEach((l) => l({ message, level, ...(extra || {}) }))
+ _subscribers.forEach((l) => l({ message, level, ...extra }))
}
function subscribe(subscriber) {