diff --git a/csharp/App/SaliMax/HostList.sh b/csharp/App/SaliMax/HostList.sh new file mode 100755 index 000000000..2d493c965 --- /dev/null +++ b/csharp/App/SaliMax/HostList.sh @@ -0,0 +1,6 @@ + +Prototype ie-entwicklung@10.2.3.115 +Salimax001 ie-entwicklung@10.2.3.104 +Salimax002 ie-entwicklung@10.2.4.29 +Salimax003 ie-entwicklung@10.2.4.33 +Salimax004 ie-entwicklung@10.2.4.32 \ No newline at end of file diff --git a/csharp/App/SaliMax/run (Salimax0002).sh b/csharp/App/SaliMax/deploy.sh old mode 100644 new mode 100755 similarity index 55% rename from csharp/App/SaliMax/run (Salimax0002).sh rename to csharp/App/SaliMax/deploy.sh index 44b8fe549..b55e6e141 --- a/csharp/App/SaliMax/run (Salimax0002).sh +++ b/csharp/App/SaliMax/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash dotnet_version='net6.0' -salimax_ip='10.2.4.29' +salimax_ip="$1" username='ie-entwicklung' set -e @@ -20,15 +20,3 @@ rsync -v \ ./bin/Release/$dotnet_version/linux-x64/publish/* \ $username@$salimax_ip:~/salimax -echo -e "\n============================ Restart Salimax sevice ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - sudo systemctl restart salimax.service - - -echo -e "\n============================ Print service output ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - journalctl -f -u salimax.service \ No newline at end of file diff --git a/csharp/App/SaliMax/run (Salimax 0001).sh b/csharp/App/SaliMax/run (Salimax 0001).sh deleted file mode 100755 index c54c82e6f..000000000 --- a/csharp/App/SaliMax/run (Salimax 0001).sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -dotnet_version='net6.0' -salimax_ip='10.2.3.104' -username='ie-entwicklung' - -set -e - -echo -e "\n============================ Build ============================\n" - -dotnet publish \ - ./SaliMax.csproj \ - -p:PublishTrimmed=false \ - -c Release \ - -r linux-x64 - -echo -e "\n============================ Deploy ============================\n" - -rsync -v \ - ./bin/Release/$dotnet_version/linux-x64/publish/* \ - $username@$salimax_ip:~/salimax - -echo -e "\n============================ Restart Salimax sevice ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - sudo systemctl restart salimax.service - - -echo -e "\n============================ Print service output ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - journalctl -f -u salimax.service diff --git a/csharp/App/SaliMax/run (Salimax0003).sh b/csharp/App/SaliMax/run (Salimax0003).sh deleted file mode 100644 index a47112ee4..000000000 --- a/csharp/App/SaliMax/run (Salimax0003).sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -dotnet_version='net6.0' -salimax_ip='10.2.4.33' -username='ie-entwicklung' - -set -e - -echo -e "\n============================ Build ============================\n" - -dotnet publish \ - ./SaliMax.csproj \ - -p:PublishTrimmed=false \ - -c Release \ - -r linux-x64 - -echo -e "\n============================ Deploy ============================\n" - -rsync -v \ - ./bin/Release/$dotnet_version/linux-x64/publish/* \ - $username@$salimax_ip:~/salimax - -echo -e "\n============================ Restart Salimax sevice ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - sudo systemctl restart salimax.service - - -echo -e "\n============================ Print service output ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - journalctl -f -u salimax.service \ No newline at end of file diff --git a/csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh b/csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh deleted file mode 100755 index d2ed6e95d..000000000 --- a/csharp/App/SaliMax/run (SalimnaxProto Meiringen).sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -dotnet_version='net6.0' -salimax_ip='10.2.3.115' -username='ie-entwicklung' - -set -e - -echo -e "\n============================ Build ============================\n" - -dotnet publish \ - ./SaliMax.csproj \ - -p:PublishTrimmed=false \ - -c Release \ - -r linux-x64 - -echo -e "\n============================ Deploy ============================\n" - -rsync -v \ - ./bin/Release/$dotnet_version/linux-x64/publish/* \ - $username@$salimax_ip:~/salimax - -echo -e "\n============================ Restart Salimax sevice ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - sudo systemctl restart salimax.service - - -echo -e "\n============================ Print service output ============================\n" - -ssh -tt \ - $username@$salimax_ip \ - journalctl -f -u salimax.service diff --git a/csharp/App/SaliMax/src/SystemConfig/Config.cs b/csharp/App/SaliMax/src/SystemConfig/Config.cs index 36ef337d5..426bcf294 100644 --- a/csharp/App/SaliMax/src/SystemConfig/Config.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Config.cs @@ -14,34 +14,47 @@ public class Config //TODO: let IE choose from config files (Json) and connect t private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true }; - public required Double MinSoc { get; set; } - public required UnixTime LastEoc { get; set; } - public required Double PConstant { get; set; } - public required Double GridSetPoint { get; set; } - public required Double BatterySelfDischargePower { get; set; } - public required Double HoldSocZone { get; set; } + public required Double MinSoc { get; set; } + public required UnixTime LastEoc { get; set; } + public required Double PConstant { get; set; } + public required Double GridSetPoint { get; set; } + public required Double BatterySelfDischargePower { get; set; } + public required Double HoldSocZone { get; set; } - public required Double MaxDcBusVoltage { get; set; } - public required Double MinDcBusVoltage { get; set; } - public required Double ReferenceDcBusVoltage { get; set; } + public required Double MaxDcLinkVoltageFromAcDc { get; set; } + public required Double MinDcLinkVoltageFromAcDc { get; set; } + public required Double ReferenceDcLinkVoltageFromAcDc { get; set; } - public required DeviceConfig Devices { get; set; } - public required S3Config? S3 { get; set; } + public required Double LowerDcLinkVoltageFromDc { get; set; } + public required Double ReferenceDcLinkVoltageFromDc { get; set; } + public required Double UpperDcLinkVoltageFromDc { get; set; } + + public required Double MaxBatteryChargingCurrent { get; set; } + public required Double MaxBatteryDischargingCurrent { get; set; } + + public required Double MaxChargeBatteryVoltage { get; set; } + public required Double MinDischargeBatteryVoltage { get; set; } + + public required DeviceConfig Devices { get; set; } + public required S3Config? S3 { get; set; } #if DEBUG public static Config Default => new() { - MinSoc = 20, - LastEoc = UnixTime.Epoch, // TODO: remove, use new LastEoc feature from BMS - PConstant = .5, - GridSetPoint = 0, - BatterySelfDischargePower = 200, - HoldSocZone = 1, // TODO: find better name, - MinDcBusVoltage = 690, - ReferenceDcBusVoltage = 750, - MaxDcBusVoltage = 810, + MinSoc = 20, + LastEoc = UnixTime.Epoch, // TODO: remove, use new LastEoc feature from BMS + PConstant = .5, + GridSetPoint = 0, + BatterySelfDischargePower = 200, + HoldSocZone = 1, // TODO: find better name, + MinDcBusVoltage = 690, + ReferenceDcBusVoltage = 750, + MaxDcBusVoltage = 810, + LowerDcBusVoltageWindow = 50, + ReferenceDcBusVoltageWindow = 750, + UpperDcBusVoltageWindow = 50, Devices = new () { TruConvertAcIp = new() { Host = "localhost", Port = 5001}, @@ -58,15 +71,22 @@ public class Config //TODO: let IE choose from config files (Json) and connect t #else public static Config Default => new() { - MinSoc = 20, - LastEoc = UnixTime.Epoch, // TODO: remove, use new LastEoc feature from BMS - PConstant = .5, - GridSetPoint = 0, - BatterySelfDischargePower = 200, - HoldSocZone = 1, // TODO: find better name, - MinDcBusVoltage = 690, - ReferenceDcBusVoltage = 750, - MaxDcBusVoltage = 810, + MinSoc = 20, + LastEoc = UnixTime.Epoch, // TODO: remove, use new LastEoc feature from BMS + PConstant = .5, + GridSetPoint = 0, + BatterySelfDischargePower = 200, + HoldSocZone = 1, // TODO: find better name, + MinDcLinkVoltageFromAcDc = 690, + ReferenceDcLinkVoltageFromAcDc = 750, + MaxDcLinkVoltageFromAcDc = 810, + LowerDcLinkVoltageFromDc = 50, + ReferenceDcLinkVoltageFromDc = 750, + UpperDcLinkVoltageFromDc = 50, + MaxBatteryChargingCurrent = 210, + MaxBatteryDischargingCurrent = 210, + MaxChargeBatteryVoltage = 57, + MinDischargeBatteryVoltage = 0, S3 = new() { Bucket = "saliomameiringen", diff --git a/csharp/App/SaliMax/tunnels.html b/csharp/App/SaliMax/tunnels.html deleted file mode 100644 index 42c64fa50..000000000 --- a/csharp/App/SaliMax/tunnels.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Tunnels - - - -
- Inverter
- DCDC
- Emu Meter
- ADAM
- AMPT
-
- - - \ No newline at end of file diff --git a/csharp/App/SaliMax/tunnelsToProto.sh b/csharp/App/SaliMax/tunnelsToProto.sh deleted file mode 100755 index 0a136e54b..000000000 --- a/csharp/App/SaliMax/tunnelsToProto.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -host=ie-entwicklung@10.2.3.115 - -tunnel() { - name=$1 - ip=$2 - rPort=$3 - lPort=$4 - - echo -n "localhost:$lPort $name " - ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null & - - until nc -vz 127.0.0.1 $lPort 2> /dev/null - do - echo -n . - sleep 0.3 - done - - echo "ok" -} - -echo "" - -tunnel "Trumpf Inverter (http) " 10.0.2.1 80 8001 -tunnel "Trumpf DCDC (http) " 10.0.3.1 80 8002 -tunnel "Ext Emu Meter (http) " 10.0.4.1 80 8003 -tunnel "Int Emu Meter (http) " 10.0.4.2 80 8004 -tunnel "AMPT (http) " 10.0.5.1 8080 8005 - -tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 5001 -tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 5002 -tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 5003 -tunnel "Int Emu Meter " 10.0.4.2 502 5004 -tunnel "AMPT (modbus) " 10.0.5.1 502 5005 -tunnel "Adam " 10.0.1.1 502 5006 -tunnel "Batteries " 127.0.0.1 6855 5007 - - -echo -echo "press any key to close the tunnels ..." -read -r -n 1 -s -kill $(jobs -p) -echo "done" diff --git a/csharp/App/SaliMax/tunnelstoSalimax0002.sh b/csharp/App/SaliMax/tunnelstoSalimax0002.sh deleted file mode 100755 index 98cd400bd..000000000 --- a/csharp/App/SaliMax/tunnelstoSalimax0002.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -host=ie-entwicklung@10.2.4.29 - -tunnel() { - name=$1 - ip=$2 - rPort=$3 - lPort=$4 - - echo -n "localhost:$lPort $name " - ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null & - - until nc -vz 127.0.0.1 $lPort 2> /dev/null - do - echo -n . - sleep 0.3 - done - - echo "ok" -} - -echo "" - -tunnel "Trumpf Inverter (http) " 10.0.2.1 80 9001 -tunnel "Trumpf DCDC (http) " 10.0.3.1 80 9002 -tunnel "Ext Emu Meter (http) " 10.0.4.1 80 9003 -tunnel "Int Emu Meter (http) " 10.0.4.2 80 9004 -tunnel "AMPT (http) " 10.0.5.1 8080 9005 - -#tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 3001 -#tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 3002 -#tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 3003 -#tunnel "Int Emu Meter " 10.0.4.2 502 3004 -#tunnel "AMPT (modbus) " 10.0.5.1 502 3005 -#tunnel "Batteries " 127.0.0.1 6855 3007 - - -echo -echo "press any key to close the tunnels ..." -read -r -n 1 -s -kill $(jobs -p) -echo "done" diff --git a/csharp/App/SaliMax/tunnelstoSalimax0003.sh b/csharp/App/SaliMax/tunnelstoSalimax0003.sh deleted file mode 100755 index a5e184247..000000000 --- a/csharp/App/SaliMax/tunnelstoSalimax0003.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -host=ie-entwicklung@10.2.4.33 - -tunnel() { - name=$1 - ip=$2 - rPort=$3 - lPort=$4 - - echo -n "localhost:$lPort $name " - ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null & - - until nc -vz 127.0.0.1 $lPort 2> /dev/null - do - echo -n . - sleep 0.3 - done - - echo "ok" -} - -echo "" - -tunnel "Trumpf Inverter (http) " 10.0.2.1 80 6001 -tunnel "Trumpf DCDC (http) " 10.0.3.1 80 6002 -tunnel "Ext Emu Meter (http) " 10.0.4.1 80 6003 -tunnel "Int Emu Meter (http) " 10.0.4.2 80 6004 -tunnel "AMPT (http) " 10.0.5.1 8080 6005 - -tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 3001 -tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 3002 -tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 3003 -tunnel "Int Emu Meter " 10.0.4.2 502 3004 -tunnel "AMPT (modbus) " 10.0.5.1 502 3005 -tunnel "Batteries " 127.0.0.1 6855 3007 - - -echo -echo "press any key to close the tunnels ..." -read -r -n 1 -s -kill $(jobs -p) -echo "done" diff --git a/csharp/App/SaliMax/tunnelstoSalimax0001.sh b/csharp/App/SaliMax/tunnelstoSalimaxX.sh similarity index 67% rename from csharp/App/SaliMax/tunnelstoSalimax0001.sh rename to csharp/App/SaliMax/tunnelstoSalimaxX.sh index 8b5fb2697..b3676d486 100755 --- a/csharp/App/SaliMax/tunnelstoSalimax0001.sh +++ b/csharp/App/SaliMax/tunnelstoSalimaxX.sh @@ -1,6 +1,6 @@ #!/bin/bash -host=ie-entwicklung@10.2.3.104 +host="ie-entwicklung@$1" tunnel() { name=$1 @@ -27,17 +27,14 @@ tunnel "Trumpf DCDC (http) " 10.0.3.1 80 7002 tunnel "Ext Emu Meter (http) " 10.0.4.1 80 7003 tunnel "Int Emu Meter (http) " 10.0.4.2 80 7004 tunnel "AMPT (http) " 10.0.5.1 8080 7005 +tunnel "Doepke (http) " 10.0.6.1 80 7006 - -tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 4001 -tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 4002 -tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 4003 -tunnel "Int Emu Meter " 10.0.4.2 502 4004 -tunnel "AMPT (modbus) " 10.0.5.1 502 4005 -tunnel "Adam " 10.0.1.1 502 4006 +tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 5001 +tunnel "Trumpf DCDC (modbus) " 10.0.3.1 502 5002 +tunnel "Ext Emu Meter (modbus) " 10.0.4.1 502 5003 +tunnel "Int Emu Meter " 10.0.4.2 502 5004 +tunnel "AMPT (modbus) " 10.0.5.1 502 5005 tunnel "Batteries " 127.0.0.1 6855 5007 - - echo diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs b/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs index 6db267887..06b6ac12e 100644 --- a/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs +++ b/csharp/Lib/Devices/Battery48TL/Battery48TlRecords.cs @@ -13,9 +13,10 @@ public class Battery48TlRecords Eoc = !empty && records.All(r => r.Eoc); Warnings = records.SelectMany(r => r.Warnings).Distinct().ToList(); Alarms = records.SelectMany(r => r.Alarms).Distinct().ToList(); - Soc = empty ? 0 : records.Min(r => r.Soc.Value); - Temperature = records.Any() ? records.Average(b => b.Temperatures.Cells.Average.Value) : 0; - HeatingCurrent = records.Any() ? records.Sum(b => b.HeatingCurrent) : 0; + Soc = empty ? 0 : records.Average(r => r.Soc.Value); + CurrentMinSoc = empty ? 0 : records.Min(r => r.Soc.Value); + Temperature = records.Any() ? records.Average(b => b.Temperatures.Cells.Average.Value) : 0; + HeatingCurrent = records.Any() ? records.Sum(b => b.HeatingCurrent) : 0; Dc = empty ? DcBus.FromVoltageCurrent(0, 0) @@ -31,6 +32,7 @@ public class Battery48TlRecords public IReadOnlyList Warnings { get; init; } public IReadOnlyList Alarms { get; init; } public Percent Soc { get; init; } + public Percent CurrentMinSoc { get; init; } public Temperature Temperature { get; init; } public Current HeatingCurrent { get; init; } diff --git a/csharp/Lib/Time/Unix/UnixTime.cs b/csharp/Lib/Time/Unix/UnixTime.cs index f771fd01d..b370ad764 100644 --- a/csharp/Lib/Time/Unix/UnixTime.cs +++ b/csharp/Lib/Time/Unix/UnixTime.cs @@ -8,5 +8,7 @@ public readonly partial struct UnixTime { // IMPORTANT: init is necessary for JSON deserializer // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public UInt32 Ticks { get; init; } + public UInt32 Ticks { get; init; } + + public UnixTime RoundTo(UnixTimeSpan span) => Epoch + this / span * span; } \ No newline at end of file diff --git a/typescript/Frontend/lang/de.json b/typescript/Frontend/lang/de.json deleted file mode 100644 index 903777cdf..000000000 --- a/typescript/Frontend/lang/de.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "Information": { - "defaultMessage": "Information" - }, - "addNewChild": { - "defaultMessage": "Neues Kind hinzufügen" - }, - "addNewDialogButton": { - "defaultMessage": "Neue Dialogschaltfläche hinzufügen" - }, - "addUser": { - "defaultMessage": "Nutzer erstellen" - }, - "alarms": { - "defaultMessage": "Alarme" - }, - "applyChanges": { - "defaultMessage": "Änderungen speichern" - }, - "country": { - "defaultMessage": "Land" - }, - "createNewFolder": { - "defaultMessage": "Neuen Ordner erstellen" - }, - "createNewUser": { - "defaultMessage": "Neuen Nutzer erstellen" - }, - "customerName": { - "defaultMessage": "Kundenname" - }, - "email": { - "defaultMessage": "Email" - }, - "english": { - "defaultMessage": "Englisch" - }, - "error": { - }, - "folder": { - "defaultMessage": "Ordner" - }, - "german": { - "defaultMessage": "Deutsch" - }, - "groupTabs": { - "defaultMessage": "Gruppen" - }, - "groupTree": { - "defaultMessage": "Gruppenbaum" - }, - "information": { - "defaultMessage": "Information" - }, - "inheritedAccess": { - "defaultMessage": "Vererbter Zugriff von" - }, - "installation": { - "defaultMessage": "Installation" - }, - "installationTabs": { - "defaultMessage": "Installationen" - }, - "installations": { - "defaultMessage": "Installationen" - }, - "lastWeek": { - "defaultMessage": "Letzte Woche" - }, - "location": { - "defaultMessage": "Standort" - }, - "log": { - "defaultMessage": "Logbuch" - }, - "logout": { - "defaultMessage": "Abmelden" - }, - "makeASelection": { - "defaultMessage": "Bitte wählen Sie links eine Auswahl" - }, - "manageAccess": { - "defaultMessage": "Zugriff verwalten" - }, - "move": { - "defaultMessage": "Verschieben" - }, - "moveTo": { - "defaultMessage": "Verschieben zu" - }, - "moveTree": { - "defaultMessage": "Baum verschieben" - }, - "name": { - "defaultMessage": "Name" - }, - "navigationTabs": { - "defaultMessage": "Navigation" - }, - "orderNumbers": { - "defaultMessage": "Bestellnummer" - }, - "region": { - "defaultMessage": "Region" - }, - "requiredLocation": { - "defaultMessage": "Standort ist erforderlich" - }, - "requiredName": { - "defaultMessage": "Name ist erforderlich" - }, - "requiredRegion": { - "defaultMessage": "Region ist erforderlich" - }, - "search": { - "defaultMessage": "Suche" - }, - "submit": { - "defaultMessage": "Senden" - }, - "updateFolderErrorMessage": { - "defaultMessage": "Fehler, Ordner kann nicht aktualisiert werden" - }, - "updatedSuccessfully": { - "defaultMessage": "Erfolgreich aktualisiert" - }, - "user": { - "defaultMessage": "Nutzer" - }, - "userTabs": { - "defaultMessage": "Nutzer" - }, - "users": { - "defaultMessage": "Nutzer" - } -} diff --git a/typescript/Frontend/lang/en.json b/typescript/Frontend/lang/en.json deleted file mode 100644 index 57cfe6a64..000000000 --- a/typescript/Frontend/lang/en.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "Information": "Information", - "addNewChild": "Add new child", - "addNewDialogButton": "Add new dialog button", - "addUser": "Create user", - "alarms": "Alarms", - "applyChanges": "Apply changes", - "country": "Country", - "createNewFolder": "Create new folder", - "createNewUser": "Create new user", - "customerName": "Customer", - "email": "Email", - "english": "English", - "error": {}, - "folder": "Folder", - "german": "German", - "groupTabs": "Group tabs", - "groupTree": "Group tree", - "information": "Information", - "inheritedAccess": "Inherited access from", - "installation": "Installation", - "installationTabs": "Installation tabs", - "installations": "Installations", - "lastWeek": "Last week", - "location": "Location", - "log": "Log", - "logout": "Logout", - "makeASelection": "Please make a selection on the left", - "manageAccess": "Manage access", - "move": "Move", - "moveTo": "Move to", - "moveTree": "Move tree", - "name": "Name", - "navigationTabs": "Navigation tabs", - "orderNumbers": "Order number", - "region": "Region", - "requiredLocation": "Location is required", - "requiredName": "Name is required", - "requiredRegion": "Region is required", - "search": "Search", - "submit": "Submit", - "updateFolderErrorMessage": "Couldn't update folder, an error occured", - "updatedSuccessfully": "Updated successfully", - "user": "User", - "userTabs": "user tabs", - "users": "Users" -} diff --git a/typescript/Frontend/lang/fr.json b/typescript/Frontend/lang/fr.json deleted file mode 100644 index a1178e87d..000000000 --- a/typescript/Frontend/lang/fr.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "Information": { - "defaultMessage": "Informations" - }, - "addNewChild": { - "defaultMessage": "Ajouter un nouvel enfant" - }, - "addNewDialogButton": { - "defaultMessage": "Ajouter un nouveau bouton de dialogue" - }, - "addUser": { - "defaultMessage": "Créer un utilisateur" - }, - "alarms": { - "defaultMessage": "Alarmes" - }, - "applyChanges": { - "defaultMessage": "Appliquer les modifications" - }, - "country": { - "defaultMessage": "Pays" - }, - "createNewFolder": { - "defaultMessage": "Créer un nouveau dossier" - }, - "createNewUser": { - "defaultMessage": "Créer un nouvel utilisateur" - }, - "customerName": { - "defaultMessage": "Nom du client" - }, - "email": { - "defaultMessage": "E-mail" - }, - "english": { - "defaultMessage": "Anglais" - }, - "error": { - }, - "folder": { - "defaultMessage": "Dossier" - }, - "german": { - "defaultMessage": "Allemand" - }, - "groupTabs": { - "defaultMessage": "Onglets de groupe" - }, - "groupTree": { - "defaultMessage": "Arbre de groupe" - }, - "information": { - "defaultMessage": "Informations" - }, - "inheritedAccess": { - "defaultMessage": "Accès hérité de" - }, - "installation": { - "defaultMessage": "Installation" - }, - "installationTabs": { - "defaultMessage": "Onglets d'installation" - }, - "installations": { - "defaultMessage": "Installations" - }, - "lastWeek": { - "defaultMessage": "La semaine dernière" - }, - "location": { - "defaultMessage": "Localisation" - }, - "log": { - "defaultMessage": "Journal" - }, - "logout": { - "defaultMessage": "Déconnexion" - }, - "makeASelection": { - "defaultMessage": "Veuillez faire une sélection à gauche" - }, - "manageAccess": { - "defaultMessage": "Gérer l'accès" - }, - "move": { - "defaultMessage": "Déplacer" - }, - "moveTo": { - "defaultMessage": "Déplacer à" - }, - "moveTree": { - "defaultMessage": "Déplacer l'arbre" - }, - "name": { - "defaultMessage": "Nom" - }, - "navigationTabs": { - "defaultMessage": "Onglets de navigation" - }, - "orderNumbers": { - "defaultMessage": "Numéro de commande" - }, - "region": { - "defaultMessage": "Région" - }, - "requiredLocation": { - "defaultMessage": "L'emplacement est requis" - }, - "requiredName": { - "defaultMessage": "Le nom est obligatoire" - }, - "requiredRegion": { - "defaultMessage": "La région est obligatoire" - }, - "search": { - "defaultMessage": "Recherche" - }, - "submit": { - "defaultMessage": "Soumettre" - }, - "updateFolderErrorMessage": { - "defaultMessage": "Une erreur s'est produite, impossible de mettre à jour le dossier." - }, - "updatedSuccessfully": { - "defaultMessage": "Mise à jour réussie" - }, - "user": { - "defaultMessage": "Utilisateur" - }, - "userTabs": { - "defaultMessage": "Onglets utilisateurs" - }, - "users": { - "defaultMessage": "Utilisateurs" - } -} diff --git a/typescript/Frontend/src/App.css b/typescript/Frontend/src/App.css deleted file mode 100644 index cde4cdc47..000000000 --- a/typescript/Frontend/src/App.css +++ /dev/null @@ -1,39 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/typescript/Frontend/src/App.test.tsx b/typescript/Frontend/src/App.test.tsx deleted file mode 100644 index 2a68616d9..000000000 --- a/typescript/Frontend/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/typescript/Frontend/src/App.tsx b/typescript/Frontend/src/App.tsx index 52117d18a..ea3ca8592 100644 --- a/typescript/Frontend/src/App.tsx +++ b/typescript/Frontend/src/App.tsx @@ -4,7 +4,7 @@ import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { Container, Grid, Box } from "@mui/material"; import routes from "./routes.json"; -import { IntlProvider, MessageFormatElement } from "react-intl"; +import { IntlProvider } from "react-intl"; import { useContext, useState } from "react"; import en from "./lang/en.json"; import de from "./lang/de.json"; @@ -28,9 +28,9 @@ const App = () => { const getTranslations = () => { switch (language) { case "en": - return de; - case "de": return en; + case "de": + return de; case "fr": return fr; } @@ -42,7 +42,6 @@ const App = () => { if (token && getCurrentUser()?.mustResetPassword) { return ; } - console.log("lang", language); return ( { bgcolor={colors.greyLight} pt={3} borderTop="2px solid" - borderColor=" #a8b0be" + borderColor={colors.borderColor} flex="1 0 auto" > diff --git a/typescript/Frontend/src/Login.tsx b/typescript/Frontend/src/Login.tsx index f23bcb958..8718f5ef1 100644 --- a/typescript/Frontend/src/Login.tsx +++ b/typescript/Frontend/src/Login.tsx @@ -1,11 +1,5 @@ import React, { useContext, useState } from "react"; -import { - Alert, - CircularProgress, - Grid, - Typography, - useTheme, -} from "@mui/material"; +import { Alert, CircularProgress, Grid, useTheme } from "@mui/material"; import Container from "@mui/material/Container"; import { axiosConfigWithoutToken } from "./config/axiosConfig"; import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield"; diff --git a/typescript/Frontend/src/components/Context/GroupContextProvider.tsx b/typescript/Frontend/src/components/Context/GroupContextProvider.tsx index 8032ebf48..7d29ca104 100644 --- a/typescript/Frontend/src/components/Context/GroupContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/GroupContextProvider.tsx @@ -2,8 +2,9 @@ import { createContext, ReactNode, useCallback, useState } from "react"; import axiosConfig from "../../config/axiosConfig"; import { transformArrayToTree } from "../../util/group.util"; import { I_Folder, I_Installation } from "../../util/types"; +import { AxiosError } from "axios"; -interface GroupContextProviderProps { +interface I_GroupContextProviderProps { currentType: string; setCurrentType: (value: string) => void; data: (I_Folder | I_Installation)[]; @@ -11,10 +12,10 @@ interface GroupContextProviderProps { fetchData: () => Promise; loading: boolean; setLoading: (value: boolean) => void; - getError: boolean; + getError?: AxiosError; } -export const GroupContext = createContext({ +export const GroupContext = createContext({ currentType: "", setCurrentType: () => {}, data: [], @@ -22,14 +23,14 @@ export const GroupContext = createContext({ fetchData: () => Promise.resolve(), loading: false, setLoading: () => {}, - getError: false, + getError: undefined, }); const GroupContextProvider = ({ children }: { children: ReactNode }) => { const [currentType, setCurrentType] = useState(""); const [data, setData] = useState<(I_Folder | I_Installation)[]>([]); const [loading, setLoading] = useState(false); - const [getError, setGetError] = useState(false); + const [getError, setGetError] = useState(); const fetchData = useCallback(async () => { setLoading(true); diff --git a/typescript/Frontend/src/components/Context/InstallationsContextProvider.tsx b/typescript/Frontend/src/components/Context/InstallationsContextProvider.tsx index 37a74533b..e75462c99 100644 --- a/typescript/Frontend/src/components/Context/InstallationsContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/InstallationsContextProvider.tsx @@ -3,7 +3,7 @@ import { createContext, ReactNode, useCallback, useState } from "react"; import axiosConfig from "../../config/axiosConfig"; import { I_Installation } from "../../util/types"; -interface InstallationContextProviderProps { +interface I_InstallationContextProviderProps { data: I_Installation[]; setData: (value: I_Installation[]) => void; fetchData: () => Promise; @@ -14,7 +14,7 @@ interface InstallationContextProviderProps { } export const InstallationsContext = - createContext({ + createContext({ data: [], setData: () => {}, fetchData: () => Promise.resolve(), diff --git a/typescript/Frontend/src/components/Context/LogContextProvider.tsx b/typescript/Frontend/src/components/Context/LogContextProvider.tsx index 6b09c5672..ac7ea4f7d 100644 --- a/typescript/Frontend/src/components/Context/LogContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/LogContextProvider.tsx @@ -1,16 +1,16 @@ import { createContext, ReactNode, useState } from "react"; -import { TreeElement } from "../Installations/Log/CheckboxTree"; +import { I_TreeElement } from "../Installations/Log/CheckboxTree"; import React from "react"; -interface LogContextProviderProps { - toggles: TreeElement[] | null; - setToggles: (value: TreeElement[]) => void; +interface I_LogContextProviderProps { + toggles: I_TreeElement[] | null; + setToggles: (value: I_TreeElement[]) => void; checkedToggles: string[]; setChecked: (newValue: string) => void; removeChecked: (value: string) => void; } -export const LogContext = createContext({ +export const LogContext = createContext({ toggles: [], setToggles: () => {}, checkedToggles: [], @@ -19,7 +19,7 @@ export const LogContext = createContext({ }); const LogContextProvider = ({ children }: { children: ReactNode }) => { - const [toggles, setToggles] = useState(null); + const [toggles, setToggles] = useState(null); const [checkedToggles, setCheckedToggles] = useState([]); const setChecked = (newValue: string) => { diff --git a/typescript/Frontend/src/components/Context/S3CredentialsContextProvider.tsx b/typescript/Frontend/src/components/Context/S3CredentialsContextProvider.tsx index 133031e6a..32dffa734 100644 --- a/typescript/Frontend/src/components/Context/S3CredentialsContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/S3CredentialsContextProvider.tsx @@ -1,21 +1,20 @@ import { createContext, ReactNode, useState } from "react"; -import { I_User } from "../../util/user.util"; -import { I_Installation, S3Credentials } from "../../util/types"; +import { I_S3Credentials } from "../../util/types"; import { UnixTime } from "../../dataCache/time"; import { FetchResult } from "../../dataCache/dataCache"; import { DataRecord } from "../../dataCache/data"; import { S3Access } from "../../dataCache/S3/S3Access"; import { parseCsv } from "../../util/graph.util"; -interface S3CredentialsContextProviderProps { - s3Credentials?: S3Credentials; - saveS3Credentials: (credentials: S3Credentials, id: string) => void; +interface I_S3CredentialsContextProviderProps { + s3Credentials?: I_S3Credentials; + saveS3Credentials: (credentials: I_S3Credentials, id: string) => void; fetchData: (timestamp: UnixTime) => Promise>; } export const S3CredentialsContext = - createContext({ - s3Credentials: {} as S3Credentials, + createContext({ + s3Credentials: {} as I_S3Credentials, saveS3Credentials: () => {}, fetchData: () => ({} as Promise>), }); @@ -25,18 +24,11 @@ const S3CredentialsContextProvider = ({ }: { children: ReactNode; }) => { - const [s3Credentials, setS3Credentials] = useState(); + const [s3Credentials, setS3Credentials] = useState(); - const saveS3Credentials = (credentials: S3Credentials, id: string) => { + const saveS3Credentials = (credentials: I_S3Credentials, id: string) => { const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c10d"; setS3Credentials({ s3Bucket, ...credentials }); - /* setS3Credentials({ - s3Region: "sos-ch-dk-2", - s3Provider: "exo.io", - s3Key: "EXO15c0bf710e158e9b83270f0a", - s3Secret: "Dd5jYSiZtt_Zt5Ba5mDmaiLCdASUaKLfduSKY-SU-lg", - s3Bucket: "saliomameiringen", - });*/ }; const fetchData = (timestamp: UnixTime): Promise> => { @@ -63,7 +55,6 @@ const S3CredentialsContextProvider = ({ } }) .catch((e) => { - console.log("catch", e); return Promise.resolve(FetchResult.tryLater); }); } diff --git a/typescript/Frontend/src/components/Context/UserContextProvider.tsx b/typescript/Frontend/src/components/Context/UserContextProvider.tsx index 6b9f35adb..2a1ea29b5 100644 --- a/typescript/Frontend/src/components/Context/UserContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/UserContextProvider.tsx @@ -1,14 +1,14 @@ import { createContext, ReactNode, useState } from "react"; import { I_User } from "../../util/user.util"; -interface InstallationContextProviderProps { +interface I_InstallationContextProviderProps { currentUser?: I_User; setCurrentUser: (value: I_User) => void; saveCurrentUser: (user: I_User) => void; getCurrentUser: () => I_User; } -export const UserContext = createContext({ +export const UserContext = createContext({ currentUser: {} as I_User, setCurrentUser: () => {}, saveCurrentUser: () => {}, diff --git a/typescript/Frontend/src/components/Context/UsersContextProvider.tsx b/typescript/Frontend/src/components/Context/UsersContextProvider.tsx index 9952b2b7d..b6fce138c 100644 --- a/typescript/Frontend/src/components/Context/UsersContextProvider.tsx +++ b/typescript/Frontend/src/components/Context/UsersContextProvider.tsx @@ -8,14 +8,14 @@ import { } from "react"; import { useParams } from "react-router-dom"; import axiosConfig from "../../config/axiosConfig"; -import { I_User, UserWithInheritedAccess } from "../../util/user.util"; +import { I_User, I_UserWithInheritedAccess } from "../../util/user.util"; import { GroupContext } from "./GroupContextProvider"; -interface UsersContextProviderProps { +interface I_UsersContextProviderProps { directAccessUsers: I_User[]; setDirectAccessUsers: (value: I_User[]) => void; - inheritedAccessUsers: UserWithInheritedAccess[]; - setInheritedAccessUsers: (value: UserWithInheritedAccess[]) => void; + inheritedAccessUsers: I_UserWithInheritedAccess[]; + setInheritedAccessUsers: (value: I_UserWithInheritedAccess[]) => void; availableUsers: I_User[]; setAvailableUsers: (value: I_User[]) => void; getAvailableUsersForResource: () => I_User[]; @@ -24,7 +24,7 @@ interface UsersContextProviderProps { fetchAvailableUsers: () => Promise; } -export const UsersContext = createContext({ +export const UsersContext = createContext({ directAccessUsers: [], setDirectAccessUsers: () => {}, inheritedAccessUsers: [], @@ -48,7 +48,7 @@ export const UsersContext = createContext({ const UsersContextProvider = ({ children }: { children: ReactNode }) => { const [directAccessUsers, setDirectAccessUsers] = useState([]); const [inheritedAccessUsers, setInheritedAccessUsers] = useState< - UserWithInheritedAccess[] + I_UserWithInheritedAccess[] >([]); const [availableUsers, setAvailableUsers] = useState([]); diff --git a/typescript/Frontend/src/components/Groups/AccessManagement/AccessManagement.tsx b/typescript/Frontend/src/components/Groups/AccessManagement/AccessManagement.tsx index fa8c5da02..c2ba3a750 100644 --- a/typescript/Frontend/src/components/Groups/AccessManagement/AccessManagement.tsx +++ b/typescript/Frontend/src/components/Groups/AccessManagement/AccessManagement.tsx @@ -1,7 +1,7 @@ import { Grid } from "@mui/material"; import UsersContextProvider from "../../Context/UsersContextProvider"; import AvailableUserDialog from "./AvailableUserDialog"; -import InnovenergyList from "./InnovenergyList"; +import InnovenergyList from "../../Layout/InnovenergyList"; import UsersWithDirectAccess from "./UsersWithDirectAccess"; import UsersWithInheritedAccess from "./UsersWithInheritedAccess"; import { useContext } from "react"; diff --git a/typescript/Frontend/src/components/Groups/AccessManagement/AvailableUserDialog.tsx b/typescript/Frontend/src/components/Groups/AccessManagement/AvailableUserDialog.tsx index 9d576d323..cbdf00408 100644 --- a/typescript/Frontend/src/components/Groups/AccessManagement/AvailableUserDialog.tsx +++ b/typescript/Frontend/src/components/Groups/AccessManagement/AvailableUserDialog.tsx @@ -4,7 +4,6 @@ import { DialogContent, DialogTitle, TextField, - colors, Alert, } from "@mui/material"; import DialogActions from "@mui/material/DialogActions"; diff --git a/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithDirectAccess.tsx b/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithDirectAccess.tsx index 360d2727f..79ddc89ce 100644 --- a/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithDirectAccess.tsx +++ b/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithDirectAccess.tsx @@ -18,11 +18,11 @@ import { UserContext } from "../../Context/UserContextProvider"; import { FormattedMessage } from "react-intl"; const UsersWithDirectAccess = () => { + const { id } = useParams(); + const [error, setError] = useState(); const { fetchUsersWithDirectAccessForResource, directAccessUsers } = useContext(UsersContext); - const [error, setError] = useState(); const { currentType } = useContext(GroupContext); - const { id } = useParams(); const { getCurrentUser } = useContext(UserContext); useEffect(() => { diff --git a/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithInheritedAccess.tsx b/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithInheritedAccess.tsx index 94b2c9827..b22301e9d 100644 --- a/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithInheritedAccess.tsx +++ b/typescript/Frontend/src/components/Groups/AccessManagement/UsersWithInheritedAccess.tsx @@ -10,7 +10,7 @@ import { Fragment, useContext, useEffect } from "react"; import { Link } from "react-router-dom"; import { filterDuplicateUsers, - UserWithInheritedAccess, + I_UserWithInheritedAccess, } from "../../../util/user.util"; import routes from "../../../routes.json"; import PersonIcon from "@mui/icons-material/Person"; @@ -29,7 +29,7 @@ const UsersWithInheritedAccess = () => { return ( <> {filterDuplicateUsers(inheritedAccessUsers).map( - ({ user, folderName }: UserWithInheritedAccess) => { + ({ user, folderName }: I_UserWithInheritedAccess) => { return ( diff --git a/typescript/Frontend/src/components/Groups/AddNewButtons.tsx b/typescript/Frontend/src/components/Groups/AddNewButtons.tsx index 7f43adee0..19f549ab4 100644 --- a/typescript/Frontend/src/components/Groups/AddNewButtons.tsx +++ b/typescript/Frontend/src/components/Groups/AddNewButtons.tsx @@ -1,13 +1,9 @@ -import { Dialog, DialogContent, DialogTitle, IconButton } from "@mui/material"; -import { useState } from "react"; -import { FormattedMessage, useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import axiosConfig from "../../config/axiosConfig"; import { I_Folder, I_Installation } from "../../util/types"; -import FolderForm from "./FolderForm"; -import CloseIcon from "@mui/icons-material/Close"; -import InnovenergyButton from "../Layout/InnovenergyButton"; -import InstallationForm from "../Installations/InstallationForm"; -import AddNewDialog from "./AddNew"; +import FolderForm from "./Detail/FolderForm"; +import InstallationForm from "../Installations/Detail/InstallationForm"; +import AddNewDialog from "./AddNewDialog"; interface AddNewDialogProps { values: I_Folder | I_Installation; @@ -15,8 +11,6 @@ interface AddNewDialogProps { } const AddNewButtons = (props: AddNewDialogProps) => { - const [open, setOpen] = useState(false); - const handleFolderSubmit = (data: I_Folder, childData: Partial) => { return axiosConfig .post("/CreateFolder", { @@ -24,7 +18,6 @@ const AddNewButtons = (props: AddNewDialogProps) => { parentId: data.id, }) .then((res) => { - setOpen(false); return res; }); }; @@ -39,7 +32,6 @@ const AddNewButtons = (props: AddNewDialogProps) => { parentId: data.id, }) .then((res) => { - setOpen(false); return res; }); }; diff --git a/typescript/Frontend/src/components/Groups/AddNew.tsx b/typescript/Frontend/src/components/Groups/AddNewDialog.tsx similarity index 95% rename from typescript/Frontend/src/components/Groups/AddNew.tsx rename to typescript/Frontend/src/components/Groups/AddNewDialog.tsx index 9215ee72b..0de1f9e58 100644 --- a/typescript/Frontend/src/components/Groups/AddNew.tsx +++ b/typescript/Frontend/src/components/Groups/AddNewDialog.tsx @@ -5,15 +5,15 @@ import CloseIcon from "@mui/icons-material/Close"; import { ReactNode, useState } from "react"; import InnovenergyButton from "../Layout/InnovenergyButton"; -interface AddNewDialogProps { +interface I_AddNewDialogProps { form: ReactNode; message: ReactNode; id: number; } -const AddNewDialog = (props: AddNewDialogProps) => { - const [open, setOpen] = useState(false); +const AddNewDialog = (props: I_AddNewDialogProps) => { const { form, id, message } = props; + const [open, setOpen] = useState(false); const intl = useIntl(); return ( <> diff --git a/typescript/Frontend/src/components/Groups/Folder.tsx b/typescript/Frontend/src/components/Groups/Detail/Folder.tsx similarity index 88% rename from typescript/Frontend/src/components/Groups/Folder.tsx rename to typescript/Frontend/src/components/Groups/Detail/Folder.tsx index 991d7d2a4..3666411d9 100644 --- a/typescript/Frontend/src/components/Groups/Folder.tsx +++ b/typescript/Frontend/src/components/Groups/Detail/Folder.tsx @@ -2,12 +2,11 @@ import { Box, CircularProgress, Alert, useTheme } from "@mui/material"; import { AxiosError } from "axios"; import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; -import axiosConfig from "../../config/axiosConfig"; -import { I_Folder } from "../../util/types"; -import AddNewButtons from "./AddNewButtons"; +import axiosConfig from "../../../config/axiosConfig"; +import { I_Folder } from "../../../util/types"; +import AddNewButtons from "../AddNewButtons"; import FolderForm from "./FolderForm"; -import MoveDialog from "./Tree/MoveDialog"; -import { colors } from "index"; +import MoveDialog from "./MoveDialog"; const Folder = () => { const { id } = useParams(); @@ -43,7 +42,7 @@ const Folder = () => { { borderBottomLeftRadius: 4, borderBottomRightRadius: 4, borderColor: theme.palette.text.disabled, - marginTop: 0.05, }} > { const { values, additionalButtons, handleSubmit } = props; - const intl = useIntl(); - const { fetchData } = useContext(GroupContext); - const { getCurrentUser } = useContext(UserContext); const [snackbarOpen, setSnackbarOpen] = useState(false); const [error, setError] = useState(); const [loading, setLoading] = useState(false); + const intl = useIntl(); + const theme = useTheme(); + const { fetchData } = useContext(GroupContext); + const { getCurrentUser } = useContext(UserContext); + const readOnly = !getCurrentUser().hasWriteAccess; const formik = useFormik({ @@ -55,7 +55,6 @@ const FolderForm = (props: I_CustomerFormProps) => { }); }, }); - const theme = useTheme(); const rows: I_InnovenergyTextfieldProps[] = [ { @@ -83,33 +82,31 @@ const FolderForm = (props: I_CustomerFormProps) => { ]; return ( - <> -
- - - {loading && ( - - )} - {!readOnly && - additionalButtons && - additionalButtons.map((button) => button)} - {!readOnly && ( - - - - )} - - - - +
+ + + {loading && ( + + )} + {!readOnly && + additionalButtons && + additionalButtons.map((button) => button)} + {!readOnly && ( + + + + )} + + + ); }; diff --git a/typescript/Frontend/src/components/Groups/Tree/MoveDialog.tsx b/typescript/Frontend/src/components/Groups/Detail/MoveDialog.tsx similarity index 97% rename from typescript/Frontend/src/components/Groups/Tree/MoveDialog.tsx rename to typescript/Frontend/src/components/Groups/Detail/MoveDialog.tsx index 8df1da56d..0e68ba542 100644 --- a/typescript/Frontend/src/components/Groups/Tree/MoveDialog.tsx +++ b/typescript/Frontend/src/components/Groups/Detail/MoveDialog.tsx @@ -9,21 +9,21 @@ import { GroupContext } from "../../Context/GroupContextProvider"; import InnovenergyButton from "../../Layout/InnovenergyButton"; import MoveTree from "./MoveTree"; -interface MoveDialogProps { +interface I_MoveDialogProps { values: I_Folder; } -const MoveDialog = (props: MoveDialogProps) => { +const MoveDialog = (props: I_MoveDialogProps) => { const { values } = props; const [open, setOpen] = useState(false); - const [selectedParentId, setSelectedParentId] = useState( values.parentId ); + const [error, setError] = useState(); + const { fetchData, currentType } = useContext(GroupContext); const { id } = useParams(); - const [error, setError] = useState(); const handleMove = () => { const route = diff --git a/typescript/Frontend/src/components/Groups/Tree/MoveTree.tsx b/typescript/Frontend/src/components/Groups/Detail/MoveTree.tsx similarity index 90% rename from typescript/Frontend/src/components/Groups/Tree/MoveTree.tsx rename to typescript/Frontend/src/components/Groups/Detail/MoveTree.tsx index bf7b5744e..762f087fa 100644 --- a/typescript/Frontend/src/components/Groups/Tree/MoveTree.tsx +++ b/typescript/Frontend/src/components/Groups/Detail/MoveTree.tsx @@ -6,14 +6,14 @@ import { I_Folder, I_Installation } from "../../../util/types"; import { GroupContext } from "../../Context/GroupContextProvider"; import { instanceOfFolder } from "../../../util/group.util"; import { useIntl } from "react-intl"; -import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem"; +import InnovenergyTreeItem from "../../Layout/InnovenergyTreeItem"; -interface MoveTreeProps { +interface I_MoveTreeProps { setSelectedParentId: (value: number) => void; parentId: number; } -const MoveTree = (props: MoveTreeProps) => { +const MoveTree = (props: I_MoveTreeProps) => { const { data } = useContext(GroupContext); const intl = useIntl(); @@ -29,7 +29,7 @@ const MoveTree = (props: MoveTreeProps) => { .filter((element) => element.type === "Folder") .map((element) => { return ( - { label={element.name} > {getNodes(element)} - + ); }); }; diff --git a/typescript/Frontend/src/components/Groups/GroupTabs.tsx b/typescript/Frontend/src/components/Groups/GroupTabs.tsx index e53311afa..9e1ec6d73 100644 --- a/typescript/Frontend/src/components/Groups/GroupTabs.tsx +++ b/typescript/Frontend/src/components/Groups/GroupTabs.tsx @@ -12,6 +12,8 @@ import { useTheme } from "@mui/material"; const GroupTabs = () => { const theme = useTheme(); + const intl = useIntl(); + const { currentType } = useContext(GroupContext); const routeMatch = useRouteMatch([ routes.installations + routes.tree + routes.folder + ":id", routes.installations + routes.tree + routes.manageAccess + ":id", @@ -19,8 +21,6 @@ const GroupTabs = () => { ]); const id = routeMatch?.params?.id; - const intl = useIntl(); - const { currentType } = useContext(GroupContext); if (id) { return ( @@ -29,14 +29,15 @@ const GroupTabs = () => { {currentType === "Folder" ? ( { to={routes.folder + id} /> ) : ( - { sx={{ "&.Mui-selected": { color: colors.black, - backgroundColor: theme.palette.primary.dark, + backgroundColor: theme.palette.primary.dark, borderColor: theme.palette.text.disabled, borderBottom: 0, - mb:-1/8, - }}} + mb: -1 / 8, + }, + }} id="styled-tab-manage-access" label={intl.formatMessage({ id: "manageAccess", diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx b/typescript/Frontend/src/components/Groups/GroupTree.tsx similarity index 70% rename from typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx rename to typescript/Frontend/src/components/Groups/GroupTree.tsx index 26c85dc8a..c9af1c0fd 100644 --- a/typescript/Frontend/src/components/Groups/Tree/GroupTree.tsx +++ b/typescript/Frontend/src/components/Groups/GroupTree.tsx @@ -1,20 +1,20 @@ import TreeView from "@mui/lab/TreeView"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import { ReactNode, useContext, useEffect, useState } from "react"; -import { TreeItem } from "@mui/lab"; -import { I_Folder, I_Installation } from "../../../util/types"; +import React, { ReactNode, useContext, useEffect, useState } from "react"; +import { I_Folder, I_Installation } from "../../util/types"; import { Link } from "react-router-dom"; -import routes from "../../../routes.json"; -import { GroupContext } from "../../Context/GroupContextProvider"; -import { instanceOfFolder } from "../../../util/group.util"; -import { Grid, CircularProgress, useTheme } from "@mui/material"; +import routes from "../../routes.json"; +import { GroupContext } from "../Context/GroupContextProvider"; +import { instanceOfFolder } from "../../util/group.util"; +import { Grid, CircularProgress, useTheme, Alert } from "@mui/material"; import { useIntl } from "react-intl"; -import { colors } from "../../.."; -import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem"; +import InnovenergyTreeItem from "../Layout/InnovenergyTreeItem"; +import TypeIcon from "./TypeIcon"; const GroupTree = () => { - const { setCurrentType, fetchData, data, loading } = useContext(GroupContext); + const { setCurrentType, fetchData, data, loading, getError } = + useContext(GroupContext); const [openNodes, setOpenNodes] = useState([]); const [selected, setSelected] = useState(""); const intl = useIntl(); @@ -48,19 +48,27 @@ const GroupTree = () => { }} draggable={false} > - setCurrentType(element.type)} + label={ + <> + {element.name} + + } + onClick={() => { + setCurrentType(element.type); + }} + sx={{ ".MuiTreeItem-label": { display: "flex" } }} > {getNodes(element)} - + ); }); }; + if (loading) { return ( @@ -68,6 +76,9 @@ const GroupTree = () => { ); } + if (getError) { + return {getError.message} ; + } if (data) { return ( { const id = routeMatch?.params?.id; useEffect(() => { - // TODO remove if getInstallation(id); }, [id]); return ( diff --git a/typescript/Frontend/src/components/Groups/Tree/GroupTree.scss b/typescript/Frontend/src/components/Groups/Tree/GroupTree.scss deleted file mode 100644 index 2c7cc8fa1..000000000 --- a/typescript/Frontend/src/components/Groups/Tree/GroupTree.scss +++ /dev/null @@ -1,4 +0,0 @@ -.groupTreeLink { - text-decoration: "none"; - color: "red"; -} \ No newline at end of file diff --git a/typescript/Frontend/src/components/Groups/TypeIcon.tsx b/typescript/Frontend/src/components/Groups/TypeIcon.tsx index c86b4afd7..bf4b42ce4 100644 --- a/typescript/Frontend/src/components/Groups/TypeIcon.tsx +++ b/typescript/Frontend/src/components/Groups/TypeIcon.tsx @@ -1,14 +1,19 @@ import FolderIcon from "@mui/icons-material/Folder"; import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; -interface TypeIconProps { +interface I_TypeIconProps { type: string | undefined; } -const TypeIcon = (props: TypeIconProps) => { + +const TypeIcon = (props: I_TypeIconProps) => { return ( -
- {props.type === "Folder" ? : } -
+ <> + {props.type === "Folder" ? ( + + ) : ( + + )} + ); }; diff --git a/typescript/Frontend/src/components/Installations/Installation.tsx b/typescript/Frontend/src/components/Installations/Detail/Installation.tsx similarity index 83% rename from typescript/Frontend/src/components/Installations/Installation.tsx rename to typescript/Frontend/src/components/Installations/Detail/Installation.tsx index c1b1b2eae..008556738 100644 --- a/typescript/Frontend/src/components/Installations/Installation.tsx +++ b/typescript/Frontend/src/components/Installations/Detail/Installation.tsx @@ -1,13 +1,10 @@ import { Alert, Box, CircularProgress, useTheme } from "@mui/material"; import { AxiosError } from "axios"; -import { useContext, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import axiosConfig from "../../config/axiosConfig"; -import { I_Folder, I_Installation } from "../../util/types"; +import axiosConfig from "../../../config/axiosConfig"; +import { I_Folder, I_Installation } from "../../../util/types"; import InstallationForm from "./InstallationForm"; -import { colors } from "../.."; -import { S3CredentialsContext } from "../Context/S3CredentialsContextProvider"; -import MoveDialog from "../Groups/Tree/MoveDialog"; +import MoveDialog from "../../Groups/Detail/MoveDialog"; interface I_InstallationProps { loading?: boolean; @@ -31,7 +28,7 @@ const Installation = (props: I_InstallationProps) => { { {values.s3WriteKey &&
{`Write key: ${values.s3WriteKey}`}
} diff --git a/typescript/Frontend/src/components/Installations/InstallationForm.tsx b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx similarity index 79% rename from typescript/Frontend/src/components/Installations/InstallationForm.tsx rename to typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx index b99675b09..e780ab854 100644 --- a/typescript/Frontend/src/components/Installations/InstallationForm.tsx +++ b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx @@ -2,18 +2,15 @@ import { Alert, Grid, Snackbar } from "@mui/material"; import { useFormik } from "formik"; import { ReactNode, useContext, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import axiosConfig from "../../config/axiosConfig"; -import { I_Folder, I_Installation } from "../../util/types"; -import { InstallationsContext } from "../Context/InstallationsContextProvider"; -import MoveDialog from "../Groups/Tree/MoveDialog"; -import InnovenergyButton from "../Layout/InnovenergyButton"; -import InnovenergyTextfield, { - I_InnovenergyTextfieldProps, - IePropertyGrid, -} from "../Layout/InnovenergyTextfield"; -import { UserContext } from "../Context/UserContextProvider"; +import { I_Installation } from "../../../util/types"; +import { InstallationsContext } from "../../Context/InstallationsContextProvider"; +import InnovenergyButton from "../../Layout/InnovenergyButton"; +import { I_InnovenergyTextfieldProps } from "../../Layout/InnovenergyTextfield"; +import { UserContext } from "../../Context/UserContextProvider"; import * as Yup from "yup"; -import { AxiosResponse } from "axios"; +import { AxiosError, AxiosResponse } from "axios"; +import InnovenergyPropertyGrid from "../../Layout/InnovenergyPropertyGrid"; +import { GroupContext } from "../../Context/GroupContextProvider"; interface I_InstallationFormProps { values: I_Installation; @@ -27,8 +24,11 @@ interface I_InstallationFormProps { const InstallationForm = (props: I_InstallationFormProps) => { const { values, additionalButtons, handleSubmit } = props; const [open, setOpen] = useState(false); + const [error, setError] = useState(); + + const { fetchData: fetchInstallations } = useContext(InstallationsContext); + const { fetchData: fetchGroups } = useContext(GroupContext); - const { fetchData } = useContext(InstallationsContext); const { getCurrentUser } = useContext(UserContext); const readOnly = !getCurrentUser().hasWriteAccess; @@ -66,9 +66,15 @@ const InstallationForm = (props: I_InstallationFormProps) => { onSubmit: (formikValues) => { handleSubmit(values, formikValues) .then(() => { - fetchData(); + setOpen(true); + additionalButtons && additionalButtons.length > 0 + ? fetchGroups() + : fetchInstallations(); }) - .catch((err) => {}); + .catch((err) => { + setError(err); + setOpen(true); + }); }, validationSchema, }); @@ -150,7 +156,7 @@ const InstallationForm = (props: I_InstallationFormProps) => { return (
- + {/*{hasMoveButton && !readOnly && }*/} {!readOnly && @@ -175,10 +181,14 @@ const InstallationForm = (props: I_InstallationFormProps) => { onClose={handleClose} > - + {error ? ( + error.message + ) : ( + + )} diff --git a/typescript/Frontend/src/components/Installations/ExampleLogData.tsx b/typescript/Frontend/src/components/Installations/ExampleLogData.tsx deleted file mode 100644 index 643386b7b..000000000 --- a/typescript/Frontend/src/components/Installations/ExampleLogData.tsx +++ /dev/null @@ -1,492 +0,0 @@ -import { I_GraphData } from "../../util/types"; - -export type Datum = string | number | boolean | Array; -type LogData = Record; - -export type TimeSeries = Record; - -export const timeSeries: TimeSeries = { - 1673427378: { - "TruConvertAc/Ac/Current": 124, - "TruConvertAc/Ac/Voltage": 200, - "TruConvertAc/Ac/Phi": 2.89, - "TruConvertAc/Frequency": 2.89, - }, - 1674427380: { - "TruConvertAc/Ac/Current": 150, - "TruConvertAc/Ac/Voltage": 120, - "TruConvertAc/Ac/Phi": 3.21, - "TruConvertAc/Frequency": 2.41, - }, - 1675427382: { - "TruConvertAc/Ac/Current": 153, - "TruConvertAc/Ac/Voltage": 140, - "TruConvertAc/Ac/Phi": 3.9, - "TruConvertAc/Frequency": 4.41, - }, -}; - -const exampleLogData: I_GraphData[] = [ - { - TimeStamp: "1673427378", - Devices: [ - { - Name: "TruConvertAc", - Type: "Inverter", - Ac: [ - { - Current: 2.85, - Voltage: 236.2, - Phi: 2.896, - }, - { - Current: 2.87, - Voltage: 234.5, - Phi: 2.793, - }, - { - Current: 2.65, - Voltage: 240.6, - Phi: 2.941, - }, - ], - Frequency: 50.02, - Dc: { - Current: -2.254, - Voltage: 869, - }, - Alarms: [], - MainState: "Operation", - }, - { - Name: "TruConvertDc", - Type: "DcDc", - Dc: { - Current: -2.017, - Voltage: 868, - }, - Dc48: { - Current: -31, - Voltage: 55.9, - }, - Warnings: [], - Alarms: [], - "DC Power": -1751, - }, - { - Name: "EmuMeter", - Type: "Grid", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "EmuMeter", - Type: "AcInToAcOut", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "AMPT", - Type: "PvOnDc", - Dc: { - Current: 0.229, - Voltage: 921.855, - }, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: 38.56, - Voltage: 53.29, - }, - Alarms: [], - Warnings: [], - Soc: 21.6, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 265.3, - }, - ], - }, - { - TimeStamp: "1673427380", - Devices: [ - { - Name: "TruConvertAc", - Type: "Inverter", - Ac: [ - { - Current: 3.01, - Voltage: 235.6, - Phi: 2.941, - }, - { - Current: 3.04, - Voltage: 234.4, - Phi: 2.739, - }, - { - Current: 2.48, - Voltage: 241.1, - Phi: 2.765, - }, - ], - Frequency: 50.02, - Dc: { - Current: -2.215, - Voltage: 868, - }, - Alarms: [], - MainState: "Operation", - }, - { - Name: "TruConvertDc", - Type: "DcDc", - Dc: { - Current: -1.953, - Voltage: 868, - }, - Dc48: { - Current: -30, - Voltage: 55.9, - }, - Warnings: [], - Alarms: [], - "DC Power": -1695, - }, - { - Name: "EmuMeter", - Type: "Grid", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "EmuMeter", - Type: "AcInToAcOut", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "AMPT", - Type: "PvOnDc", - Dc: { - Current: 0.229, - Voltage: 921.855, - }, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: 38.51, - Voltage: 53.29, - }, - Alarms: [], - Warnings: [], - Soc: 21.6, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 265.3, - }, - ], - }, - { - TimeStamp: "1673427381", - Devices: [ - { - Name: "TruConvertAc", - Type: "Inverter", - Ac: [ - { - Current: 0.4, - Voltage: 237.2, - Phi: 1.4, - }, - { - Current: 0.55, - Voltage: 235.6, - Phi: 1.551, - }, - { - Current: 0.44, - Voltage: 241.7, - Phi: 1.501, - }, - ], - Frequency: 50.02, - Dc: { - Current: 0.007, - Voltage: 849, - }, - Alarms: [], - MainState: "Operation", - }, - { - Name: "TruConvertDc", - Type: "DcDc", - Dc: { - Current: 0.153, - Voltage: 849, - }, - Dc48: { - Current: 3, - Voltage: 52.0, - }, - Warnings: [], - Alarms: [], - "DC Power": 130, - }, - { - Name: "EmuMeter", - Type: "Grid", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "EmuMeter", - Type: "AcInToAcOut", - Ac: [ - { - Current: 15.658, - Voltage: 236.3, - Phi: 0.318, - }, - { - Current: 14.052, - Voltage: 234.8, - Phi: 0.2, - }, - { - Current: 9.046, - Voltage: 240.4, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "AMPT", - Type: "PvOnDc", - Dc: { - Current: 0.229, - Voltage: 921.855, - }, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: 6.14, - Voltage: 51.96, - }, - Alarms: [], - Warnings: [], - Soc: 21.6, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 265.3, - }, - ], - }, - { - TimeStamp: "1673427383", - Devices: [ - { - Name: "TruConvertAc", - Type: "Inverter", - Ac: [ - { - Current: 1.16, - Voltage: 237.7, - Phi: 0.376, - }, - { - Current: 1.25, - Voltage: 236.0, - Phi: 0.707, - }, - { - Current: 0.98, - Voltage: 242.1, - Phi: 0.246, - }, - ], - Frequency: 50.02, - Dc: { - Current: 0.847, - Voltage: 847, - }, - Alarms: [], - MainState: "Operation", - }, - { - Name: "TruConvertDc", - Type: "DcDc", - Dc: { - Current: 0.979, - Voltage: 846, - }, - Dc48: { - Current: 18, - Voltage: 51.2, - }, - Warnings: [], - Alarms: [], - "DC Power": 828, - }, - { - Name: "EmuMeter", - Type: "Grid", - Ac: [ - { - Current: 12.563, - Voltage: 237.2, - Phi: 0.376, - }, - { - Current: 10.913, - Voltage: 235.6, - Phi: 0.246, - }, - { - Current: 5.983, - Voltage: 241.2, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "EmuMeter", - Type: "AcInToAcOut", - Ac: [ - { - Current: 12.563, - Voltage: 237.2, - Phi: 0.376, - }, - { - Current: 10.913, - Voltage: 235.6, - Phi: 0.246, - }, - { - Current: 5.983, - Voltage: 241.2, - Phi: 0.142, - }, - ], - Frequency: 50, - }, - { - Name: "AMPT", - Type: "PvOnDc", - Dc: { - Current: 0.229, - Voltage: 921.855, - }, - }, - { - Name: "48TL Battery", - Type: "Battery", - Dc48: { - Current: -7.99, - Voltage: 51.31, - }, - Alarms: [], - Warnings: [], - Soc: 21.6, - HeaterOn: true, - EocReached: false, - BatteryCold: false, - Temperature: 265.3, - }, - ], - }, -]; - -export default exampleLogData; diff --git a/typescript/Frontend/src/components/Installations/InstallationList.tsx b/typescript/Frontend/src/components/Installations/InstallationList.tsx index 2b19404a0..b0093af8c 100644 --- a/typescript/Frontend/src/components/Installations/InstallationList.tsx +++ b/typescript/Frontend/src/components/Installations/InstallationList.tsx @@ -1,13 +1,7 @@ import List from "@mui/material/List"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemText from "@mui/material/ListItemText"; -import { - Alert, - CircularProgress, - Divider, - Grid, - useTheme, -} from "@mui/material"; +import { Alert, CircularProgress, Grid, useTheme } from "@mui/material"; import { Link } from "react-router-dom"; import useRouteMatch from "../../hooks/useRouteMatch"; import routes from "../../routes.json"; @@ -21,14 +15,6 @@ interface InstallationListProps { searchQuery: string; } -const getPathWithoutId = (path?: string) => { - if (path) { - const splitString = path.split(":"); - return splitString[0]; - } - return routes.installation; -}; - const filterData = ( searchQuery: string, data: I_Installation[] | undefined @@ -45,15 +31,12 @@ const filterData = ( const InstallationList = (props: InstallationListProps) => { const { fetchData, data, loading, error } = useContext(InstallationsContext); - const filteredData = filterData(props.searchQuery, data); - const routeMatch = useRouteMatch([ routes.installations + routes.list + routes.installation + ":id", routes.installations + routes.list + routes.liveView + ":id", routes.installations + routes.list + routes.log + ":id", ]); - const theme = useTheme(); useEffect(() => { @@ -108,13 +91,13 @@ const InstallationList = (props: InstallationListProps) => { borderStyle: "solid", backgroundColor: theme.palette.primary.dark, "&.Mui-selected": { - backgroundColor: colors.orangeSelected, + backgroundColor: theme.palette.secondary.main, }, ":hover": { - backgroundColor: colors.orangeHover, + backgroundColor: theme.palette.secondary.light, }, "&.Mui-selected:hover": { - backgroundColor: colors.orangeSelected, + backgroundColor: theme.palette.secondary.main, }, }} > diff --git a/typescript/Frontend/src/components/Installations/InstallationTabs.tsx b/typescript/Frontend/src/components/Installations/InstallationTabs.tsx index 4f69755b8..2799f61e0 100644 --- a/typescript/Frontend/src/components/Installations/InstallationTabs.tsx +++ b/typescript/Frontend/src/components/Installations/InstallationTabs.tsx @@ -5,14 +5,13 @@ import routes from "../../routes.json"; import useRouteMatch from "../../hooks/useRouteMatch"; import { useIntl } from "react-intl"; import InnovenergyTab from "../Layout/InnovenergyTab"; -import InnovenergyTabBorder from "../Layout/InnovenergyTab"; import InnovenergyTabs from "components/Layout/InnovenergyTabs"; import { useTheme } from "@mui/material"; -import { red } from "@mui/material/colors"; import { colors } from "index"; const InstallationTabs = () => { const theme = useTheme(); + const intl = useIntl(); const routeMatch = useRouteMatch([ routes.installations + routes.list + routes.installation + ":id", routes.installations + routes.list + routes.liveView + ":id", @@ -20,7 +19,6 @@ const InstallationTabs = () => { ]); const id = routeMatch?.params?.id; - const intl = useIntl(); if (id) { return ( diff --git a/typescript/Frontend/src/components/Installations/Installations.tsx b/typescript/Frontend/src/components/Installations/Installations.tsx index 3106c2779..2548e345c 100644 --- a/typescript/Frontend/src/components/Installations/Installations.tsx +++ b/typescript/Frontend/src/components/Installations/Installations.tsx @@ -1,13 +1,13 @@ -import { Grid, colors } from "@mui/material"; +import { Grid } from "@mui/material"; import { Routes, Route } from "react-router"; -import LiveView from "./LiveView"; +import LiveView from "./LiveView/LiveView"; import InstallationTabs from "./InstallationTabs"; import Log from "./Log/Log"; import routes from "../../routes.json"; import InstallationsContextProvider from "../Context/InstallationsContextProvider"; import SearchSidebar from "../Layout/Search"; import InstallationList from "./InstallationList"; -import Installation from "./Installation"; +import Installation from "./Detail/Installation"; import CheckboxTree from "./Log/CheckboxTree"; import LogContextProvider from "../Context/LogContextProvider"; import useRouteMatch from "../../hooks/useRouteMatch"; @@ -27,7 +27,6 @@ const Installations = () => { const id = routeMatch?.params?.id; useEffect(() => { - // TODO remove if getInstallation(id); }, [id]); diff --git a/typescript/Frontend/src/components/Installations/LiveView.tsx b/typescript/Frontend/src/components/Installations/LiveView/LiveView.tsx similarity index 81% rename from typescript/Frontend/src/components/Installations/LiveView.tsx rename to typescript/Frontend/src/components/Installations/LiveView/LiveView.tsx index 7091cb9e6..f02f6c135 100644 --- a/typescript/Frontend/src/components/Installations/LiveView.tsx +++ b/typescript/Frontend/src/components/Installations/LiveView/LiveView.tsx @@ -1,6 +1,5 @@ -import TopologyView from "./Log/TopologyView"; +import TopologyView from "./TopologyView"; import { Box, useTheme } from "@mui/material"; -import { colors } from "../../index"; const LiveView = () => { const theme = useTheme(); @@ -8,7 +7,7 @@ const LiveView = () => { { const [values, setValues] = useState(null); const { fetchData } = useContext(S3CredentialsContext); - const theme = useTheme(); useEffect(() => { const interval = setInterval(() => { @@ -225,7 +224,6 @@ const TopologyView = () => { /> ); - return
TopologyView
; }; export default TopologyView; diff --git a/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx b/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx index e0f833799..52a7b9b96 100644 --- a/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx +++ b/typescript/Frontend/src/components/Installations/Log/CheckboxTree.tsx @@ -1,5 +1,5 @@ -import { TreeItem, TreeView } from "@mui/lab"; -import { Checkbox, Divider, useTheme } from "@mui/material"; +import { TreeView } from "@mui/lab"; +import { Checkbox } from "@mui/material"; import { useContext, ReactNode } from "react"; import { LogContext } from "../../Context/LogContextProvider"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; @@ -7,17 +7,12 @@ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import useRouteMatch from "../../../hooks/useRouteMatch"; import routes from "../../../routes.json"; import React from "react"; -import { colors } from "index"; -import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem"; +import InnovenergyTreeItem from "../../Layout/InnovenergyTreeItem"; -export interface ToggleElement { - [key: string]: boolean; -} - -export interface TreeElement { +export interface I_TreeElement { id: string; name: string; - children: TreeElement[]; + children: I_TreeElement[]; checked?: boolean; } @@ -28,13 +23,13 @@ const CheckboxTree = () => { routes.installations + routes.list + routes.log + ":id", ]); - const getNodes = (element: TreeElement): null | ReactNode => { + const getNodes = (element: I_TreeElement): null | ReactNode => { return element.children.length > 0 ? renderTree(element.children) : null; }; const handleClick = ( event: React.MouseEvent, - element: TreeElement, + element: I_TreeElement, checked?: string ) => { if (checked) { @@ -50,12 +45,13 @@ const CheckboxTree = () => { ) => { event.stopPropagation(); }; - const renderTree = (data: TreeElement[]): ReactNode => { + + const renderTree = (data: I_TreeElement[]): ReactNode => { return data.map((element) => { const checked = checkedToggles.find((toggle) => element.id === toggle); const splitName = element.name.split("/"); return ( - { } > {getNodes(element)} - + ); }); }; diff --git a/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx b/typescript/Frontend/src/components/Installations/Log/DatePicker/DateRangePicker.tsx similarity index 90% rename from typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx rename to typescript/Frontend/src/components/Installations/Log/DatePicker/DateRangePicker.tsx index 3a25c78d5..ee562ced1 100644 --- a/typescript/Frontend/src/components/Installations/Log/DateRangePicker.tsx +++ b/typescript/Frontend/src/components/Installations/Log/DatePicker/DateRangePicker.tsx @@ -3,21 +3,21 @@ import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs from "dayjs"; import { FormattedMessage } from "react-intl"; +import { createTimes } from "../../../../util/graph.util"; +import { TimeRange, UnixTime } from "../../../../dataCache/time"; +import { useTheme } from "@mui/material"; +import { colors } from "../../../../index"; import ShortcutButton from "./ShortcutButton"; -import { createTimes } from "../../../util/graph.util"; -import { TimeRange, UnixTime } from "../../../dataCache/time"; -import { TextField, useTheme } from "@mui/material"; -import { colors } from "../../.."; -interface DateRangePickerProps { +interface I_DateRangePickerProps { setRange: (value: Date[]) => void; range: Date[]; getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void; } -const DateRangePicker = (props: DateRangePickerProps) => { - const theme = useTheme(); +const DateRangePicker = (props: I_DateRangePickerProps) => { const { setRange, range, getCacheSeries } = props; + const theme = useTheme(); const handleChange = (fromDate: Date, toDate: Date) => { const timeRange = createTimes( diff --git a/typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx b/typescript/Frontend/src/components/Installations/Log/DatePicker/ShortcutButton.tsx similarity index 65% rename from typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx rename to typescript/Frontend/src/components/Installations/Log/DatePicker/ShortcutButton.tsx index 31c5b3cff..c4d4b2a41 100644 --- a/typescript/Frontend/src/components/Installations/Log/ShortcutButton.tsx +++ b/typescript/Frontend/src/components/Installations/Log/DatePicker/ShortcutButton.tsx @@ -1,17 +1,15 @@ -import { colors } from "@mui/material"; -import { UnixTime, TimeSpan } from "../../../dataCache/time"; -import { createTimes } from "../../../util/graph.util"; -import InnovenergyButton from "../../Layout/InnovenergyButton"; -import { red } from "@mui/material/colors"; +import { UnixTime, TimeSpan } from "../../../../dataCache/time"; +import { createTimes } from "../../../../util/graph.util"; +import InnovenergyButton from "../../../Layout/InnovenergyButton"; -interface ShortcutButtonProps { +interface I_ShortcutButtonProps { setRange: (value: Date[]) => void; getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void; dayRange: number; children?: React.ReactNode; } -const ShortcutButton = (props: ShortcutButtonProps) => { +const ShortcutButton = (props: I_ShortcutButtonProps) => { return ( { diff --git a/typescript/Frontend/src/components/Installations/Log/Log.tsx b/typescript/Frontend/src/components/Installations/Log/Log.tsx index 2cc0cdb1a..430647ec2 100644 --- a/typescript/Frontend/src/components/Installations/Log/Log.tsx +++ b/typescript/Frontend/src/components/Installations/Log/Log.tsx @@ -19,6 +19,7 @@ const Log = () => { borderBottomLeftRadius: 4, borderBottomRightRadius: 4, borderColor: theme.palette.text.disabled, + overflowX: "auto", }} > diff --git a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx index 5287a2ef1..4565931bb 100644 --- a/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx +++ b/typescript/Frontend/src/components/Installations/Log/ScalarGraph.tsx @@ -1,50 +1,51 @@ import Plot from "react-plotly.js"; -import { DataRecord, RecordSeries } from "../../../dataCache/data"; +import { RecordSeries } from "../../../dataCache/data"; import { - GraphData, + I_GraphData, createTimes, getTreeElements, isNumeric, - parseCsv, stringToColor, transformToBarGraphData, } from "../../../util/graph.util"; import { TimeRange, TimeSpan, UnixTime } from "../../../dataCache/time"; import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from "rxjs"; -import { S3Access } from "../../../dataCache/S3/S3Access"; -import DataCache, { FetchResult } from "../../../dataCache/dataCache"; +import DataCache from "../../../dataCache/dataCache"; import { LogContext } from "../../Context/LogContextProvider"; import { isDefined } from "../../../dataCache/utils/maybe"; -import { Data, Icons, Layout, PlotRelayoutEvent, relayout } from "plotly.js"; +import { Data, Layout, PlotRelayoutEvent } from "plotly.js"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { FormattedMessage } from "react-intl"; -import DateRangePicker from "./DateRangePicker"; +import DateRangePicker from "./DatePicker/DateRangePicker"; import { LocalizationProvider } from "@mui/x-date-pickers"; -import { Alert, useTheme } from "@mui/material"; +import { Alert } from "@mui/material"; import { S3CredentialsContext } from "../../Context/S3CredentialsContextProvider"; const NUMBER_OF_NODES = 100; const ScalarGraph = () => { const timeRange = createTimes( - UnixTime.now() - /* .fromTicks(1682085650) */ - .rangeBefore(TimeSpan.fromDays(1)), + UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), NUMBER_OF_NODES ); + const [timeSeries, setTimeSeries] = useState([]); + const [plotTitles, setPlotTitles] = useState([]); const [range, setRange] = useState([ timeRange[0].toDate(), timeRange[timeRange.length - 1].toDate(), ]); - const [plotTitles, setPlotTitles] = useState([]); const { toggles, setToggles, checkedToggles } = useContext(LogContext); const { fetchData } = useContext(S3CredentialsContext); const times$ = useMemo(() => new BehaviorSubject(timeRange), []); + const cache = useMemo(() => { + return new DataCache(fetchData, TimeSpan.fromSeconds(2)); + }, []); + useEffect(() => { const subscription = cache.gotData .pipe( @@ -57,8 +58,6 @@ const ScalarGraph = () => { setTimeSeries(timeSeries); const toggleValues = timeSeries.find((timeStamp) => timeStamp.value); - console.log("toggles", timeSeries, toggleValues); - if (toggles === null && toggleValues && toggleValues.value) { const treeElements = getTreeElements(toggleValues.value); setToggles(treeElements); @@ -67,11 +66,7 @@ const ScalarGraph = () => { return () => subscription.unsubscribe(); }, [toggles]); - const cache = useMemo(() => { - return new DataCache(fetchData, TimeSpan.fromSeconds(2)); - }, []); - - const transformToGraphData = (input: RecordSeries): GraphData => { + const transformToGraphData = (input: RecordSeries): I_GraphData => { const transformedObject: any = {}; input.forEach((item) => { @@ -95,6 +90,7 @@ const ScalarGraph = () => { setPlotTitles(Object.keys(transformedObject)); } }); + return Object.keys(transformedObject).length > 0 ? transformedObject : plotTitles.reduce( @@ -105,7 +101,7 @@ const ScalarGraph = () => { y: [], }, }), - {} as GraphData + {} as I_GraphData ); }; @@ -138,9 +134,7 @@ const ScalarGraph = () => { [getCacheSeries] ); - const theme = useTheme(); const renderGraphs = () => { - console.log("toggles", toggles); if (checkedToggles.length > 0) { const coordinateTimeSeries = transformToGraphData(timeSeries); const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => { @@ -158,7 +152,6 @@ const ScalarGraph = () => { {visibleGraphs.map((path) => { const isScalar = isNumeric(coordinateTimeSeries[path].y[0]); - console.log("graphdata", timeSeries, coordinateTimeSeries); const data = isScalar ? [ @@ -212,6 +205,15 @@ const ScalarGraph = () => { ); } + } else if (checkedToggles.length === 0) { + return ( + + + + ); } else if (toggles === null) { return ( @@ -222,14 +224,7 @@ const ScalarGraph = () => { ); } - return ( - - - - ); + return null; }; return <>{renderGraphs()}; diff --git a/typescript/Frontend/src/components/Installations/Log/TopologyBox.scss b/typescript/Frontend/src/components/Installations/Log/TopologyBox.scss deleted file mode 100644 index ea381ae6c..000000000 --- a/typescript/Frontend/src/components/Installations/Log/TopologyBox.scss +++ /dev/null @@ -1,10 +0,0 @@ -.topologyBoxTitle{ - margin-block-start: "0"; - margin-block-end: "0"; - background-color: titleColor; - padding: "5px"; - border-top-left-radius: "4px"; - border-top-right-radius: "4px"; - display: "flex"; - justify-content: "center"; - } \ No newline at end of file diff --git a/typescript/Frontend/src/components/Layout/Detail.tsx b/typescript/Frontend/src/components/Layout/Detail.tsx deleted file mode 100644 index c71b1eaae..000000000 --- a/typescript/Frontend/src/components/Layout/Detail.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Box, CircularProgress, Alert, useTheme } from "@mui/material"; -import { AxiosError } from "axios"; -import { useState, useEffect, FC } from "react"; -import { useParams } from "react-router-dom"; -import axiosConfig from "../../config/axiosConfig"; -export interface I_FormProps { - values: T; - id: string; - hasMoveButton?: boolean; -} -interface I_DetailProps { - formComponent: FC>; - route: string; - hasMoveButton?: boolean; -} -const Detail = (props: I_DetailProps) => { - const { id } = useParams(); - const { formComponent: FormComponent, route, hasMoveButton } = props; - const [values, setValues] = useState(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(); - - useEffect(() => { - setLoading(true); - axiosConfig - .get(route + id) - .then((res) => { - setValues(res.data); - setLoading(false); - }) - .catch((err: AxiosError) => { - setError(err); - setLoading(false); - }); - }, [id, route]); - const theme = useTheme(); - - if (values && values.id && values.id.toString() === id) { - return ( - - ); - } else if (loading) { - return ( - - - - ); - } else if (error) { - return ( - - {error.message} - - ); - } - return null; -}; - -export default Detail; diff --git a/typescript/Frontend/src/components/Layout/InnovenergyButton.tsx b/typescript/Frontend/src/components/Layout/InnovenergyButton.tsx index dfbff7637..9e656d632 100644 --- a/typescript/Frontend/src/components/Layout/InnovenergyButton.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyButton.tsx @@ -1,4 +1,4 @@ -import { Button, SxProps, Theme, colors, useTheme } from "@mui/material"; +import { Button, SxProps, Theme, useTheme } from "@mui/material"; import { ReactNode } from "react"; interface I_InnovenergyButtonProps { diff --git a/typescript/Frontend/src/components/Groups/AccessManagement/InnovenergyList.tsx b/typescript/Frontend/src/components/Layout/InnovenergyList.tsx similarity index 76% rename from typescript/Frontend/src/components/Groups/AccessManagement/InnovenergyList.tsx rename to typescript/Frontend/src/components/Layout/InnovenergyList.tsx index 7bdff47da..b44678572 100644 --- a/typescript/Frontend/src/components/Groups/AccessManagement/InnovenergyList.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyList.tsx @@ -1,12 +1,12 @@ import { List, useTheme } from "@mui/material"; import { ReactNode } from "react"; -interface InnovenergyListProps { +interface I_InnovenergyListProps { id: string; children: ReactNode; } -const InnovenergyList = (props: InnovenergyListProps) => { +const InnovenergyList = (props: I_InnovenergyListProps) => { const theme = useTheme(); return ( { pb: 0, }} component="nav" - aria-labelledby="nested-list-subheader" > {props.children} diff --git a/typescript/Frontend/src/components/Layout/InnovenergyPropertyGrid.tsx b/typescript/Frontend/src/components/Layout/InnovenergyPropertyGrid.tsx new file mode 100644 index 000000000..8e12272ff --- /dev/null +++ b/typescript/Frontend/src/components/Layout/InnovenergyPropertyGrid.tsx @@ -0,0 +1,68 @@ +import { InputLabel, TextField } from "@mui/material"; +import { colors } from "../../index"; +import { I_InnovenergyTextfieldProps } from "./InnovenergyTextfield"; + +interface I_InnovenergyPropertyGridProps { + rows: I_InnovenergyTextfieldProps[]; +} + +export const InnovenergyPropertyGrid = ( + props: I_InnovenergyPropertyGridProps +) => { + return ( +
+
+ {props.rows.map((prop) => ( + {prop.label} + ))} +
+
+ {props.rows.map((element) => { + return ( + + ); + })} +
+
+ ); +}; + +export default InnovenergyPropertyGrid; diff --git a/typescript/Frontend/src/components/InnovenergySnackbar.tsx b/typescript/Frontend/src/components/Layout/InnovenergySnackbar.tsx similarity index 90% rename from typescript/Frontend/src/components/InnovenergySnackbar.tsx rename to typescript/Frontend/src/components/Layout/InnovenergySnackbar.tsx index 10718d0d7..bb09aaa3e 100644 --- a/typescript/Frontend/src/components/InnovenergySnackbar.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergySnackbar.tsx @@ -1,13 +1,13 @@ import { Alert, Snackbar } from "@mui/material"; import { FormattedMessage } from "react-intl"; -interface InnovenergySnackbarProps { +interface I_InnovenergySnackbarProps { open: boolean; setOpen: (value: boolean) => void; error?: any; } -const InnovenergySnackbar = (props: InnovenergySnackbarProps) => { +const InnovenergySnackbar = (props: I_InnovenergySnackbarProps) => { const { open, setOpen, error } = props; const handleClose = () => { diff --git a/typescript/Frontend/src/components/Layout/InnovenergyTabs.tsx b/typescript/Frontend/src/components/Layout/InnovenergyTabs.tsx index 1d56e376b..1960e0637 100644 --- a/typescript/Frontend/src/components/Layout/InnovenergyTabs.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyTabs.tsx @@ -1,15 +1,15 @@ -import { SxProps, Tabs, Theme, useTheme } from "@mui/material"; +import { Tabs, useTheme } from "@mui/material"; import { ReactNode } from "react"; import { colors } from "index"; -interface AntTabsProps { +interface I_AntTabsProps { id: string; value?: string; sx?: any; children: ReactNode; } -const InnovenergyTabs = (props: AntTabsProps) => { +const InnovenergyTabs = (props: I_AntTabsProps) => { const theme = useTheme(); return ( { "&.Mui-selected": { color: colors.black, backgroundColor: theme.palette.primary.light, - borderColor: `#90A7c5 #90A7c5 #fff`, }, "& .MuiTabs-indicator": { display: "flex", diff --git a/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx b/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx index c342118e2..14abdfb7d 100644 --- a/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyTextfield.tsx @@ -1,17 +1,4 @@ -import { - Grid, - InputLabel, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - TextField, - useTheme, -} from "@mui/material"; -import { colors } from "../../index"; +import { Grid, InputLabel, TextField } from "@mui/material"; export interface I_InnovenergyTextfieldProps { id: string; @@ -26,67 +13,7 @@ export interface I_InnovenergyTextfieldProps { error?: boolean; } -export const IePropertyGrid = (props: { - rows: I_InnovenergyTextfieldProps[]; -}) => { - return ( -
-
- {props.rows.map((prop) => ( - {prop.label} - ))} -
-
- {props.rows.map((element) => { - return ( - - ); - })} -
-
- ); -}; - const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => { - const theme = useTheme(); return ( @@ -107,7 +34,6 @@ const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => { borderRadius: 1, }, my: 0.5, - borderColor: "red", }} value={props.value || ""} onChange={props.handleChange} diff --git a/typescript/Frontend/src/components/Layout/InnovEnergyTreeItem.tsx b/typescript/Frontend/src/components/Layout/InnovenergyTreeItem.tsx similarity index 87% rename from typescript/Frontend/src/components/Layout/InnovEnergyTreeItem.tsx rename to typescript/Frontend/src/components/Layout/InnovenergyTreeItem.tsx index 28753d649..5f36b6c1c 100644 --- a/typescript/Frontend/src/components/Layout/InnovEnergyTreeItem.tsx +++ b/typescript/Frontend/src/components/Layout/InnovenergyTreeItem.tsx @@ -2,14 +2,14 @@ import { TreeItem, TreeItemProps } from "@mui/lab"; import { colors } from "../../index"; import { useTheme } from "@mui/material"; -const InnovEnergyTreeItem = (props: TreeItemProps) => { +const InnovenergyTreeItem = (props: TreeItemProps) => { const theme = useTheme(); return ( props.onClick} + onClick={props.onClick} sx={{ ".MuiTreeItem-content": { py: "10px", @@ -30,6 +30,7 @@ const InnovEnergyTreeItem = (props: TreeItemProps) => { }, borderRadius: "4px", bgcolor: colors.greyDark, + ...props.sx, }} > {props.children} @@ -37,4 +38,4 @@ const InnovEnergyTreeItem = (props: TreeItemProps) => { ); }; -export default InnovEnergyTreeItem; +export default InnovenergyTreeItem; diff --git a/typescript/Frontend/src/components/Layout/LanguageSelect.tsx b/typescript/Frontend/src/components/Layout/LanguageSelect.tsx index c998b8c11..e3bcdd8e0 100644 --- a/typescript/Frontend/src/components/Layout/LanguageSelect.tsx +++ b/typescript/Frontend/src/components/Layout/LanguageSelect.tsx @@ -1,12 +1,12 @@ import { MenuItem, Select } from "@mui/material"; import { FormattedMessage } from "react-intl"; -interface LanguageSelectProps { +interface I_LanguageSelectProps { language: string; setLanguage: (language: string) => void; } -const LanguageSelect = (props: LanguageSelectProps) => { +const LanguageSelect = (props: I_LanguageSelectProps) => { return (