Update frontend. Added Features: SodioHome installations, User Access
This commit is contained in:
parent
b424643213
commit
0d8978c6b0
|
|
@ -1,4 +1,4 @@
|
||||||
npm run build && rsync -rv .* ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
|
npm run build && rsync -rv .* ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.inesco.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
|
||||||
|
|
||||||
|
|
||||||
#npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
#npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.inesco.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/stage.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
npm run build && rsync -rv .* ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/stage.inesco.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<link href="%PUBLIC_URL%/favicon.png" rel="shortcut icon"/>
|
<link href="%PUBLIC_URL%/Logo.svg" rel="shortcut icon"/>
|
||||||
<meta
|
<meta
|
||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
name="viewport"
|
name="viewport"
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>InnovEnergy</title>
|
<title>Inesco Energy</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ function App() {
|
||||||
path={routes.sodiohome_installations + '*'}
|
path={routes.sodiohome_installations + '*'}
|
||||||
element={
|
element={
|
||||||
<AccessContextProvider>
|
<AccessContextProvider>
|
||||||
<SodioHomeInstallationTabs />
|
<SodioHomeInstallationTabs product={2} />
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -1,12 +1,12 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export const axiosConfigWithoutToken = axios.create({
|
export const axiosConfigWithoutToken = axios.create({
|
||||||
baseURL: 'https://monitor.innov.energy/api'
|
baseURL: 'https://monitor.inesco.energy/api'
|
||||||
//baseURL: 'http://127.0.0.1:7087/api'
|
//baseURL: 'http://127.0.0.1:7087/api'
|
||||||
});
|
});
|
||||||
|
|
||||||
const axiosConfig = axios.create({
|
const axiosConfig = axios.create({
|
||||||
baseURL: 'https://monitor.innov.energy/api'
|
baseURL: 'https://monitor.inesco.energy/api'
|
||||||
//baseURL: 'http://127.0.0.1:7087/api'
|
//baseURL: 'http://127.0.0.1:7087/api'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ function Footer() {
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
© 2024 - InnovEnergy AG
|
© 2025 - Inesco Energy AG
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
|
|
@ -29,11 +29,11 @@ function Footer() {
|
||||||
>
|
>
|
||||||
Crafted by{' '}
|
Crafted by{' '}
|
||||||
<Link
|
<Link
|
||||||
href="https://www.innov.energy/"
|
href="https://www.inesco.energy/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
InnovEnergy AG
|
Inesco Energy AG
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
|
@ -76,8 +77,8 @@ function ForgotPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -100,7 +101,7 @@ function ForgotPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -134,15 +135,15 @@ function ForgotPassword() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && <CircularProgress sx={{ color: '#ffc04d' }} />}
|
{loading && <CircularProgress sx={{ color: '#00b33c' }} />}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -181,9 +182,9 @@ function ForgotPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setErrorModalOpen(false)}
|
onClick={() => setErrorModalOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
@ -221,9 +222,9 @@ function ForgotPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={handleReturn}
|
onClick={handleReturn}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
|
@ -73,8 +74,8 @@ function ResetPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -97,7 +98,7 @@ function ResetPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -137,7 +138,7 @@ function ResetPassword() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '170px' }} />
|
<CircularProgress sx={{ color: '#00b33c', marginLeft: '170px' }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{password != verifypassword && (
|
{password != verifypassword && (
|
||||||
|
|
@ -155,8 +156,8 @@ function ResetPassword() {
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -195,9 +196,9 @@ function ResetPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
|
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
|
@ -74,8 +75,8 @@ function SetNewPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inesco logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -98,7 +99,7 @@ function SetNewPassword() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -138,7 +139,7 @@ function SetNewPassword() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '0px' }} />
|
<CircularProgress sx={{ color: '#00b33c', marginLeft: '0px' }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{password != verifypassword && (
|
{password != verifypassword && (
|
||||||
|
|
@ -156,8 +157,8 @@ function SetNewPassword() {
|
||||||
mt: 3,
|
mt: 3,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -196,9 +197,9 @@ function SetNewPassword() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import inescologo from 'src/Resources/Logo.svg';
|
||||||
import { axiosConfigWithoutToken } from 'src/Resources/axiosConfig';
|
import { axiosConfigWithoutToken } from 'src/Resources/axiosConfig';
|
||||||
import Cookies from 'universal-cookie';
|
import Cookies from 'universal-cookie';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
|
@ -93,6 +93,8 @@ function Login() {
|
||||||
navigate(routes.installations);
|
navigate(routes.installations);
|
||||||
} else if (response.data.accessToSalidomo) {
|
} else if (response.data.accessToSalidomo) {
|
||||||
navigate(routes.salidomo_installations);
|
navigate(routes.salidomo_installations);
|
||||||
|
} else if (response.data.accessToSodistoreMax) {
|
||||||
|
navigate(routes.sodistore_installations);
|
||||||
} else {
|
} else {
|
||||||
navigate(routes.sodiohome_installations);
|
navigate(routes.sodiohome_installations);
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +115,8 @@ function Login() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
|
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://monitor.innov.energy/">
|
<a href="https://monitor.inesco.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={inescologo} alt="inescologo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -137,7 +139,7 @@ function Login() {
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
<Avatar sx={{ m: 1, bgcolor: '#00b33c' }}>
|
||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
|
|
@ -193,7 +195,7 @@ function Login() {
|
||||||
checked={rememberMe}
|
checked={rememberMe}
|
||||||
onChange={handleRememberMeChange}
|
onChange={handleRememberMeChange}
|
||||||
icon={<CheckBoxOutlineBlankIcon style={{ color: 'grey' }} />}
|
icon={<CheckBoxOutlineBlankIcon style={{ color: 'grey' }} />}
|
||||||
checkedIcon={<CheckBoxIcon style={{ color: '#ffc04d' }} />}
|
checkedIcon={<CheckBoxIcon style={{ color: '#00b33c' }} />}
|
||||||
style={{ marginLeft: -175 }}
|
style={{ marginLeft: -175 }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
@ -204,8 +206,8 @@ function Login() {
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
|
@ -218,7 +220,7 @@ function Login() {
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
sx={{
|
sx={{
|
||||||
color: '#ffc04d',
|
color: '#009933',
|
||||||
marginLeft: '20px'
|
marginLeft: '20px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -253,9 +255,9 @@ function Login() {
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
bgcolor: '#ffc04d',
|
bgcolor: '#00b33c',
|
||||||
color: '#111111',
|
color: '#111111',
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
'&:hover': { bgcolor: '#009933' }
|
||||||
}}
|
}}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
|
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
const sortedBatteryView =
|
const sortedBatteryView =
|
||||||
props.values != null && props.values?.Battery?.Devices
|
props.values != null && props.values?.Battery?.Devices
|
||||||
? Object.entries(props.values.Battery.Devices)
|
? Object.entries(props.values.Battery.Devices)
|
||||||
|
|
@ -58,8 +60,6 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
navigate(routes.mainstats);
|
navigate(routes.mainstats);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { product, setProduct } = useContext(ProductIdContext);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sortedBatteryView.length == 0) {
|
if (sortedBatteryView.length == 0) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -232,7 +232,7 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
<TableCell align="center">Battery</TableCell>
|
<TableCell align="center">Battery</TableCell>
|
||||||
<TableCell align="center">Firmware</TableCell>
|
<TableCell align="center">Firmware</TableCell>
|
||||||
<TableCell align="center">Power</TableCell>
|
<TableCell align="center">Power</TableCell>
|
||||||
<TableCell align="center">Voltage</TableCell>
|
<TableCell align="center">Battery Voltage</TableCell>
|
||||||
<TableCell align="center">SoC</TableCell>
|
<TableCell align="center">SoC</TableCell>
|
||||||
<TableCell align="center">Temperature</TableCell>
|
<TableCell align="center">Temperature</TableCell>
|
||||||
{product === 0 ? (
|
{product === 0 ? (
|
||||||
|
|
@ -293,7 +293,7 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
sx={{
|
sx={{
|
||||||
width: '10%',
|
width: '14%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
Paper,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography
|
||||||
|
} from '@mui/material';
|
||||||
|
import { JSONRecordData } from '../Log/graph.util';
|
||||||
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
|
||||||
|
interface BatteryViewSodioHomeProps {
|
||||||
|
values: JSONRecordData;
|
||||||
|
s3Credentials: I_S3Credentials;
|
||||||
|
installationId: number;
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
|
if (props.values === null && props.connected == true) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const sortedBatteryView =
|
||||||
|
props.values != null &&
|
||||||
|
props.values?.AcDcGrowatt?.BatteriesRecords?.Batteries
|
||||||
|
? Object.entries(props.values.AcDcGrowatt.BatteriesRecords.Batteries)
|
||||||
|
.map(([BatteryId, battery]) => {
|
||||||
|
return { BatteryId, battery }; // Here we return an object with the id and device
|
||||||
|
})
|
||||||
|
.sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
console.log('battery view', sortedBatteryView);
|
||||||
|
const [loading, setLoading] = useState(sortedBatteryView.length == 0);
|
||||||
|
const handleMainStatsButton = () => {
|
||||||
|
navigate(routes.mainstats);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sortedBatteryView.length == 0) {
|
||||||
|
setLoading(true);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [sortedBatteryView]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!props.connected && (
|
||||||
|
<Container
|
||||||
|
maxWidth="xl"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '70vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{ color: 'black', fontWeight: 'bold' }}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
Unable to communicate with the installation
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" style={{ color: 'black' }}>
|
||||||
|
Please wait or refresh the page
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
{loading && props.connected && (
|
||||||
|
<Container
|
||||||
|
maxWidth="xl"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '70vh'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
style={{ color: 'black', fontWeight: 'bold' }}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
Battery service is not available at the moment
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" style={{ color: 'black' }}>
|
||||||
|
Please wait or refresh the page
|
||||||
|
</Typography>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && props.connected && (
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid container>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={6}
|
||||||
|
md={6}
|
||||||
|
sx={{
|
||||||
|
display:
|
||||||
|
!currentLocation.pathname.includes('detailed_view') &&
|
||||||
|
!currentLocation.pathname.includes('mainstats')
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: '#808080',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="battery_view"
|
||||||
|
defaultMessage="Battery View"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleMainStatsButton}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/*<Grid container>*/}
|
||||||
|
{/* <Routes>*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* path={routes.mainstats + '*'}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <MainStats*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* id={props.installationId}*/}
|
||||||
|
{/* ></MainStats>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* {product === 0*/}
|
||||||
|
{/* ? Object.entries(props.values.Battery.Devices).map(*/}
|
||||||
|
{/* ([BatteryId, battery]) => (*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* key={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* path={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <DetailedBatteryView*/}
|
||||||
|
{/* batteryId={Number(BatteryId)}*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* batteryData={battery}*/}
|
||||||
|
{/* installationId={props.installationId}*/}
|
||||||
|
{/* productNum={product}*/}
|
||||||
|
{/* ></DetailedBatteryView>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* : Object.entries(props.values.Battery.Devices).map(*/}
|
||||||
|
{/* ([BatteryId, battery]) => (*/}
|
||||||
|
{/* <Route*/}
|
||||||
|
{/* key={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* path={routes.detailed_view + BatteryId}*/}
|
||||||
|
{/* element={*/}
|
||||||
|
{/* <DetailedBatteryViewSodistore*/}
|
||||||
|
{/* batteryId={Number(BatteryId)}*/}
|
||||||
|
{/* s3Credentials={props.s3Credentials}*/}
|
||||||
|
{/* batteryData={battery}*/}
|
||||||
|
{/* installationId={props.installationId}*/}
|
||||||
|
{/* productNum={product}*/}
|
||||||
|
{/* ></DetailedBatteryViewSodistore>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* )}*/}
|
||||||
|
{/* </Routes>*/}
|
||||||
|
{/*</Grid>*/}
|
||||||
|
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
display:
|
||||||
|
!currentLocation.pathname.includes('detailed_view') &&
|
||||||
|
!currentLocation.pathname.includes('mainstats')
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center">Battery</TableCell>
|
||||||
|
<TableCell align="center">Power</TableCell>
|
||||||
|
<TableCell align="center">Battery Voltage</TableCell>
|
||||||
|
<TableCell align="center">Current</TableCell>
|
||||||
|
<TableCell align="center">SoC</TableCell>
|
||||||
|
<TableCell align="center">SoH</TableCell>
|
||||||
|
<TableCell align="center">Daily Charge Energy</TableCell>
|
||||||
|
<TableCell align="center">Daily Discharge Energy</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{sortedBatteryView.map(({ BatteryId, battery }) => (
|
||||||
|
<TableRow
|
||||||
|
key={BatteryId}
|
||||||
|
style={{
|
||||||
|
height: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
component="th"
|
||||||
|
scope="row"
|
||||||
|
align="center"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
style={{ color: 'black' }}
|
||||||
|
to={routes.detailed_view + BatteryId}
|
||||||
|
>
|
||||||
|
{'Battery Cluster ' + BatteryId}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Power + ' ' + 'W'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center',
|
||||||
|
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'black'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Voltage + ' ' + 'V'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '13%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Current + ' A'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '8%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Soc + ' %'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '8%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.Soh + ' %'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '15%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.DailyChargeEnergy + ' Wh'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
width: '15%',
|
||||||
|
textAlign: 'center',
|
||||||
|
backgroundColor: '#32CD32',
|
||||||
|
color: 'inherit'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{battery.DailyDischargeEnergy + ' Wh'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BatteryViewSodioHome;
|
||||||
|
|
@ -169,7 +169,7 @@ function DetailedBatteryViewSodistore(
|
||||||
align="left"
|
align="left"
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
Total Battery Voltage
|
Bus Voltage
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
align="right"
|
align="right"
|
||||||
|
|
@ -183,6 +183,29 @@ function DetailedBatteryViewSodistore(
|
||||||
' V'}
|
' V'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
component="th"
|
||||||
|
scope="row"
|
||||||
|
align="left"
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Battery Voltage
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="right"
|
||||||
|
sx={{
|
||||||
|
width: '6ch',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
paddingRight: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.batteryData.BatteryDeligreenDataRecord
|
||||||
|
.TotalBatteryVoltage + ' V'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
component="th"
|
component="th"
|
||||||
|
|
@ -349,25 +372,6 @@ function DetailedBatteryViewSodistore(
|
||||||
{props.batteryData.BatteryDeligreenDataRecord.Soh + ' %'}
|
{props.batteryData.BatteryDeligreenDataRecord.Soh + ' %'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
component="th"
|
|
||||||
scope="row"
|
|
||||||
align="left"
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Port Voltage
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
align="right"
|
|
||||||
sx={{
|
|
||||||
width: '6ch',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
paddingRight: '12px'
|
|
||||||
}}
|
|
||||||
></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,13 @@ import {
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|
@ -29,6 +32,7 @@ import { UserContext } from '../../../contexts/userContext';
|
||||||
|
|
||||||
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
||||||
import { TimePicker } from '@mui/lab';
|
import { TimePicker } from '@mui/lab';
|
||||||
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
|
|
||||||
interface ConfigurationProps {
|
interface ConfigurationProps {
|
||||||
values: JSONRecordData;
|
values: JSONRecordData;
|
||||||
|
|
@ -40,6 +44,9 @@ function Configuration(props: ConfigurationProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log(props.values.Config);
|
||||||
|
const [activeTab, setActiveTab] = useState<'charge' | 'discharge'>('charge');
|
||||||
|
|
||||||
const CalibrationChargeOptions = [
|
const CalibrationChargeOptions = [
|
||||||
'Repetitive Calibration',
|
'Repetitive Calibration',
|
||||||
'Additional Calibration',
|
'Additional Calibration',
|
||||||
|
|
@ -84,11 +91,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
|
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser, setUser } = context;
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
const [formValues, setFormValues] = useState<ConfigurationValues>({
|
const [formValues, setFormValues] = useState<ConfigurationValues>({
|
||||||
minimumSoC: props.values.Config.MinSoc,
|
minimumSoC: props.values.Config.MinSoc,
|
||||||
gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000,
|
gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000,
|
||||||
CalibrationChargeState: CalibrationChargeOptionsController.indexOf(
|
calibrationChargeState: CalibrationChargeOptionsController.indexOf(
|
||||||
props.values.Config.ForceCalibrationChargeState.toString()
|
props.values.Config.ForceCalibrationChargeState.toString()
|
||||||
),
|
),
|
||||||
calibrationChargeDate:
|
calibrationChargeDate:
|
||||||
|
|
@ -100,9 +108,27 @@ function Configuration(props: ConfigurationProps) {
|
||||||
.toDate()
|
.toDate()
|
||||||
: dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
|
: dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
|
||||||
// .add(localOffset, 'minute')
|
// .add(localOffset, 'minute')
|
||||||
.toDate()
|
.toDate(),
|
||||||
|
|
||||||
|
...(product === 3 && {
|
||||||
|
calibrationDischargeState: CalibrationChargeOptionsController.indexOf(
|
||||||
|
props.values.Config.ForceCalibrationDischargeState.toString()
|
||||||
|
),
|
||||||
|
calibrationDischargeDate:
|
||||||
|
CalibrationChargeOptionsController.indexOf(
|
||||||
|
props.values.Config.ForceCalibrationDischargeState.toString()
|
||||||
|
) == 0
|
||||||
|
? dayjs(
|
||||||
|
props.values.Config.DownDayAndTimeForRepetitiveCalibration
|
||||||
|
).toDate()
|
||||||
|
: dayjs(
|
||||||
|
props.values.Config.DownDayAndTimeForAdditionalCalibration
|
||||||
|
).toDate()
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// console.log(formValues);
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
if (
|
if (
|
||||||
CalibrationChargeOptionsController.indexOf(
|
CalibrationChargeOptionsController.indexOf(
|
||||||
|
|
@ -116,7 +142,7 @@ function Configuration(props: ConfigurationProps) {
|
||||||
setErrorDateModalOpen(true);
|
setErrorDateModalOpen(true);
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
formValues.CalibrationChargeState === 1 &&
|
formValues.calibrationChargeState === 1 &&
|
||||||
dayjs(formValues.calibrationChargeDate).isBefore(dayjs())
|
dayjs(formValues.calibrationChargeDate).isBefore(dayjs())
|
||||||
) {
|
) {
|
||||||
//console.log('asked for', dayjs(formValues.calibrationChargeDate));
|
//console.log('asked for', dayjs(formValues.calibrationChargeDate));
|
||||||
|
|
@ -128,13 +154,17 @@ function Configuration(props: ConfigurationProps) {
|
||||||
const configurationToSend: ConfigurationValues = {
|
const configurationToSend: ConfigurationValues = {
|
||||||
minimumSoC: formValues.minimumSoC,
|
minimumSoC: formValues.minimumSoC,
|
||||||
gridSetPoint: formValues.gridSetPoint,
|
gridSetPoint: formValues.gridSetPoint,
|
||||||
CalibrationChargeState: formValues.CalibrationChargeState,
|
calibrationChargeState: formValues.calibrationChargeState,
|
||||||
calibrationChargeDate: dayjs
|
calibrationChargeDate: dayjs
|
||||||
.utc(formValues.calibrationChargeDate)
|
.utc(formValues.calibrationChargeDate)
|
||||||
.add(localOffset, 'minute')
|
.add(localOffset, 'minute')
|
||||||
|
.toDate(),
|
||||||
|
calibrationDischargeState: formValues.calibrationDischargeState,
|
||||||
|
calibrationDischargeDate: dayjs
|
||||||
|
.utc(formValues.calibrationDischargeDate)
|
||||||
|
.add(localOffset, 'minute')
|
||||||
.toDate()
|
.toDate()
|
||||||
};
|
};
|
||||||
// console.log('will send ', dayjs(formValues.calibrationChargeDate));
|
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await axiosConfig
|
const res = await axiosConfig
|
||||||
|
|
@ -169,6 +199,15 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfirmDischarge = (newDate) => {
|
||||||
|
//console.log('non adapted day is ', newDate);
|
||||||
|
//console.log('adapted day is ', dayjs.utc(newDate).toDate());
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeDate']: dayjs(newDate).toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectedCalibrationChargeDay = (event) => {
|
const handleSelectedCalibrationChargeDay = (event) => {
|
||||||
const selectedDay = daysInWeek.indexOf(event.target.value);
|
const selectedDay = daysInWeek.indexOf(event.target.value);
|
||||||
const currentDate = dayjs();
|
const currentDate = dayjs();
|
||||||
|
|
@ -184,10 +223,25 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectedCalibrationDisChargeDay = (event) => {
|
||||||
|
const selectedDay = daysInWeek.indexOf(event.target.value);
|
||||||
|
const currentDate = dayjs();
|
||||||
|
let difference = selectedDay - currentDate.day();
|
||||||
|
if (difference < 0) {
|
||||||
|
difference += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adjustedDate = currentDate.add(difference, 'day');
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeDate']: adjustedDate.toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectedCalibrationChargeChange = (event) => {
|
const handleSelectedCalibrationChargeChange = (event) => {
|
||||||
setFormValues({
|
setFormValues({
|
||||||
...formValues,
|
...formValues,
|
||||||
['CalibrationChargeState']: CalibrationChargeOptions.indexOf(
|
['calibrationChargeState']: CalibrationChargeOptions.indexOf(
|
||||||
event.target.value
|
event.target.value
|
||||||
),
|
),
|
||||||
['calibrationChargeDate']:
|
['calibrationChargeDate']:
|
||||||
|
|
@ -201,6 +255,23 @@ function Configuration(props: ConfigurationProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectedCalibrationDisChargeChange = (event) => {
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
['calibrationDischargeState']: CalibrationChargeOptions.indexOf(
|
||||||
|
event.target.value
|
||||||
|
),
|
||||||
|
['calibrationDischargeDate']:
|
||||||
|
CalibrationChargeOptions.indexOf(event.target.value) == 0
|
||||||
|
? dayjs(props.values.Config.DownDayAndTimeForRepetitiveCalibration)
|
||||||
|
// .add(localOffset, 'minute')
|
||||||
|
.toDate()
|
||||||
|
: dayjs(props.values.Config.DownDayAndTimeForAdditionalCalibration)
|
||||||
|
// .add(localOffset, 'minute')
|
||||||
|
.toDate()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
|
@ -284,7 +355,84 @@ function Configuration(props: ConfigurationProps) {
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
mt: 2,
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(e, newValue) => setActiveTab(newValue)}
|
||||||
|
textColor="inherit"
|
||||||
|
TabIndicatorProps={{ style: { display: 'none' } }} // hide default underline
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#f5f5f5',
|
||||||
|
borderRadius: 2,
|
||||||
|
p: 0.5,
|
||||||
|
boxShadow: 1,
|
||||||
|
width: 500,
|
||||||
|
height: 47
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
value="charge"
|
||||||
|
label="Calibration Charge"
|
||||||
|
sx={(theme) => ({
|
||||||
|
flex: 2,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
color:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? 'white'
|
||||||
|
: theme.palette.text.primary,
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: 'transparent',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'charge'
|
||||||
|
? theme.palette.primary.dark
|
||||||
|
: '#eee'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{product === 3 && (
|
||||||
|
<Tab
|
||||||
|
value="discharge"
|
||||||
|
label="Calibration Discharge"
|
||||||
|
sx={(theme) => ({
|
||||||
|
flex: 2,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
color:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? 'white'
|
||||||
|
: theme.palette.text.primary,
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? theme.palette.primary.main
|
||||||
|
: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor:
|
||||||
|
activeTab === 'discharge'
|
||||||
|
? theme.palette.primary.dark
|
||||||
|
: '#eee'
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
|
|
@ -294,98 +442,34 @@ function Configuration(props: ConfigurationProps) {
|
||||||
noValidate
|
noValidate
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{activeTab === 'charge' && (
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="minimum_soc "
|
|
||||||
defaultMessage="Minimum SoC (%)"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="minimumSoC"
|
|
||||||
value={formValues.minimumSoC}
|
|
||||||
onChange={handleChange}
|
|
||||||
helperText={
|
|
||||||
errors.minimumSoC ? (
|
|
||||||
<span style={{ color: 'red' }}>
|
|
||||||
Value should be between 0-100%
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
|
||||||
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
|
||||||
<InputLabel
|
|
||||||
sx={{
|
|
||||||
fontSize: 14,
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="forced_calibration_charge"
|
|
||||||
defaultMessage="Calibration Charge State"
|
|
||||||
/>
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={
|
|
||||||
CalibrationChargeOptions[
|
|
||||||
formValues.CalibrationChargeState
|
|
||||||
]
|
|
||||||
}
|
|
||||||
onChange={handleSelectedCalibrationChargeChange}
|
|
||||||
>
|
|
||||||
{CalibrationChargeOptions.map((option) => (
|
|
||||||
<MenuItem key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</div>
|
|
||||||
{formValues.CalibrationChargeState == 1 && (
|
|
||||||
<div>
|
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
||||||
<DateTimePicker
|
|
||||||
label="Select Next Calibration Charge Date"
|
|
||||||
value={dayjs(formValues.calibrationChargeDate)}
|
|
||||||
onChange={handleConfirm}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
sx={{
|
|
||||||
marginTop: 2, // Apply styles here
|
|
||||||
width: '100%' // Optional: You can adjust the width or other styling here
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*<DateTimePicker*/}
|
|
||||||
{/* format="DD/MM/YYYY HH:mm"*/}
|
|
||||||
{/* ampm={false}*/}
|
|
||||||
{/* label="Select Next Calibration Charge Date"*/}
|
|
||||||
{/* value={dayjs(formValues.calibrationChargeDate)}*/}
|
|
||||||
{/* onChange={handleConfirm}*/}
|
|
||||||
{/* sx={{*/}
|
|
||||||
{/* marginTop: 2*/}
|
|
||||||
{/* }} // This should work with the correct imports*/}
|
|
||||||
{/*/>*/}
|
|
||||||
</LocalizationProvider>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{formValues.CalibrationChargeState == 0 && (
|
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
<div style={{ marginBottom: '5px' }}>
|
||||||
<FormControl
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="minimum_soc "
|
||||||
|
defaultMessage="Minimum SoC (%)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="minimumSoC"
|
||||||
|
value={formValues.minimumSoC}
|
||||||
|
onChange={handleChange}
|
||||||
|
helperText={
|
||||||
|
errors.minimumSoC ? (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Value should be between 0-100%
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
/>
|
||||||
>
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -393,106 +477,283 @@ function Configuration(props: ConfigurationProps) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="calibration_charge_day"
|
id="forced_calibration_charge"
|
||||||
defaultMessage="Calibration Charge Day"
|
defaultMessage="Calibration Charge State"
|
||||||
/>
|
/>
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={
|
value={
|
||||||
daysInWeek[formValues.calibrationChargeDate.getDay()]
|
CalibrationChargeOptions[
|
||||||
|
formValues.calibrationChargeState
|
||||||
|
]
|
||||||
}
|
}
|
||||||
onChange={handleSelectedCalibrationChargeDay}
|
onChange={handleSelectedCalibrationChargeChange}
|
||||||
>
|
>
|
||||||
{daysInWeek.map((day) => (
|
{CalibrationChargeOptions.map((option) => (
|
||||||
<MenuItem key={day} value={day}>
|
<MenuItem key={option} value={option}>
|
||||||
{day}
|
{option}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
{formValues.calibrationChargeState == 1 && (
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<div>
|
||||||
<TimePicker
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
ampm={false}
|
<DateTimePicker
|
||||||
label="Calibration Charge Hour"
|
label="Select Next Calibration Charge Date"
|
||||||
value={dayjs(formValues.calibrationChargeDate)}
|
value={dayjs(formValues.calibrationChargeDate)}
|
||||||
onChange={(newTime) => handleConfirm(dayjs(newTime))}
|
onChange={handleConfirm}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formValues.calibrationChargeState == 0 && (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2, // Apply styles here
|
fontSize: 14,
|
||||||
width: '100%' // Optional: You can adjust the width or other styling here
|
backgroundColor: 'white'
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="calibration_charge_day"
|
||||||
|
defaultMessage="Calibration Charge Day"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
daysInWeek[
|
||||||
|
formValues.calibrationChargeDate.getDay()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={handleSelectedCalibrationChargeDay}
|
||||||
|
>
|
||||||
|
{daysInWeek.map((day) => (
|
||||||
|
<MenuItem key={day} value={day}>
|
||||||
|
{day}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
label="Calibration Charge Hour"
|
||||||
|
value={dayjs(formValues.calibrationChargeDate)}
|
||||||
|
onChange={(newTime) =>
|
||||||
|
handleConfirm(dayjs(newTime))
|
||||||
|
}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
</LocalizationProvider>
|
||||||
/>
|
</div>
|
||||||
</LocalizationProvider>
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="grid_set_point"
|
||||||
|
defaultMessage="Grid Set Point (kW)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="gridSetPoint"
|
||||||
|
value={formValues.gridSetPoint}
|
||||||
|
onChange={handleChange}
|
||||||
|
helperText={
|
||||||
|
errors.gridSetPoint ? (
|
||||||
|
<span style={{ color: 'red' }}>
|
||||||
|
Please provide a valid number
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Installed_Power_DC1010"
|
||||||
|
defaultMessage="Installed Power DC1010 (kW)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
(props.values.DcDc.SystemControl
|
||||||
|
.NumberOfConnectedSlaves as number) * 10
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.values.Config.MaxBatteryDischargingCurrent && (
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Maximum_Discharge_Power"
|
||||||
|
defaultMessage="Maximum Discharge Power (W)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
(props.values.Config
|
||||||
|
.MaxBatteryDischargingCurrent as number) *
|
||||||
|
48 *
|
||||||
|
(props.values.DcDc.SystemControl
|
||||||
|
.NumberOfConnectedSlaves as number)
|
||||||
|
}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{product == 3 && activeTab === 'discharge' && (
|
||||||
<TextField
|
<>
|
||||||
label={
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
<FormattedMessage
|
<FormControl fullWidth sx={{ marginLeft: 1, width: 390 }}>
|
||||||
id="grid_set_point"
|
<InputLabel
|
||||||
defaultMessage="Grid Set Point (kW)"
|
sx={{
|
||||||
/>
|
fontSize: 14,
|
||||||
}
|
backgroundColor: 'white'
|
||||||
name="gridSetPoint"
|
}}
|
||||||
value={formValues.gridSetPoint}
|
>
|
||||||
onChange={handleChange}
|
<FormattedMessage
|
||||||
helperText={
|
id="calibration_discharge_state"
|
||||||
errors.gridSetPoint ? (
|
defaultMessage="Calibration Discharge State"
|
||||||
<span style={{ color: 'red' }}>
|
/>
|
||||||
Please provide a valid number
|
</InputLabel>
|
||||||
</span>
|
<Select
|
||||||
) : (
|
value={
|
||||||
''
|
CalibrationChargeOptions[
|
||||||
)
|
formValues.calibrationDischargeState
|
||||||
}
|
]
|
||||||
fullWidth
|
}
|
||||||
/>
|
onChange={handleSelectedCalibrationDisChargeChange}
|
||||||
</div>
|
>
|
||||||
<div style={{ marginBottom: '5px' }}>
|
{CalibrationChargeOptions.map((option) => (
|
||||||
<TextField
|
<MenuItem key={option} value={option}>
|
||||||
label={
|
{option}
|
||||||
<FormattedMessage
|
</MenuItem>
|
||||||
id="Installed_Power_DC1010"
|
))}
|
||||||
defaultMessage="Installed Power DC1010 (kW)"
|
</Select>
|
||||||
/>
|
</FormControl>
|
||||||
}
|
</div>
|
||||||
value={
|
|
||||||
(props.values.DcDc.SystemControl
|
|
||||||
.NumberOfConnectedSlaves as number) * 10
|
|
||||||
}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{props.values.Config.MaxBatteryDischargingCurrent && (
|
{formValues.calibrationDischargeState == 1 && (
|
||||||
<div style={{ marginBottom: '5px' }}>
|
<div>
|
||||||
<TextField
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
label={
|
<DateTimePicker
|
||||||
<FormattedMessage
|
label="Select Next Calibration Discharge Date"
|
||||||
id="Maximum_Discharge_Power"
|
value={dayjs(formValues.calibrationDischargeDate)}
|
||||||
defaultMessage="Maximum Discharge Power (W)"
|
onChange={handleConfirmDischarge}
|
||||||
/>
|
renderInput={(params) => (
|
||||||
}
|
<TextField
|
||||||
value={
|
{...params}
|
||||||
(props.values.Config
|
sx={{
|
||||||
.MaxBatteryDischargingCurrent as number) *
|
marginTop: 2, // Apply styles here
|
||||||
48 *
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
(props.values.DcDc.SystemControl
|
}}
|
||||||
.NumberOfConnectedSlaves as number)
|
/>
|
||||||
}
|
)}
|
||||||
fullWidth
|
/>
|
||||||
/>
|
</LocalizationProvider>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{formValues.calibrationDischargeState == 0 && (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: '5px' }}>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
sx={{ marginLeft: 1, width: 390, marginTop: 2 }}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="calibration_charge_day"
|
||||||
|
defaultMessage="Calibration Discharge Day"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
daysInWeek[
|
||||||
|
formValues.calibrationDischargeDate.getDay()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={handleSelectedCalibrationDisChargeDay}
|
||||||
|
>
|
||||||
|
{daysInWeek.map((day) => (
|
||||||
|
<MenuItem key={day} value={day}>
|
||||||
|
{day}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '5px', marginTop: '10px' }}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
|
<TimePicker
|
||||||
|
ampm={false}
|
||||||
|
label="Calibration Discharge Hour"
|
||||||
|
value={dayjs(formValues.calibrationDischargeDate)}
|
||||||
|
onChange={(newTime) =>
|
||||||
|
handleConfirmDischarge(dayjs(newTime))
|
||||||
|
}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2, // Apply styles here
|
||||||
|
width: '100%' // Optional: You can adjust the width or other styling here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</LocalizationProvider>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/*<div>*/}
|
{/*<div>*/}
|
||||||
{/* <TextField*/}
|
{/* <TextField*/}
|
||||||
{/* label={*/}
|
{/* label={*/}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
|
|
||||||
interface InformationProps {
|
interface InformationProps {
|
||||||
values: I_Installation;
|
values: I_Installation;
|
||||||
|
|
@ -38,6 +39,7 @@ function Information(props: InformationProps) {
|
||||||
const [formValues, setFormValues] = useState(props.values);
|
const [formValues, setFormValues] = useState(props.values);
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
const requiredFields = ['name', 'region', 'location', 'country'];
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
const {
|
const {
|
||||||
updateInstallation,
|
updateInstallation,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -265,6 +267,30 @@ function Information(props: InformationProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="information"
|
||||||
|
defaultMessage="Information"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="information"
|
||||||
|
value={formValues.information}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
minRows={6} // 👈 Makes it visually bigger
|
||||||
|
maxRows={12} // 👈 Optional max height before scroll
|
||||||
|
inputProps={{
|
||||||
|
style: {
|
||||||
|
fontFamily: 'monospace' // optional: makes tabs/formatting more clear
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -282,8 +308,11 @@ function Information(props: InformationProps) {
|
||||||
label="S3 Bucket Name"
|
label="S3 Bucket Name"
|
||||||
name="s3bucketname"
|
name="s3bucketname"
|
||||||
value={
|
value={
|
||||||
formValues.s3BucketId +
|
product === 0 || product == 3
|
||||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
? formValues.s3BucketId +
|
||||||
|
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||||
|
: formValues.s3BucketId +
|
||||||
|
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
||||||
}
|
}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
|
||||||
|
|
@ -1,393 +0,0 @@
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
CardContent,
|
|
||||||
CircularProgress,
|
|
||||||
Container,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import React, { useContext, useState } from 'react';
|
|
||||||
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
|
||||||
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
|
||||||
import { UserContext } from '../../../contexts/userContext';
|
|
||||||
import routes from '../../../Resources/routes.json';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
|
||||||
|
|
||||||
interface InformationSodioHomeProps {
|
|
||||||
values: I_Installation;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function InformationSodioHome(props: InformationSodioHomeProps) {
|
|
||||||
if (props.values === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = useContext(UserContext);
|
|
||||||
const { currentUser } = context;
|
|
||||||
const theme = useTheme();
|
|
||||||
const [formValues, setFormValues] = useState(props.values);
|
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
|
||||||
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
|
|
||||||
useState(false);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const DeviceTypes = ['Cerbo', 'Venus'];
|
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
|
||||||
const {
|
|
||||||
updateInstallation,
|
|
||||||
deleteInstallation,
|
|
||||||
loading,
|
|
||||||
setLoading,
|
|
||||||
error,
|
|
||||||
setError,
|
|
||||||
updated,
|
|
||||||
setUpdated
|
|
||||||
} = installationContext;
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleSubmit = () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
updateInstallation(formValues, props.type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
setOpenModalDeleteInstallation(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandle = () => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
deleteInstallation(formValues, props.type);
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
navigate(routes.salidomo_installations + routes.list, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandleCancel = () => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{openModalDeleteInstallation && (
|
|
||||||
<Modal
|
|
||||||
open={openModalDeleteInstallation}
|
|
||||||
onClose={deleteInstallationModalHandleCancel}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 350,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Do you want to delete this installation?
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandle}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
marginLeft: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandleCancel}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Container maxWidth="xl">
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="stretch"
|
|
||||||
spacing={3}
|
|
||||||
>
|
|
||||||
<Grid item xs={12} md={12}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="installation_name"
|
|
||||||
defaultMessage="Installation Name"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="region" defaultMessage="Region" />
|
|
||||||
}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.region === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="location"
|
|
||||||
defaultMessage="Location"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.location === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.country === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="information"
|
|
||||||
defaultMessage="Information"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="information"
|
|
||||||
value={formValues.information}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="BitWatt Cloud Access Key"
|
|
||||||
name="s3WriteKey"
|
|
||||||
value={formValues.s3WriteKey}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="BitWatt Cloud Secret Key"
|
|
||||||
name="s3WriteSecret"
|
|
||||||
onChange={handleChange}
|
|
||||||
value={formValues.s3WriteSecret}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="applyChanges"
|
|
||||||
defaultMessage="Apply Changes"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleDelete}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="deleteInstallation"
|
|
||||||
defaultMessage="Delete Installation"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occurred"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)}
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{updated && (
|
|
||||||
<Alert
|
|
||||||
severity="success"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="successfullyUpdated"
|
|
||||||
defaultMessage="Successfully updated"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InformationSodioHome;
|
|
||||||
|
|
@ -135,7 +135,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSelectOneInstallation = (installationID: number): void => {
|
const handleSelectOneInstallation = (installationID: number): void => {
|
||||||
console.log('when selecting installation', product);
|
|
||||||
if (selectedInstallation != installationID) {
|
if (selectedInstallation != installationID) {
|
||||||
setSelectedInstallation(installationID);
|
setSelectedInstallation(installationID);
|
||||||
setSelectedInstallation(-1);
|
setSelectedInstallation(-1);
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,14 @@ interface installationFormProps {
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
parentid: number;
|
parentid: number;
|
||||||
|
productToInsert: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function installationForm(props: installationFormProps) {
|
function installationForm(props: installationFormProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
||||||
|
console.log('productToInsert IS ', props.productToInsert);
|
||||||
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
||||||
installationName: '',
|
installationName: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -59,7 +62,7 @@ function installationForm(props: installationFormProps) {
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
formValues.parentId = props.parentid;
|
formValues.parentId = props.parentid;
|
||||||
formValues.product = 0;
|
formValues.product = props.productToInsert;
|
||||||
const responseData = await createInstallation(formValues);
|
const responseData = await createInstallation(formValues);
|
||||||
props.submit();
|
props.submit();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,20 @@ export interface Line {
|
||||||
Power: Power;
|
Power: Power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SodioHomeBattery {
|
||||||
|
AccumulatedChargeEnergy: number;
|
||||||
|
AccumulatedDischargeEnergy: number;
|
||||||
|
Current: number;
|
||||||
|
DailyChargeEnergy: number;
|
||||||
|
DailyDischargeEnergy: number;
|
||||||
|
MaxAllowableChargePower: number;
|
||||||
|
MaxAllowableDischargePower: number;
|
||||||
|
Power: number;
|
||||||
|
Soc: number;
|
||||||
|
Soh: number;
|
||||||
|
Voltage: number;
|
||||||
|
}
|
||||||
|
|
||||||
// The interface for the Battery structure, with dynamic keys (Device IDs)
|
// The interface for the Battery structure, with dynamic keys (Device IDs)
|
||||||
export interface JSONRecordData {
|
export interface JSONRecordData {
|
||||||
Battery: {
|
Battery: {
|
||||||
|
|
@ -282,9 +296,12 @@ export interface JSONRecordData {
|
||||||
CurtailP: number;
|
CurtailP: number;
|
||||||
DayAndTimeForAdditionalCalibration: string;
|
DayAndTimeForAdditionalCalibration: string;
|
||||||
DayAndTimeForRepetitiveCalibration: string;
|
DayAndTimeForRepetitiveCalibration: string;
|
||||||
|
DownDayAndTimeForAdditionalCalibration: string;
|
||||||
|
DownDayAndTimeForRepetitiveCalibration: string;
|
||||||
DisplayIndividualBatteries: string;
|
DisplayIndividualBatteries: string;
|
||||||
MaxBatteryDischargingCurrent: number;
|
MaxBatteryDischargingCurrent: number;
|
||||||
ForceCalibrationChargeState: string;
|
ForceCalibrationChargeState: string;
|
||||||
|
ForceCalibrationDischargeState: string;
|
||||||
GridSetPoint: number;
|
GridSetPoint: number;
|
||||||
HoldSocZone: number;
|
HoldSocZone: number;
|
||||||
MinSoc: number;
|
MinSoc: number;
|
||||||
|
|
@ -392,17 +409,101 @@ export interface JSONRecordData {
|
||||||
LoadOnDc: { Power: number };
|
LoadOnDc: { Power: number };
|
||||||
|
|
||||||
PvOnDc: {
|
PvOnDc: {
|
||||||
DcWh: number;
|
[deviceId: string]: {
|
||||||
NbrOfStrings: number;
|
DcWh: number;
|
||||||
Dc: {
|
Dc: {
|
||||||
Voltage: number;
|
Voltage: number;
|
||||||
Current: number;
|
Current: number;
|
||||||
Power: number;
|
Power: number;
|
||||||
};
|
NbrOfStrings: number;
|
||||||
Strings: {
|
};
|
||||||
[PvId: string]: PvString;
|
Strings: {
|
||||||
|
[PvId: string]: PvString;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AcDcGrowatt: {
|
||||||
|
AcChargeEnable: number;
|
||||||
|
ActivePowerPercentDerating: number;
|
||||||
|
ActualChargeDischargePowerControlValue: number;
|
||||||
|
AlarmMainCode: number;
|
||||||
|
AlarmSubCode: number;
|
||||||
|
BatteriesRecords: {
|
||||||
|
AverageSoc: number;
|
||||||
|
AverageSoh: number;
|
||||||
|
Batteries: { [deviceId: string]: SodioHomeBattery };
|
||||||
|
LowestSoc: number;
|
||||||
|
Power: number;
|
||||||
|
TotalChargeEnergy: number;
|
||||||
|
TotalDischargeEnergy: number;
|
||||||
|
TotalMaxCharge: number;
|
||||||
|
TotalMaxDischarge: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
BatteryChargeCutoffVoltage: number;
|
||||||
|
BatteryClusterIndex: number;
|
||||||
|
BatteryDischargeCutoffVoltage: number;
|
||||||
|
BatteryMaxChargeCurrent: number;
|
||||||
|
BatteryMaxChargePower: number;
|
||||||
|
BatteryMaxDischargePower: number;
|
||||||
|
BatteryMaxdischargeCurrent: number;
|
||||||
|
BatteryOperatingMode: string;
|
||||||
|
BatteryType: number;
|
||||||
|
ChargeCutoffSoc: number;
|
||||||
|
ControlPermession: number;
|
||||||
|
DischargeCutoffSoc: number;
|
||||||
|
EmsCommunicationFailureTime: number;
|
||||||
|
EnableCommand: number;
|
||||||
|
EnableEmsCommunicationFailureTime: number;
|
||||||
|
EnableSyn: string;
|
||||||
|
EnergyToGrid: number;
|
||||||
|
EnergyToUser: number;
|
||||||
|
FaultMainCode: number;
|
||||||
|
FaultSubCode: number;
|
||||||
|
Frequency: number;
|
||||||
|
GridAbLineVoltage: number;
|
||||||
|
GridBcLineVoltage: number;
|
||||||
|
GridCaLineVoltage: number;
|
||||||
|
InverterActivePower: number;
|
||||||
|
InverterReactivePower: number;
|
||||||
|
InverterTemperature: number;
|
||||||
|
LoadPriorityDischargeCutoffSoc: number;
|
||||||
|
MaxActivePower: number;
|
||||||
|
MeterPower: number;
|
||||||
|
OffGridDischargeCutoffSoc: number;
|
||||||
|
OperatingPriority: string;
|
||||||
|
PhaseACurrent: number;
|
||||||
|
PhaseBCurrent: number;
|
||||||
|
PhaseCCurrent: number;
|
||||||
|
PowerFactor: number;
|
||||||
|
Pv1Current: number;
|
||||||
|
Pv1Voltage: number;
|
||||||
|
Pv2Current: number;
|
||||||
|
Pv2Voltage: number;
|
||||||
|
PvInputMaxPower: number;
|
||||||
|
RatedPower: number;
|
||||||
|
RemoteChargDischargePower: number;
|
||||||
|
RemotePowerControl: number;
|
||||||
|
SystemDateTime: string;
|
||||||
|
SystemOperatingMode: string;
|
||||||
|
TotalEnergyToGrid: number;
|
||||||
|
TotalEnergyToUser: number;
|
||||||
|
VppProtocolVerNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// DcWh: number;
|
||||||
|
// // NbrOfStrings: number;
|
||||||
|
// Dc: { [deviceId: string]: Dc };
|
||||||
|
// // Dc: {
|
||||||
|
// // Voltage: number;
|
||||||
|
// // Current: number;
|
||||||
|
// // Power: number;
|
||||||
|
// // };
|
||||||
|
// Strings: {
|
||||||
|
// [PvId: string]: PvString;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseChunkJson = (
|
export const parseChunkJson = (
|
||||||
|
|
@ -497,8 +598,10 @@ export interface I_BoxDataValue {
|
||||||
export type ConfigurationValues = {
|
export type ConfigurationValues = {
|
||||||
minimumSoC: string | number;
|
minimumSoC: string | number;
|
||||||
gridSetPoint: number;
|
gridSetPoint: number;
|
||||||
CalibrationChargeState: number;
|
calibrationChargeState: number;
|
||||||
calibrationChargeDate: Date | null;
|
calibrationChargeDate: Date | null;
|
||||||
|
calibrationDischargeState: number;
|
||||||
|
calibrationDischargeDate: Date | null;
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
// export interface Pv {
|
// export interface Pv {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
import React, {
|
||||||
|
Fragment,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
InputLabel,
|
||||||
|
ListItem,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { InnovEnergyUser, UserType } from '../../../interfaces/UserTypes';
|
||||||
|
import PersonRemoveIcon from '@mui/icons-material/PersonRemove';
|
||||||
|
import {
|
||||||
|
I_Folder,
|
||||||
|
I_Installation
|
||||||
|
} from '../../../interfaces/InstallationTypes';
|
||||||
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
|
|
||||||
|
interface UserAccessProps {
|
||||||
|
current_user: InnovEnergyUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserAccess(props: UserAccessProps) {
|
||||||
|
if (props.current_user == undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const tokencontext = useContext(TokenContext);
|
||||||
|
const { removeToken } = tokencontext;
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser, setUser } = context;
|
||||||
|
const [openFolder, setOpenFolder] = useState(false);
|
||||||
|
const [openInstallation, setOpenInstallation] = useState(false);
|
||||||
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
|
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
||||||
|
const [selectedInstallationNames, setSelectedInstallationNames] = useState<
|
||||||
|
string[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const [folders, setFolders] = useState<I_Folder[]>([]);
|
||||||
|
const [installations, setInstallations] = useState<I_Installation[]>([]);
|
||||||
|
const accessContext = useContext(AccessContext);
|
||||||
|
const {
|
||||||
|
fetchInstallationsForUser,
|
||||||
|
accessibleInstallationsForUser,
|
||||||
|
error,
|
||||||
|
setError,
|
||||||
|
updated,
|
||||||
|
setUpdated,
|
||||||
|
updatedmessage,
|
||||||
|
errormessage,
|
||||||
|
setErrorMessage,
|
||||||
|
setUpdatedMessage,
|
||||||
|
RevokeAccessFromResource
|
||||||
|
} = accessContext;
|
||||||
|
|
||||||
|
const fetchFolders = useCallback(async () => {
|
||||||
|
return axiosConfig
|
||||||
|
.get('/GetAllFolders')
|
||||||
|
.then((res) => {
|
||||||
|
setFolders(res.data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.response && err.response.status == 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [setFolders]);
|
||||||
|
|
||||||
|
const fetchInstallations = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
// fetch product 0
|
||||||
|
const res0 = await axiosConfig.get(
|
||||||
|
`/GetAllInstallationsFromProduct?product=0`
|
||||||
|
);
|
||||||
|
const installations0 = res0.data;
|
||||||
|
|
||||||
|
// fetch product 1
|
||||||
|
const res1 = await axiosConfig.get(
|
||||||
|
`/GetAllInstallationsFromProduct?product=3`
|
||||||
|
);
|
||||||
|
const installations1 = res1.data;
|
||||||
|
|
||||||
|
// aggregate
|
||||||
|
const combined = [...installations0, ...installations1];
|
||||||
|
|
||||||
|
// update
|
||||||
|
setInstallations(combined);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response && err.response.status === 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}, [setInstallations]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
}, [props.current_user]);
|
||||||
|
|
||||||
|
const handleGrantAccess = () => {
|
||||||
|
fetchFolders();
|
||||||
|
fetchInstallations();
|
||||||
|
setOpenModal(true);
|
||||||
|
setSelectedFolderNames([]);
|
||||||
|
setSelectedInstallationNames([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFolderChange = (event) => {
|
||||||
|
setSelectedFolderNames(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstallationChange = (event) => {
|
||||||
|
setSelectedInstallationNames(event.target.value);
|
||||||
|
};
|
||||||
|
const handleOpenFolder = () => {
|
||||||
|
setOpenFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFolder = () => {
|
||||||
|
setOpenFolder(false);
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpenModal(false);
|
||||||
|
};
|
||||||
|
const handleOpenInstallation = () => {
|
||||||
|
setOpenInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseInstallation = () => {
|
||||||
|
setOpenInstallation(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
for (const folderName of selectedFolderNames) {
|
||||||
|
const folder = folders.find((folder) => folder.name === folderName);
|
||||||
|
|
||||||
|
await axiosConfig
|
||||||
|
.post(
|
||||||
|
`/GrantUserAccessToFolder?UserId=${props.current_user.id}&FolderId=${folder.id}`
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(
|
||||||
|
'Granted access to user ' + props.current_user.name
|
||||||
|
);
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setErrorMessage('An error has occured');
|
||||||
|
setError(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const installationName of selectedInstallationNames) {
|
||||||
|
const installation = installations.find(
|
||||||
|
(installation) => installation.name === installationName
|
||||||
|
);
|
||||||
|
|
||||||
|
await axiosConfig
|
||||||
|
.post(
|
||||||
|
`/GrantUserAccessToInstallation?UserId=${props.current_user.id}&InstallationId=${installation.id}`
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setUpdatedMessage(
|
||||||
|
'Granted access to user ' + props.current_user.name
|
||||||
|
);
|
||||||
|
setUpdated(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setErrorMessage('An error has occured');
|
||||||
|
setError(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOpenModal(false);
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
{updated && (
|
||||||
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
mt: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{updatedmessage}
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{errormessage}
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setError(false)}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={openModal}
|
||||||
|
onClose={() => {}}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '30%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 500,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center'
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FormControl fullWidth sx={{ marginTop: 1, width: 390 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToFolders"
|
||||||
|
defaultMessage="Grant access to folders"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedFolderNames}
|
||||||
|
onChange={handleFolderChange}
|
||||||
|
open={openFolder}
|
||||||
|
onClose={handleCloseFolder}
|
||||||
|
onOpen={handleOpenFolder}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<div>
|
||||||
|
{selected.map((folder) => (
|
||||||
|
<span key={folder}>{folder}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{folders.map((folder) => (
|
||||||
|
<MenuItem key={folder.id} value={folder.name}>
|
||||||
|
{folder.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseFolder}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="grantAccessToInstallations"
|
||||||
|
defaultMessage="Grant access to installations"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedInstallationNames}
|
||||||
|
onChange={handleInstallationChange}
|
||||||
|
open={openInstallation}
|
||||||
|
onClose={handleCloseInstallation}
|
||||||
|
onOpen={handleOpenInstallation}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<div>
|
||||||
|
{selected.map((installation) => (
|
||||||
|
<span key={installation}>{installation}, </span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{installations.map((installation) => (
|
||||||
|
<MenuItem
|
||||||
|
key={installation.id}
|
||||||
|
value={installation.name}
|
||||||
|
>
|
||||||
|
{installation.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginLeft: '150px',
|
||||||
|
marginTop: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleCloseInstallation}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleCancel}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: theme.colors.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.colors.primary.dark
|
||||||
|
},
|
||||||
|
padding: '6px 8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleGrantAccess}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="grantAccess" defaultMessage="Grant Access" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
{accessibleInstallationsForUser.map((installation, index) => {
|
||||||
|
const isLast = index === accessibleInstallationsForUser.length - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment key={installation.name}>
|
||||||
|
<ListItem
|
||||||
|
sx={{
|
||||||
|
mb: isLast ? 4 : 0 // Apply margin-bottom to the last item only
|
||||||
|
}}
|
||||||
|
secondaryAction={
|
||||||
|
currentUser.userType === UserType.admin && (
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
RevokeAccessFromResource(
|
||||||
|
'ToInstallation',
|
||||||
|
props.current_user.id,
|
||||||
|
'InstallationId',
|
||||||
|
installation.id,
|
||||||
|
props.current_user.name
|
||||||
|
);
|
||||||
|
|
||||||
|
fetchInstallationsForUser(props.current_user.id);
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
<PersonRemoveIcon />
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<PersonIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText primary={installation.name} />
|
||||||
|
</ListItem>
|
||||||
|
<Divider />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{accessibleInstallationsForUser.length == 0 && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="theUserDoesNOtHaveAccessToAnyInstallation"
|
||||||
|
defaultMessage="The user does not have access to any installation "
|
||||||
|
/>
|
||||||
|
<IconButton color="inherit" size="small"></IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserAccess;
|
||||||
|
|
@ -24,34 +24,37 @@ function PvView(props: PvViewProps) {
|
||||||
if (props.values === null && props.connected == true) {
|
if (props.values === null && props.connected == true) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const sortedPvView =
|
// ✅ Flatten, sort, and assign unique displayId from 1-N
|
||||||
props.values != null && props.values.PvOnDc
|
const sortedPvView = props.values?.PvOnDc
|
||||||
? Object.entries(props.values.PvOnDc.Strings)
|
? Object.entries(props.values.PvOnDc)
|
||||||
.map(([pvId, pv]) => {
|
.flatMap(([deviceId, device]) =>
|
||||||
return { pvId, pv }; // Here we return an object with the id and device
|
Object.entries(device.Strings).map(([pvId, pv], index) => ({
|
||||||
})
|
pvId,
|
||||||
.sort((a, b) => parseInt(b.pvId) - parseInt(a.pvId))
|
pv,
|
||||||
: [];
|
deviceId,
|
||||||
|
displayId: `CU${deviceId} -> AMPT ${index + 1}`
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.deviceId === b.deviceId) {
|
||||||
|
return parseInt(a.pvId) - parseInt(b.pvId);
|
||||||
|
}
|
||||||
|
return a.deviceId.localeCompare(b.deviceId);
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
const [loading, setLoading] = useState(sortedPvView.length == 0);
|
const [loading, setLoading] = useState(sortedPvView.length === 0);
|
||||||
|
|
||||||
const handleMainStatsButton = () => {
|
const handleMainStatsButton = () => {
|
||||||
navigate(routes.mainstats);
|
navigate(routes.mainstats);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const findBatteryData = (batteryId: number) => {
|
|
||||||
// for (let i = 0; i < props.values.batteryView.length; i++) {
|
|
||||||
// if (props.values.batteryView[i].BatteryId == batteryId) {
|
|
||||||
// return props.values.batteryView[i];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sortedPvView.length == 0) {
|
if (sortedPvView.length === 0) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -84,6 +87,7 @@ function PvView(props: PvViewProps) {
|
||||||
</Typography>
|
</Typography>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loading && props.connected && (
|
{loading && props.connected && (
|
||||||
<Container
|
<Container
|
||||||
maxWidth="xl"
|
maxWidth="xl"
|
||||||
|
|
@ -111,80 +115,72 @@ function PvView(props: PvViewProps) {
|
||||||
|
|
||||||
{!loading && props.connected && (
|
{!loading && props.connected && (
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<TableContainer
|
{Object.entries(
|
||||||
component={Paper}
|
sortedPvView.reduce((acc, entry) => {
|
||||||
sx={{
|
if (!acc[entry.deviceId]) acc[entry.deviceId] = [];
|
||||||
marginTop: '20px',
|
acc[entry.deviceId].push(entry);
|
||||||
marginBottom: '20px'
|
return acc;
|
||||||
}}
|
}, {} as Record<string, typeof sortedPvView>)
|
||||||
>
|
).map(([deviceId, entries]) => (
|
||||||
<Table sx={{ minWidth: 250 }} aria-label="simple table">
|
<TableContainer
|
||||||
<TableHead>
|
key={deviceId}
|
||||||
<TableRow>
|
component={Paper}
|
||||||
<TableCell align="center">Pv</TableCell>
|
sx={{ marginTop: '30px', marginBottom: '40px', boxShadow: 3 }}
|
||||||
<TableCell align="center">Power</TableCell>
|
>
|
||||||
<TableCell align="center">Voltage</TableCell>
|
<Table sx={{ minWidth: 250 }} aria-label={`CU${deviceId} table`}>
|
||||||
<TableCell align="center">Current</TableCell>
|
<TableHead>
|
||||||
</TableRow>
|
<TableRow>
|
||||||
</TableHead>
|
<TableCell align="center">Pv</TableCell>
|
||||||
<TableBody>
|
<TableCell align="center">Power</TableCell>
|
||||||
{sortedPvView.map(({ pvId, pv }) => (
|
<TableCell align="center">Voltage</TableCell>
|
||||||
<TableRow
|
<TableCell align="center">Current</TableCell>
|
||||||
key={pvId}
|
|
||||||
style={{
|
|
||||||
height: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TableCell
|
|
||||||
component="th"
|
|
||||||
scope="row"
|
|
||||||
align="center"
|
|
||||||
sx={{ width: '10%', fontWeight: 'bold', color: 'black' }}
|
|
||||||
>
|
|
||||||
{'AMPT ' + pvId}
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Power + ' W'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Voltage + ' V'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
width: '10%',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
backgroundColor:
|
|
||||||
pv.Current == 0 ? '#FF033E' : '#32CD32',
|
|
||||||
color: 'inherit'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pv.Current + ' A'}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHead>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{entries.map(({ displayId, pv }, index) => (
|
||||||
</TableContainer>
|
<TableRow key={index}>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{ fontWeight: 'bold', color: 'black' }}
|
||||||
|
>
|
||||||
|
{displayId}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Power} W
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Voltage} V
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor:
|
||||||
|
pv.Current === 0 ? '#FF033E' : '#32CD32'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{pv.Current} A
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
routes.installation +
|
routes.installation +
|
||||||
`${installationID}` +
|
`${installationID}` +
|
||||||
'/' +
|
'/' +
|
||||||
routes.live,
|
routes.batteryview,
|
||||||
{
|
{
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,11 @@ import HistoryOfActions from '../History/History';
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
import AccessContextProvider from '../../../contexts/AccessContextProvider';
|
import AccessContextProvider from '../../../contexts/AccessContextProvider';
|
||||||
import Access from '../ManageAccess/Access';
|
import Access from '../ManageAccess/Access';
|
||||||
import InformationSodioHome from '../Information/InformationSodioHome';
|
import Information from '../Information/Information';
|
||||||
import CryptoJS from 'crypto-js';
|
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||||
|
import { fetchDataJson } from '../Installations/fetchData';
|
||||||
|
import { FetchResult } from '../../../dataCache/dataCache';
|
||||||
|
import BatteryViewSodioHome from '../BatteryView/BatteryViewSodioHome';
|
||||||
|
|
||||||
interface singleInstallationProps {
|
interface singleInstallationProps {
|
||||||
current_installation?: I_Installation;
|
current_installation?: I_Installation;
|
||||||
|
|
@ -31,6 +34,19 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
if (props.current_installation == undefined) {
|
if (props.current_installation == undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const S3data = {
|
||||||
|
s3Region: props.current_installation.s3Region,
|
||||||
|
s3Provider: props.current_installation.s3Provider,
|
||||||
|
s3Key: props.current_installation.s3Key,
|
||||||
|
s3Secret: props.current_installation.s3Secret,
|
||||||
|
s3BucketId: props.current_installation.s3BucketId
|
||||||
|
};
|
||||||
|
|
||||||
|
const s3Bucket =
|
||||||
|
props.current_installation.s3BucketId.toString() +
|
||||||
|
'-' +
|
||||||
|
'e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa';
|
||||||
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
const location = useLocation().pathname;
|
const location = useLocation().pathname;
|
||||||
|
|
@ -45,6 +61,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
const [connected, setConnected] = useState(true);
|
const [connected, setConnected] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||||
|
const s3Credentials = { s3Bucket, ...S3data };
|
||||||
|
|
||||||
//In React, useRef creates a mutable object that persists across renders without triggering re-renders when its value changes.
|
//In React, useRef creates a mutable object that persists across renders without triggering re-renders when its value changes.
|
||||||
//While fetching, we check the value of continueFetching, if its false, we break. This means that either the user changed tab or the object has been finished its execution (return)
|
//While fetching, we check the value of continueFetching, if its false, we break. This means that either the user changed tab or the object has been finished its execution (return)
|
||||||
|
|
@ -91,48 +108,75 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
const fetchDataPeriodically = async () => {
|
||||||
while (continueFetching.current) {
|
var timeperiodToSearch = 200;
|
||||||
//Fetch data from Bitwatt cloud
|
let res;
|
||||||
console.log('Fetching from Bitwatt cloud');
|
let timestampToFetch;
|
||||||
|
|
||||||
console.log(props.current_installation.serialNumber);
|
for (var i = 0; i < timeperiodToSearch; i += 2) {
|
||||||
console.log(props.current_installation.s3WriteKey);
|
if (!continueFetching.current) {
|
||||||
console.log(props.current_installation.s3WriteSecret);
|
return false;
|
||||||
|
}
|
||||||
const timeStamp = Date.now().toString();
|
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||||
|
|
||||||
// Encrypt timestamp using AES-ECB with PKCS7 padding
|
|
||||||
const key = CryptoJS.enc.Utf8.parse(
|
|
||||||
props.current_installation.s3WriteSecret
|
|
||||||
);
|
|
||||||
const encrypted = CryptoJS.AES.encrypt(timeStamp, key, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
// Set headers
|
|
||||||
const headers = {
|
|
||||||
'X-Signature': encrypted,
|
|
||||||
'X-AccessKey': props.current_installation.s3WriteKey,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
|
|
||||||
// API URL
|
|
||||||
const url = `https://www.biwattpower.com/gateway/admin/open/device/currentEnergyFlowData/${props.current_installation.serialNumber}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { method: 'GET', headers });
|
res = await fetchDataJson(timestampToFetch, s3Credentials, false);
|
||||||
const result = await response.json();
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
console.log('API Response:', result);
|
break;
|
||||||
} catch (error) {
|
}
|
||||||
console.error('Request failed:', error);
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i >= timeperiodToSearch) {
|
||||||
|
setConnected(false);
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setConnected(true);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
while (continueFetching.current) {
|
||||||
|
for (const timestamp of Object.keys(res)) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
setFetchFunctionCalled(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log(`Timestamp: ${timestamp}`);
|
||||||
|
console.log(res[timestamp]);
|
||||||
|
|
||||||
|
setValues(res[timestamp]);
|
||||||
|
await timeout(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for 2 seconds before fetching again
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(60));
|
||||||
await timeout(200000);
|
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||||
console.log('ssssssssssssssssssssssssssssssssssssss');
|
|
||||||
|
for (i = 0; i < 30; i++) {
|
||||||
|
if (!continueFetching.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||||
|
res = await fetchDataJson(timestampToFetch, s3Credentials, false);
|
||||||
|
if (
|
||||||
|
res !== FetchResult.notAvailable &&
|
||||||
|
res !== FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching data:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||||
|
}
|
||||||
|
if (i == 30) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setFetchFunctionCalled(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -147,11 +191,16 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(currentTab);
|
if (
|
||||||
if (currentTab == 'live' || location.includes('batteryview')) {
|
currentTab == 'live' ||
|
||||||
//Fetch periodically if the tab is live or batteryview
|
currentTab == 'pvview' ||
|
||||||
|
currentTab == 'configuration' ||
|
||||||
|
location.includes('batteryview')
|
||||||
|
) {
|
||||||
|
//Fetch periodically if the tab is live, pvview or batteryview
|
||||||
if (
|
if (
|
||||||
currentTab == 'live' ||
|
currentTab == 'live' ||
|
||||||
|
currentTab == 'pvview' ||
|
||||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
(location.includes('batteryview') && !location.includes('mainstats'))
|
||||||
) {
|
) {
|
||||||
if (!continueFetching.current) {
|
if (!continueFetching.current) {
|
||||||
|
|
@ -163,6 +212,10 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Fetch only one time in configuration tab
|
||||||
|
// if (currentTab == 'configuration') {
|
||||||
|
// fetchDataForOneTime();
|
||||||
|
// }
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
continueFetching.current = false;
|
continueFetching.current = false;
|
||||||
|
|
@ -322,10 +375,11 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
<Route
|
<Route
|
||||||
path={routes.information}
|
path={routes.information}
|
||||||
element={
|
element={
|
||||||
<InformationSodioHome
|
<Information
|
||||||
values={props.current_installation}
|
values={props.current_installation}
|
||||||
|
s3Credentials={s3Credentials}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
></InformationSodioHome>
|
></Information>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -351,28 +405,17 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*<Route*/}
|
<Route
|
||||||
{/* path={routes.overview}*/}
|
path={routes.batteryview + '*'}
|
||||||
{/* element={*/}
|
element={
|
||||||
{/* <SalidomoOverview*/}
|
<BatteryViewSodioHome
|
||||||
{/* s3Credentials={s3Credentials}*/}
|
values={values}
|
||||||
{/* id={props.current_installation.id}*/}
|
s3Credentials={s3Credentials}
|
||||||
{/* ></SalidomoOverview>*/}
|
installationId={props.current_installation.id}
|
||||||
{/* }*/}
|
connected={connected}
|
||||||
{/*/>*/}
|
></BatteryViewSodioHome>
|
||||||
|
}
|
||||||
{/*<Route*/}
|
></Route>
|
||||||
{/* path={routes.batteryview + '*'}*/}
|
|
||||||
{/* element={*/}
|
|
||||||
{/* <BatteryViewSalidomo*/}
|
|
||||||
{/* values={values}*/}
|
|
||||||
{/* s3Credentials={s3Credentials}*/}
|
|
||||||
{/* installationId={props.current_installation.id}*/}
|
|
||||||
{/* productNum={props.current_installation.product}*/}
|
|
||||||
{/* connected={connected}*/}
|
|
||||||
{/* ></BatteryViewSalidomo>*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/*></Route>*/}
|
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
|
|
|
||||||
|
|
@ -1,286 +0,0 @@
|
||||||
import React, { useContext, useState } from 'react';
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
CircularProgress,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
interface SodiohomeInstallationFormProps {
|
|
||||||
cancel: () => void;
|
|
||||||
submit: () => void;
|
|
||||||
parentid: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SodiohomeInstallationForm(props: SodiohomeInstallationFormProps) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const [open, setOpen] = useState(true);
|
|
||||||
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
|
||||||
name: '',
|
|
||||||
region: '',
|
|
||||||
location: '',
|
|
||||||
country: '',
|
|
||||||
serialNumber: '',
|
|
||||||
s3WriteSecret: '',
|
|
||||||
s3WriteKey: ''
|
|
||||||
});
|
|
||||||
const requiredFields = ['name', 'location', 'country', 'serialNumber'];
|
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
|
||||||
const { createInstallation, loading, setLoading, error, setError } =
|
|
||||||
installationContext;
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
formValues.parentId = props.parentid;
|
|
||||||
formValues.product = 2;
|
|
||||||
const responseData = await createInstallation(formValues);
|
|
||||||
props.submit();
|
|
||||||
};
|
|
||||||
const handleCancelSubmit = (e) => {
|
|
||||||
props.cancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
open={open}
|
|
||||||
onClose={() => {}}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: isMobile ? '50%' : '40%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 500,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center', // Center items horizontally
|
|
||||||
'& .MuiTextField-root': {
|
|
||||||
m: 1,
|
|
||||||
width: 390
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="installationName"
|
|
||||||
defaultMessage="Installation Name"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={<FormattedMessage id="region" defaultMessage="Region" />}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.region === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="location" defaultMessage="Location" />
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.location === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.country === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="serialNumber"
|
|
||||||
defaultMessage="Serial Number"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="serialNumber"
|
|
||||||
value={formValues.serialNumber}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.serialNumber === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="s3WriteKey"
|
|
||||||
defaultMessage="BitWatt Cloud Access Key"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="s3WriteKey"
|
|
||||||
value={formValues.s3WriteKey}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.s3WriteKey === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="s3WriteSecret"
|
|
||||||
defaultMessage="BitWatt Cloud Secret Key"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="s3WriteSecret"
|
|
||||||
value={formValues.s3WriteSecret}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.s3WriteSecret === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="Information"
|
|
||||||
defaultMessage="Information"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="information"
|
|
||||||
value={formValues.information}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleCancelSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occured"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)}
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SodiohomeInstallationForm;
|
|
||||||
|
|
@ -14,12 +14,17 @@ import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import SodioHomeInstallation from './Installation';
|
import SodioHomeInstallation from './Installation';
|
||||||
|
|
||||||
function SodioHomeInstallationTabs() {
|
interface SodioHomeInstallationTabsProps {
|
||||||
|
product: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
const tabList = [
|
const tabList = [
|
||||||
'live',
|
'live',
|
||||||
|
'batteryview',
|
||||||
'information',
|
'information',
|
||||||
'manage',
|
'manage',
|
||||||
'overview',
|
'overview',
|
||||||
|
|
@ -53,26 +58,14 @@ function SodioHomeInstallationTabs() {
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sodiohomeInstallations.length === 0 && fetchedInstallations === false) {
|
setProduct(props.product);
|
||||||
|
}, [props.product]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (product == props.product) {
|
||||||
fetchAllSodiohomeInstallations();
|
fetchAllSodiohomeInstallations();
|
||||||
setFetchedInstallations(true);
|
|
||||||
}
|
}
|
||||||
}, [sodiohomeInstallations]);
|
}, [product]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (sodiohomeInstallations && sodiohomeInstallations.length > 0) {
|
|
||||||
if (!socket) {
|
|
||||||
openSocket(2);
|
|
||||||
} else if (product != 2) {
|
|
||||||
closeSocket();
|
|
||||||
openSocket(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [sodiohomeInstallations]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setProduct(2);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
|
|
@ -101,14 +94,23 @@ function SodioHomeInstallationTabs() {
|
||||||
const singleInstallationTabs =
|
const singleInstallationTabs =
|
||||||
currentUser.userType == UserType.admin
|
currentUser.userType == UserType.admin
|
||||||
? [
|
? [
|
||||||
|
// {
|
||||||
|
// value: 'live',
|
||||||
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'live',
|
value: 'batteryview',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: (
|
||||||
},
|
<FormattedMessage
|
||||||
{
|
id="batteryview"
|
||||||
value: 'overview',
|
defaultMessage="Battery View"
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'overview',
|
||||||
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
@ -141,14 +143,14 @@ function SodioHomeInstallationTabs() {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
// {
|
||||||
value: 'live',
|
// value: 'live',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
value: 'overview',
|
// value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
|
|
@ -172,14 +174,23 @@ function SodioHomeInstallationTabs() {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'live',
|
||||||
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'live',
|
value: 'batteryview',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
label: (
|
||||||
},
|
<FormattedMessage
|
||||||
{
|
id="batteryview"
|
||||||
value: 'overview',
|
defaultMessage="Battery View"
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// value: 'overview',
|
||||||
|
// label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
@ -224,10 +235,10 @@ function SodioHomeInstallationTabs() {
|
||||||
value: 'tree',
|
value: 'tree',
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
value: 'live',
|
// value: 'live',
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
// label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ function Topology(props: TopologyProps) {
|
||||||
|
|
||||||
const { product, setProduct } = useContext(ProductIdContext);
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
|
||||||
//console.log('product VALUE IS ', product);
|
|
||||||
const [showValues, setShowValues] = useState(false);
|
const [showValues, setShowValues] = useState(false);
|
||||||
|
|
||||||
const handleSwitch = () => () => {
|
const handleSwitch = () => () => {
|
||||||
|
|
@ -38,6 +37,13 @@ function Topology(props: TopologyProps) {
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
|
const totalPvPower = props.values?.PvOnDc
|
||||||
|
? Object.values(props.values.PvOnDc).reduce(
|
||||||
|
(sum, device) => sum + (device?.Dc?.Power || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
|
@ -408,7 +414,7 @@ function Topology(props: TopologyProps) {
|
||||||
data: props.values?.PvOnDc
|
data: props.values?.PvOnDc
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
value: props.values.PvOnDc.Dc.Power,
|
value: totalPvPower,
|
||||||
unit: 'W'
|
unit: 'W'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -421,15 +427,12 @@ function Topology(props: TopologyProps) {
|
||||||
position: 'top',
|
position: 'top',
|
||||||
data: props.values?.PvOnDc
|
data: props.values?.PvOnDc
|
||||||
? {
|
? {
|
||||||
value: props.values.PvOnDc.Dc.Power,
|
value: totalPvPower,
|
||||||
unit: 'W'
|
unit: 'W'
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
amount: props.values?.PvOnDc
|
amount: props.values?.PvOnDc
|
||||||
? getAmount(
|
? getAmount(highestConnectionValue, totalPvPower)
|
||||||
highestConnectionValue,
|
|
||||||
props.values.PvOnDc.Dc.Power
|
|
||||||
)
|
|
||||||
: 0,
|
: 0,
|
||||||
showValues: showValues
|
showValues: showValues
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import { InstallationsContext } from '../../../contexts/InstallationsContextProv
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import InstallationForm from '../Installations/installationForm';
|
import InstallationForm from '../Installations/installationForm';
|
||||||
import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm';
|
import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm';
|
||||||
import SodiohomeInstallationForm from '../SodiohomeInstallations/SodiohomeInstallationForm';
|
|
||||||
|
|
||||||
interface TreeInformationProps {
|
interface TreeInformationProps {
|
||||||
folder: I_Folder;
|
folder: I_Folder;
|
||||||
|
|
@ -65,7 +64,7 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
// console.log('Selected Product:', e.target.value);
|
// console.log('Selected Product:', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductTypes = ['Salimax', 'Salidomo', 'Sodiohome'];
|
const ProductTypes = ['Salimax', 'Salidomo', 'Sodiohome', 'SodistoreMax'];
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
|
|
@ -322,13 +321,17 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
{openModalInstallation && product == 'Salimax' && (
|
{openModalInstallation &&
|
||||||
<InstallationForm
|
(product == 'Salimax' ||
|
||||||
cancel={handleFormCancel}
|
product == 'Sodiohome' ||
|
||||||
submit={handleInstallationFormSubmit}
|
product == 'SodistoreMax') && (
|
||||||
parentid={props.folder.id}
|
<InstallationForm
|
||||||
/>
|
cancel={handleFormCancel}
|
||||||
)}
|
submit={handleInstallationFormSubmit}
|
||||||
|
parentid={props.folder.id}
|
||||||
|
productToInsert={ProductTypes.indexOf(product)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{openModalInstallation && product == 'Salidomo' && (
|
{openModalInstallation && product == 'Salidomo' && (
|
||||||
<SalidomoInstallationForm
|
<SalidomoInstallationForm
|
||||||
cancel={handleFormCancel}
|
cancel={handleFormCancel}
|
||||||
|
|
@ -336,13 +339,13 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
parentid={props.folder.id}
|
parentid={props.folder.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{openModalInstallation && product == 'Sodiohome' && (
|
{/*{openModalInstallation && product == 'Sodiohome' && (*/}
|
||||||
<SodiohomeInstallationForm
|
{/* <SodiohomeInstallationForm*/}
|
||||||
cancel={handleFormCancel}
|
{/* cancel={handleFormCancel}*/}
|
||||||
submit={handleInstallationFormSubmit}
|
{/* submit={handleInstallationFormSubmit}*/}
|
||||||
parentid={props.folder.id}
|
{/* parentid={props.folder.id}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
)}
|
{/*)}*/}
|
||||||
|
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid
|
<Grid
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
<Grid item xs={12} md={isMobile ? 5 : 4}>
|
<Grid item xs={6} md={5}>
|
||||||
<Card>
|
<Card>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TableContainer sx={{ maxHeight: '520px', overflowY: 'auto' }}>
|
<TableContainer sx={{ maxHeight: '520px', overflowY: 'auto' }}>
|
||||||
|
|
@ -113,13 +113,14 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={6} md={7}>
|
||||||
{selectedUser && (
|
{selectedUser && (
|
||||||
<User
|
<User
|
||||||
current_user={findUser(selectedUser)}
|
current_user={findUser(selectedUser)}
|
||||||
fetchDataAgain={props.fetchDataAgain}
|
fetchDataAgain={props.fetchDataAgain}
|
||||||
></User>
|
></User>
|
||||||
)}
|
)}
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import UserAccess from '../ManageAccess/UserAccess';
|
||||||
|
|
||||||
interface singleUserProps {
|
interface singleUserProps {
|
||||||
current_user: InnovEnergyUser;
|
current_user: InnovEnergyUser;
|
||||||
|
|
@ -41,7 +42,10 @@ function User(props: singleUserProps) {
|
||||||
const [formValues, setFormValues] = useState(props.current_user);
|
const [formValues, setFormValues] = useState(props.current_user);
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
const tabs = [{ value: 'user', label: 'User' }];
|
const tabs = [
|
||||||
|
{ value: 'user', label: 'User' },
|
||||||
|
{ value: 'manage', label: 'Access Management' }
|
||||||
|
];
|
||||||
const [openModalDeleteUser, setOpenModalDeleteUser] = useState(false);
|
const [openModalDeleteUser, setOpenModalDeleteUser] = useState(false);
|
||||||
|
|
||||||
const UserTypes = ['Client', 'Partner', 'Admin'];
|
const UserTypes = ['Client', 'Partner', 'Admin'];
|
||||||
|
|
@ -226,7 +230,7 @@ function User(props: singleUserProps) {
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Grid item xs={12} md={isMobile ? 7 : 8}>
|
<Grid item xs={12} md={12}>
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
onChange={handleTabsChange}
|
onChange={handleTabsChange}
|
||||||
|
|
@ -301,7 +305,7 @@ function User(props: singleUserProps) {
|
||||||
<div>
|
<div>
|
||||||
<FormControl
|
<FormControl
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ marginLeft: 1, marginTop: 1, width: 390 }}
|
sx={{ marginLeft: 1, marginTop: 1, width: 445 }}
|
||||||
>
|
>
|
||||||
<InputLabel
|
<InputLabel
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -413,6 +417,9 @@ function User(props: singleUserProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
{currentTab === 'manage' && (
|
||||||
|
<UserAccess current_user={props.current_user}></UserAccess>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -69,25 +69,38 @@ function userForm(props: userFormProps) {
|
||||||
|
|
||||||
const fetchInstallations = useCallback(async () => {
|
const fetchInstallations = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
return axiosConfig
|
|
||||||
.get('/GetAllInstallations')
|
try {
|
||||||
.then((res) => {
|
// fetch product 0
|
||||||
setInstallations(res.data);
|
const res0 = await axiosConfig.get(
|
||||||
setLoading(false);
|
`/GetAllInstallationsFromProduct?product=0`
|
||||||
})
|
);
|
||||||
.catch((err) => {
|
const installations0 = res0.data;
|
||||||
setLoading(false);
|
|
||||||
if (err.response && err.response.status == 401) {
|
// fetch product 1
|
||||||
removeToken();
|
const res1 = await axiosConfig.get(
|
||||||
}
|
`/GetAllInstallationsFromProduct?product=3`
|
||||||
});
|
);
|
||||||
|
const installations1 = res1.data;
|
||||||
|
|
||||||
|
// aggregate
|
||||||
|
const combined = [...installations0, ...installations1];
|
||||||
|
|
||||||
|
// update
|
||||||
|
setInstallations(combined);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response && err.response.status === 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}, [setInstallations]);
|
}, [setInstallations]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchFolders();
|
fetchFolders();
|
||||||
fetchInstallations();
|
fetchInstallations();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormValues({
|
setFormValues({
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@ import {
|
||||||
InnovEnergyUser
|
InnovEnergyUser
|
||||||
} from '../interfaces/UserTypes';
|
} from '../interfaces/UserTypes';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { I_Installation } from '../interfaces/InstallationTypes';
|
||||||
|
|
||||||
interface AccessContextProviderProps {
|
interface AccessContextProviderProps {
|
||||||
|
fetchInstallationsForUser: (userId: number) => void;
|
||||||
|
accessibleInstallationsForUser: I_Installation[];
|
||||||
availableUsers: InnovEnergyUser[];
|
availableUsers: InnovEnergyUser[];
|
||||||
fetchAvailableUsers: () => Promise<void>;
|
fetchAvailableUsers: () => Promise<void>;
|
||||||
usersWithDirectAccess: InnovEnergyUser[];
|
usersWithDirectAccess: InnovEnergyUser[];
|
||||||
|
|
@ -44,6 +47,8 @@ interface AccessContextProviderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccessContext = createContext<AccessContextProviderProps>({
|
export const AccessContext = createContext<AccessContextProviderProps>({
|
||||||
|
fetchInstallationsForUser: () => Promise.resolve(),
|
||||||
|
accessibleInstallationsForUser: [],
|
||||||
availableUsers: [],
|
availableUsers: [],
|
||||||
fetchAvailableUsers: () => {
|
fetchAvailableUsers: () => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
@ -74,6 +79,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
InnovEnergyUser[]
|
InnovEnergyUser[]
|
||||||
>([]);
|
>([]);
|
||||||
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
|
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
|
||||||
|
const [accessibleInstallationsForUser, setAccessibleInstallationsForUser] =
|
||||||
|
useState<I_Installation[]>([]);
|
||||||
|
|
||||||
const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState<
|
const [usersWithInheritedAccess, setUsersWithInheritedAccess] = useState<
|
||||||
I_UserWithInheritedAccess[]
|
I_UserWithInheritedAccess[]
|
||||||
|
|
@ -104,6 +111,26 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchInstallationsForUser = useCallback(async (userId: number) => {
|
||||||
|
axiosConfig
|
||||||
|
.get(`/GetInstallationsTheUserHasAccess?userId=${userId}`)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
setAccessibleInstallationsForUser(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(true);
|
||||||
|
const message = (
|
||||||
|
<FormattedMessage
|
||||||
|
id="unableToLoadData"
|
||||||
|
defaultMessage="Unable to load data"
|
||||||
|
/>
|
||||||
|
).props.defaultMessage;
|
||||||
|
setErrorMessage(message);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchUsersWithInheritedAccessForResource = useCallback(
|
const fetchUsersWithInheritedAccessForResource = useCallback(
|
||||||
async (tempresourceType: string, id: number) => {
|
async (tempresourceType: string, id: number) => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
|
|
@ -192,6 +219,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<AccessContext.Provider
|
<AccessContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
fetchInstallationsForUser,
|
||||||
|
accessibleInstallationsForUser,
|
||||||
availableUsers,
|
availableUsers,
|
||||||
fetchAvailableUsers,
|
fetchAvailableUsers,
|
||||||
usersWithDirectAccess,
|
usersWithDirectAccess,
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ const InstallationsContextProvider = ({
|
||||||
}
|
}
|
||||||
const tokenString = localStorage.getItem('token');
|
const tokenString = localStorage.getItem('token');
|
||||||
const token = tokenString !== null ? tokenString : '';
|
const token = tokenString !== null ? tokenString : '';
|
||||||
const urlWithToken = `wss://monitor.innov.energy/api/CreateWebSocket?authToken=${token}`;
|
const urlWithToken = `wss://monitor.inesco.energy/api/CreateWebSocket?authToken=${token}`;
|
||||||
|
|
||||||
const new_socket = new WebSocket(urlWithToken);
|
const new_socket = new WebSocket(urlWithToken);
|
||||||
|
|
||||||
|
|
@ -200,9 +200,10 @@ const InstallationsContextProvider = ({
|
||||||
const fetchAllSodiohomeInstallations = useCallback(async () => {
|
const fetchAllSodiohomeInstallations = useCallback(async () => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.get('/GetAllSodioHomeInstallations')
|
.get('/GetAllSodioHomeInstallations')
|
||||||
.then((res: AxiosResponse<I_Installation[]>) =>
|
.then((res: AxiosResponse<I_Installation[]>) => {
|
||||||
setSodiohomeInstallations(res.data)
|
setSodiohomeInstallations(res.data);
|
||||||
)
|
openSocket(res.data);
|
||||||
|
})
|
||||||
.catch((err: AxiosError) => {
|
.catch((err: AxiosError) => {
|
||||||
if (err.response?.status === 401) {
|
if (err.response?.status === 401) {
|
||||||
removeToken();
|
removeToken();
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ export const transformInputToDailyDataJson = async (
|
||||||
//'Battery.Dc.Power' for salimax,
|
//'Battery.Dc.Power' for salimax,
|
||||||
// 'Battery.Power',
|
// 'Battery.Power',
|
||||||
'GridMeter.Ac.Power.Active',
|
'GridMeter.Ac.Power.Active',
|
||||||
'PvOnDc.Dc.Power',
|
'PvOnDc',
|
||||||
'DcDc.Dc.Link.Voltage',
|
'DcDc.Dc.Link.Voltage',
|
||||||
'LoadOnAcGrid.Power.Active',
|
'LoadOnAcGrid.Power.Active',
|
||||||
'LoadOnDc.Power'
|
'LoadOnDc.Power'
|
||||||
|
|
@ -420,23 +420,35 @@ export const transformInputToDailyDataJson = async (
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
if (get(result, path) !== undefined) {
|
if (get(result, path) !== undefined) {
|
||||||
const value = path
|
let value: number | undefined = undefined;
|
||||||
.split('.')
|
|
||||||
.reduce((o, key) => (o ? o[key] : undefined), result);
|
|
||||||
|
|
||||||
if (value < chartOverview[categories[category_index]].min) {
|
if (category_index === 4) {
|
||||||
chartOverview[categories[category_index]].min = value;
|
// Custom logic for 'PvOnDc.Dc.Power'
|
||||||
|
value = Object.values(
|
||||||
|
result.PvOnDc as Record<string, { Dc?: { Power?: number } }>
|
||||||
|
).reduce((sum, device) => sum + (device.Dc?.Power || 0), 0);
|
||||||
|
} else if (get(result, path) !== undefined) {
|
||||||
|
// Default path-based extraction
|
||||||
|
value = path
|
||||||
|
.split('.')
|
||||||
|
.reduce((o, key) => (o ? o[key] : undefined), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value > chartOverview[categories[category_index]].max) {
|
// Only push value if defined
|
||||||
chartOverview[categories[category_index]].max = value;
|
if (value !== undefined) {
|
||||||
|
if (value < chartOverview[categories[category_index]].min) {
|
||||||
|
chartOverview[categories[category_index]].min = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > chartOverview[categories[category_index]].max) {
|
||||||
|
chartOverview[categories[category_index]].max = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData[categories[category_index]].data.push([
|
||||||
|
adjustedTimestampArray[i],
|
||||||
|
value
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
chartData[categories[category_index]].data.push([
|
|
||||||
adjustedTimestampArray[i],
|
|
||||||
value
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
//data[path].push([adjustedTimestamp, null]);
|
|
||||||
}
|
}
|
||||||
category_index++;
|
category_index++;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,7 @@ function SidebarMenu() {
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="sodistore"
|
id="sodistore"
|
||||||
defaultMessage="Sodistore"
|
defaultMessage="SodistoreMax"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import Scrollbar from 'src/components/Scrollbar';
|
import Scrollbar from 'src/components/Scrollbar';
|
||||||
import { SidebarContext } from 'src/contexts/SidebarContext';
|
import { SidebarContext } from 'src/contexts/SidebarContext';
|
||||||
import innovenergyLogo from 'src/Resources/images/innovenergy-Logo_Speichern-mit-Salz_R_color.svg';
|
import inescoLogo from 'src/Resources/images/inesco_logo.png';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -17,8 +18,8 @@ import SidebarMenu from './SidebarMenu';
|
||||||
|
|
||||||
const SidebarWrapper = styled(Box)(
|
const SidebarWrapper = styled(Box)(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
width: ${theme.sidebar.width};
|
width: 280px; /* previously theme.sidebar.width */
|
||||||
min-width: ${theme.sidebar.width};
|
min-width: 280px;
|
||||||
color: ${theme.colors.alpha.trueWhite[70]};
|
color: ${theme.colors.alpha.trueWhite[70]};
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 7;
|
z-index: 7;
|
||||||
|
|
@ -54,16 +55,24 @@ function Sidebar() {
|
||||||
<Scrollbar>
|
<Scrollbar>
|
||||||
<Box mt={3}>
|
<Box mt={3}>
|
||||||
<Box
|
<Box
|
||||||
mx={2}
|
|
||||||
sx={{
|
sx={{
|
||||||
width: 52
|
px: 2, // Padding left & right
|
||||||
|
py: 1.5, // Optional: padding top & bottom
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
border: '2px solid white', // Optional: border around logo
|
||||||
|
borderRadius: '8px',
|
||||||
|
backgroundColor: '#fff', // Optional: white background
|
||||||
|
mx: 2 // Horizontal margin to avoid sticking to edge
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={innovenergyLogo}
|
src={inescoLogo}
|
||||||
alt="innovenergy logo"
|
alt="inesco logo"
|
||||||
style={{
|
style={{
|
||||||
width: '150px' // Width of the image
|
width: '160px',
|
||||||
|
objectFit: 'contain'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -105,8 +114,8 @@ function Sidebar() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={innovenergyLogo}
|
src={inescoLogo}
|
||||||
alt="innovenergy logo"
|
alt="inesco logo"
|
||||||
style={{
|
style={{
|
||||||
width: '150px' // Width of the image
|
width: '150px' // Width of the image
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const SidebarLayout = (props: SidebarLayoutProps) => {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
pt: `${theme.header.height}`,
|
pt: `${theme.header.height}`,
|
||||||
[theme.breakpoints.up('lg')]: {
|
[theme.breakpoints.up('lg')]: {
|
||||||
ml: `${theme.sidebar.width}`
|
ml: '260px'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { alpha, createTheme, lighten, darken } from '@mui/material';
|
import { alpha, createTheme, darken, lighten } from '@mui/material';
|
||||||
import '@mui/lab/themeAugmentation';
|
import '@mui/lab/themeAugmentation';
|
||||||
|
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue