Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation buttons #11

Merged
merged 14 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/datasources/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 16 additions & 5 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down Expand Up @@ -73,11 +74,21 @@ const routes = [
},
{
path: '/installation',
element: <InstallationsPage />,
},
{
path: '/installation/:contract_number',
element: <DetailInstallationPage />,
element: (
<InstallationContextProvider>
<Outlet />
</InstallationContextProvider>
),
children: [
{
path: '',
element: <InstallationsPage />,
},
{
path: ':contract_number',
element: <DetailInstallationPage />,
},
],
},
{
path: '/invoices',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/BreakPointIndicator.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/components/InstallationProvider.jsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<InstallationContext.Provider value={contextValue}>
{children}
</InstallationContext.Provider>
) : null
}

InstallationContextProvider.propTypes = {
children: PropTypes.node.isRequired,
}

export { InstallationContext, InstallationContextProvider }
20 changes: 13 additions & 7 deletions frontend/src/components/NavigationButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,29 @@ const StyledButton = styled(Button)(({ theme }) => ({

const NavigationButtons = (props) => {
const { toBefore, toNext, toReturn, returnIcon } = props

return (
<Box
sx={{
widht: '100%',
width: '100%',
ml: '.5rem',
display: 'flex',
justifyContent: 'flex-end',
marginRight: '1rem',
marginTop: '1rem',
}}
>
<ButtonGroup size="small">
<StyledButton component={Link} to={toBefore}>
<NavigateBeforeIcon />
</StyledButton>
<StyledButton component={Link} to={toNext}>
<NavigateNextIcon />
</StyledButton>
{toBefore && (
<StyledButton component={Link} to={toBefore}>
<NavigateBeforeIcon />
</StyledButton>
)}
{toNext && (
<StyledButton component={Link} to={toNext}>
<NavigateNextIcon />
</StyledButton>
)}
<StyledButton component={Link} to={toReturn}>
{returnIcon}
</StyledButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const contractFields = [
'discharge_date',
'status',
]

export function transformInstallationDetails(data) {
const t = i18n.t
const productionTecnologyOptions = {
Expand All @@ -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'),
Expand Down Expand Up @@ -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 }
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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)
})
})
})
58 changes: 36 additions & 22 deletions frontend/src/pages/DetailInstallationPage/index.jsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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) ? (
Expand All @@ -50,23 +66,21 @@ export default function DetailInstallationPage(params) {
<Container>
<PageTitle Icon={SolarPowerIcon}>
{t('INSTALLATION_DETAIL.DETAILS_TITLE')}
<NavigationButtons
toBefore={navigationBeforeUrl}
toNext={navigationNextUrl}
toReturn="/installation"
returnIcon={<FormatListBulletedIcon />}
/>
</PageTitle>
{error ? (
<ErrorSplash
title={t('INSTALLATION_DETAIL.ERROR_LOADING_DATA')}
message={error.error}
message={t('INSTALLATION_DETAIL.ERROR_LOADING_DATA')}
backlink="/installation"
backtext={t('INSTALLATION_DETAIL.BACK_TO_INSTALLATIONS')}
/>
) : (
<>
{/* TODO: get the before and after user installations */}
<NavigationButtons
toBefore="/installation/00001"
toNext="/installation/00001"
toReturn="/installation"
returnIcon={<FormatListBulletedIcon />}
/>
<SimpleTable
fields={installationDetail}
fieldsOrder={installationFields}
Expand Down
Loading
Loading