diff --git a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx index 449d12308..dbb832c1d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx @@ -29,7 +29,6 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import dayjs from 'dayjs'; import axiosConfig from '../../../Resources/axiosConfig'; import utc from 'dayjs/plugin/utc'; -import { Action } from '../../../interfaces/S3Types'; import { UserContext } from '../../../contexts/userContext'; interface ConfigurationProps { @@ -136,14 +135,6 @@ function Configuration(props: ConfigurationProps) { .add(localOffset, 'minute') .toDate() }; - - const historyAction: Action = { - configuration: configurationToSend, - date: new Date().toISOString().split('T')[0], // Gets the current date in YYYY-MM-DD format - time: new Date().toISOString().split('T')[1].split('.')[0], // Gets the current time in HH:MM:SS format - user: currentUser.name - }; - // console.log('will send ', dayjs(formValues.calibrationChargeDate)); setLoading(true); @@ -160,21 +151,8 @@ function Configuration(props: ConfigurationProps) { }); if (res) { - const historyRes = await axiosConfig - .post( - `/UpdateActionHistory?installationId=${props.id}`, - historyAction - ) - .catch((err) => { - if (err.response) { - setError(true); - setLoading(false); - } - }); - if (historyRes) { - setUpdated(true); - setLoading(false); - } + setUpdated(true); + setLoading(false); } } }; diff --git a/typescript/frontend-marios2/src/content/dashboards/History/History.tsx b/typescript/frontend-marios2/src/content/dashboards/History/History.tsx index 49ffd8a68..86923395e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/History/History.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/History/History.tsx @@ -122,102 +122,135 @@ function HistoryOfActions(props: HistoryProps) { - {/**/} - {/* */} - {/* */} - {/* */} - {/**/} +
+ + + +
- {history.map((action, index) => ( - <> - -
-
- - {action.user} - -
+ {history.map((action, index) => { + // Parse the timestamp string to a Date object + const date = new Date(action.timestamp); + // Extract the date part (e.g., "2023-05-31") + const datePart = date.toLocaleDateString(); + + // Extract the time part (e.g., "12:34:56") + const timePart = date.toLocaleTimeString(); + return ( + <> +
- - {action.date} - -
-
- + {action.userName} + +
+ +
- {action.time} - + + {datePart} + +
+
+ + {timePart} + +
+ +
+ + {action.description} + +
-
- - ))} + + ); + })} diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index d6f4cf84b..23f25107a 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -24,6 +24,7 @@ import Information from '../Information/Information'; import BatteryView from '../BatteryView/BatteryView'; import { UserType } from '../../../interfaces/UserTypes'; import HistoryOfActions from '../History/History'; +import PvView from '../PvView/PvView'; interface singleInstallationProps { current_installation?: I_Installation; @@ -124,6 +125,7 @@ function Installation(props: singleInstallationProps) { useEffect(() => { if ( currentTab == 'live' || + currentTab == 'pvview' || currentTab == 'configuration' || location.includes('batteryview') ) { @@ -131,7 +133,8 @@ function Installation(props: singleInstallationProps) { if ( currentTab == 'live' || - (location.includes('batteryview') && !location.includes('mainstats')) + (location.includes('batteryview') && !location.includes('mainstats')) || + currentTab == 'pvview' ) { fetchDataPeriodically(); interval = setInterval(fetchDataPeriodically, 2000); @@ -144,6 +147,7 @@ function Installation(props: singleInstallationProps) { return () => { if ( currentTab == 'live' || + currentTab == 'pvview' || (location.includes('batteryview') && !location.includes('mainstats')) ) { clearInterval(interval); @@ -314,13 +318,7 @@ function Installation(props: singleInstallationProps) { + } > diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index 34d6e9e21..bd10ab9d4 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -140,15 +140,15 @@ function InstallationTabs() { /> ) }, - // { - // value: 'history', - // label: ( - // - // ) - // }, + { + value: 'history', + label: ( + + ) + }, { value: 'pvview', label: @@ -271,16 +271,16 @@ function InstallationTabs() { defaultMessage="Configuration" /> ) + }, + { + value: 'history', + label: ( + + ) } - // { - // value: 'history', - // label: ( - // - // ) - // } ] : currentUser.userType == UserType.partner ? [ diff --git a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx index ec4dad776..9ecb56639 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx @@ -35,7 +35,12 @@ export type ConfigurationValues = { calibrationChargeDate: Date | null; }; -export interface Pv {} +export interface Pv { + PvId: number; + Power: I_BoxDataValue; + Voltage: I_BoxDataValue; + Current: I_BoxDataValue; +} export interface Battery { BatteryId: number; @@ -81,6 +86,8 @@ export interface Battery { MaxDischargePower: I_BoxDataValue; } +const PvKeys = ['PvId', 'Power', 'Voltage', 'Current']; + const BatteryKeys = [ 'BatteryId', 'FwVersion', @@ -172,10 +179,15 @@ type TopologyPaths = { [key in keyof TopologyValues]: string[] }; const batteryIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +const pvIds = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30 +]; + const PvPaths = [ + '/PvOnDc/Strings/%id%/Power', '/PvOnDc/Strings/%id%/Voltage', - '/PvOnDc/Strings/%id%/Current', - '/PvOnDc/Strings/%id%/Power' + '/PvOnDc/Strings/%id%/Current' ]; const batteryPaths = [ @@ -300,7 +312,7 @@ export const topologyPaths: TopologyPaths = { batteryPaths.map((path) => path.replace('%id%', id.toString())) ), - pvView: batteryIds.flatMap((id) => + pvView: pvIds.flatMap((id) => PvPaths.map((path) => path.replace('%id%', id.toString())) ), @@ -331,15 +343,47 @@ export const extractValues = ( timeSeriesData: DataPoint ): TopologyValues | null => { const extractedValues: TopologyValues = {} as TopologyValues; - - // console.log('timeSeriesData=', timeSeriesData); - for (const topologyKey of Object.keys(topologyPaths)) { //Each topologykey may have more than one paths (for example inverter) const paths = topologyPaths[topologyKey]; let topologyValues: { unit: string; value: string | number }[] = []; - if (topologyKey === 'batteryView') { + if (topologyKey === 'pvView') { + extractedValues[topologyKey] = []; + let pv_index = 0; + let pathIndex = 0; + + while (pathIndex < paths.length) { + let pv = {}; + let existingKeys = 0; + + //We prepare a pv object for each node. We extract the number of nodes from the '/PvOnDc/NbrOfStrings' path. + //PvKeys[0] is the pv id. + pv[PvKeys[0]] = pv_index; + //Then, search all the remaining battery keys + for (let i = 1; i < PvKeys.length; i++) { + const path = paths[pathIndex]; + if (timeSeriesData.value.hasOwnProperty(path)) { + existingKeys++; + + pv[PvKeys[i]] = { + unit: timeSeriesData.value[path].unit.includes('~') + ? timeSeriesData.value[path].unit.replace('~', '') + : timeSeriesData.value[path].unit, + value: + typeof timeSeriesData.value[path].value === 'string' + ? timeSeriesData.value[path].value + : Number(timeSeriesData.value[path].value).toFixed(1) + }; + } + pathIndex++; + } + pv_index++; + if (existingKeys > 0) { + extractedValues[topologyKey].push(pv as Pv); + } + } + } else if (topologyKey === 'batteryView') { extractedValues[topologyKey] = []; const node_ids_from_csv = timeSeriesData.value[ '/Config/Devices/BatteryNodes' diff --git a/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx b/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx index e69de29bb..548fb36fc 100644 --- a/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx @@ -0,0 +1,187 @@ +import React, { useEffect, useState } from 'react'; +import { + Container, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; +import { TopologyValues } from '../Log/graph.util'; +import { useLocation, useNavigate } from 'react-router-dom'; +import routes from '../../../Resources/routes.json'; +import CircularProgress from '@mui/material/CircularProgress'; + +interface PvViewProps { + values: TopologyValues; + connected: boolean; +} + +function PvView(props: PvViewProps) { + if (props.values === null && props.connected == true) { + return null; + } + const currentLocation = useLocation(); + const navigate = useNavigate(); + const sortedPvView = + props.values != null + ? [...props.values.pvView].sort((a, b) => a.PvId - b.PvId) + : []; + + const [loading, setLoading] = useState(sortedPvView.length == 0); + + const handleMainStatsButton = () => { + navigate(routes.mainstats); + }; + + const findBatteryData = (batteryId: number) => { + for (let i = 0; i < props.values.batteryView.length; i++) { + if (props.values.batteryView[i].BatteryId == batteryId) { + return props.values.batteryView[i]; + } + } + }; + + useEffect(() => { + if (sortedPvView.length == 0) { + setLoading(true); + } else { + setLoading(false); + } + }, [sortedPvView]); + + return ( + <> + {!props.connected && ( + + + + Unable to communicate with the installation + + + Please wait or refresh the page + + + )} + {loading && props.connected && ( + + + + Battery service is not available at the moment + + + Please wait or refresh the page + + + )} + + {!loading && props.connected && ( + + + + + + Pv + Power + Voltage + Current + + + + {sortedPvView.map((pv) => ( + + + {'String ' + pv.PvId} + + + + {pv.Power.value + ' ' + pv.Power.unit} + + + {pv.Voltage.value + ' ' + pv.Voltage.unit} + + + {pv.Current.value + ' ' + pv.Current.unit} + + + ))} + +
+
+
+ )} + + ); +} + +export default PvView; diff --git a/typescript/frontend-marios2/src/interfaces/S3Types.tsx b/typescript/frontend-marios2/src/interfaces/S3Types.tsx index 078a011bc..6d7c56db0 100644 --- a/typescript/frontend-marios2/src/interfaces/S3Types.tsx +++ b/typescript/frontend-marios2/src/interfaces/S3Types.tsx @@ -1,5 +1,3 @@ -import { ConfigurationValues } from '../content/dashboards/Log/graph.util'; - export interface I_S3Credentials { s3Region: string; s3Provider: string; @@ -20,8 +18,9 @@ export interface ErrorMessage { } export interface Action { - configuration: ConfigurationValues; - date: string; - time: string; - user: string; + id: number; + userName: string; + installationId: number; + timestamp: string; + description: String; }