Update frontend to support json using the same interfaces.

All the products use json over the same message interfaces. Salimax and sodistoreMax use the same files avoiding copying everything twice.
This commit is contained in:
Noe 2025-04-08 19:30:52 +02:00
parent 4f75912026
commit 5849507ad1
18 changed files with 311 additions and 215 deletions

View File

@ -30,7 +30,7 @@ function App() {
const navigate = useNavigate(); const navigate = useNavigate();
const searchParams = new URLSearchParams(location.search); const searchParams = new URLSearchParams(location.search);
const username = searchParams.get('username'); const username = searchParams.get('username');
const { setAccessToSalimax, setAccessToSalidomo,setAccessToSodiohome } = const { setAccessToSalimax, setAccessToSalidomo, setAccessToSodiohome } =
useContext(ProductIdContext); useContext(ProductIdContext);
const [language, setLanguage] = useState('en'); const [language, setLanguage] = useState('en');
@ -75,9 +75,9 @@ function App() {
setAccessToSodiohome(response.data.accessToSodiohome); setAccessToSodiohome(response.data.accessToSodiohome);
if (response.data.accessToSalimax) { if (response.data.accessToSalimax) {
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{ } else {
navigate(routes.sodiohome_installations); navigate(routes.sodiohome_installations);
} }
} }
@ -152,7 +152,16 @@ function App() {
path={routes.installations + '*'} path={routes.installations + '*'}
element={ element={
<AccessContextProvider> <AccessContextProvider>
<InstallationTabs /> <InstallationTabs product={0} />
</AccessContextProvider>
}
/>
<Route
path={routes.sodistore_installations + '*'}
element={
<AccessContextProvider>
<InstallationTabs product={3} />
</AccessContextProvider> </AccessContextProvider>
} }
/> />
@ -161,7 +170,7 @@ function App() {
path={routes.salidomo_installations + '*'} path={routes.salidomo_installations + '*'}
element={ element={
<AccessContextProvider> <AccessContextProvider>
<SalidomoInstallationTabs /> <SalidomoInstallationTabs product={1} />
</AccessContextProvider> </AccessContextProvider>
} }
/> />

View File

@ -2,6 +2,7 @@
"users": "/users/", "users": "/users/",
"installations": "/installations/", "installations": "/installations/",
"salidomo_installations": "/salidomo_installations/", "salidomo_installations": "/salidomo_installations/",
"sodistore_installations": "/sodistore_installations/",
"sodiohome_installations": "/sodiohome_installations/", "sodiohome_installations": "/sodiohome_installations/",
"installation": "installation/", "installation": "installation/",
"login": "/login/", "login": "/login/",

View File

@ -40,8 +40,6 @@ function Configuration(props: ConfigurationProps) {
return null; return null;
} }
console.log('111111111111111111111111111111111111111111111111');
const CalibrationChargeOptions = [ const CalibrationChargeOptions = [
'Repetitive Calibration', 'Repetitive Calibration',
'Additional Calibration', 'Additional Calibration',

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useContext, useState } from 'react';
import { import {
Card, Card,
CircularProgress, CircularProgress,
@ -18,6 +18,7 @@ import BuildIcon from '@mui/icons-material/Build';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface FlatInstallationViewProps { interface FlatInstallationViewProps {
installations: I_Installation[]; installations: I_Installation[];
@ -28,6 +29,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1); const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
const currentLocation = useLocation(); const currentLocation = useLocation();
const { product, setProduct } = useContext(ProductIdContext);
const sortedInstallations = [...props.installations].sort((a, b) => { const sortedInstallations = [...props.installations].sort((a, b) => {
// Compare the status field of each installation and sort them based on the status. // Compare the status field of each installation and sort them based on the status.
@ -45,17 +47,25 @@ 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);
navigate( navigate(
routes.installations + product === 0
routes.list + ? routes.installations +
routes.installation + routes.list +
`${installationID}` + routes.installation +
'/' + `${installationID}` +
routes.live, '/' +
routes.live
: routes.sodistore_installations +
routes.list +
routes.installation +
`${installationID}` +
'/' +
routes.live,
{ {
replace: true replace: true
} }
@ -82,7 +92,11 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
sx={{ sx={{
display: display:
currentLocation.pathname === routes.installations + 'list' || currentLocation.pathname === routes.installations + 'list' ||
currentLocation.pathname === routes.installations + routes.list currentLocation.pathname === routes.installations + routes.list ||
currentLocation.pathname ===
routes.sodistore_installations + 'list' ||
currentLocation.pathname ===
routes.sodistore_installations + routes.list
? 'block' ? 'block'
: 'none' : 'none'
}} }}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material'; import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView'; import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
@ -6,6 +6,7 @@ import { I_Installation } from '../../../interfaces/InstallationTypes';
import { Route, Routes, useLocation } from 'react-router-dom'; import { Route, Routes, useLocation } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
import Installation from './Installation'; import Installation from './Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface installationSearchProps { interface installationSearchProps {
installations: I_Installation[]; installations: I_Installation[];
@ -15,6 +16,7 @@ function InstallationSearch(props: installationSearchProps) {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const currentLocation = useLocation(); const currentLocation = useLocation();
const [filteredData, setFilteredData] = useState(props.installations); const [filteredData, setFilteredData] = useState(props.installations);
const { product, setProduct } = useContext(ProductIdContext);
useEffect(() => { useEffect(() => {
const filtered = props.installations.filter( const filtered = props.installations.filter(
@ -35,7 +37,11 @@ function InstallationSearch(props: installationSearchProps) {
sx={{ sx={{
display: display:
currentLocation.pathname === routes.installations + 'list' || currentLocation.pathname === routes.installations + 'list' ||
currentLocation.pathname === routes.installations + routes.list currentLocation.pathname === routes.installations + routes.list ||
currentLocation.pathname ===
routes.sodistore_installations + 'list' ||
currentLocation.pathname ===
routes.sodistore_installations + routes.list
? 'block' ? 'block'
: 'none' : 'none'
}} }}

View File

@ -53,6 +53,7 @@ export const fetchAggregatedDataJson = (
s3Credentials?: I_S3Credentials s3Credentials?: I_S3Credentials
): Promise<FetchResult<any>> => { ): Promise<FetchResult<any>> => {
const s3Path = `${date}.json`; const s3Path = `${date}.json`;
if (s3Credentials && s3Credentials.s3Bucket) { if (s3Credentials && s3Credentials.s3Bucket) {
const s3Access = new S3Access( const s3Access = new S3Access(
s3Credentials.s3Bucket, s3Credentials.s3Bucket,
@ -81,7 +82,7 @@ export const fetchAggregatedDataJson = (
const zip = await JSZip.loadAsync(byteArray); const zip = await JSZip.loadAsync(byteArray);
// Assuming the CSV file is named "data.csv" inside the ZIP archive // Assuming the CSV file is named "data.csv" inside the ZIP archive
const jsonContent = await zip.file('data.json').async('text'); const jsonContent = await zip.file('data.json').async('text');
// console.log(jsonContent); //console.log(jsonContent);
return JSON.parse(jsonContent); return JSON.parse(jsonContent);
} else { } else {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);

View File

@ -15,7 +15,11 @@ import Installation from './Installation';
import { UserType } from '../../../interfaces/UserTypes'; import { UserType } from '../../../interfaces/UserTypes';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
function InstallationTabs() { interface InstallationTabsProps {
product: number;
}
function InstallationTabs(props: InstallationTabsProps) {
const location = useLocation(); const location = useLocation();
const context = useContext(UserContext); const context = useContext(UserContext);
const { currentUser } = context; const { currentUser } = context;
@ -32,10 +36,10 @@ function InstallationTabs() {
]; ];
const [currentTab, setCurrentTab] = useState<string>(undefined); const [currentTab, setCurrentTab] = useState<string>(undefined);
const { const {
salimaxInstallations, salimax_or_sodistore_Installations,
fetchAllInstallations, fetchAllInstallations,
currentProduct,
socket, socket,
openSocket, openSocket,
closeSocket closeSocket
@ -56,25 +60,14 @@ function InstallationTabs() {
}, [location]); }, [location]);
useEffect(() => { useEffect(() => {
if (salimaxInstallations.length === 0) { setProduct(props.product);
fetchAllInstallations(); }, [props.product]);
}
}, [salimaxInstallations]);
useEffect(() => { useEffect(() => {
if (salimaxInstallations && salimaxInstallations.length > 0) { if (product == props.product) {
if (!socket) { fetchAllInstallations(product);
openSocket(0);
} else if (currentProduct != 0) {
closeSocket();
openSocket(0);
}
} }
}, [salimaxInstallations, currentProduct]); }, [product]);
useEffect(() => {
setProduct(0);
}, []);
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
setCurrentTab(value); setCurrentTab(value);
@ -378,7 +371,7 @@ function InstallationTabs() {
} }
]; ];
return salimaxInstallations.length > 1 ? ( return salimax_or_sodistore_Installations.length > 1 ? (
<> <>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe"> <Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<TabsContainerWrapper> <TabsContainerWrapper>
@ -421,17 +414,24 @@ function InstallationTabs() {
<Grid item xs={12}> <Grid item xs={12}>
<Box p={4}> <Box p={4}>
<InstallationSearch <InstallationSearch
installations={salimaxInstallations} installations={salimax_or_sodistore_Installations}
/> />
</Box> </Box>
</Grid> </Grid>
} }
/> />
<Route path={routes.tree + '*'} element={<TreeView />} /> <Route path={routes.tree + '*'} element={<TreeView />} />
<Route <Route
path={'*'} path={'*'}
element={ element={
<Navigate to={routes.installations + routes.list}></Navigate> props.product === 0 ? (
<Navigate to={routes.installations + routes.list} />
) : (
<Navigate
to={routes.sodistore_installations + routes.list}
/>
)
} }
></Route> ></Route>
</Routes> </Routes>
@ -440,7 +440,7 @@ function InstallationTabs() {
</Container> </Container>
<Footer /> <Footer />
</> </>
) : salimaxInstallations.length === 1 ? ( ) : salimax_or_sodistore_Installations.length === 1 ? (
<> <>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe"> <Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<TabsContainerWrapper> <TabsContainerWrapper>
@ -478,7 +478,9 @@ function InstallationTabs() {
<Grid item xs={12}> <Grid item xs={12}>
<Box p={4}> <Box p={4}>
<Installation <Installation
current_installation={salimaxInstallations[0]} current_installation={
salimax_or_sodistore_Installations[0]
}
type="installation" type="installation"
></Installation> ></Installation>
</Box> </Box>

View File

@ -28,6 +28,7 @@ import { UserContext } from '../../../contexts/userContext';
import { UserType } from '../../../interfaces/UserTypes'; import { UserType } from '../../../interfaces/UserTypes';
import { TimeSpan, UnixTime } from '../../../dataCache/time'; import { TimeSpan, UnixTime } from '../../../dataCache/time';
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface OverviewProps { interface OverviewProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
@ -69,6 +70,8 @@ function Overview(props: OverviewProps) {
}[] }[]
>([]); >([]);
//console.log(dailyData);
const [aggregatedDataArray, setAggregatedDataArray] = useState< const [aggregatedDataArray, setAggregatedDataArray] = useState<
{ {
chartData: chartAggregatedDataInterface; chartData: chartAggregatedDataInterface;
@ -82,6 +85,8 @@ function Overview(props: OverviewProps) {
const [endDate, setEndDate] = useState(dayjs()); const [endDate, setEndDate] = useState(dayjs());
const [isZooming, setIsZooming] = useState(false); const [isZooming, setIsZooming] = useState(false);
const { product } = useContext(ProductIdContext);
// console.log( // console.log(
// UnixTime.fromTicks(new Date().getTime() / 1000).earlier( // UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
// TimeSpan.fromDays(1) // TimeSpan.fromDays(1)
@ -102,6 +107,7 @@ function Overview(props: OverviewProps) {
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyDataJson( }> = transformInputToDailyDataJson(
product,
props.s3Credentials, props.s3Credentials,
props.id, props.id,
UnixTime.fromTicks(new Date().getTime() / 1000).earlier( UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
@ -137,6 +143,7 @@ function Overview(props: OverviewProps) {
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyDataJson( }> = transformInputToDailyDataJson(
product,
props.s3Credentials, props.s3Credentials,
props.id, props.id,
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)), UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
@ -265,6 +272,7 @@ function Overview(props: OverviewProps) {
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyDataJson( }> = transformInputToDailyDataJson(
product,
props.s3Credentials, props.s3Credentials,
props.id, props.id,
UnixTime.fromTicks(startDate.unix()), UnixTime.fromTicks(startDate.unix()),
@ -1320,47 +1328,47 @@ function Overview(props: OverviewProps) {
</Grid> </Grid>
</Grid> </Grid>
<Grid {dailyData && (
container <Grid
direction="row" container
justifyContent="center" direction="row"
alignItems="stretch" justifyContent="center"
spacing={3} alignItems="stretch"
> spacing={3}
<Grid item md={6} xs={12}> >
<Card <Grid item md={6} xs={12}>
sx={{ <Card
overflow: 'visible',
marginTop: '30px',
marginBottom: '30px'
}}
>
<Box
sx={{ sx={{
marginLeft: '20px' overflow: 'visible',
marginTop: '30px',
marginBottom: '30px'
}} }}
> >
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="ac_load"
defaultMessage="AC Load"
/>
</Typography>
</Box>
</Box>
<Box <Box
sx={{ sx={{
display: 'flex', marginLeft: '20px'
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}} }}
></Box> >
</Box> <Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="ac_load"
defaultMessage="AC Load"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
{dailyData && (
<ReactApexChart <ReactApexChart
options={{ options={{
...getChartOptions( ...getChartOptions(
@ -1387,42 +1395,41 @@ function Overview(props: OverviewProps) {
type="line" type="line"
height={400} height={400}
/> />
)} </Card>
</Card> </Grid>
</Grid> <Grid item md={6} xs={12}>
<Grid item md={6} xs={12}> <Card
<Card
sx={{
overflow: 'visible',
marginTop: '30px',
marginBottom: '30px'
}}
>
<Box
sx={{ sx={{
marginLeft: '20px' overflow: 'visible',
marginTop: '30px',
marginBottom: '30px'
}} }}
> >
<Box display="flex" alignItems="center">
<Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="dc_load"
defaultMessage="DC Load"
/>
</Typography>
</Box>
</Box>
<Box <Box
sx={{ sx={{
display: 'flex', marginLeft: '20px'
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}} }}
></Box> >
</Box> <Box display="flex" alignItems="center">
{dailyData && ( <Box>
<Typography variant="subtitle1" noWrap>
<FormattedMessage
id="dc_load"
defaultMessage="DC Load"
/>
</Typography>
</Box>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
pt: 3
}}
></Box>
</Box>
<ReactApexChart <ReactApexChart
options={{ options={{
...getChartOptions( ...getChartOptions(
@ -1449,10 +1456,10 @@ function Overview(props: OverviewProps) {
type="line" type="line"
height={400} height={400}
/> />
)} </Card>
</Card> </Grid>
</Grid> </Grid>
</Grid> )}
</Grid> </Grid>
)} )}
</Grid> </Grid>

View File

@ -132,7 +132,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
{sortedInstallations {sortedInstallations
// .filter( // .filter(
// (installation) => // (installation) =>
// installation.status === -1 && // installation.status === -1 &&
// installation.testingMode == false // installation.testingMode == false
// ) // )
.map((installation) => { .map((installation) => {

View File

@ -14,7 +14,11 @@ import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import { UserType } from '../../../interfaces/UserTypes'; import { UserType } from '../../../interfaces/UserTypes';
import SalidomoInstallation from './Installation'; import SalidomoInstallation from './Installation';
function SalidomoInstallationTabs() { interface InstallationTabsProps {
product: number;
}
function SalidomoInstallationTabs(props: InstallationTabsProps) {
const location = useLocation(); const location = useLocation();
const context = useContext(UserContext); const context = useContext(UserContext);
const { currentUser } = context; const { currentUser } = context;
@ -28,12 +32,10 @@ function SalidomoInstallationTabs() {
]; ];
const [currentTab, setCurrentTab] = useState<string>(undefined); const [currentTab, setCurrentTab] = useState<string>(undefined);
const [fetchedInstallations, setFetchedInstallations] =
useState<boolean>(false);
const { const {
salidomoInstallations, salidomoInstallations,
fetchAllSalidomoInstallations, fetchAllSalidomoInstallations,
currentProduct,
socket, socket,
openSocket, openSocket,
closeSocket closeSocket
@ -55,29 +57,14 @@ function SalidomoInstallationTabs() {
}, [location]); }, [location]);
useEffect(() => { useEffect(() => {
//The first time this component will be loaded, it needs to call the fetchAllSalidomoInstallations function from the InstallationsContextProvider setProduct(props.product);
if (salidomoInstallations.length === 0 && fetchedInstallations === false) { }, [props.product]);
useEffect(() => {
if (product == props.product) {
fetchAllSalidomoInstallations(); fetchAllSalidomoInstallations();
setFetchedInstallations(true);
} }
}, [salidomoInstallations]); }, [product]);
useEffect(() => {
//Since we know the ids of the installations we have access to, we need to open a web socket with the backend.
if (salidomoInstallations && salidomoInstallations.length > 0) {
if (!socket) {
openSocket(1);
} else if (currentProduct != 1) {
//If there is any other open websocket for another product, close it.
closeSocket();
openSocket(1);
}
}
}, [salidomoInstallations]);
useEffect(() => {
setProduct(1);
}, []);
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
setCurrentTab(value); setCurrentTab(value);

View File

@ -33,7 +33,6 @@ function SodioHomeInstallationTabs() {
const { const {
sodiohomeInstallations, sodiohomeInstallations,
fetchAllSodiohomeInstallations, fetchAllSodiohomeInstallations,
currentProduct,
socket, socket,
openSocket, openSocket,
closeSocket closeSocket
@ -64,7 +63,7 @@ function SodioHomeInstallationTabs() {
if (sodiohomeInstallations && sodiohomeInstallations.length > 0) { if (sodiohomeInstallations && sodiohomeInstallations.length > 0) {
if (!socket) { if (!socket) {
openSocket(2); openSocket(2);
} else if (currentProduct != 2) { } else if (product != 2) {
closeSocket(); closeSocket();
openSocket(2); openSocket(2);
} }

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useContext, useState } from 'react';
import { import {
CircularProgress, CircularProgress,
Container, Container,
@ -12,6 +12,7 @@ import {
getHighestConnectionValue, getHighestConnectionValue,
JSONRecordData JSONRecordData
} from '../Log/graph.util'; } from '../Log/graph.util';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
interface TopologyProps { interface TopologyProps {
values: JSONRecordData; values: JSONRecordData;
@ -26,6 +27,10 @@ function Topology(props: TopologyProps) {
const highestConnectionValue = const highestConnectionValue =
props.values != null ? getHighestConnectionValue(props.values) : 0; props.values != null ? getHighestConnectionValue(props.values) : 0;
const { product, setProduct } = useContext(ProductIdContext);
console.log('product VALUE IS ', product);
//console.log(props.values.DcDc.Dc.Battery.Voltage); //console.log(props.values.DcDc.Dc.Battery.Voltage);
const [showValues, setShowValues] = useState(false); const [showValues, setShowValues] = useState(false);
@ -514,14 +519,19 @@ function Topology(props: TopologyProps) {
data: props.values?.Battery data: props.values?.Battery
? { ? {
//value: props.values.Battery.Dc.Power for salimax, //value: props.values.Battery.Dc.Power for salimax,
value: props.values.Battery.Power, value:
product == 0
? props.values.Battery.Dc.Power
: props.values.Battery.Power,
unit: 'W' unit: 'W'
} }
: undefined, : undefined,
amount: props.values?.Battery amount: props.values?.Battery
? getAmount( ? getAmount(
highestConnectionValue, highestConnectionValue,
props.values.Battery.Power product == 0
? props.values.Battery.Dc.Power
: props.values.Battery.Power
) )
: 0, : 0,
showValues: showValues showValues: showValues
@ -540,18 +550,27 @@ function Topology(props: TopologyProps) {
}, },
{ {
//value: props.values.Battery.Dc.Voltage for salimax, //value: props.values.Battery.Dc.Voltage for salimax,
value: props.values.Battery.Voltage, value:
product === 0
? props.values.Battery.Dc.Voltage
: props.values.Battery.Voltage,
unit: 'V' unit: 'V'
}, },
{ {
//value: props.values.Battery.Dc.Current for salimax, //value: props.values.Battery.Dc.Current for salimax,
value: props.values.Battery.Current, value:
product == 0
? props.values.Battery.Dc.Current
: props.values.Battery.Current,
unit: 'A' unit: 'A'
}, },
{ {
//value: props.values.Battery.Temperature for salimax, //value: props.values.Battery.Temperature for salimax,
value: props.values.Battery.TemperatureCell1, value:
product == 0
? props.values.Battery.Temperature
: props.values.Battery.TemperatureCell1,
unit: '°C' unit: '°C'
} }
// { // {

View File

@ -51,14 +51,18 @@ function CustomTreeItem(props: CustomTreeItemProps) {
? routes.installations ? routes.installations
: installation.product == 1 : installation.product == 1
? routes.salidomo_installations ? routes.salidomo_installations
: routes.sodiohome_installations; : installation.product == 2
? routes.sodiohome_installations
: routes.sodistore_installations;
let folder_path = let folder_path =
product == 0 product == 0
? routes.installations ? routes.installations
: product == 1 : product == 1
? routes.salidomo_installations ? routes.salidomo_installations
: routes.sodiohome_installations; : installation.product == 2
? routes.sodiohome_installations
: routes.sodistore_installations;
if (installation.type != 'Folder') { if (installation.type != 'Folder') {
navigate( navigate(
@ -175,6 +179,8 @@ function CustomTreeItem(props: CustomTreeItemProps) {
display: display:
currentLocation.pathname === currentLocation.pathname ===
routes.salidomo_installations + routes.tree || routes.salidomo_installations + routes.tree ||
currentLocation.pathname ===
routes.sodistore_installations + routes.tree ||
currentLocation.pathname === routes.installations + routes.tree || currentLocation.pathname === routes.installations + routes.tree ||
currentLocation.pathname === currentLocation.pathname ===
routes.sodiohome_installations + routes.tree || routes.sodiohome_installations + routes.tree ||

View File

@ -11,7 +11,6 @@ import routes from '../../../Resources/routes.json';
import SalidomoInstallation from '../SalidomoInstallations/Installation'; import SalidomoInstallation from '../SalidomoInstallations/Installation';
import Folder from './Folder'; import Folder from './Folder';
import SodioHomeInstallation from '../SodiohomeInstallations/Installation'; import SodioHomeInstallation from '../SodiohomeInstallations/Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
function InstallationTree() { function InstallationTree() {
const { foldersAndInstallations, fetchAllFoldersAndInstallations } = const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
@ -36,8 +35,6 @@ function InstallationTree() {
return 0; return 0;
}); });
const { product } = useContext(ProductIdContext);
useEffect(() => { useEffect(() => {
fetchAllFoldersAndInstallations(); fetchAllFoldersAndInstallations();
}, []); }, []);

View File

@ -22,9 +22,10 @@ const InstallationsContextProvider = ({
}: { }: {
children: ReactNode; children: ReactNode;
}) => { }) => {
const [salimaxInstallations, setSalimaxInstallations] = useState< const [
I_Installation[] salimax_or_sodistore_Installations,
>([]); setSalimax_Or_Sodistore_Installations
] = useState<I_Installation[]>([]);
const [salidomoInstallations, setSalidomoInstallations] = useState< const [salidomoInstallations, setSalidomoInstallations] = useState<
I_Installation[] I_Installation[]
>([]); >([]);
@ -38,8 +39,11 @@ const InstallationsContextProvider = ({
const navigate = useNavigate(); const navigate = useNavigate();
const tokencontext = useContext(TokenContext); const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext; const { removeToken } = tokencontext;
const [socket, setSocket] = useState<WebSocket>(null); // const [socket, setSocket] = useState<WebSocket>(null);
const [currentProduct, setcurrentProduct] = useState<number>(0);
const socket = useRef<WebSocket | null>(null); // Using useRef instead of useState
//const [currentProduct, setcurrentProduct] = useState<number>(0);
//Store pending updates and apply them in batches //Store pending updates and apply them in batches
const pendingUpdates = useRef< const pendingUpdates = useRef<
@ -66,16 +70,18 @@ const InstallationsContextProvider = ({
: installation; : installation;
}); });
const updatedSalimax = salimaxInstallations.map((installation) => { const updatedSalimax = salimax_or_sodistore_Installations.map(
const update = pendingUpdates.current[installation.id]; (installation) => {
return update const update = pendingUpdates.current[installation.id];
? { return update
...installation, ? {
status: update.status, ...installation,
testingMode: update.testingMode status: update.status,
} testingMode: update.testingMode
: installation; }
}); : installation;
}
);
const updatedSodiohome = sodiohomeInstallations.map((installation) => { const updatedSodiohome = sodiohomeInstallations.map((installation) => {
const update = pendingUpdates.current[installation.id]; const update = pendingUpdates.current[installation.id];
@ -89,12 +95,16 @@ const InstallationsContextProvider = ({
}); });
setSalidomoInstallations(updatedSalidomo); setSalidomoInstallations(updatedSalidomo);
setSalimaxInstallations(updatedSalimax); setSalimax_Or_Sodistore_Installations(updatedSalimax);
setSodiohomeInstallations(updatedSodiohome); setSodiohomeInstallations(updatedSodiohome);
// Clear the pending updates after applying // Clear the pending updates after applying
pendingUpdates.current = {}; pendingUpdates.current = {};
}, [salidomoInstallations, salimaxInstallations, sodiohomeInstallations]); }, [
salidomoInstallations,
salimax_or_sodistore_Installations,
sodiohomeInstallations
]);
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
@ -104,27 +114,20 @@ const InstallationsContextProvider = ({
return () => clearInterval(timer); // Cleanup timer on component unmount return () => clearInterval(timer); // Cleanup timer on component unmount
}, [applyBatchUpdates]); }, [applyBatchUpdates]);
const openSocket = (product) => { const openSocket = (installationsToSend) => {
setcurrentProduct(product); if (socket.current) {
socket.current.close();
socket.current = null;
}
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.innov.energy/api/CreateWebSocket?authToken=${token}`;
const socket = new WebSocket(urlWithToken); const new_socket = new WebSocket(urlWithToken);
socket.addEventListener('open', () => {
let installationsToSend = [];
if (product === 0) {
installationsToSend = salimaxInstallations;
} else if (product === 1) {
installationsToSend = salidomoInstallations;
} else if (product === 2) {
installationsToSend = sodiohomeInstallations;
}
new_socket.addEventListener('open', () => {
// Send the corresponding installation IDs to the backend // Send the corresponding installation IDs to the backend
socket.send( new_socket.send(
JSON.stringify( JSON.stringify(
installationsToSend.map((installation) => installation.id) installationsToSend.map((installation) => installation.id)
) )
@ -133,12 +136,12 @@ const InstallationsContextProvider = ({
// Periodically send ping messages to keep the connection alive // Periodically send ping messages to keep the connection alive
const pingInterval = setInterval(() => { const pingInterval = setInterval(() => {
if (socket.readyState === WebSocket.OPEN) { if (new_socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify([-1])); new_socket.send(JSON.stringify([-1]));
} }
}, 10000); }, 10000);
socket.addEventListener('message', (event) => { new_socket.addEventListener('message', (event) => {
const message = JSON.parse(event.data); // Parse the JSON data const message = JSON.parse(event.data); // Parse the JSON data
if (message.id !== -1) { if (message.id !== -1) {
//For each received message (except the first one which is a batch, call the updateInstallationStatus function in order to import the message to the pendingUpdates list //For each received message (except the first one which is a batch, call the updateInstallationStatus function in order to import the message to the pendingUpdates list
@ -150,36 +153,42 @@ const InstallationsContextProvider = ({
} }
}); });
socket.addEventListener('close', () => { new_socket.addEventListener('close', () => {
clearInterval(pingInterval); // Cleanup ping interval on socket close clearInterval(pingInterval); // Cleanup ping interval on socket close
setSocket(null); //socket.current = null;
}); });
setSocket(socket); socket.current = new_socket;
}; };
const closeSocket = () => socket?.close(); const closeSocket = () => socket?.current?.close();
const fetchAllInstallations = useCallback(async () => { // Function to fetch installations and manage the socket
axiosConfig const fetchAllInstallations = useCallback(
.get('/GetAllInstallations') async (product: number) => {
.then((res: AxiosResponse<I_Installation[]>) => axiosConfig
setSalimaxInstallations(res.data) .get(`/GetAllInstallationsFromProduct?product=${product}`)
) .then((res: AxiosResponse<I_Installation[]>) => {
.catch((err: AxiosError) => { setSalimax_Or_Sodistore_Installations(res.data); // Update installations
if (err.response?.status === 401) { openSocket(res.data); // Open a new socket after installations are fetched
removeToken(); })
navigate(routes.login); .catch((err: AxiosError) => {
} if (err.response?.status === 401) {
}); removeToken();
}, [navigate, removeToken]); navigate(routes.login);
}
});
},
[navigate, removeToken]
);
const fetchAllSalidomoInstallations = useCallback(async () => { const fetchAllSalidomoInstallations = useCallback(async () => {
axiosConfig axiosConfig
.get('/GetAllSalidomoInstallations') .get('/GetAllSalidomoInstallations')
.then((res: AxiosResponse<I_Installation[]>) => .then((res: AxiosResponse<I_Installation[]>) => {
setSalidomoInstallations(res.data) setSalidomoInstallations(res.data);
) openSocket(res.data);
})
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
if (err.response?.status === 401) { if (err.response?.status === 401) {
removeToken(); removeToken();
@ -244,7 +253,7 @@ const InstallationsContextProvider = ({
setUpdated(true); setUpdated(true);
setLoading(false); setLoading(false);
if (formValues.product === 0 && view === 'installation') if (formValues.product === 0 && view === 'installation')
fetchAllInstallations(); fetchAllInstallations(formValues.product);
else if (formValues.product === 1 && view === 'installation') else if (formValues.product === 1 && view === 'installation')
fetchAllSalidomoInstallations(); fetchAllSalidomoInstallations();
else if (formValues.product === 2 && view === 'installation') else if (formValues.product === 2 && view === 'installation')
@ -278,7 +287,7 @@ const InstallationsContextProvider = ({
setUpdated(true); setUpdated(true);
setLoading(false); setLoading(false);
if (formValues.product === 0 && view === 'installation') if (formValues.product === 0 && view === 'installation')
fetchAllInstallations(); fetchAllInstallations(formValues.product);
else if (formValues.product === 1 && view === 'installation') else if (formValues.product === 1 && view === 'installation')
fetchAllSalidomoInstallations(); fetchAllSalidomoInstallations();
else if (formValues.product === 2 && view === 'installation') else if (formValues.product === 2 && view === 'installation')
@ -369,7 +378,7 @@ const InstallationsContextProvider = ({
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
salimaxInstallations, salimax_or_sodistore_Installations,
salidomoInstallations, salidomoInstallations,
sodiohomeInstallations, sodiohomeInstallations,
foldersAndInstallations, foldersAndInstallations,
@ -389,21 +398,21 @@ const InstallationsContextProvider = ({
createFolder, createFolder,
updateFolder, updateFolder,
deleteFolder, deleteFolder,
currentProduct, //currentProduct,
socket, socket,
openSocket, openSocket,
closeSocket closeSocket
}), }),
[ [
salimaxInstallations, salimax_or_sodistore_Installations,
salidomoInstallations, salidomoInstallations,
sodiohomeInstallations, sodiohomeInstallations,
foldersAndInstallations, foldersAndInstallations,
loading, loading,
error, error,
updated, updated,
socket, socket
currentProduct //currentProduct
] ]
); );

View File

@ -8,9 +8,11 @@ interface ProductIdContextType {
accessToSalimax: boolean; accessToSalimax: boolean;
accessToSalidomo: boolean; accessToSalidomo: boolean;
accessToSodiohome: boolean; accessToSodiohome: boolean;
accessToSodistore: boolean;
setAccessToSalimax: (access: boolean) => void; setAccessToSalimax: (access: boolean) => void;
setAccessToSalidomo: (access: boolean) => void; setAccessToSalidomo: (access: boolean) => void;
setAccessToSodiohome: (access: boolean) => void; setAccessToSodiohome: (access: boolean) => void;
setAccessToSodistore: (access: boolean) => void;
} }
// Create the context. // Create the context.
@ -37,6 +39,10 @@ export const ProductIdContextProvider = ({
const storedValue = localStorage.getItem('accessToSodiohome'); const storedValue = localStorage.getItem('accessToSodiohome');
return storedValue === 'true'; return storedValue === 'true';
}); });
const [accessToSodistore, setAccessToSodistore] = useState(() => {
const storedValue = localStorage.getItem('accessToSodistore');
return storedValue === 'true';
});
// const [product, setProduct] = useState(location.includes('salidomo') ? 1 : 0); // const [product, setProduct] = useState(location.includes('salidomo') ? 1 : 0);
// const [product, setProduct] = useState<number>( // const [product, setProduct] = useState<number>(
// productMapping[Object.keys(productMapping).find((key) => location.includes(key)) || ''] || -1 // productMapping[Object.keys(productMapping).find((key) => location.includes(key)) || ''] || -1
@ -70,6 +76,10 @@ export const ProductIdContextProvider = ({
setAccessToSodiohome(access); setAccessToSodiohome(access);
localStorage.setItem('accessToSodiohome', JSON.stringify(access)); localStorage.setItem('accessToSodiohome', JSON.stringify(access));
}; };
const changeAccessSodistore = (access: boolean) => {
setAccessToSodistore(access);
localStorage.setItem('accessToSodistore', JSON.stringify(access));
};
return ( return (
<ProductIdContext.Provider <ProductIdContext.Provider
@ -79,9 +89,11 @@ export const ProductIdContextProvider = ({
accessToSalimax, accessToSalimax,
accessToSalidomo, accessToSalidomo,
accessToSodiohome, accessToSodiohome,
accessToSodistore,
setAccessToSalimax: changeAccessSalimax, setAccessToSalimax: changeAccessSalimax,
setAccessToSalidomo: changeAccessSalidomo, setAccessToSalidomo: changeAccessSalidomo,
setAccessToSodiohome: changeAccessSodiohome setAccessToSodiohome: changeAccessSodiohome,
setAccessToSodistore: changeAccessSodistore
}} }}
> >
{children} {children}

View File

@ -287,6 +287,7 @@ export const transformInputToBatteryViewDataJson = async (
}; };
export const transformInputToDailyDataJson = async ( export const transformInputToDailyDataJson = async (
product: number,
s3Credentials: I_S3Credentials, s3Credentials: I_S3Credentials,
id: number, id: number,
start_time?: UnixTime, start_time?: UnixTime,
@ -296,14 +297,17 @@ export const transformInputToDailyDataJson = async (
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> => { }> => {
const prefixes = ['', 'k', 'M', 'G', 'T']; const prefixes = ['', 'k', 'M', 'G', 'T'];
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const pathsToSearch = [ const pathsToSearch = [
'Battery.Soc', 'Battery.Soc',
product == 0 ? 'Battery.Temperature' : 'Battery.TemperatureCell1',
//'Battery.Temperature' for salimax, //'Battery.Temperature' for salimax,
'Battery.TemperatureCell1', //'Battery.TemperatureCell1',
product == 0 ? 'Battery.Dc.Power' : 'Battery.Power',
//'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.Dc.Power',
'DcDc.Dc.Link.Voltage', 'DcDc.Dc.Link.Voltage',
@ -400,6 +404,7 @@ export const transformInputToDailyDataJson = async (
Object.keys(results[i]).length - 1 Object.keys(results[i]).length - 1
]; ];
const result = results[i][timestamp]; const result = results[i][timestamp];
//console.log(result);
let category_index = 0; let category_index = 0;
// eslint-disable-next-line @typescript-eslint/no-loop-func // eslint-disable-next-line @typescript-eslint/no-loop-func
pathsToSearch.forEach((path) => { pathsToSearch.forEach((path) => {
@ -596,6 +601,7 @@ export const transformInputToAggregatedDataJson = async (
) { ) {
// Handle not available or try later case // Handle not available or try later case
} else { } else {
// console.log(result);
dateList.push(currentDay.format('DD-MM')); dateList.push(currentDay.format('DD-MM'));
pathsToSearch.forEach((path) => { pathsToSearch.forEach((path) => {
const value = path const value = path

View File

@ -164,8 +164,12 @@ function SidebarMenu() {
const { closeSidebar } = useContext(SidebarContext); const { closeSidebar } = useContext(SidebarContext);
const context = useContext(UserContext); const context = useContext(UserContext);
const { currentUser, setUser } = context; const { currentUser, setUser } = context;
const { accessToSalimax, accessToSalidomo, accessToSodiohome } = const {
useContext(ProductIdContext); accessToSalimax,
accessToSodistore,
accessToSalidomo,
accessToSodiohome
} = useContext(ProductIdContext);
return ( return (
<> <>
@ -197,6 +201,25 @@ function SidebarMenu() {
</List> </List>
)} )}
<List component="div">
<ListItem component="div">
<Button
disableRipple
component={RouterLink}
onClick={closeSidebar}
to="/sodistore_installations"
startIcon={<BrightnessLowTwoToneIcon />}
>
<Box sx={{ marginTop: '3px' }}>
<FormattedMessage
id="sodistore"
defaultMessage="Sodistore"
/>
</Box>
</Button>
</ListItem>
</List>
{accessToSalidomo && ( {accessToSalidomo && (
<List component="div"> <List component="div">
<ListItem component="div"> <ListItem component="div">