added Main Stats on Battery View page for SodistoreHome
This commit is contained in:
parent
1b6d5a5916
commit
ed1efbddeb
|
|
@ -12,13 +12,14 @@ import {
|
|||
Typography
|
||||
} from '@mui/material';
|
||||
import { JSONRecordData } from '../Log/graph.util';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Link, Route, Routes, 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';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import MainStatsSodioHome from './MainStatsSodioHome';
|
||||
|
||||
interface BatteryViewSodioHomeProps {
|
||||
values: JSONRecordData;
|
||||
|
|
@ -164,54 +165,20 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
|||
</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>*/}
|
||||
<Grid container>
|
||||
<Routes>
|
||||
<Route
|
||||
path={routes.mainstats + '/*'}
|
||||
element={
|
||||
<MainStatsSodioHome
|
||||
s3Credentials={props.s3Credentials}
|
||||
id={props.installationId}
|
||||
batteryClusterNumber={props.installation.batteryClusterNumber}
|
||||
></MainStatsSodioHome>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Grid>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,818 @@
|
|||
import {
|
||||
Box,
|
||||
Card,
|
||||
Container,
|
||||
Grid,
|
||||
IconButton,
|
||||
Modal,
|
||||
TextField,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||
import ReactApexChart from 'react-apexcharts';
|
||||
import { getChartOptions } from '../Overview/chartOptions';
|
||||
import {
|
||||
BatteryDataInterface,
|
||||
BatteryOverviewInterface,
|
||||
transformInputToBatteryViewDataJson
|
||||
} from '../../../interfaces/Chart';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||
import Button from '@mui/material/Button';
|
||||
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
|
||||
interface MainStatsSodioHomeProps {
|
||||
s3Credentials: I_S3Credentials;
|
||||
id: number;
|
||||
batteryClusterNumber: number;
|
||||
}
|
||||
|
||||
function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
|
||||
const [chartState, setChartState] = useState(0);
|
||||
const [batteryViewDataArray, setBatteryViewDataArray] = useState<
|
||||
{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}[]
|
||||
>([]);
|
||||
|
||||
const [isDateModalOpen, setIsDateModalOpen] = useState(false);
|
||||
const [dateOpen, setDateOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [startDate, setStartDate] = useState(dayjs().add(-1, 'day'));
|
||||
const [endDate, setEndDate] = useState(dayjs());
|
||||
const [isErrorDateModalOpen, setErrorDateModalOpen] = useState(false);
|
||||
const [dateSelectionError, setDateSelectionError] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const location = useLocation();
|
||||
|
||||
const blueColors = [
|
||||
'#99CCFF',
|
||||
'#80BFFF',
|
||||
'#6699CC',
|
||||
'#4D99FF',
|
||||
'#2670E6',
|
||||
'#3366CC',
|
||||
'#1A4D99',
|
||||
'#133366',
|
||||
'#0D274D',
|
||||
'#081A33'
|
||||
];
|
||||
const redColors = [
|
||||
'#ff9090',
|
||||
'#ff7070',
|
||||
'#ff3f3f',
|
||||
'#ff1e1e',
|
||||
'#ff0606',
|
||||
'#fc0000',
|
||||
'#f40000',
|
||||
'#d40000',
|
||||
'#a30000',
|
||||
'#7a0000'
|
||||
];
|
||||
const orangeColors = [
|
||||
'#ffdb99',
|
||||
'#ffc968',
|
||||
'#ffb837',
|
||||
'#ffac16',
|
||||
'#ffa706',
|
||||
'#FF8C00',
|
||||
'#d48900',
|
||||
'#CC7A00',
|
||||
'#a36900',
|
||||
'#993D00'
|
||||
];
|
||||
const greenColors = [
|
||||
'#90EE90',
|
||||
'#77DD77',
|
||||
'#5ECE5E',
|
||||
'#45BF45',
|
||||
'#32CD32',
|
||||
'#28A428',
|
||||
'#1E7B1E',
|
||||
'#145214',
|
||||
'#0A390A',
|
||||
'#052905'
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
const resultPromise: Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewDataJson(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
2,
|
||||
UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
|
||||
TimeSpan.fromDays(1)
|
||||
),
|
||||
UnixTime.fromTicks(new Date().getTime() / 1000),
|
||||
props.batteryClusterNumber
|
||||
);
|
||||
|
||||
resultPromise
|
||||
.then((result) => {
|
||||
setBatteryViewDataArray((prevData) =>
|
||||
prevData.concat({
|
||||
chartData: result.chartData,
|
||||
chartOverview: result.chartOverview
|
||||
})
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [isZooming, setIsZooming] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isZooming) {
|
||||
setLoading(true);
|
||||
} else if (!isZooming && batteryViewDataArray.length > 0) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [isZooming, batteryViewDataArray]);
|
||||
|
||||
function generateSeries(chartData, category, color) {
|
||||
const series = [];
|
||||
const pathsToSearch = [];
|
||||
for (let i = 0; i < props.batteryClusterNumber; i++) {
|
||||
pathsToSearch.push('Node' + i);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
pathsToSearch.forEach((devicePath) => {
|
||||
if (
|
||||
Object.hasOwnProperty.call(chartData[category].data, devicePath) &&
|
||||
chartData[category].data[devicePath].data.length != 0
|
||||
) {
|
||||
series.push({
|
||||
...chartData[category].data[devicePath],
|
||||
color:
|
||||
color === 'blue'
|
||||
? blueColors[i]
|
||||
: color === 'red'
|
||||
? redColors[i]
|
||||
: color === 'green'
|
||||
? greenColors[i]
|
||||
: orangeColors[i]
|
||||
});
|
||||
}
|
||||
i++;
|
||||
});
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsDateModalOpen(false);
|
||||
setDateOpen(false);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setIsDateModalOpen(false);
|
||||
setDateOpen(false);
|
||||
|
||||
if (endDate.isAfter(dayjs())) {
|
||||
setDateSelectionError('You cannot ask for future data');
|
||||
setErrorDateModalOpen(true);
|
||||
return;
|
||||
} else if (startDate.isAfter(endDate)) {
|
||||
setDateSelectionError('End date must precede start date');
|
||||
setErrorDateModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const resultPromise: Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewDataJson(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
2,
|
||||
UnixTime.fromTicks(startDate.unix()),
|
||||
UnixTime.fromTicks(endDate.unix()),
|
||||
props.batteryClusterNumber
|
||||
);
|
||||
|
||||
resultPromise
|
||||
.then((result) => {
|
||||
setBatteryViewDataArray((prevData) =>
|
||||
prevData.concat({
|
||||
chartData: result.chartData,
|
||||
chartOverview: result.chartOverview
|
||||
})
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
setChartState(batteryViewDataArray.length);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
};
|
||||
const handleSetDate = () => {
|
||||
setDateOpen(true);
|
||||
setIsDateModalOpen(true);
|
||||
};
|
||||
|
||||
const handleBatteryViewButton = () => {
|
||||
navigate(
|
||||
location.pathname.split('/').slice(0, -2).join('/') + '/batteryview'
|
||||
);
|
||||
};
|
||||
|
||||
const handleGoBack = () => {
|
||||
if (chartState > 0) {
|
||||
setChartState(chartState - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoForward = () => {
|
||||
if (chartState + 1 < batteryViewDataArray.length) {
|
||||
setChartState(chartState + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOkOnErrorDateModal = () => {
|
||||
setErrorDateModalOpen(false);
|
||||
};
|
||||
|
||||
const startZoom = () => {
|
||||
setIsZooming(true);
|
||||
};
|
||||
|
||||
const handleBeforeZoom = (chartContext, { xaxis }) => {
|
||||
const startX = parseInt(xaxis.min) / 1000;
|
||||
const endX = parseInt(xaxis.max) / 1000;
|
||||
|
||||
const resultPromise: Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewDataJson(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
2,
|
||||
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
||||
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)),
|
||||
props.batteryClusterNumber
|
||||
);
|
||||
|
||||
resultPromise
|
||||
.then((result) => {
|
||||
setBatteryViewDataArray((prevData) =>
|
||||
prevData.concat({
|
||||
chartData: result.chartData,
|
||||
chartOverview: result.chartOverview
|
||||
})
|
||||
);
|
||||
|
||||
setIsZooming(false);
|
||||
setChartState(batteryViewDataArray.length);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && (
|
||||
<Container
|
||||
maxWidth="xl"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh'
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
|
||||
<Typography variant="body2" style={{ color: 'black' }} mt={2}>
|
||||
Fetching data...
|
||||
</Typography>
|
||||
</Container>
|
||||
)}
|
||||
{isErrorDateModalOpen && (
|
||||
<Modal open={isErrorDateModalOpen} onClose={() => {}}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 450,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 4,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
gutterBottom
|
||||
sx={{ fontWeight: 'bold' }}
|
||||
>
|
||||
{dateSelectionError}
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
textTransform: 'none',
|
||||
bgcolor: '#ffc04d',
|
||||
color: '#111111',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
onClick={handleOkOnErrorDateModal}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
)}
|
||||
{isDateModalOpen && (
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<Modal open={isDateModalOpen} onClose={() => {}}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 450,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 4,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<DateTimePicker
|
||||
label="Select Start Date"
|
||||
value={startDate}
|
||||
onChange={(newDate: Dayjs | null) => {
|
||||
if (newDate) {
|
||||
setStartDate(newDate);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<DateTimePicker
|
||||
label="Select End Date"
|
||||
value={endDate}
|
||||
onChange={(newDate: Dayjs | null) => {
|
||||
if (newDate) {
|
||||
setEndDate(newDate);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
textTransform: 'none',
|
||||
bgcolor: '#ffc04d',
|
||||
color: '#111111',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
sx={{
|
||||
marginTop: 2,
|
||||
marginLeft: 2,
|
||||
textTransform: 'none',
|
||||
bgcolor: '#ffc04d',
|
||||
color: '#111111',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Modal>
|
||||
</LocalizationProvider>
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<>
|
||||
<Grid item xs={6} md={6}>
|
||||
<IconButton
|
||||
aria-label="go back"
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
backgroundColor: 'grey',
|
||||
color: '#000000',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
onClick={handleBatteryViewButton}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButton>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSetDate}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginLeft: '20px',
|
||||
backgroundColor: dateOpen ? '#808080' : '#ffc04d',
|
||||
color: '#000000',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="set_date" defaultMessage="Set Date" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
item
|
||||
xs={6}
|
||||
md={6}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!(chartState > 0)}
|
||||
onClick={handleGoBack}
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginLeft: '10px',
|
||||
backgroundColor: '#ffc04d',
|
||||
color: '#000000',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="goback" defaultMessage="Zoom out" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!(chartState < batteryViewDataArray.length - 1)}
|
||||
onClick={handleGoForward}
|
||||
sx={{
|
||||
marginTop: '20px',
|
||||
marginLeft: '10px',
|
||||
backgroundColor: '#ffc04d',
|
||||
color: '#000000',
|
||||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
>
|
||||
<FormattedMessage id="goback" defaultMessage="Zoom in" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
{/* Battery SOC Chart */}
|
||||
<Grid item md={12} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: '20px' }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
<FormattedMessage
|
||||
id="battery_soc"
|
||||
defaultMessage="Battery SOC (State Of Charge)"
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
||||
<ReactApexChart
|
||||
options={{
|
||||
...getChartOptions(
|
||||
batteryViewDataArray[chartState].chartOverview.Soc,
|
||||
'daily',
|
||||
[],
|
||||
true
|
||||
),
|
||||
chart: {
|
||||
events: {
|
||||
beforeZoom: (chartContext, options) => {
|
||||
startZoom();
|
||||
handleBeforeZoom(chartContext, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={generateSeries(
|
||||
batteryViewDataArray[chartState].chartData,
|
||||
'Soc',
|
||||
'blue'
|
||||
)}
|
||||
type="line"
|
||||
height={420}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Battery Power Chart */}
|
||||
<Grid item md={12} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '10px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: '20px' }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
<FormattedMessage
|
||||
id="battery_power"
|
||||
defaultMessage="Battery Power"
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
||||
<ReactApexChart
|
||||
options={{
|
||||
...getChartOptions(
|
||||
batteryViewDataArray[chartState].chartOverview.Power,
|
||||
'daily',
|
||||
[],
|
||||
true
|
||||
),
|
||||
chart: {
|
||||
events: {
|
||||
beforeZoom: (chartContext, options) => {
|
||||
startZoom();
|
||||
handleBeforeZoom(chartContext, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={generateSeries(
|
||||
batteryViewDataArray[chartState].chartData,
|
||||
'Power',
|
||||
'red'
|
||||
)}
|
||||
type="line"
|
||||
height={420}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Battery Voltage Chart */}
|
||||
<Grid item md={12} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '10px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: '20px' }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
<FormattedMessage
|
||||
id="battery_voltage"
|
||||
defaultMessage="Battery Voltage"
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
||||
<ReactApexChart
|
||||
options={{
|
||||
...getChartOptions(
|
||||
batteryViewDataArray[chartState].chartOverview.Voltage,
|
||||
'daily',
|
||||
[],
|
||||
true
|
||||
),
|
||||
chart: {
|
||||
events: {
|
||||
beforeZoom: (chartContext, options) => {
|
||||
startZoom();
|
||||
handleBeforeZoom(chartContext, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={generateSeries(
|
||||
batteryViewDataArray[chartState].chartData,
|
||||
'Voltage',
|
||||
'orange'
|
||||
)}
|
||||
type="line"
|
||||
height={420}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Battery Current Chart */}
|
||||
<Grid item md={12} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '10px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: '20px' }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
<FormattedMessage
|
||||
id="battery_current"
|
||||
defaultMessage="Battery Current"
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
||||
<ReactApexChart
|
||||
options={{
|
||||
...getChartOptions(
|
||||
batteryViewDataArray[chartState].chartOverview.Current,
|
||||
'daily',
|
||||
[],
|
||||
true
|
||||
),
|
||||
chart: {
|
||||
events: {
|
||||
beforeZoom: (chartContext, options) => {
|
||||
startZoom();
|
||||
handleBeforeZoom(chartContext, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={generateSeries(
|
||||
batteryViewDataArray[chartState].chartData,
|
||||
'Current',
|
||||
'orange'
|
||||
)}
|
||||
type="line"
|
||||
height={420}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Battery SoH Chart */}
|
||||
<Grid item md={12} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '10px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: '20px' }}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
<FormattedMessage
|
||||
id="battery_soh"
|
||||
defaultMessage="Battery SOH (State Of Health)"
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
|
||||
<ReactApexChart
|
||||
options={{
|
||||
...getChartOptions(
|
||||
batteryViewDataArray[chartState].chartOverview.Soh,
|
||||
'daily',
|
||||
[],
|
||||
true
|
||||
),
|
||||
chart: {
|
||||
events: {
|
||||
beforeZoom: (chartContext, options) => {
|
||||
startZoom();
|
||||
handleBeforeZoom(chartContext, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
series={generateSeries(
|
||||
batteryViewDataArray[chartState].chartData,
|
||||
'Soh',
|
||||
'green'
|
||||
)}
|
||||
type="line"
|
||||
height={420}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainStatsSodioHome;
|
||||
|
|
@ -60,6 +60,7 @@ export interface BatteryDataInterface {
|
|||
Power: { name: string; data: [] };
|
||||
Voltage: { name: string; data: [] };
|
||||
Current: { name: string; data: [] };
|
||||
Soh?: { name: string; data: [] };
|
||||
}
|
||||
|
||||
export interface BatteryOverviewInterface {
|
||||
|
|
@ -68,6 +69,7 @@ export interface BatteryOverviewInterface {
|
|||
Power: chartInfoInterface;
|
||||
Voltage: chartInfoInterface;
|
||||
Current: chartInfoInterface;
|
||||
Soh?: chartInfoInterface;
|
||||
}
|
||||
|
||||
export const transformInputToBatteryViewDataJson = async (
|
||||
|
|
@ -75,14 +77,18 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
id: number,
|
||||
product: number,
|
||||
start_time?: UnixTime,
|
||||
end_time?: UnixTime
|
||||
end_time?: UnixTime,
|
||||
batteryClusterNumber?: number
|
||||
): Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}> => {
|
||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||
const MAX_NUMBER = 9999999;
|
||||
const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
|
||||
const isSodioHome = product === 2;
|
||||
const categories = isSodioHome
|
||||
? ['Soc', 'Power', 'Voltage', 'Current', 'Soh']
|
||||
: ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
|
||||
const pathCategories =
|
||||
product === 3
|
||||
? [
|
||||
|
|
@ -120,7 +126,8 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
Temperature: { name: 'Temperature', data: [] },
|
||||
Power: { name: 'Power', data: [] },
|
||||
Voltage: { name: 'Voltage', data: [] },
|
||||
Current: { name: 'Current', data: [] }
|
||||
Current: { name: 'Current', data: [] },
|
||||
...(isSodioHome && { Soh: { name: 'State Of Health', data: [] } })
|
||||
};
|
||||
|
||||
const chartOverview: BatteryOverviewInterface = {
|
||||
|
|
@ -128,7 +135,8 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
Temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
Power: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
Voltage: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
Current: { magnitude: 0, unit: '', min: 0, max: 0 }
|
||||
Current: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
...(isSodioHome && { Soh: { magnitude: 0, unit: '', min: 0, max: 0 } })
|
||||
};
|
||||
|
||||
let initialiation = true;
|
||||
|
|
@ -159,7 +167,7 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
);
|
||||
|
||||
const adjustedTimestamp =
|
||||
product == 0 || product == 3
|
||||
product == 0 || product == 2 || product == 3
|
||||
? new Date(timestampArray[i] * 1000)
|
||||
: new Date(timestampArray[i] * 100000);
|
||||
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||
|
|
@ -181,7 +189,78 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
];
|
||||
|
||||
const result = results[i][timestamp];
|
||||
//console.log(result);
|
||||
|
||||
if (isSodioHome) {
|
||||
// SodistoreHome: extract battery data from InverterRecord
|
||||
const inv = (result as any)?.InverterRecord;
|
||||
if (!inv) continue;
|
||||
|
||||
const numBatteries = batteryClusterNumber || 1;
|
||||
let old_length = pathsToSave.length;
|
||||
|
||||
if (numBatteries > old_length) {
|
||||
for (let b = old_length; b < numBatteries; b++) {
|
||||
const nodeName = 'Node' + b;
|
||||
if (!pathsToSave.includes(nodeName)) {
|
||||
pathsToSave.push(nodeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialiation) {
|
||||
initialiation = false;
|
||||
categories.forEach((category) => {
|
||||
chartData[category].data = [];
|
||||
chartOverview[category] = {
|
||||
magnitude: 0,
|
||||
unit: '',
|
||||
min: MAX_NUMBER,
|
||||
max: -MAX_NUMBER
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (numBatteries > old_length) {
|
||||
categories.forEach((category) => {
|
||||
pathsToSave.forEach((path) => {
|
||||
if (pathsToSave.indexOf(path) >= old_length) {
|
||||
chartData[category].data[path] = { name: path, data: [] };
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Map category names to InverterRecord field suffixes
|
||||
const categoryFieldMap = {
|
||||
Soc: 'Soc',
|
||||
Power: 'Power',
|
||||
Voltage: 'Voltage',
|
||||
Current: 'Current',
|
||||
Soh: 'Soh'
|
||||
};
|
||||
|
||||
for (let j = 0; j < pathsToSave.length; j++) {
|
||||
const batteryIndex = j + 1; // Battery1, Battery2, ...
|
||||
categories.forEach((category) => {
|
||||
const fieldName = `Battery${batteryIndex}${categoryFieldMap[category]}`;
|
||||
const value = inv[fieldName];
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
if (value < chartOverview[category].min) {
|
||||
chartOverview[category].min = value;
|
||||
}
|
||||
if (value > chartOverview[category].max) {
|
||||
chartOverview[category].max = value;
|
||||
}
|
||||
chartData[category].data[pathsToSave[j]].data.push([
|
||||
adjustedTimestampArray[i],
|
||||
value
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// SaliMax, Salidomo, SodistoreMax: existing logic
|
||||
const battery_nodes =
|
||||
result.Config.Devices.BatteryNodes.toString().split(',');
|
||||
|
||||
|
|
@ -198,8 +277,6 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
});
|
||||
}
|
||||
|
||||
// console.log(pathsToSave);
|
||||
|
||||
if (initialiation) {
|
||||
initialiation = false;
|
||||
categories.forEach((category) => {
|
||||
|
|
@ -249,11 +326,7 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
adjustedTimestampArray[i],
|
||||
value
|
||||
]);
|
||||
} else {
|
||||
// chartData[category].data[pathsToSave[j]].data.push([
|
||||
// adjustedTimestampArray[i],
|
||||
// null
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -280,16 +353,20 @@ export const transformInputToBatteryViewDataJson = async (
|
|||
chartOverview.Soc.unit = '(%)';
|
||||
chartOverview.Soc.min = 0;
|
||||
chartOverview.Soc.max = 100;
|
||||
if (!isSodioHome) {
|
||||
chartOverview.Temperature.unit = '(°C)';
|
||||
}
|
||||
chartOverview.Power.unit =
|
||||
'(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')';
|
||||
chartOverview.Voltage.unit =
|
||||
'(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')';
|
||||
|
||||
chartOverview.Current.unit =
|
||||
'(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')';
|
||||
|
||||
// console.log(chartData);
|
||||
if (isSodioHome) {
|
||||
chartOverview.Soh.unit = '(%)';
|
||||
chartOverview.Soh.min = 0;
|
||||
chartOverview.Soh.max = 100;
|
||||
}
|
||||
|
||||
return {
|
||||
chartData: chartData,
|
||||
|
|
|
|||
Loading…
Reference in New Issue