updated frontend
This commit is contained in:
parent
88b79dd398
commit
d071f81d27
|
@ -0,0 +1,10 @@
|
|||
/* Disable text selection for the entire page */
|
||||
body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Style the selected text */
|
||||
::selection {
|
||||
background-color: transparent; /* Set the background color to transparent */
|
||||
color: #000; /* Set the text color for selected text */
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
export function formatPowerForGraph(value, max): { value: number } {
|
||||
if (isNaN(value)) {
|
||||
return null;
|
||||
}
|
||||
let negative = false;
|
||||
if (max > 1000) {
|
||||
value = parseFloat(value);
|
||||
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
negative = true;
|
||||
}
|
||||
|
||||
value = value / 1000;
|
||||
|
||||
while (value >= 1000) {
|
||||
value /= 1000;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value: negative === false ? value : -value
|
||||
};
|
||||
}
|
||||
|
||||
export function findPower(value) {
|
||||
value = parseFloat(value);
|
||||
|
||||
if (value === 0) {
|
||||
return { value: 0 };
|
||||
}
|
||||
|
||||
// Determine the sign of the input value
|
||||
const sign = Math.sign(value);
|
||||
|
||||
// Take the absolute value for calculations
|
||||
value = Math.abs(value);
|
||||
|
||||
// Calculate the power of 10 that's greater or equal to the absolute value
|
||||
let exponent = Math.floor(Math.log10(value));
|
||||
|
||||
// Compute the nearest power of 10
|
||||
const nearestPowerOf10 = Math.pow(10, exponent);
|
||||
|
||||
// Restore the sign to the result
|
||||
const result = nearestPowerOf10 * sign;
|
||||
|
||||
return { value: result };
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 438 B |
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"installation": "installation/",
|
||||
"liveView": "liveView/",
|
||||
"users": "/users/",
|
||||
"log": "log/",
|
||||
"installations": "/installations/",
|
||||
"groups": "/groups/",
|
||||
"group": "group/",
|
||||
"folder": "folder/",
|
||||
"manageAccess": "manageAccess/",
|
||||
"user": "user/",
|
||||
"tree": "tree",
|
||||
"list": "list"
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import { TopologyValues } from '../Log/graph.util';
|
||||
import { Box, CardContent, Container, Grid, TextField } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
interface ConfigurationProps {
|
||||
values: TopologyValues;
|
||||
}
|
||||
|
||||
function Configuration(props: ConfigurationProps) {
|
||||
if (props.values === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<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="Minimum SoC"
|
||||
value={props.values.minimumSoC.values[0].value + ' %'}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label="Calibration Charge forced"
|
||||
value={props.values.calibrationChargeForced.values[0].value}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Grid Set Point"
|
||||
value={
|
||||
(
|
||||
(props.values.gridSetPoint.values[0].value as number) /
|
||||
1000
|
||||
).toString() + ' kW'
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Installed Power DC1010"
|
||||
value={
|
||||
(
|
||||
(props.values.installedDcDcPower.values[0]
|
||||
.value as number) * 10
|
||||
).toString() + ' kW'
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Maximum Discharge Power"
|
||||
value={
|
||||
(
|
||||
(props.values.maximumDischargePower.values[0]
|
||||
.value as number) * 48
|
||||
).toString() + ' W'
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Number of Batteries"
|
||||
value={props.values.battery.values.length - 4}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Configuration;
|
|
@ -0,0 +1,37 @@
|
|||
import { UnixTime } from 'src/dataCache/time';
|
||||
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
||||
import { FetchResult } from 'src/dataCache/dataCache';
|
||||
import { DataRecord } from 'src/dataCache/data';
|
||||
import { S3Access } from 'src/dataCache/S3/S3Access';
|
||||
import { parseCsv } from '../Log/graph.util';
|
||||
|
||||
export const fetchData = (
|
||||
timestamp: UnixTime,
|
||||
s3Credentials?: I_S3Credentials
|
||||
): Promise<FetchResult<DataRecord>> => {
|
||||
const s3Path = `${timestamp.ticks}.csv`;
|
||||
if (s3Credentials && s3Credentials.s3Bucket) {
|
||||
const s3Access = new S3Access(
|
||||
s3Credentials.s3Bucket,
|
||||
s3Credentials.s3Region,
|
||||
s3Credentials.s3Provider,
|
||||
s3Credentials.s3Key,
|
||||
s3Credentials.s3Secret
|
||||
);
|
||||
return s3Access
|
||||
.get(s3Path)
|
||||
.then(async (r) => {
|
||||
if (r.status === 404) {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
} else if (r.status === 200) {
|
||||
const text = await r.text();
|
||||
return parseCsv(text);
|
||||
} else {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return Promise.resolve(FetchResult.tryLater);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
import { Box, Grid, useTheme } from '@mui/material';
|
||||
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
||||
import InstallationSearch from './InstallationSearch';
|
||||
|
||||
function FlatView() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<InstallationsContextProvider>
|
||||
<Grid item xs={12}>
|
||||
<Box p={4}>
|
||||
<InstallationSearch />
|
||||
</Box>
|
||||
</Grid>
|
||||
</InstallationsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default FlatView;
|
|
@ -0,0 +1,37 @@
|
|||
import { UnixTime } from '../../../dataCache/time';
|
||||
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||
import { FetchResult } from '../../../dataCache/dataCache';
|
||||
import { DataRecord } from '../../../dataCache/data';
|
||||
import { S3Access } from '../../../dataCache/S3/S3Access';
|
||||
import { parseCsv } from './graph.util';
|
||||
|
||||
export const fetchData = (
|
||||
timestamp: UnixTime,
|
||||
s3Credentials?: I_S3Credentials
|
||||
): Promise<FetchResult<DataRecord>> => {
|
||||
const s3Path = `${timestamp.ticks}.csv`;
|
||||
if (s3Credentials && s3Credentials.s3Bucket) {
|
||||
const s3Access = new S3Access(
|
||||
s3Credentials.s3Bucket,
|
||||
s3Credentials.s3Region,
|
||||
s3Credentials.s3Provider,
|
||||
s3Credentials.s3Key,
|
||||
s3Credentials.s3Secret
|
||||
);
|
||||
return s3Access
|
||||
.get(s3Path)
|
||||
.then(async (r) => {
|
||||
if (r.status === 404) {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
} else if (r.status === 200) {
|
||||
const text = await r.text();
|
||||
return parseCsv(text);
|
||||
} else {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return Promise.resolve(FetchResult.tryLater);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
// chartOptions.ts
|
||||
|
||||
import { ApexOptions } from 'apexcharts';
|
||||
import { chartInfoInterface } from 'src/interfaces/Chart';
|
||||
import { findPower, formatPowerForGraph } from '../../../Resources/formatPower';
|
||||
|
||||
export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
||||
const chartOptions: ApexOptions = {
|
||||
chart: {
|
||||
id: 'area-datetime',
|
||||
type: 'area',
|
||||
height: 350,
|
||||
zoom: {
|
||||
autoScaleYaxis: false
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.9,
|
||||
stops: [0, 100]
|
||||
}
|
||||
},
|
||||
//colors: ['#FF5733', '#3498db'],
|
||||
colors: ['#3498db', '#2ecc71'],
|
||||
//colors: ['#1abc9c', '#e91e63'],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
datetimeFormatter: {
|
||||
year: 'yyyy',
|
||||
month: "MMM 'yy",
|
||||
day: 'dd MMM',
|
||||
hour: 'HH:mm'
|
||||
}
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
width: 2
|
||||
},
|
||||
yaxis: {
|
||||
min: chartInfo.min > 0 ? 0 : undefined,
|
||||
max:
|
||||
chartInfo.min > 0
|
||||
? Math.round(chartInfo.max / findPower(chartInfo.max).value) *
|
||||
findPower(chartInfo.max).value
|
||||
: undefined,
|
||||
title: {
|
||||
text: chartInfo.unit,
|
||||
style: {
|
||||
fontSize: '12px'
|
||||
},
|
||||
offsetY: -160,
|
||||
offsetX: 25,
|
||||
rotate: 0
|
||||
},
|
||||
labels: {
|
||||
formatter:
|
||||
chartInfo.min > 0
|
||||
? function (value: number) {
|
||||
return formatPowerForGraph(
|
||||
value,
|
||||
chartInfo.max
|
||||
).value.toString();
|
||||
}
|
||||
: function (value: number) {
|
||||
return formatPowerForGraph(
|
||||
value,
|
||||
chartInfo.max
|
||||
).value.toString();
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
format: 'dd MMM HH:mm'
|
||||
},
|
||||
y: {
|
||||
formatter: function (val, opts) {
|
||||
return (
|
||||
formatPowerForGraph(val, chartInfo.max).value.toFixed(2) +
|
||||
' ' +
|
||||
chartInfo.unit
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return chartOptions;
|
||||
};
|
|
@ -0,0 +1,509 @@
|
|||
import {
|
||||
Box,
|
||||
Card,
|
||||
Container,
|
||||
Grid,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import ReactApexChart from 'react-apexcharts';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import DataCache from 'src/dataCache/dataCache';
|
||||
import { TimeSpan, UnixTime } from 'src/dataCache/time';
|
||||
import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from 'rxjs';
|
||||
import { RecordSeries } from 'src/dataCache/data';
|
||||
import { createTimes } from '../Log/graph.util';
|
||||
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
||||
import { getChartOptions } from './chartOptions';
|
||||
import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart';
|
||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
||||
|
||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||
const MAX_NUMBER = 9999999;
|
||||
|
||||
interface OverviewProps {
|
||||
s3Credentials: I_S3Credentials;
|
||||
}
|
||||
|
||||
function Overview(props: OverviewProps) {
|
||||
const theme = useTheme();
|
||||
const timeRange = createTimes(
|
||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||
200
|
||||
);
|
||||
|
||||
const [chartData, setChartData] = useState<chartDataInterface>({
|
||||
soc: [],
|
||||
temperature: [],
|
||||
dcPower: [],
|
||||
gridPower: [],
|
||||
pvProduction: [],
|
||||
dcBusVoltage: []
|
||||
});
|
||||
|
||||
const [chartOverview, setChartOverview] = useState<overviewInterface>({
|
||||
soc: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }
|
||||
});
|
||||
|
||||
const pathsToSearch = [
|
||||
'/Battery/Soc',
|
||||
'/Battery/Temperature',
|
||||
'/Battery/Dc/Power',
|
||||
'/GridMeter/Ac/Power/Active',
|
||||
'/PvOnDc/Dc/Power',
|
||||
'/DcDc/Dc/Link/Voltage'
|
||||
];
|
||||
|
||||
const cache = useMemo(() => {
|
||||
return new DataCache(
|
||||
fetchData,
|
||||
TimeSpan.fromSeconds(2),
|
||||
props.s3Credentials
|
||||
);
|
||||
}, []);
|
||||
|
||||
const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
|
||||
|
||||
const transformToGraphData = (
|
||||
input: RecordSeries
|
||||
): {
|
||||
chartData: chartDataInterface;
|
||||
chartOverview: overviewInterface;
|
||||
} => {
|
||||
const data = {};
|
||||
const overviewData = {};
|
||||
|
||||
const chartData: chartDataInterface = {
|
||||
soc: [],
|
||||
temperature: [],
|
||||
dcPower: [],
|
||||
gridPower: [],
|
||||
pvProduction: [],
|
||||
dcBusVoltage: []
|
||||
};
|
||||
|
||||
const chartOverview: overviewInterface = {
|
||||
soc: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||
dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }
|
||||
};
|
||||
|
||||
pathsToSearch.forEach((path) => {
|
||||
data[path] = [];
|
||||
overviewData[path] = {
|
||||
magnitude: 0,
|
||||
unit: '',
|
||||
min: MAX_NUMBER,
|
||||
max: 0
|
||||
};
|
||||
});
|
||||
|
||||
input.forEach((item) => {
|
||||
const csvContent = item.value;
|
||||
|
||||
pathsToSearch.forEach((path) => {
|
||||
if (csvContent && csvContent[path]) {
|
||||
const timestamp = item.time.ticks * 1000;
|
||||
const value = csvContent[path];
|
||||
// const result: { magnitude: number; value: number } = formatPower(
|
||||
// value.value
|
||||
// );
|
||||
if (value.value < overviewData[path].min) {
|
||||
overviewData[path].min = value.value;
|
||||
}
|
||||
|
||||
if (value.value > overviewData[path].max) {
|
||||
overviewData[path].max = value.value;
|
||||
}
|
||||
|
||||
// if (result.magnitude > result.magnitude) {
|
||||
// overviewData[path].magnitude = result.magnitude;
|
||||
// }
|
||||
data[path].push([timestamp, value.value]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
pathsToSearch.forEach((path) => {
|
||||
let value = overviewData[path].max;
|
||||
let negative = false;
|
||||
let magnitude = 0;
|
||||
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
negative = true;
|
||||
}
|
||||
while (value >= 1000) {
|
||||
value /= 1000;
|
||||
magnitude++;
|
||||
}
|
||||
console.log(path, magnitude);
|
||||
|
||||
overviewData[path].magnitude = prefixes[magnitude];
|
||||
});
|
||||
|
||||
let path = '/Battery/Soc';
|
||||
chartData.soc = [{ name: 'State of Charge', data: data[path] }];
|
||||
|
||||
chartOverview.soc = {
|
||||
unit: '(%)',
|
||||
magnitude: overviewData[path].magnitude,
|
||||
min: 0,
|
||||
max: 100
|
||||
};
|
||||
|
||||
path = '/Battery/Temperature';
|
||||
chartData.temperature = [
|
||||
{ name: 'Battery Temperature:', data: data[path] }
|
||||
];
|
||||
|
||||
chartOverview.temperature = {
|
||||
unit: '(°C)',
|
||||
magnitude: overviewData[path].magnitude,
|
||||
min: overviewData[path].min,
|
||||
max: overviewData[path].max
|
||||
};
|
||||
|
||||
path = '/Battery/Dc/Power';
|
||||
chartData.dcPower = [{ name: 'Battery Power', data: data[path] }];
|
||||
|
||||
//console.log(overviewData[path]);
|
||||
//console.log(data[path]);
|
||||
chartOverview.dcPower = {
|
||||
magnitude: overviewData[path].magnitude,
|
||||
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||
min: overviewData[path].min,
|
||||
max: overviewData[path].max
|
||||
};
|
||||
|
||||
path = '/GridMeter/Ac/Power/Active';
|
||||
chartData.gridPower = [{ name: 'Grid Power', data: data[path] }];
|
||||
|
||||
chartOverview.gridPower = {
|
||||
magnitude: overviewData[path].magnitude,
|
||||
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||
min: overviewData[path].min,
|
||||
max: overviewData[path].max
|
||||
};
|
||||
|
||||
path = '/PvOnDc/Dc/Power';
|
||||
chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }];
|
||||
|
||||
chartOverview.pvProduction = {
|
||||
magnitude: overviewData[path].magnitude,
|
||||
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||
min: overviewData[path].min,
|
||||
max: overviewData[path].max
|
||||
};
|
||||
|
||||
path = '/DcDc/Dc/Link/Voltage';
|
||||
chartData.dcBusVoltage = [{ name: 'DC Bus Voltage', data: data[path] }];
|
||||
|
||||
chartOverview.dcBusVoltage = {
|
||||
magnitude: overviewData[path].magnitude,
|
||||
unit: '(' + overviewData[path].magnitude + 'V' + ')',
|
||||
min: overviewData[path].min,
|
||||
max: overviewData[path].max
|
||||
};
|
||||
|
||||
return {
|
||||
chartData: chartData,
|
||||
chartOverview: chartOverview
|
||||
};
|
||||
|
||||
//return chartData;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = cache.gotData
|
||||
.pipe(
|
||||
startWith(0),
|
||||
throttleTime(200, undefined, { leading: true, trailing: true }),
|
||||
withLatestFrom(times$)
|
||||
)
|
||||
.subscribe(([_, times]) => {
|
||||
const timeSeries = cache.getSeries(times);
|
||||
|
||||
console.log(timeSeries);
|
||||
|
||||
const result: {
|
||||
chartData: chartDataInterface;
|
||||
chartOverview: overviewInterface;
|
||||
} = transformToGraphData(timeSeries);
|
||||
|
||||
setChartData(result.chartData);
|
||||
setChartOverview(result.chartOverview);
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
const renderGraphs = () => {
|
||||
return (
|
||||
<Container maxWidth="xl">
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={12}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
Battery SOC (State Of Charge)
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.soc)}
|
||||
series={chartData.soc}
|
||||
type="area"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
Battery Temperature
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.temperature)}
|
||||
series={chartData.temperature}
|
||||
type="line"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
Battery Power
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.dcPower)}
|
||||
series={chartData.dcPower}
|
||||
type="area"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
Grid Power
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.gridPower)}
|
||||
series={chartData.gridPower}
|
||||
type="area"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
PV Production
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.pvProduction)}
|
||||
series={chartData.pvProduction}
|
||||
type="area"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
overflow: 'visible',
|
||||
marginTop: '30px',
|
||||
marginBottom: '30px'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box>
|
||||
<Typography variant="subtitle1" noWrap>
|
||||
DC Bus Voltage
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
pt: 3
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
<ReactApexChart
|
||||
options={getChartOptions(chartOverview.dcBusVoltage)}
|
||||
series={chartData.dcBusVoltage}
|
||||
type="line"
|
||||
height={350}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
return <>{renderGraphs()}</>;
|
||||
}
|
||||
|
||||
export default Overview;
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
.line {
|
||||
overflow: hidden;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.horizontalLine {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border-left: none;
|
||||
margin-left: 173px;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.dotRight {
|
||||
position: absolute;
|
||||
margin-left: 35px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: #f7b34d;
|
||||
animation: rightflow 2s linear infinite;
|
||||
}
|
||||
|
||||
.dotLeft {
|
||||
position: absolute;
|
||||
margin-left: 35px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: #f7b34d;
|
||||
animation: leftflow 2s linear infinite;
|
||||
}
|
||||
|
||||
.verticalDotDown {
|
||||
position: absolute;
|
||||
margin-top: 35px;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background-color: #f7b34d;
|
||||
animation: verticalDownFlow 2s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@keyframes rightflow {
|
||||
0% {
|
||||
left: -35px;
|
||||
}
|
||||
100% {
|
||||
left: 110%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes leftflow {
|
||||
0% {
|
||||
left: 110px;
|
||||
}
|
||||
100% {
|
||||
left: -35px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes verticalDownFlow {
|
||||
0% {
|
||||
top: -35px;
|
||||
}
|
||||
100% {
|
||||
top: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Typography,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import { AvatarWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||
import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60';
|
||||
import OutletIcon from '@mui/icons-material/Outlet';
|
||||
import SolarPowerIcon from '@mui/icons-material/SolarPower';
|
||||
import PowerInputIcon from '@mui/icons-material/PowerInput';
|
||||
import BoltIcon from '@mui/icons-material/Bolt';
|
||||
import { BoxData } from '../Log/graph.util';
|
||||
import inverterImage from 'src/Resources/images/inverter.png';
|
||||
import acCurrentImage from 'src/Resources/images/ac-current.png';
|
||||
import converterImage from 'src/Resources/images/converter.png';
|
||||
|
||||
export interface TopologyBoxProps {
|
||||
title: string;
|
||||
data?: BoxData;
|
||||
}
|
||||
|
||||
const isInt = (value: number) => {
|
||||
return value % 1 === 0;
|
||||
};
|
||||
|
||||
function formatPower(value) {
|
||||
if (isNaN(value)) {
|
||||
return 'Invalid';
|
||||
}
|
||||
|
||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||
let magnitude = 0;
|
||||
let negative = false;
|
||||
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
negative = true;
|
||||
}
|
||||
while (value >= 1000) {
|
||||
value /= 1000;
|
||||
magnitude++;
|
||||
}
|
||||
|
||||
const roundedValue = value.toFixed(2);
|
||||
|
||||
return negative === false
|
||||
? `${roundedValue} ${prefixes[magnitude]}`
|
||||
: `-${roundedValue} ${prefixes[magnitude]}`;
|
||||
}
|
||||
|
||||
function TopologyBox(props: TopologyBoxProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
visibility:
|
||||
props.data && props.data.values[0].value != 0 ? 'visible' : 'hidden',
|
||||
width: '104px',
|
||||
height:
|
||||
props.title === 'Battery'
|
||||
? '165px'
|
||||
: props.title === 'AC Loads' ||
|
||||
props.title === 'DC Loads' ||
|
||||
props.title === 'Pv Inverter' ||
|
||||
props.title === 'Pv DcDc'
|
||||
? '100px'
|
||||
: '150px',
|
||||
backgroundColor:
|
||||
props.title === 'Grid Bus' ||
|
||||
props.title === 'Island Bus' ||
|
||||
props.title === 'DC Link'
|
||||
? '#f7b34d'
|
||||
: 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{props.title === 'Battery' && (
|
||||
<Typography variant="h5" noWrap>
|
||||
{props.title} (x{props.data.values.length - 4})
|
||||
</Typography>
|
||||
)}
|
||||
{props.title != 'Battery' && (
|
||||
<Typography variant="h5" noWrap>
|
||||
{props.title}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{props.title === 'AC-DC' && (
|
||||
<img
|
||||
src={inverterImage}
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
color: 'orange'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.title === 'DC Link' && (
|
||||
<PowerInputIcon
|
||||
style={{
|
||||
fontSize: 40,
|
||||
color: 'black',
|
||||
backgroundColor: '#f7b34d',
|
||||
padding: '5px',
|
||||
borderTopLeftRadius: '4px',
|
||||
borderTopRightRadius: '4px'
|
||||
}}
|
||||
></PowerInputIcon>
|
||||
)}
|
||||
|
||||
{props.title === 'DC-DC' && (
|
||||
<img
|
||||
src={converterImage}
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
color: 'orange'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(props.title === 'Grid Bus' || props.title === 'Island Bus') && (
|
||||
<img
|
||||
src={acCurrentImage}
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
backgroundColor: '#f7b34d'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.title != 'AC-DC' &&
|
||||
props.title != 'DC Link' &&
|
||||
props.title != 'DC-DC' && (
|
||||
<AvatarWrapper
|
||||
sx={{
|
||||
display:
|
||||
props.title === 'Grid Bus' || props.title === 'Island Bus'
|
||||
? 'none'
|
||||
: 'flex',
|
||||
marginTop: '4px'
|
||||
}}
|
||||
>
|
||||
{(props.title === 'Pv Inverter' ||
|
||||
props.title === 'Pv DcDc') && (
|
||||
<SolarPowerIcon
|
||||
style={{
|
||||
fontSize: 30,
|
||||
color: 'orange',
|
||||
padding: '5px',
|
||||
borderTopLeftRadius: '4px',
|
||||
borderTopRightRadius: '4px'
|
||||
}}
|
||||
></SolarPowerIcon>
|
||||
)}
|
||||
|
||||
{props.title === 'Battery' && (
|
||||
<BatteryCharging60Icon
|
||||
style={{
|
||||
fontSize: 40,
|
||||
color: 'green',
|
||||
padding: '5px',
|
||||
borderTopLeftRadius: '4px',
|
||||
borderTopRightRadius: '4px'
|
||||
}}
|
||||
></BatteryCharging60Icon>
|
||||
)}
|
||||
{(props.title === 'AC Loads' || props.title === 'DC Loads') && (
|
||||
<OutletIcon
|
||||
style={{
|
||||
fontSize: 30,
|
||||
color: 'orange',
|
||||
padding: '5px',
|
||||
borderTopLeftRadius: '4px',
|
||||
borderTopRightRadius: '4px'
|
||||
}}
|
||||
></OutletIcon>
|
||||
)}
|
||||
{props.title === 'Grid' && (
|
||||
<BoltIcon
|
||||
style={{
|
||||
fontSize: 30,
|
||||
color: 'orange',
|
||||
padding: '5px',
|
||||
borderTopLeftRadius: '4px',
|
||||
borderTopRightRadius: '4px'
|
||||
}}
|
||||
></BoltIcon>
|
||||
)}
|
||||
</AvatarWrapper>
|
||||
)}
|
||||
|
||||
{props.data && <Divider sx={{ width: '150%', marginTop: '0px' }} />}
|
||||
{props.data && (
|
||||
<Box
|
||||
sx={{
|
||||
pt: 1
|
||||
}}
|
||||
>
|
||||
{props.data.values.map((boxData, index) => {
|
||||
return (
|
||||
<Typography key={index}>
|
||||
{formatPower(boxData.value)}
|
||||
{boxData.unit}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopologyBox;
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import TopologyBox, { TopologyBoxProps } from './topologyBox';
|
||||
import { Box } from '@mui/material';
|
||||
import './dotsAnimation.css';
|
||||
import TopologyFlow, { TopologyFlowProps } from './topologyFlow';
|
||||
|
||||
interface TopologyColumnProps {
|
||||
topBox?: TopologyBoxProps;
|
||||
centerBox?: TopologyBoxProps;
|
||||
bottomBox?: TopologyBoxProps;
|
||||
topConnection?: TopologyFlowProps;
|
||||
centerConnection?: TopologyFlowProps;
|
||||
bottomConnection?: TopologyFlowProps;
|
||||
isLast: boolean;
|
||||
isFirst: boolean;
|
||||
}
|
||||
|
||||
function TopologyColumn(props: TopologyColumnProps) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: props.isFirst ? '0px' : '68px'
|
||||
}}
|
||||
>
|
||||
<TopologyBox {...props.topBox}></TopologyBox>
|
||||
|
||||
{props.topBox && props.centerBox && (
|
||||
<TopologyFlow {...props.topConnection} />
|
||||
)}
|
||||
<TopologyBox {...props.centerBox}></TopologyBox>
|
||||
{!props.isLast && <TopologyFlow {...props.centerConnection} />}
|
||||
|
||||
{props.centerBox && props.bottomBox && (
|
||||
<TopologyFlow {...props.bottomConnection} />
|
||||
)}
|
||||
<TopologyBox {...props.bottomBox}></TopologyBox>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopologyColumn;
|
|
@ -0,0 +1,190 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import './dotsAnimation.css';
|
||||
import { BoxData } from '../Log/graph.util';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
export interface TopologyFlowProps {
|
||||
orientation: string;
|
||||
position?: string;
|
||||
data?: BoxData;
|
||||
amount?: number;
|
||||
showValues: boolean;
|
||||
}
|
||||
|
||||
function getRandomStyle() {
|
||||
const animationDelay = `${Math.random() * 2}s`; // Random delay between 0 and 2 seconds
|
||||
const top = `${Math.random() * 100}%`; // Random top position within the container
|
||||
return { animationDelay, top };
|
||||
}
|
||||
|
||||
function getRandomStyleVertical() {
|
||||
const animationDelay = `${Math.random() * 2}s`; // Random delay between 0 and 2 seconds
|
||||
const left = `${Math.random() * 100}%`; // Random top position within the container
|
||||
return { animationDelay, left };
|
||||
}
|
||||
|
||||
const isInt = (value: number) => {
|
||||
return value % 1 === 0;
|
||||
};
|
||||
|
||||
function formatPower(value) {
|
||||
if (isNaN(value)) {
|
||||
return 'Invalid';
|
||||
}
|
||||
|
||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||
let magnitude = 0;
|
||||
let negative = false;
|
||||
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
negative = true;
|
||||
}
|
||||
while (value >= 1000) {
|
||||
value /= 1000;
|
||||
magnitude++;
|
||||
}
|
||||
|
||||
const roundedValue = value.toFixed(2);
|
||||
|
||||
return negative === false
|
||||
? `${roundedValue} ${prefixes[magnitude]}`
|
||||
: `-${roundedValue} ${prefixes[magnitude]}`;
|
||||
}
|
||||
|
||||
function TopologyFlow(props: TopologyFlowProps) {
|
||||
const [dotStyles, setDotStyle] = useState<
|
||||
{ animationDelay: string; top: string }[]
|
||||
>([]);
|
||||
|
||||
const [dotStylesVertical, setDotStyleVertical] = useState<
|
||||
{ animationDelay: string; left: string }[]
|
||||
>([]);
|
||||
|
||||
const numOfDots = 400;
|
||||
const minNumberOfDots = 100;
|
||||
const minHeight = 2;
|
||||
const maxHeight = 70;
|
||||
const minWidth = 2;
|
||||
const maxWidth = 68;
|
||||
|
||||
const desiredNumOfDots =
|
||||
numOfDots * props.amount < minNumberOfDots
|
||||
? minNumberOfDots
|
||||
: numOfDots * props.amount > numOfDots
|
||||
? numOfDots
|
||||
: numOfDots * props.amount;
|
||||
|
||||
useEffect(() => {
|
||||
setDotStyle(
|
||||
Array.from({ length: desiredNumOfDots }, () => getRandomStyle())
|
||||
);
|
||||
|
||||
setDotStyleVertical(
|
||||
Array.from({ length: desiredNumOfDots }, () => getRandomStyleVertical())
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.data && props.orientation === 'horizontal' && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
marginLeft: '170px',
|
||||
marginTop: '2px',
|
||||
backgroundColor: 'transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{props.showValues && props.data.values[0].value != 0 && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: 'black',
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 5,
|
||||
fontSize: '0.75rem',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{formatPower(props.data.values[0].value)}
|
||||
{props.data.values[0].unit}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={props.orientation === 'vertical' ? 'line' : 'horizontalLine'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position:
|
||||
props.orientation === 'horizontal' ? 'absolute' : 'relative',
|
||||
|
||||
height:
|
||||
props.orientation === 'horizontal'
|
||||
? `${Math.min(
|
||||
Math.max(props.amount * maxHeight, minHeight),
|
||||
maxHeight
|
||||
)}px`
|
||||
: `${maxHeight}px`,
|
||||
|
||||
width:
|
||||
props.orientation === 'vertical'
|
||||
? `${Math.min(
|
||||
Math.max(props.amount * maxWidth, minWidth),
|
||||
maxWidth
|
||||
)}px`
|
||||
: `${maxWidth}px`
|
||||
}}
|
||||
>
|
||||
{props.orientation === 'horizontal' &&
|
||||
props.data &&
|
||||
props.data.values[0].value != 0 && (
|
||||
<>
|
||||
<div className="container">
|
||||
{dotStyles.map((style, index) => (
|
||||
<div
|
||||
className={
|
||||
(props.data.values[0].value as number) >= 0
|
||||
? 'dotRight'
|
||||
: 'dotLeft'
|
||||
}
|
||||
key={index}
|
||||
style={{
|
||||
animationDelay: style.animationDelay,
|
||||
top: style.top
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{props.orientation === 'vertical' &&
|
||||
props.data &&
|
||||
props.data.values[0].value != 0 && (
|
||||
<div className="dot-container">
|
||||
{dotStylesVertical.map((style, index) => (
|
||||
<div
|
||||
className="verticalDotDown"
|
||||
key={index}
|
||||
style={{
|
||||
animationDelay: style.animationDelay,
|
||||
left: style.left
|
||||
}}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopologyFlow;
|
|
@ -0,0 +1,19 @@
|
|||
import { Box, Grid, useTheme } from '@mui/material';
|
||||
import InstallationTree from './InstallationTree';
|
||||
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
||||
|
||||
function TreeView() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<InstallationsContextProvider>
|
||||
<Grid item xs={12}>
|
||||
<Box p={4}>
|
||||
<InstallationTree />
|
||||
</Box>
|
||||
</Grid>
|
||||
</InstallationsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default TreeView;
|
|
@ -0,0 +1,26 @@
|
|||
import { ApexOptions } from 'apexcharts';
|
||||
|
||||
export interface overviewInterface {
|
||||
soc: chartInfoInterface;
|
||||
temperature: chartInfoInterface;
|
||||
dcPower: chartInfoInterface;
|
||||
gridPower: chartInfoInterface;
|
||||
pvProduction: chartInfoInterface;
|
||||
dcBusVoltage: chartInfoInterface;
|
||||
}
|
||||
|
||||
export interface chartInfoInterface {
|
||||
magnitude: number;
|
||||
unit: string;
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface chartDataInterface {
|
||||
soc: ApexOptions['series'];
|
||||
temperature: ApexOptions['series'];
|
||||
dcPower: ApexOptions['series'];
|
||||
gridPower: ApexOptions['series'];
|
||||
pvProduction: ApexOptions['series'];
|
||||
dcBusVoltage: ApexOptions['series'];
|
||||
}
|
Loading…
Reference in New Issue