Updated battery view, fixed frontend routes,
This commit is contained in:
parent
aa0f33fcc6
commit
152efcdc15
|
@ -299,6 +299,7 @@ internal static class Program
|
||||||
{
|
{
|
||||||
subscribedNow = true;
|
subscribedNow = true;
|
||||||
_subscribeToQueueForTheFirstTime = true;
|
_subscribeToQueueForTheFirstTime = true;
|
||||||
|
_prevSalimaxState = currentSalimaxState.Status;
|
||||||
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
|
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,8 +128,8 @@ public partial class Battery48TlRecord
|
||||||
{
|
{
|
||||||
Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0;
|
Boolean HasBit(Int16 bit) => (_AlarmFlags & 1uL << bit) > 0;
|
||||||
|
|
||||||
if (HasBit(0) ) yield return "Tam : BMS temperature too low";
|
if (HasBit(0)) yield return "Tam : BMS temperature too low";
|
||||||
if (HasBit(2) ) yield return "TaM2 : BMS temperature too high";
|
if (HasBit(2)) yield return "TaM2 : BMS temperature too high";
|
||||||
if (HasBit(3) ) yield return "Tbm : Battery temperature too low";
|
if (HasBit(3) ) yield return "Tbm : Battery temperature too low";
|
||||||
if (HasBit(5) ) yield return "TbM2 : Battery temperature too high";
|
if (HasBit(5) ) yield return "TbM2 : Battery temperature too high";
|
||||||
if (HasBit(7) ) yield return "VBm2 : Bus voltage too low";
|
if (HasBit(7) ) yield return "VBm2 : Bus voltage too low";
|
||||||
|
|
|
@ -25,8 +25,7 @@ function App() {
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser, setUser } = context;
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { token, setNewToken, removeToken } = tokencontext;
|
const { token, setNewToken } = tokencontext;
|
||||||
const [forgotPassword, setForgotPassword] = useState(false);
|
|
||||||
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');
|
||||||
|
@ -43,14 +42,6 @@ function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onForgotPassword = () => {
|
|
||||||
setForgotPassword(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetPassword = () => {
|
|
||||||
setForgotPassword(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Loader = (Component) => (props) =>
|
const Loader = (Component) => (props) =>
|
||||||
(
|
(
|
||||||
<Suspense fallback={<SuspenseLoader />}>
|
<Suspense fallback={<SuspenseLoader />}>
|
||||||
|
@ -58,11 +49,6 @@ function App() {
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dashboards
|
|
||||||
const Installations = Loader(
|
|
||||||
lazy(() => import('src/content/dashboards/Installations/'))
|
|
||||||
);
|
|
||||||
|
|
||||||
const ResetPassword = Loader(
|
const ResetPassword = Loader(
|
||||||
lazy(() => import('src/components/ResetPassword'))
|
lazy(() => import('src/components/ResetPassword'))
|
||||||
);
|
);
|
||||||
|
@ -84,23 +70,9 @@ function App() {
|
||||||
navigate(routes.installations);
|
navigate(routes.installations);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Status
|
|
||||||
const Status404 = Loader(
|
|
||||||
lazy(() => import('src/content/pages/Status/Status404'))
|
|
||||||
);
|
|
||||||
const Status500 = Loader(
|
|
||||||
lazy(() => import('src/content/pages/Status/Status500'))
|
|
||||||
);
|
|
||||||
const StatusComingSoon = Loader(
|
|
||||||
lazy(() => import('src/content/pages/Status/ComingSoon'))
|
|
||||||
);
|
|
||||||
const StatusMaintenance = Loader(
|
|
||||||
lazy(() => import('src/content/pages/Status/Maintenance'))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
loginToResetPassword();
|
loginToResetPassword();
|
||||||
}
|
}
|
||||||
|
@ -174,7 +146,6 @@ function App() {
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={routes.users + '*'} element={<Users />} />
|
<Route path={routes.users + '*'} element={<Users />} />
|
||||||
<Route
|
<Route
|
||||||
path={'*'}
|
path={'*'}
|
||||||
|
|
|
@ -18,5 +18,7 @@
|
||||||
"information": "information",
|
"information": "information",
|
||||||
"configuration": "configuration",
|
"configuration": "configuration",
|
||||||
"login": "/login/",
|
"login": "/login/",
|
||||||
"forgotPassword": "/forgotPassword/"
|
"forgotPassword": "/forgotPassword/",
|
||||||
|
"mainstats": "mainstats",
|
||||||
|
"general": "general"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,47 @@
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
Paper,
|
Paper,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { TopologyValues } from '../Log/graph.util';
|
import { TopologyValues } from '../Log/graph.util';
|
||||||
import { Link } 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 MainStats from './MainStats';
|
||||||
|
|
||||||
interface BatteryViewProps {
|
interface BatteryViewProps {
|
||||||
values: TopologyValues;
|
values: TopologyValues;
|
||||||
|
s3Credentials: I_S3Credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BatteryView(props: BatteryViewProps) {
|
function BatteryView(props: BatteryViewProps) {
|
||||||
if (props.values === null) {
|
if (props.values === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const theme = useTheme();
|
const currentPath = useLocation();
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const navigate = useNavigate();
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||||
const currentTab = searchParams.get('tab');
|
|
||||||
|
|
||||||
const numOfBatteries = props.values.batteryView.values[0].value
|
const numOfBatteries = props.values.batteryView.values[0].value
|
||||||
.toString()
|
.toString()
|
||||||
.split(',').length;
|
.split(',').length;
|
||||||
|
|
||||||
const batteryData = [];
|
const batteryData = [];
|
||||||
let batteryId = 1;
|
let batteryId = 1;
|
||||||
|
|
||||||
// Use a for loop to generate battery data
|
// Use a for loop to generate battery data
|
||||||
for (let index = 1; index <= numOfBatteries * 7; index += 7) {
|
for (let index = 1; index <= numOfBatteries * 7; index += 7) {
|
||||||
const battery = {
|
const battery = {
|
||||||
|
@ -61,8 +71,68 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
batteryData.push(battery);
|
batteryData.push(battery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleMainStatsButton = () => {
|
||||||
|
navigate(routes.mainstats);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let path = currentPath.pathname.split('/');
|
||||||
|
|
||||||
|
setCurrentTab(path[path.length - 1]);
|
||||||
|
}, [currentPath]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<>
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid container>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path={routes.mainstats}
|
||||||
|
element={
|
||||||
|
<MainStats s3Credentials={props.s3Credentials}></MainStats>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
{currentTab === 'batteryview' && (
|
||||||
|
<Grid item xs={6} md={6}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: '#808080',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="main_stats"
|
||||||
|
defaultMessage="Battery View"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleMainStatsButton}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '20px',
|
||||||
|
// backgroundColor: mainStatsData ? '#808080' : '#ffc04d',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{currentTab === 'batteryview' && (
|
||||||
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{ marginTop: '20px', marginBottom: '20px' }}
|
||||||
|
>
|
||||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -142,7 +212,9 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
width: '10%',
|
width: '10%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
battery.AverageTemperature > 270 ? '#FF033E' : '#32CD32 '
|
battery.AverageTemperature > 270
|
||||||
|
? '#FF033E'
|
||||||
|
: '#32CD32 '
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{battery.AverageTemperature}
|
{battery.AverageTemperature}
|
||||||
|
@ -153,7 +225,8 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
width: '20%',
|
width: '20%',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
fontWeight: battery.Warnings !== '' ? 'bold' : 'inherit',
|
fontWeight:
|
||||||
|
battery.Warnings !== '' ? 'bold' : 'inherit',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
battery.Warnings === '' ? 'inherit' : '#ff9900',
|
battery.Warnings === '' ? 'inherit' : '#ff9900',
|
||||||
color: battery.Warnings != '' ? 'black' : 'inherit'
|
color: battery.Warnings != '' ? 'black' : 'inherit'
|
||||||
|
@ -165,10 +238,12 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
<Link
|
<Link
|
||||||
style={{ color: 'black' }}
|
style={{ color: 'black' }}
|
||||||
to={
|
to={
|
||||||
'?installation=' +
|
currentPath.pathname.substring(
|
||||||
installationId +
|
0,
|
||||||
'&tab=log' +
|
currentPath.pathname.lastIndexOf('/') + 1
|
||||||
'&open=warning'
|
) +
|
||||||
|
routes.log +
|
||||||
|
'?open=warning'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Multiple Warnings
|
Multiple Warnings
|
||||||
|
@ -193,10 +268,12 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
<Link
|
<Link
|
||||||
style={{ color: 'black' }}
|
style={{ color: 'black' }}
|
||||||
to={
|
to={
|
||||||
'?installation=' +
|
currentPath.pathname.substring(
|
||||||
installationId +
|
0,
|
||||||
'&tab=log' +
|
currentPath.pathname.lastIndexOf('/') + 1
|
||||||
'&open=error'
|
) +
|
||||||
|
routes.log +
|
||||||
|
'?open=error'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Multiple Alarms
|
Multiple Alarms
|
||||||
|
@ -210,6 +287,9 @@ function BatteryView(props: BatteryViewProps) {
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,772 @@
|
||||||
|
import { Box, Card, Container, Grid, Modal, 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,
|
||||||
|
transformInputToBatteryViewData
|
||||||
|
} from '../../../interfaces/Chart';
|
||||||
|
import 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';
|
||||||
|
|
||||||
|
interface MainStatsProps {
|
||||||
|
s3Credentials: I_S3Credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MainStats(props: MainStatsProps) {
|
||||||
|
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'
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const resultPromise: Promise<{
|
||||||
|
chartData: BatteryDataInterface;
|
||||||
|
chartOverview: BatteryOverviewInterface;
|
||||||
|
}> = transformInputToBatteryViewData(
|
||||||
|
props.s3Credentials,
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
||||||
|
UnixTime.now()
|
||||||
|
);
|
||||||
|
|
||||||
|
resultPromise
|
||||||
|
.then((result) => {
|
||||||
|
setBatteryViewDataArray((prevData) =>
|
||||||
|
prevData.concat({
|
||||||
|
chartData: result.chartData,
|
||||||
|
chartOverview: result.chartOverview
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function generateSeries(chartData, category, color) {
|
||||||
|
const series = [];
|
||||||
|
const pathsToSearch = [
|
||||||
|
'Battery1',
|
||||||
|
'Battery2',
|
||||||
|
'Battery3',
|
||||||
|
'Battery4',
|
||||||
|
'Battery5',
|
||||||
|
'Battery6',
|
||||||
|
'Battery7',
|
||||||
|
'Battery8',
|
||||||
|
'Battery9',
|
||||||
|
'Battery10'
|
||||||
|
];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
// Assuming the chartData.Soc.data structure
|
||||||
|
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]
|
||||||
|
: 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('Εnd date must precede start date');
|
||||||
|
setErrorDateModalOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const resultPromise: Promise<{
|
||||||
|
chartData: BatteryDataInterface;
|
||||||
|
chartOverview: BatteryOverviewInterface;
|
||||||
|
}> = transformInputToBatteryViewData(
|
||||||
|
props.s3Credentials,
|
||||||
|
UnixTime.fromTicks(startDate.unix()),
|
||||||
|
UnixTime.fromTicks(endDate.unix())
|
||||||
|
);
|
||||||
|
|
||||||
|
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 handleBeforeZoom = (chartContext, { xaxis }) => {
|
||||||
|
const startX = parseInt(xaxis.min) / 1000;
|
||||||
|
const endX = parseInt(xaxis.max) / 1000;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
const resultPromise: Promise<{
|
||||||
|
chartData: BatteryDataInterface;
|
||||||
|
chartOverview: BatteryOverviewInterface;
|
||||||
|
}> = transformInputToBatteryViewData(
|
||||||
|
props.s3Credentials,
|
||||||
|
UnixTime.fromTicks(startX),
|
||||||
|
UnixTime.fromTicks(endX)
|
||||||
|
);
|
||||||
|
|
||||||
|
resultPromise
|
||||||
|
.then((result) => {
|
||||||
|
setBatteryViewDataArray((prevData) =>
|
||||||
|
prevData.concat({
|
||||||
|
chartData: result.chartData,
|
||||||
|
chartOverview: result.chartOverview
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(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) => setStartDate(newDate)}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DateTimePicker
|
||||||
|
label="Select End Date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(newDate) => setEndDate(newDate)}
|
||||||
|
sx={{
|
||||||
|
marginTop: 2
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleBatteryViewButton}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="main_stats" defaultMessage="Battery View" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '20px',
|
||||||
|
backgroundColor: '#808080',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="main_stats" defaultMessage="Main Stats" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<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}
|
||||||
|
>
|
||||||
|
<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: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={generateSeries(
|
||||||
|
batteryViewDataArray[chartState].chartData,
|
||||||
|
'Soc',
|
||||||
|
'blue'
|
||||||
|
)}
|
||||||
|
type="line"
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<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_soc"
|
||||||
|
defaultMessage="Battery Temperature"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
pt: 3
|
||||||
|
}}
|
||||||
|
></Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
...getChartOptions(
|
||||||
|
batteryViewDataArray[chartState].chartOverview
|
||||||
|
.Temperature,
|
||||||
|
'daily',
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
),
|
||||||
|
chart: {
|
||||||
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={generateSeries(
|
||||||
|
batteryViewDataArray[chartState].chartData,
|
||||||
|
'Temperature',
|
||||||
|
'blue'
|
||||||
|
)}
|
||||||
|
type="line"
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<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: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={generateSeries(
|
||||||
|
batteryViewDataArray[chartState].chartData,
|
||||||
|
'Power',
|
||||||
|
'red'
|
||||||
|
)}
|
||||||
|
type="line"
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<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: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={generateSeries(
|
||||||
|
batteryViewDataArray[chartState].chartData,
|
||||||
|
'Voltage',
|
||||||
|
'orange'
|
||||||
|
)}
|
||||||
|
type="line"
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<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: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={generateSeries(
|
||||||
|
batteryViewDataArray[chartState].chartData,
|
||||||
|
'Current',
|
||||||
|
'orange'
|
||||||
|
)}
|
||||||
|
type="line"
|
||||||
|
height={420}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainStats;
|
|
@ -0,0 +1,421 @@
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Box,
|
||||||
|
CardContent,
|
||||||
|
CircularProgress,
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { I_S3Credentials } from '../../../interfaces/S3Types';
|
||||||
|
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
|
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||||
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
|
|
||||||
|
interface InformationProps {
|
||||||
|
values: I_Installation;
|
||||||
|
s3Credentials: I_S3Credentials;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Information(props: InformationProps) {
|
||||||
|
if (props.values === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser } = context;
|
||||||
|
const theme = useTheme();
|
||||||
|
const [formValues, setFormValues] = useState(props.values);
|
||||||
|
const requiredFields = ['name', 'region', 'location', 'country'];
|
||||||
|
const installationContext = useContext(InstallationsContext);
|
||||||
|
const {
|
||||||
|
updateInstallation,
|
||||||
|
loading,
|
||||||
|
setLoading,
|
||||||
|
error,
|
||||||
|
setError,
|
||||||
|
updated,
|
||||||
|
setUpdated,
|
||||||
|
deleteInstallation
|
||||||
|
} = installationContext;
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
[name]: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleSubmit = () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
updateInstallation(formValues, props.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const areRequiredFieldsFilled = () => {
|
||||||
|
for (const field of requiredFields) {
|
||||||
|
if (!formValues[field]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
setOpenModalDeleteInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInstallationModalHandle = () => {
|
||||||
|
setOpenModalDeleteInstallation(false);
|
||||||
|
deleteInstallation(formValues, props.type);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInstallationModalHandleCancel = () => {
|
||||||
|
setOpenModalDeleteInstallation(false);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{openModalDeleteInstallation && (
|
||||||
|
<Modal
|
||||||
|
open={openModalDeleteInstallation}
|
||||||
|
onClose={deleteInstallationModalHandleCancel}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 350,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Do you want to delete this installation?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteInstallationModalHandle}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
marginLeft: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteInstallationModalHandleCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="stretch"
|
||||||
|
spacing={3}
|
||||||
|
>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
<CardContent>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="customerName"
|
||||||
|
defaultMessage="Customer Name"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="name"
|
||||||
|
value={formValues.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.name === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage id="region" defaultMessage="Region" />
|
||||||
|
}
|
||||||
|
name="region"
|
||||||
|
value={formValues.region}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.region === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="location"
|
||||||
|
defaultMessage="Location"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="location"
|
||||||
|
value={formValues.location}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.location === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage id="country" defaultMessage="Country" />
|
||||||
|
}
|
||||||
|
name="country"
|
||||||
|
value={formValues.country}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.country === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="orderNumbers"
|
||||||
|
defaultMessage="Order Numbers"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="orderNumbers"
|
||||||
|
value={formValues.orderNumbers}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="installation_name"
|
||||||
|
defaultMessage="Installation Name"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="installationName"
|
||||||
|
value={formValues.installationName}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="Vpn IP"
|
||||||
|
name="VpnIp"
|
||||||
|
value={formValues.vpnIp}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="S3 Write Key"
|
||||||
|
name="s3writekey"
|
||||||
|
value={formValues.s3WriteKey}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="S3 Write Secret Key"
|
||||||
|
name="s3writesecretkey"
|
||||||
|
value={formValues.s3WriteSecret}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="S3 Bucket Name"
|
||||||
|
name="s3writesecretkey"
|
||||||
|
value={
|
||||||
|
formValues.id +
|
||||||
|
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleDelete}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallation"
|
||||||
|
defaultMessage="Delete Installation"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
disabled={!areRequiredFieldsFilled()}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="applyChanges"
|
||||||
|
defaultMessage="Apply Changes"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<CircularProgress
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
marginLeft: '20px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="errorOccured"
|
||||||
|
defaultMessage="An error has occurred"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setError(false)}
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{updated && (
|
||||||
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="successfullyUpdated"
|
||||||
|
defaultMessage="Successfully updated"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)} // Set error state to false on click
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Information;
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
@ -13,11 +13,11 @@ import {
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import Installation from './Installation';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
|
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
|
||||||
interface FlatInstallationViewProps {
|
interface FlatInstallationViewProps {
|
||||||
installations: I_Installation[];
|
installations: I_Installation[];
|
||||||
|
@ -28,31 +28,32 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
const webSocketContext = useContext(WebSocketContext);
|
const webSocketContext = useContext(WebSocketContext);
|
||||||
const { getStatus } = webSocketContext;
|
const { getStatus } = webSocketContext;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
|
||||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||||
|
const currentLocation = useLocation();
|
||||||
|
|
||||||
const handleSelectOneInstallation = (installationID: number): void => {
|
const handleSelectOneInstallation = (installationID: number): void => {
|
||||||
if (selectedInstallation != installationID) {
|
if (selectedInstallation != installationID) {
|
||||||
setSelectedInstallation(installationID);
|
setSelectedInstallation(installationID);
|
||||||
navigate(`?installation=${installationID}`, {
|
setSelectedInstallation(-1);
|
||||||
|
|
||||||
|
navigate(
|
||||||
|
routes.installations +
|
||||||
|
routes.list +
|
||||||
|
routes.installation +
|
||||||
|
`${installationID}` +
|
||||||
|
'/' +
|
||||||
|
routes.live,
|
||||||
|
{
|
||||||
replace: true
|
replace: true
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setSelectedInstallation(-1);
|
setSelectedInstallation(-1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedInstallation(installationId);
|
|
||||||
}, [installationId]);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const findInstallation = (id: number) => {
|
|
||||||
return props.installations.find((installation) => installation.id === id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowMouseEnter = (id: number) => {
|
const handleRowMouseEnter = (id: number) => {
|
||||||
setHoveredRow(id);
|
setHoveredRow(id);
|
||||||
};
|
};
|
||||||
|
@ -63,7 +64,16 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||||
<Grid item sx={{ display: !installationId ? 'block' : 'none' }}>
|
<Grid
|
||||||
|
item
|
||||||
|
sx={{
|
||||||
|
display:
|
||||||
|
currentLocation.pathname === routes.installations + 'list' ||
|
||||||
|
currentLocation.pathname === routes.installations + routes.list
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
|
@ -225,14 +235,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{props.installations.map((installation) => (
|
|
||||||
<Installation
|
|
||||||
key={installation.id}
|
|
||||||
current_installation={findInstallation(installation.id)}
|
|
||||||
type="installation"
|
|
||||||
></Installation>
|
|
||||||
))}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,7 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CircularProgress,
|
|
||||||
Container,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
|
||||||
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
||||||
import Access from '../ManageAccess/Access';
|
import Access from '../ManageAccess/Access';
|
||||||
import Log from 'src/content/dashboards/Log/Log';
|
import Log from 'src/content/dashboards/Log/Log';
|
||||||
|
@ -36,6 +19,9 @@ import Configuration from '../Configuration/Configuration';
|
||||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import BatteryView from '../BatteryView/BatteryView';
|
import BatteryView from '../BatteryView/BatteryView';
|
||||||
|
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import Information from '../Information/Information';
|
||||||
|
|
||||||
interface singleInstallationProps {
|
interface singleInstallationProps {
|
||||||
current_installation?: I_Installation;
|
current_installation?: I_Installation;
|
||||||
|
@ -43,80 +29,20 @@ interface singleInstallationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Installation(props: singleInstallationProps) {
|
function Installation(props: singleInstallationProps) {
|
||||||
const theme = useTheme();
|
|
||||||
const [formValues, setFormValues] = useState(props.current_installation);
|
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser } = context;
|
||||||
const tokencontext = useContext(TokenContext);
|
const location = useLocation();
|
||||||
const { removeToken } = tokencontext;
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
|
||||||
const {
|
|
||||||
updateInstallation,
|
|
||||||
loading,
|
|
||||||
setLoading,
|
|
||||||
error,
|
|
||||||
setError,
|
|
||||||
updated,
|
|
||||||
setUpdated,
|
|
||||||
deleteInstallation
|
|
||||||
} = installationContext;
|
|
||||||
|
|
||||||
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
||||||
const webSocketsContext = useContext(WebSocketContext);
|
const webSocketsContext = useContext(WebSocketContext);
|
||||||
const { getStatus } = webSocketsContext;
|
const { getStatus } = webSocketsContext;
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
|
||||||
const currentTab = searchParams.get('tab');
|
|
||||||
const [values, setValues] = useState<TopologyValues | null>(null);
|
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||||
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
|
|
||||||
useState(false);
|
|
||||||
|
|
||||||
const status = getStatus(props.current_installation.id);
|
const status = getStatus(props.current_installation.id);
|
||||||
|
|
||||||
if (formValues == undefined) {
|
if (props.current_installation == undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
updateInstallation(formValues, props.type);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
setOpenModalDeleteInstallation(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandle = (e) => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
deleteInstallation(formValues, props.type);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteInstallationModalHandleCancel = (e) => {
|
|
||||||
setOpenModalDeleteInstallation(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const S3data = {
|
const S3data = {
|
||||||
s3Region: props.current_installation.s3Region,
|
s3Region: props.current_installation.s3Region,
|
||||||
s3Provider: props.current_installation.s3Provider,
|
s3Provider: props.current_installation.s3Provider,
|
||||||
|
@ -132,15 +58,10 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
const fetchDataPeriodically = async () => {
|
||||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
||||||
const date = now.toDate();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetchData(now, s3Credentials);
|
const res = await fetchData(now, s3Credentials);
|
||||||
|
|
||||||
// if (!isMounted) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
||||||
setValues(
|
setValues(
|
||||||
extractValues({
|
extractValues({
|
||||||
|
@ -166,18 +87,22 @@ function Installation(props: singleInstallationProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let path = location.pathname.split('/');
|
||||||
|
|
||||||
|
setCurrentTab(path[path.length - 1]);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
installationId == props.current_installation.id &&
|
currentTab == 'live' ||
|
||||||
(currentTab == 'live' ||
|
|
||||||
currentTab == 'configuration' ||
|
currentTab == 'configuration' ||
|
||||||
currentTab == 'batteryview')
|
currentTab == 'batteryview'
|
||||||
) {
|
) {
|
||||||
//let isMounted = true;
|
|
||||||
setFormValues(props.current_installation);
|
|
||||||
var interval;
|
var interval;
|
||||||
|
|
||||||
if (currentTab == 'live' || currentTab == 'batteryview') {
|
if (currentTab == 'live' || currentTab == 'batteryview') {
|
||||||
|
fetchDataPeriodically();
|
||||||
interval = setInterval(fetchDataPeriodically, 2000);
|
interval = setInterval(fetchDataPeriodically, 2000);
|
||||||
}
|
}
|
||||||
if (currentTab == 'configuration') {
|
if (currentTab == 'configuration') {
|
||||||
|
@ -186,85 +111,15 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
// Cleanup function to cancel interval and update isMounted when unmounted
|
// Cleanup function to cancel interval and update isMounted when unmounted
|
||||||
return () => {
|
return () => {
|
||||||
//isMounted = false;
|
|
||||||
if (currentTab == 'live' || currentTab == 'batteryview') {
|
if (currentTab == 'live' || currentTab == 'batteryview') {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [installationId, currentTab]);
|
}, [currentTab, location.pathname]);
|
||||||
|
|
||||||
if (installationId == props.current_installation.id) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{openModalDeleteInstallation && (
|
|
||||||
<Modal
|
|
||||||
open={openModalDeleteInstallation}
|
|
||||||
onClose={deleteInstallationModalHandleCancel}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 350,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Do you want to delete this installation?
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandle}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
marginLeft: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteInstallationModalHandleCancel}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<Typography
|
<Typography
|
||||||
|
@ -397,304 +252,83 @@ function Installation(props: singleInstallationProps) {
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
spacing={0}
|
spacing={0}
|
||||||
>
|
>
|
||||||
{currentTab === 'information' && (
|
<Routes>
|
||||||
<Container maxWidth="xl">
|
<Route
|
||||||
<Grid
|
path={routes.information}
|
||||||
container
|
element={
|
||||||
direction="row"
|
<Information
|
||||||
justifyContent="center"
|
values={props.current_installation}
|
||||||
alignItems="stretch"
|
s3Credentials={s3Credentials}
|
||||||
spacing={3}
|
type={props.type}
|
||||||
>
|
></Information>
|
||||||
<Grid item xs={12} md={12}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="customerName"
|
|
||||||
defaultMessage="Customer Name"
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="region"
|
|
||||||
defaultMessage="Region"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="location"
|
|
||||||
defaultMessage="Location"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="country"
|
|
||||||
defaultMessage="Country"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="orderNumbers"
|
|
||||||
defaultMessage="Order Numbers"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="orderNumbers"
|
|
||||||
value={formValues.orderNumbers}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="installation_name"
|
|
||||||
defaultMessage="Installation Name"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="installationName"
|
|
||||||
value={formValues.installationName}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="Vpn IP"
|
|
||||||
name="VpnIp"
|
|
||||||
value={formValues.vpnIp}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="S3 Write Key"
|
|
||||||
name="s3writekey"
|
|
||||||
value={formValues.s3WriteKey}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="S3 Write Secret Key"
|
|
||||||
name="s3writesecretkey"
|
|
||||||
value={formValues.s3WriteSecret}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label="S3 Bucket Name"
|
|
||||||
name="s3writesecretkey"
|
|
||||||
value={
|
|
||||||
formValues.id +
|
|
||||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
|
||||||
}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleDelete}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="deleteInstallation"
|
|
||||||
defaultMessage="Delete Installation"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleSubmit}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="applyChanges"
|
|
||||||
defaultMessage="Apply Changes"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occurred"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)}
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{updated && (
|
|
||||||
<Alert
|
|
||||||
severity="success"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="successfullyUpdated"
|
|
||||||
defaultMessage="Successfully updated"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
<Route
|
||||||
color="inherit"
|
path={routes.batteryview + '*'}
|
||||||
size="small"
|
element={
|
||||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
<BatteryView
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
{currentTab === 'overview' && (
|
|
||||||
<Overview s3Credentials={s3Credentials}></Overview>
|
|
||||||
)}
|
|
||||||
{currentTab === 'batteryview' && (
|
|
||||||
<BatteryView values={values}></BatteryView>
|
|
||||||
)}
|
|
||||||
{currentTab === 'configuration' && currentUser.hasWriteAccess && (
|
|
||||||
<Configuration
|
|
||||||
values={values}
|
values={values}
|
||||||
id={installationId}
|
s3Credentials={s3Credentials}
|
||||||
></Configuration>
|
></BatteryView>
|
||||||
)}
|
}
|
||||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
/>
|
||||||
<AccessContextProvider>
|
|
||||||
<Access
|
<Route
|
||||||
currentResource={formValues}
|
path={routes.overview}
|
||||||
resourceType={props.type}
|
element={<Overview s3Credentials={s3Credentials}></Overview>}
|
||||||
></Access>
|
/>
|
||||||
</AccessContextProvider>
|
|
||||||
)}
|
<Route
|
||||||
{currentTab === 'live' && <Topology values={values}></Topology>}
|
path={routes.live}
|
||||||
{currentTab === 'log' && (
|
element={<Topology values={values}></Topology>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={routes.log}
|
||||||
|
element={
|
||||||
<Log
|
<Log
|
||||||
errorLoadingS3Data={errorLoadingS3Data}
|
errorLoadingS3Data={errorLoadingS3Data}
|
||||||
id={props.current_installation.id}
|
id={props.current_installation.id}
|
||||||
></Log>
|
></Log>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Route
|
||||||
|
path={routes.configuration}
|
||||||
|
element={
|
||||||
|
<Configuration
|
||||||
|
values={values}
|
||||||
|
id={props.current_installation.id}
|
||||||
|
></Configuration>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Route
|
||||||
|
path={routes.manage}
|
||||||
|
element={
|
||||||
|
<AccessContextProvider>
|
||||||
|
<Access
|
||||||
|
currentResource={props.current_installation}
|
||||||
|
resourceType={props.type}
|
||||||
|
></Access>
|
||||||
|
</AccessContextProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={'*'}
|
||||||
|
element={<Navigate to={routes.live}></Navigate>}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Installation;
|
export default Installation;
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
|
||||||
FormControl,
|
|
||||||
Grid,
|
|
||||||
InputAdornment,
|
|
||||||
TextField,
|
|
||||||
useTheme
|
|
||||||
} 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';
|
||||||
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
|
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import Installation from './Installation';
|
||||||
|
|
||||||
interface installationSearchProps {
|
interface installationSearchProps {
|
||||||
installations: I_Installation[];
|
installations: I_Installation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function InstallationSearch(props: installationSearchProps) {
|
function InstallationSearch(props: installationSearchProps) {
|
||||||
const theme = useTheme();
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const currentLocation = useLocation();
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState(props.installations);
|
const [filteredData, setFilteredData] = useState(props.installations);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -38,7 +32,13 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
item
|
item
|
||||||
xs={12}
|
xs={12}
|
||||||
md={6}
|
md={6}
|
||||||
sx={{ display: !installationId ? 'block' : 'none' }}
|
sx={{
|
||||||
|
display:
|
||||||
|
currentLocation.pathname === routes.installations + 'list' ||
|
||||||
|
currentLocation.pathname === routes.installations + routes.list
|
||||||
|
? 'block'
|
||||||
|
: 'none'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FormControl variant="outlined">
|
<FormControl variant="outlined">
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -59,6 +59,23 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<FlatInstallationView installations={filteredData} />
|
<FlatInstallationView installations={filteredData} />
|
||||||
|
<Routes>
|
||||||
|
{filteredData.map((installation) => {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={installation.id}
|
||||||
|
path={routes.installation + installation.id + '*'}
|
||||||
|
element={
|
||||||
|
<Installation
|
||||||
|
key={installation.id}
|
||||||
|
current_installation={installation}
|
||||||
|
type="installation"
|
||||||
|
></Installation>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
||||||
import Footer from 'src/components/Footer';
|
import Footer from 'src/components/Footer';
|
||||||
import { Box, Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material';
|
import { Box, Card, Container, Grid, Tab, Tabs } from '@mui/material';
|
||||||
import ListIcon from '@mui/icons-material/List';
|
import ListIcon from '@mui/icons-material/List';
|
||||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import {
|
import { Link, Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
Link,
|
|
||||||
Route,
|
|
||||||
Routes,
|
|
||||||
useLocation,
|
|
||||||
useNavigate
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import TreeView from '../Tree/treeView';
|
import TreeView from '../Tree/treeView';
|
||||||
import routes from 'src/Resources/routes.json';
|
import routes from 'src/Resources/routes.json';
|
||||||
import InstallationSearch from './InstallationSearch';
|
import InstallationSearch from './InstallationSearch';
|
||||||
|
@ -21,23 +15,42 @@ import Installation from './Installation';
|
||||||
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
|
import { WebSocketContext } from '../../../contexts/WebSocketContextProvider';
|
||||||
|
|
||||||
function InstallationTabs() {
|
function InstallationTabs() {
|
||||||
const theme = useTheme();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
|
||||||
//const currentTab = searchParams.get('tab');
|
|
||||||
const [singleInstallationID, setSingleInstallationID] = useState(-1);
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser } = context;
|
||||||
const [currentTab, setCurrentTab] = useState<string>(searchParams.get('tab'));
|
const tabList = [
|
||||||
|
'live',
|
||||||
|
'overview',
|
||||||
|
'manage',
|
||||||
|
'batteryview',
|
||||||
|
'log',
|
||||||
|
'information',
|
||||||
|
'configuration'
|
||||||
|
];
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||||
const { installations, fetchAllInstallations } =
|
const { installations, fetchAllInstallations } =
|
||||||
useContext(InstallationsContext);
|
useContext(InstallationsContext);
|
||||||
|
|
||||||
const webSocketsContext = useContext(WebSocketContext);
|
const webSocketsContext = useContext(WebSocketContext);
|
||||||
const { socket, openSocket } = webSocketsContext;
|
const { socket, openSocket } = webSocketsContext;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let path = location.pathname.split('/');
|
||||||
|
|
||||||
|
if (path[path.length - 2] === 'list') {
|
||||||
|
setCurrentTab('list');
|
||||||
|
} else if (path[path.length - 2] === 'tree') {
|
||||||
|
setCurrentTab('tree');
|
||||||
|
} else {
|
||||||
|
setCurrentTab(
|
||||||
|
tabList.includes(path[path.length - 1])
|
||||||
|
? path[path.length - 1]
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket && installations.length > 0) {
|
if (!socket && installations.length > 0) {
|
||||||
openSocket(installations);
|
openSocket(installations);
|
||||||
|
@ -48,51 +61,7 @@ function InstallationTabs() {
|
||||||
if (installations.length === 0) {
|
if (installations.length === 0) {
|
||||||
fetchAllInstallations();
|
fetchAllInstallations();
|
||||||
}
|
}
|
||||||
|
}, [installations]);
|
||||||
if (installations.length === 1) {
|
|
||||||
if (!currentTab) {
|
|
||||||
navigate(`?installation=${installations[0].id}&tab=live`, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
setCurrentTab('live');
|
|
||||||
} else {
|
|
||||||
navigate(`?installation=${installations[0].id}&tab=${currentTab}`, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (installations.length > 1) {
|
|
||||||
if (
|
|
||||||
location.pathname === '/installations' ||
|
|
||||||
location.pathname === '/installations/'
|
|
||||||
) {
|
|
||||||
navigate(routes.installations + routes.list, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
location.pathname === '/installations/tree/' &&
|
|
||||||
!installationId
|
|
||||||
) {
|
|
||||||
setCurrentTab('tree');
|
|
||||||
} else if (
|
|
||||||
location.pathname === '/installations/list/' &&
|
|
||||||
!installationId
|
|
||||||
) {
|
|
||||||
setCurrentTab('list');
|
|
||||||
}
|
|
||||||
if (installationId) {
|
|
||||||
if (currentTab == 'list' || currentTab == 'tree') {
|
|
||||||
navigate(`?installation=${installationId}&tab=live`, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
setCurrentTab('live');
|
|
||||||
} else {
|
|
||||||
navigate(`?installation=${installationId}&tab=${currentTab}`, {
|
|
||||||
replace: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [location.pathname, navigate, installationId, installations]);
|
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
|
@ -164,7 +133,10 @@ function InstallationTabs() {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const tabs = installationId
|
const tabs =
|
||||||
|
currentTab != 'list' &&
|
||||||
|
currentTab != 'tree' &&
|
||||||
|
!location.pathname.includes('folder')
|
||||||
? currentUser.hasWriteAccess
|
? currentUser.hasWriteAccess
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
@ -181,7 +153,9 @@ function InstallationTabs() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
label: (
|
||||||
|
<FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'batteryview',
|
value: 'batteryview',
|
||||||
|
@ -208,7 +182,10 @@ function InstallationTabs() {
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="information" defaultMessage="Information" />
|
<FormattedMessage
|
||||||
|
id="information"
|
||||||
|
defaultMessage="Information"
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -238,7 +215,9 @@ function InstallationTabs() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'overview',
|
value: 'overview',
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
label: (
|
||||||
|
<FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'batteryview',
|
value: 'batteryview',
|
||||||
|
@ -256,7 +235,10 @@ function InstallationTabs() {
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage id="information" defaultMessage="Information" />
|
<FormattedMessage
|
||||||
|
id="information"
|
||||||
|
defaultMessage="Information"
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -293,7 +275,10 @@ function InstallationTabs() {
|
||||||
to={
|
to={
|
||||||
tab.value === 'list' || tab.value === 'tree'
|
tab.value === 'list' || tab.value === 'tree'
|
||||||
? routes[tab.value]
|
? routes[tab.value]
|
||||||
: `?installation=${installationId}&tab=${routes[tab.value]}`
|
: location.pathname.substring(
|
||||||
|
0,
|
||||||
|
location.pathname.lastIndexOf('/') + 1
|
||||||
|
) + routes[tab.value]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -319,6 +304,12 @@ function InstallationTabs() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path={routes.tree + '*'} element={<TreeView />} />
|
<Route path={routes.tree + '*'} element={<TreeView />} />
|
||||||
|
<Route
|
||||||
|
path={'*'}
|
||||||
|
element={
|
||||||
|
<Navigate to={routes.installations + routes.list}></Navigate>
|
||||||
|
}
|
||||||
|
></Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -343,9 +334,12 @@ function InstallationTabs() {
|
||||||
value={tab.value}
|
value={tab.value}
|
||||||
component={Link}
|
component={Link}
|
||||||
label={tab.label}
|
label={tab.label}
|
||||||
to={`?installation=${installations[0].id}&tab=${
|
to={
|
||||||
routes[tab.value]
|
location.pathname.substring(
|
||||||
}`}
|
0,
|
||||||
|
location.pathname.lastIndexOf('/') + 1
|
||||||
|
) + routes[tab.value]
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { DataPoint, DataRecord } from 'src/dataCache/data';
|
import { DataPoint, DataRecord } from 'src/dataCache/data';
|
||||||
import { TimeRange, UnixTime } from 'src/dataCache/time';
|
|
||||||
|
|
||||||
export interface I_CsvEntry {
|
export interface I_CsvEntry {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
|
@ -100,9 +99,9 @@ export const topologyPaths: TopologyPaths = {
|
||||||
gridToAcInConnection: ['/GridMeter/Ac/Power/Active'],
|
gridToAcInConnection: ['/GridMeter/Ac/Power/Active'],
|
||||||
|
|
||||||
gridBus: [
|
gridBus: [
|
||||||
'/GridMeter/Ac/L1/Power/Active',
|
'/GridMeter/Ac/L1/Voltage',
|
||||||
'/GridMeter/Ac/L2/Power/Active',
|
'/GridMeter/Ac/L2/Voltage',
|
||||||
'/GridMeter/Ac/L3/Power/Active'
|
'/GridMeter/Ac/L3/Voltage'
|
||||||
],
|
],
|
||||||
gridBusToPvOnGridbusConnection: ['/PvOnAcGrid/Power/Active'],
|
gridBusToPvOnGridbusConnection: ['/PvOnAcGrid/Power/Active'],
|
||||||
|
|
||||||
|
@ -110,19 +109,19 @@ export const topologyPaths: TopologyPaths = {
|
||||||
gridBusToIslandBusConnection: ['/AcGridToAcIsland/Power/Active'],
|
gridBusToIslandBusConnection: ['/AcGridToAcIsland/Power/Active'],
|
||||||
|
|
||||||
islandBus: [
|
islandBus: [
|
||||||
'/AcDc/Ac/L1/Power/Active',
|
'/AcDc/Ac/L1/Voltage',
|
||||||
'/AcDc/Ac/L2/Power/Active',
|
'/AcDc/Ac/L2/Voltage',
|
||||||
'/AcDc/Ac/L3/Power/Active'
|
'/AcDc/Ac/L3/Voltage'
|
||||||
],
|
],
|
||||||
islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Ac/Power/Active'],
|
islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Ac/Power/Active'],
|
||||||
islandBusToInverter: ['/AcDc/Dc/Power'],
|
islandBusToInverter: ['/AcDc/Dc/Power'],
|
||||||
pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
|
pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
|
||||||
|
|
||||||
inverter: [
|
inverter: [
|
||||||
'/AcDc/Devices/1/Status/Ac/Power/Active',
|
'/AcDc/Ac/L1/Power/Active',
|
||||||
'/AcDc/Devices/2/Status/Ac/Power/Active',
|
'/AcDc/Ac/L2/Power/Active',
|
||||||
'/AcDc/Devices/3/Status/Ac/Power/Active',
|
'/AcDc/Ac/L3/Power/Active',
|
||||||
'/AcDc/Devices/4/Status/Ac/Power/Active'
|
'/AcDc/Ac/L4/Power/Active'
|
||||||
],
|
],
|
||||||
inverterToDcBus: ['/AcDcToDcLink/Power'],
|
inverterToDcBus: ['/AcDcToDcLink/Power'],
|
||||||
|
|
||||||
|
@ -256,7 +255,9 @@ export const extractValues = (
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (timeSeriesData.value.hasOwnProperty(path)) {
|
if (timeSeriesData.value.hasOwnProperty(path)) {
|
||||||
topologyValues.push({
|
topologyValues.push({
|
||||||
unit: timeSeriesData.value[path].unit,
|
unit: timeSeriesData.value[path].unit.includes('~')
|
||||||
|
? timeSeriesData.value[path].unit.replace('~', '')
|
||||||
|
: timeSeriesData.value[path].unit,
|
||||||
value: timeSeriesData.value[path].value
|
value: timeSeriesData.value[path].value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -302,19 +303,3 @@ export const getAmount = (
|
||||||
(Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
|
(Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTimes = (
|
|
||||||
range: TimeRange,
|
|
||||||
numberOfNodes: number
|
|
||||||
): UnixTime[] => {
|
|
||||||
const oneSpan = range.duration.divide(numberOfNodes);
|
|
||||||
//console.log(oneSpan);
|
|
||||||
|
|
||||||
const roundedRange = TimeRange.fromTimes(
|
|
||||||
range.start.round(oneSpan),
|
|
||||||
range.end.round(oneSpan)
|
|
||||||
);
|
|
||||||
|
|
||||||
const unixTimes = range.sample(oneSpan);
|
|
||||||
return unixTimes;
|
|
||||||
};
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ export const getChartOptions = (
|
||||||
style: {
|
style: {
|
||||||
fontSize: '12px'
|
fontSize: '12px'
|
||||||
},
|
},
|
||||||
offsetY: -190,
|
offsetY: -185,
|
||||||
offsetX: 25,
|
offsetX: 25,
|
||||||
rotate: 0
|
rotate: 0
|
||||||
},
|
},
|
||||||
|
@ -182,6 +182,7 @@ export const getChartOptions = (
|
||||||
},
|
},
|
||||||
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
shared: true,
|
||||||
x: {
|
x: {
|
||||||
format: 'dd MMM HH:mm:ss'
|
format: 'dd MMM HH:mm:ss'
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,7 +41,7 @@ const computeLast7Days = (): string[] => {
|
||||||
|
|
||||||
function Overview(props: OverviewProps) {
|
function Overview(props: OverviewProps) {
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser } = context;
|
||||||
|
|
||||||
const [dailyData, setDailyData] = useState(true);
|
const [dailyData, setDailyData] = useState(true);
|
||||||
const [weeklyData, setWeeklyData] = useState(false);
|
const [weeklyData, setWeeklyData] = useState(false);
|
||||||
|
@ -444,20 +444,20 @@ function Overview(props: OverviewProps) {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="lastweek" defaultMessage="Last week" />
|
<FormattedMessage id="lastweek" defaultMessage="Last week" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{/*<Button*/}
|
||||||
variant="contained"
|
{/* variant="contained"*/}
|
||||||
onClick={handleMonthData}
|
{/* onClick={handleMonthData}*/}
|
||||||
disabled={loading}
|
{/* disabled={loading}*/}
|
||||||
sx={{
|
{/* sx={{*/}
|
||||||
marginTop: '20px',
|
{/* marginTop: '20px',*/}
|
||||||
marginLeft: '10px',
|
{/* marginLeft: '10px',*/}
|
||||||
backgroundColor: monthlyData ? '#808080' : '#ffc04d',
|
{/* backgroundColor: monthlyData ? '#808080' : '#ffc04d',*/}
|
||||||
color: '#000000',
|
{/* color: '#000000',*/}
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
{/* '&:hover': { bgcolor: '#f7b34d' }*/}
|
||||||
}}
|
{/* }}*/}
|
||||||
>
|
{/*>*/}
|
||||||
<FormattedMessage id="lastmonth" defaultMessage="Last Month" />
|
{/* <FormattedMessage id="lastmonth" defaultMessage="Last Month" />*/}
|
||||||
</Button>
|
{/*</Button>*/}
|
||||||
{dailyData && (
|
{dailyData && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -57,17 +57,6 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/*<Switch*/}
|
|
||||||
{/* edge="start"*/}
|
|
||||||
{/* color="secondary"*/}
|
|
||||||
{/* onChange={handleSwitch()}*/}
|
|
||||||
{/* sx={{*/}
|
|
||||||
{/* '& .MuiSwitch-thumb': {*/}
|
|
||||||
{/* backgroundColor: 'orange'*/}
|
|
||||||
{/* }*/}
|
|
||||||
{/* }}*/}
|
|
||||||
{/*/>*/}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
|
|
|
@ -47,7 +47,7 @@ function formatPower(value, unit) {
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roundedValue = Math.round(value);
|
const roundedValue = value.toFixed(1);
|
||||||
|
|
||||||
//Filter all values less than 100 Watts
|
//Filter all values less than 100 Watts
|
||||||
if (magnitude === 0 && value < 100 && unit === 'W') {
|
if (magnitude === 0 && value < 100 && unit === 'W') {
|
||||||
|
|
|
@ -45,7 +45,7 @@ function formatPower(value) {
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roundedValue = Math.round(value);
|
const roundedValue = value.toFixed(1);
|
||||||
|
|
||||||
//Filter all values less than 100 Watts
|
//Filter all values less than 100 Watts
|
||||||
if (magnitude === 0 && value < 100) {
|
if (magnitude === 0 && value < 100) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { makeStyles } from '@mui/styles';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
|
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
|
||||||
import routes from 'src/Resources/routes.json';
|
import routes from 'src/Resources/routes.json';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface CustomTreeItemProps {
|
interface CustomTreeItemProps {
|
||||||
node: I_Installation | I_Folder;
|
node: I_Installation | I_Folder;
|
||||||
|
@ -43,8 +43,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
const status = getStatus(props.node.id);
|
const status = getStatus(props.node.id);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [selected, setSelected] = useState(false);
|
const [selected, setSelected] = useState(false);
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const currentLocation = useLocation();
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
|
||||||
|
|
||||||
const handleSelectOneInstallation = (): void => {
|
const handleSelectOneInstallation = (): void => {
|
||||||
let installation = props.node;
|
let installation = props.node;
|
||||||
|
@ -52,8 +51,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
navigate(
|
navigate(
|
||||||
routes.installations +
|
routes.installations +
|
||||||
routes.tree +
|
routes.tree +
|
||||||
'?installation=' +
|
routes.installation +
|
||||||
installation.id.toString(),
|
installation.id +
|
||||||
|
'/' +
|
||||||
|
routes.live,
|
||||||
{
|
{
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
|
@ -63,8 +64,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
navigate(
|
navigate(
|
||||||
routes.installations +
|
routes.installations +
|
||||||
routes.tree +
|
routes.tree +
|
||||||
'?folder=' +
|
routes.folder +
|
||||||
installation.id.toString(),
|
installation.id +
|
||||||
|
'/' +
|
||||||
|
routes.information,
|
||||||
{
|
{
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
|
@ -152,7 +155,12 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
sx={{
|
sx={{
|
||||||
display: !installationId ? 'block' : 'none',
|
display:
|
||||||
|
currentLocation.pathname === routes.installations + 'tree' ||
|
||||||
|
currentLocation.pathname === routes.installations + routes.tree ||
|
||||||
|
currentLocation.pathname.includes('folder')
|
||||||
|
? 'block'
|
||||||
|
: 'none',
|
||||||
'.MuiTreeItem-content': {
|
'.MuiTreeItem-content': {
|
||||||
width: 'inherit',
|
width: 'inherit',
|
||||||
|
|
||||||
|
|
|
@ -1,79 +1,47 @@
|
||||||
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import { Card, Grid, Tab, Tabs } from '@mui/material';
|
||||||
Alert,
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CircularProgress,
|
|
||||||
Container,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
Tab,
|
|
||||||
Tabs,
|
|
||||||
TextField,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from '@mui/material';
|
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
|
||||||
import { I_Folder } from 'src/interfaces/InstallationTypes';
|
import { I_Folder } from 'src/interfaces/InstallationTypes';
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import FolderForm from './folderForm';
|
|
||||||
import InstallationForm from '../Installations/installationForm';
|
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||||
import Access from '../ManageAccess/Access';
|
import Access from '../ManageAccess/Access';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { Link, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import TreeInformation from './Information';
|
||||||
|
|
||||||
interface singleFolderProps {
|
interface singleFolderProps {
|
||||||
current_folder: I_Folder;
|
current_folder: I_Folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Folder(props: singleFolderProps) {
|
function Folder(props: singleFolderProps) {
|
||||||
const theme = useTheme();
|
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||||
const [currentTab, setCurrentTab] = useState<string>('folder');
|
|
||||||
const [formValues, setFormValues] = useState(props.current_folder);
|
const [formValues, setFormValues] = useState(props.current_folder);
|
||||||
const [openModalFolder, setOpenModalFolder] = useState(false);
|
const location = useLocation();
|
||||||
const [openModalInstallation, setOpenModalInstallation] = useState(false);
|
|
||||||
const requiredFields = ['name'];
|
|
||||||
const tokencontext = useContext(TokenContext);
|
|
||||||
const { removeToken } = tokencontext;
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser, setUser } = context;
|
const { currentUser } = context;
|
||||||
const [isRowHovered, setHoveredRow] = useState(-1);
|
|
||||||
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
|
||||||
const selectedBulkActions = selectedUser !== -1;
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
const folderId = parseInt(searchParams.get('folder'));
|
|
||||||
const [openModalDeleteFolder, setOpenModalDeleteFolder] = useState(false);
|
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
const {
|
const { setError } = installationContext;
|
||||||
loading,
|
|
||||||
setLoading,
|
|
||||||
error,
|
|
||||||
setError,
|
|
||||||
updated,
|
|
||||||
setUpdated,
|
|
||||||
updateFolder,
|
|
||||||
deleteFolder
|
|
||||||
} = installationContext;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFormValues(props.current_folder);
|
setFormValues(props.current_folder);
|
||||||
}, [props.current_folder]);
|
}, [props.current_folder]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let path = location.pathname.split('/');
|
||||||
|
|
||||||
|
setCurrentTab(path[path.length - 1]);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
if (formValues == undefined) {
|
if (formValues == undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
value: 'folder',
|
value: 'information',
|
||||||
label: <FormattedMessage id="folder" defaultMessage="Folder" />
|
label: <FormattedMessage id="information" defaultMessage="Information" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'manage',
|
value: 'manage',
|
||||||
|
@ -88,175 +56,8 @@ function Folder(props: singleFolderProps) {
|
||||||
setError(false);
|
setError(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setFormValues({
|
|
||||||
...formValues,
|
|
||||||
[name]: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectOneUser = (installationID: number): void => {
|
|
||||||
if (selectedUser != installationID) {
|
|
||||||
setSelectedUser(installationID);
|
|
||||||
} else {
|
|
||||||
setSelectedUser(-1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowMouseEnter = (id: number) => {
|
|
||||||
setHoveredRow(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowMouseLeave = () => {
|
|
||||||
setHoveredRow(-1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFolderInformationUpdate = (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
updateFolder(formValues);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNewInstallationInsertion = (e) => {
|
|
||||||
setOpenModalInstallation(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNewFolderInsertion = (e) => {
|
|
||||||
setOpenModalFolder(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteFolder = (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(false);
|
|
||||||
setOpenModalDeleteFolder(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteFolderModalHandle = (e) => {
|
|
||||||
setOpenModalDeleteFolder(false);
|
|
||||||
deleteFolder(formValues);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteFolderModalHandleCancel = (e) => {
|
|
||||||
setOpenModalDeleteFolder(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFolderFormSubmit = () => {
|
|
||||||
setOpenModalFolder(false);
|
|
||||||
setOpenModalInstallation(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInstallationFormSubmit = () => {
|
|
||||||
setOpenModalFolder(false);
|
|
||||||
setOpenModalInstallation(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormCancel = () => {
|
|
||||||
setOpenModalFolder(false);
|
|
||||||
setOpenModalInstallation(false);
|
|
||||||
};
|
|
||||||
const areRequiredFieldsFilled = () => {
|
|
||||||
for (const field of requiredFields) {
|
|
||||||
if (!formValues[field]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (folderId == props.current_folder.id) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{openModalDeleteFolder && (
|
|
||||||
<Modal
|
|
||||||
open={openModalDeleteFolder}
|
|
||||||
onClose={() => setOpenModalDeleteFolder(false)}
|
|
||||||
aria-labelledby="error-modal"
|
|
||||||
aria-describedby="error-modal-description"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: 350,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 4,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ fontWeight: 'bold' }}
|
|
||||||
>
|
|
||||||
Do you want to delete this folder?
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ fontSize: '0.875rem' }}
|
|
||||||
>
|
|
||||||
All installations of this folder will be deleted.
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteFolderModalHandle}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
sx={{
|
|
||||||
marginTop: 2,
|
|
||||||
marginLeft: 2,
|
|
||||||
textTransform: 'none',
|
|
||||||
bgcolor: '#ffc04d',
|
|
||||||
color: '#111111',
|
|
||||||
'&:hover': { bgcolor: '#f7b34d' }
|
|
||||||
}}
|
|
||||||
onClick={deleteFolderModalHandleCancel}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
)}
|
|
||||||
{openModalFolder && (
|
|
||||||
<FolderForm
|
|
||||||
cancel={handleFormCancel}
|
|
||||||
submit={handleFolderFormSubmit}
|
|
||||||
parentid={props.current_folder.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{openModalInstallation && (
|
|
||||||
<InstallationForm
|
|
||||||
cancel={handleFormCancel}
|
|
||||||
submit={handleInstallationFormSubmit}
|
|
||||||
parentid={props.current_folder.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
@ -268,7 +69,18 @@ function Folder(props: singleFolderProps) {
|
||||||
indicatorColor="primary"
|
indicatorColor="primary"
|
||||||
>
|
>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<Tab key={tab.value} label={tab.label} value={tab.value} />
|
<Tab
|
||||||
|
key={tab.value}
|
||||||
|
label={tab.label}
|
||||||
|
value={tab.value}
|
||||||
|
component={Link}
|
||||||
|
to={
|
||||||
|
location.pathname.substring(
|
||||||
|
0,
|
||||||
|
location.pathname.lastIndexOf('/') + 1
|
||||||
|
) + routes[tab.value]
|
||||||
|
}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TabsContainerWrapper>
|
</TabsContainerWrapper>
|
||||||
|
@ -280,202 +92,34 @@ function Folder(props: singleFolderProps) {
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
spacing={0}
|
spacing={0}
|
||||||
>
|
>
|
||||||
{currentTab === 'folder' && (
|
<Routes>
|
||||||
<Container maxWidth="xl">
|
<Route
|
||||||
<Grid
|
path={routes.information}
|
||||||
container
|
element={
|
||||||
direction="row"
|
<TreeInformation
|
||||||
justifyContent="center"
|
folder={props.current_folder}
|
||||||
alignItems="stretch"
|
></TreeInformation>
|
||||||
spacing={3}
|
|
||||||
>
|
|
||||||
<Grid item xs={12} md={12}>
|
|
||||||
<CardContent>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
sx={{
|
|
||||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
|
||||||
}}
|
|
||||||
noValidate
|
|
||||||
autoComplete="off"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="name"
|
|
||||||
defaultMessage="Name"
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
name="name"
|
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="information"
|
|
||||||
defaultMessage="Information"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="information"
|
|
||||||
value={formValues.information}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentUser.hasWriteAccess && (
|
{currentUser.hasWriteAccess && (
|
||||||
<Button
|
<Route
|
||||||
variant="contained"
|
path={routes.manage}
|
||||||
onClick={handleDeleteFolder}
|
element={
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="deleteFolder"
|
|
||||||
defaultMessage="Delete Folder"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleNewFolderInsertion}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="addNewFolder"
|
|
||||||
defaultMessage="Add new folder"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleNewInstallationInsertion}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="addNewInstallation"
|
|
||||||
defaultMessage="Add new installation"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleFolderInformationUpdate}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="applyChanges"
|
|
||||||
defaultMessage="Apply Changes"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="errorOccured"
|
|
||||||
defaultMessage="An error has occurred"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setError(false)} // Set error state to false on click
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{updated && (
|
|
||||||
<Alert
|
|
||||||
severity="success"
|
|
||||||
sx={{
|
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="successfullyUpdated"
|
|
||||||
defaultMessage="Successfully updated"
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
|
||||||
sx={{ marginLeft: '4px' }}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
|
||||||
<AccessContextProvider>
|
<AccessContextProvider>
|
||||||
<Access
|
<Access
|
||||||
currentResource={formValues}
|
currentResource={formValues}
|
||||||
resourceType="folder"
|
resourceType="folder"
|
||||||
></Access>
|
></Access>
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Routes>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Folder;
|
export default Folder;
|
||||||
|
|
|
@ -0,0 +1,381 @@
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Box,
|
||||||
|
CardContent,
|
||||||
|
CircularProgress,
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { I_Folder } from '../../../interfaces/InstallationTypes';
|
||||||
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
|
import FolderForm from './folderForm';
|
||||||
|
import InstallationForm from '../Installations/installationForm';
|
||||||
|
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
|
||||||
|
|
||||||
|
interface TreeInformationProps {
|
||||||
|
folder: I_Folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TreeInformation(props: TreeInformationProps) {
|
||||||
|
if (props.folder === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const theme = useTheme();
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser } = context;
|
||||||
|
const [formValues, setFormValues] = useState(props.folder);
|
||||||
|
const [openModalFolder, setOpenModalFolder] = useState(false);
|
||||||
|
const [openModalInstallation, setOpenModalInstallation] = useState(false);
|
||||||
|
const requiredFields = ['name'];
|
||||||
|
const [openModalDeleteFolder, setOpenModalDeleteFolder] = useState(false);
|
||||||
|
const installationContext = useContext(InstallationsContext);
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
setLoading,
|
||||||
|
error,
|
||||||
|
setError,
|
||||||
|
updated,
|
||||||
|
setUpdated,
|
||||||
|
updateFolder,
|
||||||
|
deleteFolder
|
||||||
|
} = installationContext;
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormValues({
|
||||||
|
...formValues,
|
||||||
|
[name]: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFolderInformationUpdate = () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
updateFolder(formValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewInstallationInsertion = () => {
|
||||||
|
setOpenModalInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNewFolderInsertion = () => {
|
||||||
|
setOpenModalFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteFolder = () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
setOpenModalDeleteFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFolderModalHandle = () => {
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
|
deleteFolder(formValues);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFolderModalHandleCancel = () => {
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFolderFormSubmit = () => {
|
||||||
|
setOpenModalFolder(false);
|
||||||
|
setOpenModalInstallation(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstallationFormSubmit = () => {
|
||||||
|
setOpenModalFolder(false);
|
||||||
|
setOpenModalInstallation(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
setOpenModalFolder(false);
|
||||||
|
setOpenModalInstallation(false);
|
||||||
|
};
|
||||||
|
const areRequiredFieldsFilled = () => {
|
||||||
|
for (const field of requiredFields) {
|
||||||
|
if (!formValues[field]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{openModalDeleteFolder && (
|
||||||
|
<Modal
|
||||||
|
open={openModalDeleteFolder}
|
||||||
|
onClose={() => setOpenModalDeleteFolder(false)}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 350,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Do you want to delete this folder?
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontSize: '0.875rem' }}
|
||||||
|
>
|
||||||
|
All installations of this folder will be deleted.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteFolderModalHandle}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
marginLeft: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteFolderModalHandleCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
{openModalFolder && (
|
||||||
|
<FolderForm
|
||||||
|
cancel={handleFormCancel}
|
||||||
|
submit={handleFolderFormSubmit}
|
||||||
|
parentid={props.folder.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{openModalInstallation && (
|
||||||
|
<InstallationForm
|
||||||
|
cancel={handleFormCancel}
|
||||||
|
submit={handleInstallationFormSubmit}
|
||||||
|
parentid={props.folder.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="stretch"
|
||||||
|
spacing={3}
|
||||||
|
>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
<CardContent>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{
|
||||||
|
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
||||||
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={<FormattedMessage id="name" defaultMessage="Name" />}
|
||||||
|
name="name"
|
||||||
|
value={formValues.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.name === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="information"
|
||||||
|
defaultMessage="Information"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="information"
|
||||||
|
value={formValues.information}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleDeleteFolder}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteFolder"
|
||||||
|
defaultMessage="Delete Folder"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleNewFolderInsertion}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="addNewFolder"
|
||||||
|
defaultMessage="Add new folder"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleNewInstallationInsertion}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="addNewInstallation"
|
||||||
|
defaultMessage="Add new installation"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleFolderInformationUpdate}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
disabled={!areRequiredFieldsFilled()}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="applyChanges"
|
||||||
|
defaultMessage="Apply Changes"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<CircularProgress
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
marginLeft: '20px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="errorOccured"
|
||||||
|
defaultMessage="An error has occurred"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setError(false)} // Set error state to false on click
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{updated && (
|
||||||
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="successfullyUpdated"
|
||||||
|
defaultMessage="Successfully updated"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)} // Set error state to false on click
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TreeInformation;
|
|
@ -5,8 +5,10 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
import CustomTreeItem from './CustomTreeItem';
|
import CustomTreeItem from './CustomTreeItem';
|
||||||
import Installation from '../Installations/Installation';
|
import Installation from '../Installations/Installation';
|
||||||
import Folder from './Folder';
|
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||||
|
import { Route, Routes } from 'react-router-dom';
|
||||||
|
import routes from '../../../Resources/routes.json';
|
||||||
|
import Folder from './Folder';
|
||||||
|
|
||||||
function InstallationTree() {
|
function InstallationTree() {
|
||||||
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
|
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
|
||||||
|
@ -65,24 +67,38 @@ function InstallationTree() {
|
||||||
</TreeView>
|
</TreeView>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Routes>
|
||||||
{foldersAndInstallations.map((installation) => {
|
{foldersAndInstallations.map((installation) => {
|
||||||
if (installation.type == 'Installation') {
|
if (installation.type == 'Installation') {
|
||||||
return (
|
return (
|
||||||
|
<Route
|
||||||
|
key={installation.id}
|
||||||
|
path={routes.installation + installation.id + '*'}
|
||||||
|
element={
|
||||||
<Installation
|
<Installation
|
||||||
key={installation.id + installation.type}
|
key={installation.id}
|
||||||
current_installation={installation}
|
current_installation={installation}
|
||||||
type="tree"
|
type="installation"
|
||||||
></Installation>
|
></Installation>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
<Route
|
||||||
|
key={installation.id}
|
||||||
|
path={routes.folder + installation.id + '*'}
|
||||||
|
element={
|
||||||
<Folder
|
<Folder
|
||||||
key={installation.id + installation.type}
|
key={installation.id + installation.type}
|
||||||
current_folder={installation}
|
current_folder={installation}
|
||||||
></Folder>
|
></Folder>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
</Routes>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
interface FlatUsersViewProps {
|
interface FlatUsersViewProps {
|
||||||
users: InnovEnergyUser[];
|
users: InnovEnergyUser[];
|
||||||
|
@ -23,15 +22,10 @@ interface FlatUsersViewProps {
|
||||||
|
|
||||||
const FlatUsersView = (props: FlatUsersViewProps) => {
|
const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||||
const selectedBulkActions = selectedUser !== -1;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const handleSelectOneUser = (installationID: number): void => {
|
const handleSelectOneUser = (userID: number): void => {
|
||||||
if (selectedUser != installationID) {
|
if (selectedUser != userID) {
|
||||||
setSelectedUser(installationID);
|
setSelectedUser(userID);
|
||||||
// navigate(routes.users + '?user=' + installationID.toString(), {
|
|
||||||
// replace: true
|
|
||||||
// });
|
|
||||||
} else {
|
} else {
|
||||||
setSelectedUser(-1);
|
setSelectedUser(-1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,13 @@ import { FetchResult } from '../dataCache/dataCache';
|
||||||
import { I_S3Credentials } from './S3Types';
|
import { I_S3Credentials } from './S3Types';
|
||||||
import { UnixTime } from '../dataCache/time';
|
import { UnixTime } from '../dataCache/time';
|
||||||
|
|
||||||
|
export interface chartInfoInterface {
|
||||||
|
magnitude: number;
|
||||||
|
unit: string;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface overviewInterface {
|
export interface overviewInterface {
|
||||||
soc: chartInfoInterface;
|
soc: chartInfoInterface;
|
||||||
temperature: chartInfoInterface;
|
temperature: chartInfoInterface;
|
||||||
|
@ -18,13 +25,6 @@ export interface overviewInterface {
|
||||||
overview: chartInfoInterface;
|
overview: chartInfoInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface chartInfoInterface {
|
|
||||||
magnitude: number;
|
|
||||||
unit: string;
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface chartAggregatedDataInterface {
|
export interface chartAggregatedDataInterface {
|
||||||
minsoc: { name: string; data: number[] };
|
minsoc: { name: string; data: number[] };
|
||||||
maxsoc: { name: string; data: number[] };
|
maxsoc: { name: string; data: number[] };
|
||||||
|
@ -45,6 +45,197 @@ export interface chartDataInterface {
|
||||||
dcBusVoltage: { name: string; data: number[] };
|
dcBusVoltage: { name: string; data: number[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatteryDataInterface {
|
||||||
|
Soc: { name: string; data: [] };
|
||||||
|
Temperature: { name: string; data: [] };
|
||||||
|
Power: { name: string; data: [] };
|
||||||
|
Voltage: { name: string; data: [] };
|
||||||
|
Current: { name: string; data: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatteryOverviewInterface {
|
||||||
|
Soc: chartInfoInterface;
|
||||||
|
Temperature: chartInfoInterface;
|
||||||
|
Power: chartInfoInterface;
|
||||||
|
Voltage: chartInfoInterface;
|
||||||
|
Current: chartInfoInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const transformInputToBatteryViewData = async (
|
||||||
|
s3Credentials: I_S3Credentials,
|
||||||
|
startTimestamp: UnixTime,
|
||||||
|
endTimestamp: UnixTime
|
||||||
|
): Promise<{
|
||||||
|
chartData: BatteryDataInterface;
|
||||||
|
chartOverview: BatteryOverviewInterface;
|
||||||
|
}> => {
|
||||||
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
|
const MAX_NUMBER = 9999999;
|
||||||
|
|
||||||
|
const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
|
||||||
|
const pathCategories = [
|
||||||
|
'Soc',
|
||||||
|
'Temperatures/Cells/Center',
|
||||||
|
'Dc/Power',
|
||||||
|
'Dc/Voltage',
|
||||||
|
'Dc/Current'
|
||||||
|
];
|
||||||
|
|
||||||
|
const pathsToSearch = [
|
||||||
|
'/Battery/Devices/1/',
|
||||||
|
'/Battery/Devices/2/',
|
||||||
|
'/Battery/Devices/3/',
|
||||||
|
'/Battery/Devices/4/',
|
||||||
|
'/Battery/Devices/5/',
|
||||||
|
'/Battery/Devices/6/',
|
||||||
|
'/Battery/Devices/7/',
|
||||||
|
'/Battery/Devices/8/',
|
||||||
|
'/Battery/Devices/9/',
|
||||||
|
'/Battery/Devices/10/'
|
||||||
|
];
|
||||||
|
|
||||||
|
const pathsToSave = [
|
||||||
|
'Battery1',
|
||||||
|
'Battery2',
|
||||||
|
'Battery3',
|
||||||
|
'Battery4',
|
||||||
|
'Battery5',
|
||||||
|
'Battery6',
|
||||||
|
'Battery7',
|
||||||
|
'Battery8',
|
||||||
|
'Battery9',
|
||||||
|
'Battery10'
|
||||||
|
];
|
||||||
|
|
||||||
|
const chartData: BatteryDataInterface = {
|
||||||
|
Soc: { name: 'State Of Charge', data: [] },
|
||||||
|
Temperature: { name: 'Temperature', data: [] },
|
||||||
|
Power: { name: 'Power', data: [] },
|
||||||
|
Voltage: { name: 'Voltage', data: [] },
|
||||||
|
Current: { name: 'Voltage', data: [] }
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartOverview: BatteryOverviewInterface = {
|
||||||
|
Soc: { magnitude: 0, unit: '', min: 0, max: 0 },
|
||||||
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
categories.forEach((category) => {
|
||||||
|
chartData[category].data = [];
|
||||||
|
pathsToSave.forEach((path) => {
|
||||||
|
chartData[category].data[path] = { name: path, data: [] };
|
||||||
|
});
|
||||||
|
|
||||||
|
chartOverview[category] = {
|
||||||
|
magnitude: 0,
|
||||||
|
unit: '',
|
||||||
|
min: MAX_NUMBER,
|
||||||
|
max: -MAX_NUMBER
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let adjustedTimestampArray = [];
|
||||||
|
|
||||||
|
let startTimestampToNum = Number(startTimestamp);
|
||||||
|
if (startTimestampToNum % 2 != 0) {
|
||||||
|
startTimestampToNum += 1;
|
||||||
|
}
|
||||||
|
let startUnixTime = UnixTime.fromTicks(startTimestampToNum);
|
||||||
|
let diff = endTimestamp.ticks - startUnixTime.ticks;
|
||||||
|
|
||||||
|
const timestampPromises = [];
|
||||||
|
|
||||||
|
while (startUnixTime < endTimestamp) {
|
||||||
|
timestampPromises.push(fetchData(startUnixTime, s3Credentials));
|
||||||
|
|
||||||
|
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
||||||
|
if (startUnixTime.ticks % 2 !== 0) {
|
||||||
|
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
||||||
|
}
|
||||||
|
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000);
|
||||||
|
adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1);
|
||||||
|
adjustedTimestampArray.push(adjustedTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await Promise.all(timestampPromises);
|
||||||
|
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
const result = results[i];
|
||||||
|
if (
|
||||||
|
result === FetchResult.notAvailable ||
|
||||||
|
result === FetchResult.tryLater
|
||||||
|
) {
|
||||||
|
// Handle not available or try later case
|
||||||
|
} else {
|
||||||
|
for (
|
||||||
|
let category_index = 0;
|
||||||
|
category_index < pathCategories.length;
|
||||||
|
category_index++
|
||||||
|
) {
|
||||||
|
let category = categories[category_index];
|
||||||
|
|
||||||
|
for (let j = 0; j < pathsToSearch.length; j++) {
|
||||||
|
let path = pathsToSearch[j] + pathCategories[category_index];
|
||||||
|
|
||||||
|
if (result[path]) {
|
||||||
|
const value = result[path];
|
||||||
|
|
||||||
|
if (value.value < chartOverview[category].min) {
|
||||||
|
chartOverview[category].min = value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.value > chartOverview[category].max) {
|
||||||
|
chartOverview[category].max = value.value;
|
||||||
|
}
|
||||||
|
chartData[category].data[pathsToSave[j]].data.push([
|
||||||
|
adjustedTimestampArray[i],
|
||||||
|
value.value
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
//data[path].push([adjustedTimestamp, null]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
categories.forEach((category) => {
|
||||||
|
let value = Math.max(
|
||||||
|
Math.abs(chartOverview[category].max),
|
||||||
|
Math.abs(chartOverview[category].min)
|
||||||
|
);
|
||||||
|
let magnitude = 0;
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
while (value >= 1000) {
|
||||||
|
value /= 1000;
|
||||||
|
magnitude++;
|
||||||
|
}
|
||||||
|
chartOverview[category].magnitude = magnitude;
|
||||||
|
});
|
||||||
|
|
||||||
|
chartOverview.Soc.unit = '(%)';
|
||||||
|
chartOverview.Soc.min = 0;
|
||||||
|
chartOverview.Soc.max = 100;
|
||||||
|
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' + ')';
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartData: chartData,
|
||||||
|
chartOverview: chartOverview
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const transformInputToDailyData = async (
|
export const transformInputToDailyData = async (
|
||||||
s3Credentials: I_S3Credentials,
|
s3Credentials: I_S3Credentials,
|
||||||
startTimestamp: UnixTime,
|
startTimestamp: UnixTime,
|
||||||
|
@ -53,8 +244,6 @@ export const transformInputToDailyData = async (
|
||||||
chartData: chartDataInterface;
|
chartData: chartDataInterface;
|
||||||
chartOverview: overviewInterface;
|
chartOverview: overviewInterface;
|
||||||
}> => {
|
}> => {
|
||||||
const data = {};
|
|
||||||
const overviewData = {};
|
|
||||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
const MAX_NUMBER = 9999999;
|
const MAX_NUMBER = 9999999;
|
||||||
const pathsToSearch = [
|
const pathsToSearch = [
|
||||||
|
@ -65,6 +254,14 @@ export const transformInputToDailyData = async (
|
||||||
'/PvOnDc/Dc/Power',
|
'/PvOnDc/Dc/Power',
|
||||||
'/DcDc/Dc/Link/Voltage'
|
'/DcDc/Dc/Link/Voltage'
|
||||||
];
|
];
|
||||||
|
const categories = [
|
||||||
|
'soc',
|
||||||
|
'temperature',
|
||||||
|
'dcPower',
|
||||||
|
'gridPower',
|
||||||
|
'pvProduction',
|
||||||
|
'dcBusVoltage'
|
||||||
|
];
|
||||||
|
|
||||||
const chartData: chartDataInterface = {
|
const chartData: chartDataInterface = {
|
||||||
soc: { name: 'State Of Charge', data: [] },
|
soc: { name: 'State Of Charge', data: [] },
|
||||||
|
@ -86,9 +283,9 @@ export const transformInputToDailyData = async (
|
||||||
overview: { magnitude: 0, unit: '', min: 0, max: 0 }
|
overview: { magnitude: 0, unit: '', min: 0, max: 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
pathsToSearch.forEach((path) => {
|
categories.forEach((category) => {
|
||||||
data[path] = [];
|
chartData[category].data = [];
|
||||||
overviewData[path] = {
|
chartOverview[category] = {
|
||||||
magnitude: 0,
|
magnitude: 0,
|
||||||
unit: '',
|
unit: '',
|
||||||
min: MAX_NUMBER,
|
min: MAX_NUMBER,
|
||||||
|
@ -130,29 +327,33 @@ export const transformInputToDailyData = async (
|
||||||
// Handle not available or try later case
|
// Handle not available or try later case
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
let category_index = 0;
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
if (result[path]) {
|
if (result[path]) {
|
||||||
const value = result[path];
|
const value = result[path];
|
||||||
|
|
||||||
if (value.value < overviewData[path].min) {
|
if (value.value < chartOverview[categories[category_index]].min) {
|
||||||
overviewData[path].min = value.value;
|
chartOverview[categories[category_index]].min = value.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.value > overviewData[path].max) {
|
if (value.value > chartOverview[categories[category_index]].max) {
|
||||||
overviewData[path].max = value.value;
|
chartOverview[categories[category_index]].max = value.value;
|
||||||
}
|
}
|
||||||
|
chartData[categories[category_index]].data.push([
|
||||||
data[path].push([adjustedTimestampArray[i], value.value]);
|
adjustedTimestampArray[i],
|
||||||
|
value.value
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
//data[path].push([adjustedTimestamp, null]);
|
//data[path].push([adjustedTimestamp, null]);
|
||||||
}
|
}
|
||||||
|
category_index++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pathsToSearch.forEach((path) => {
|
categories.forEach((category) => {
|
||||||
let value = Math.max(
|
let value = Math.max(
|
||||||
Math.abs(overviewData[path].max),
|
Math.abs(chartOverview[category].max),
|
||||||
Math.abs(overviewData[path].min)
|
Math.abs(chartOverview[category].min)
|
||||||
);
|
);
|
||||||
let magnitude = 0;
|
let magnitude = 0;
|
||||||
|
|
||||||
|
@ -163,81 +364,35 @@ export const transformInputToDailyData = async (
|
||||||
value /= 1000;
|
value /= 1000;
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
overviewData[path].magnitude = magnitude;
|
chartOverview[category].magnitude = magnitude;
|
||||||
});
|
});
|
||||||
|
|
||||||
let path = '/Battery/Soc';
|
chartOverview.soc.unit = '(%)';
|
||||||
chartData.soc.data = data[path];
|
chartOverview.soc.min = 0;
|
||||||
|
chartOverview.soc.max = 100;
|
||||||
chartOverview.soc = {
|
chartOverview.temperature.unit = '(°C)';
|
||||||
unit: '(%)',
|
chartOverview.dcPower.unit =
|
||||||
magnitude: overviewData[path].magnitude,
|
'(' + prefixes[chartOverview['dcPower'].magnitude] + 'W' + ')';
|
||||||
min: 0,
|
chartOverview.gridPower.unit =
|
||||||
max: 100
|
'(' + prefixes[chartOverview['gridPower'].magnitude] + 'W' + ')';
|
||||||
};
|
chartOverview.pvProduction.unit =
|
||||||
|
'(' + prefixes[chartOverview['pvProduction'].magnitude] + 'W' + ')';
|
||||||
path = '/Battery/Temperature';
|
chartOverview.dcBusVoltage.unit =
|
||||||
chartData.temperature.data = data[path];
|
'(' + prefixes[chartOverview['dcBusVoltage'].magnitude] + 'V' + ')';
|
||||||
|
|
||||||
chartOverview.temperature = {
|
|
||||||
unit: '(°C)',
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/Battery/Dc/Power';
|
|
||||||
chartData.dcPower.data = data[path];
|
|
||||||
|
|
||||||
chartOverview.dcPower = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/GridMeter/Ac/Power/Active';
|
|
||||||
chartData.gridPower.data = data[path];
|
|
||||||
|
|
||||||
chartOverview.gridPower = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/PvOnDc/Dc/Power';
|
|
||||||
chartData.pvProduction.data = data[path];
|
|
||||||
|
|
||||||
chartOverview.pvProduction = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + prefixes[overviewData[path].magnitude] + 'W' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
path = '/DcDc/Dc/Link/Voltage';
|
|
||||||
chartData.dcBusVoltage.data = data[path];
|
|
||||||
chartOverview.dcBusVoltage = {
|
|
||||||
magnitude: overviewData[path].magnitude,
|
|
||||||
unit: '(' + prefixes[overviewData[path].magnitude] + 'V' + ')',
|
|
||||||
min: overviewData[path].min,
|
|
||||||
max: overviewData[path].max
|
|
||||||
};
|
|
||||||
|
|
||||||
chartOverview.overview = {
|
chartOverview.overview = {
|
||||||
magnitude: Math.max(
|
magnitude: Math.max(
|
||||||
overviewData['/GridMeter/Ac/Power/Active'].magnitude,
|
chartOverview['gridPower'].magnitude,
|
||||||
overviewData['/PvOnDc/Dc/Power'].magnitude
|
chartOverview['pvProduction'].magnitude
|
||||||
),
|
),
|
||||||
unit: '(kW)',
|
unit: '(kW)',
|
||||||
min: Math.min(
|
min: Math.min(
|
||||||
overviewData['/GridMeter/Ac/Power/Active'].min,
|
chartOverview['gridPower'].min,
|
||||||
overviewData['/PvOnDc/Dc/Power'].min
|
chartOverview['pvProduction'].min
|
||||||
),
|
),
|
||||||
max: Math.max(
|
max: Math.max(
|
||||||
overviewData['/GridMeter/Ac/Power/Active'].max,
|
chartOverview['gridPower'].max,
|
||||||
overviewData['/PvOnDc/Dc/Power'].max
|
chartOverview['pvProduction'].max
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -277,6 +432,17 @@ export const transformInputToAggregatedData = async (
|
||||||
'/HeatingPower'
|
'/HeatingPower'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
'minsoc',
|
||||||
|
'maxsoc',
|
||||||
|
'pvProduction',
|
||||||
|
'dcChargingPower',
|
||||||
|
'heatingPower',
|
||||||
|
'dcDischargingPower',
|
||||||
|
'gridImportPower',
|
||||||
|
'gridExportPower'
|
||||||
|
];
|
||||||
|
|
||||||
const chartAggregatedData: chartAggregatedDataInterface = {
|
const chartAggregatedData: chartAggregatedDataInterface = {
|
||||||
minsoc: { name: 'min SOC', data: [] },
|
minsoc: { name: 'min SOC', data: [] },
|
||||||
maxsoc: { name: 'max SOC', data: [] },
|
maxsoc: { name: 'max SOC', data: [] },
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"installation": "installation/",
|
|
||||||
"liveView": "liveView/",
|
|
||||||
"users": "/users/",
|
|
||||||
"log": "log/",
|
|
||||||
"installations": "/installations/",
|
|
||||||
"groups": "/groups/",
|
|
||||||
"group": "group/",
|
|
||||||
"folder": "folder/",
|
|
||||||
"manageAccess": "manageAccess/",
|
|
||||||
"user": "user/",
|
|
||||||
"tree": "tree/",
|
|
||||||
"list": "list/"
|
|
||||||
}
|
|
Loading…
Reference in New Issue