Compare commits
16 Commits
897f3137f5
...
812962ace0
| Author | SHA1 | Date |
|---|---|---|
|
|
812962ace0 | |
|
|
cc1ec216ee | |
|
|
68d057b919 | |
|
|
dfa2f89295 | |
|
|
c70cacf179 | |
|
|
1683ab9b9a | |
|
|
fd35248b72 | |
|
|
41031b3b87 | |
|
|
8c58ce45f6 | |
|
|
2681248bdc | |
|
|
3521da7a1d | |
|
|
d59027a277 | |
|
|
4bb35c6951 | |
|
|
0657a5fb82 | |
|
|
baaabbecd0 | |
|
|
730f337502 |
|
|
@ -202,6 +202,8 @@ public class Controller : ControllerBase
|
||||||
bucketPath = "s3://" + installation.S3BucketId + "-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa/" + startTimestamp;
|
bucketPath = "s3://" + installation.S3BucketId + "-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa/" + startTimestamp;
|
||||||
else if (installation.Product == (int)ProductType.SodistoreGrid)
|
else if (installation.Product == (int)ProductType.SodistoreGrid)
|
||||||
bucketPath = "s3://" + installation.S3BucketId + "-5109c126-e141-43ab-8658-f3c44c838ae8/" + startTimestamp;
|
bucketPath = "s3://" + installation.S3BucketId + "-5109c126-e141-43ab-8658-f3c44c838ae8/" + startTimestamp;
|
||||||
|
else if (installation.Product == (int)ProductType.SodistorePro)
|
||||||
|
bucketPath = "s3://" + installation.S3BucketId + "-325c9373-9025-4a8d-bf5a-f9eedf1f155c/" + startTimestamp;
|
||||||
else
|
else
|
||||||
bucketPath = "s3://" + installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/" + startTimestamp;
|
bucketPath = "s3://" + installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/" + startTimestamp;
|
||||||
Console.WriteLine("Fetching data for "+startTimestamp);
|
Console.WriteLine("Fetching data for "+startTimestamp);
|
||||||
|
|
@ -815,9 +817,10 @@ public class Controller : ControllerBase
|
||||||
if (installation is null || !user.HasAccessTo(installation))
|
if (installation is null || !user.HasAccessTo(installation))
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
// AI diagnostics are scoped to SodistoreHome and SodiStoreMax only
|
// AI diagnostics are scoped to SodistoreHome, SodiStoreMax, and SodistorePro only
|
||||||
if (installation.Product != (int)ProductType.SodioHome &&
|
if (installation.Product != (int)ProductType.SodioHome &&
|
||||||
installation.Product != (int)ProductType.SodiStoreMax)
|
installation.Product != (int)ProductType.SodiStoreMax &&
|
||||||
|
installation.Product != (int)ProductType.SodistorePro)
|
||||||
return BadRequest("AI diagnostics not available for this product.");
|
return BadRequest("AI diagnostics not available for this product.");
|
||||||
|
|
||||||
var result = await DiagnosticService.DiagnoseAsync(installationId, errorDescription, user.Language ?? "en");
|
var result = await DiagnosticService.DiagnoseAsync(installationId, errorDescription, user.Language ?? "en");
|
||||||
|
|
@ -2134,6 +2137,31 @@ public class Controller : ControllerBase
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var assigneeChanged = ticket.AssigneeId != existing.AssigneeId
|
||||||
|
&& ticket.AssigneeId.HasValue;
|
||||||
|
|
||||||
|
if (assigneeChanged)
|
||||||
|
{
|
||||||
|
var assignee = Db.GetUserById(ticket.AssigneeId);
|
||||||
|
|
||||||
|
Db.Create(new TicketTimelineEvent
|
||||||
|
{
|
||||||
|
TicketId = ticket.Id,
|
||||||
|
EventType = (Int32)TimelineEventType.Assigned,
|
||||||
|
Description = $"Ticket assigned to {assignee?.Name ?? "unknown"}.",
|
||||||
|
ActorType = (Int32)TimelineActorType.Human,
|
||||||
|
ActorId = user.Id,
|
||||||
|
CreatedAt = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
if (assignee is not null)
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try { await assignee.SendTicketAssignedEmail(ticket); }
|
||||||
|
catch (Exception ex) { Console.WriteLine($"Failed to send ticket assignment email: {ex}"); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Db.Update(ticket) ? ticket : StatusCode(500, "Update failed.");
|
return Db.Update(ticket) ? ticket : StatusCode(500, "Update failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2309,4 +2337,17 @@ public class Controller : ControllerBase
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPut(nameof(AcknowledgeTerms))]
|
||||||
|
public ActionResult AcknowledgeTerms(Int32 version, Token authToken)
|
||||||
|
{
|
||||||
|
var session = Db.GetSession(authToken);
|
||||||
|
if (session is null) return Unauthorized();
|
||||||
|
|
||||||
|
var user = Db.GetUserById(session.User.Id);
|
||||||
|
if (user is null) return Unauthorized();
|
||||||
|
|
||||||
|
user.AcknowledgedTermsVersion = version;
|
||||||
|
return Db.Update(user) ? Ok() : StatusCode(500);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ public enum ProductType
|
||||||
Salidomo = 1,
|
Salidomo = 1,
|
||||||
SodioHome =2,
|
SodioHome =2,
|
||||||
SodiStoreMax=3,
|
SodiStoreMax=3,
|
||||||
SodistoreGrid=4
|
SodistoreGrid=4,
|
||||||
|
SodistorePro=5
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StatusType
|
public enum StatusType
|
||||||
|
|
@ -27,6 +28,13 @@ public class Installation : TreeNode
|
||||||
public String Location { get; set; } = "";
|
public String Location { get; set; } = "";
|
||||||
public String Region { get; set; } = "";
|
public String Region { get; set; } = "";
|
||||||
public String Country { get; set; } = "";
|
public String Country { get; set; } = "";
|
||||||
|
public String Street { get; set; } = "";
|
||||||
|
public String PostCode { get; set; } = "";
|
||||||
|
public String City { get; set; } = "";
|
||||||
|
public String Canton { get; set; } = "";
|
||||||
|
public String DistributionPartner { get; set; } = "";
|
||||||
|
public String InverterFirmwareVersion { get; set; } = "";
|
||||||
|
public String BatteryFirmwareVersion { get; set; } = "";
|
||||||
public String VpnIp { get; set; } = "";
|
public String VpnIp { get; set; } = "";
|
||||||
public String InstallationName { get; set; } = "";
|
public String InstallationName { get; set; } = "";
|
||||||
|
|
||||||
|
|
@ -49,6 +57,10 @@ public class Installation : TreeNode
|
||||||
public int BatteryClusterNumber { get; set; } = 0;
|
public int BatteryClusterNumber { get; set; } = 0;
|
||||||
public int BatteryNumber { get; set; } = 0;
|
public int BatteryNumber { get; set; } = 0;
|
||||||
public string BatterySerialNumbers { get; set; } = "";
|
public string BatterySerialNumbers { get; set; } = "";
|
||||||
|
public string PvStringsPerInverter { get; set; } = "";
|
||||||
|
public string InstallationModel { get; set; } = "";
|
||||||
|
public string ExternalEms { get; set; } = "No";
|
||||||
|
public string CouplingType { get; set; } = "DC";
|
||||||
|
|
||||||
[Ignore]
|
[Ignore]
|
||||||
public String OrderNumbers { get; set; }
|
public String OrderNumbers { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ public static class ExoCmd
|
||||||
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
||||||
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
||||||
installation.Product==(int)ProductType.SodistoreGrid?Db.Installations.Count(f => f.Product == (int)ProductType.SodistoreGrid) + installation.Name:
|
installation.Product==(int)ProductType.SodistoreGrid?Db.Installations.Count(f => f.Product == (int)ProductType.SodistoreGrid) + installation.Name:
|
||||||
|
installation.Product==(int)ProductType.SodistorePro?Db.Installations.Count(f => f.Product == (int)ProductType.SodistorePro) + installation.Name:
|
||||||
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -350,6 +351,7 @@ public static class ExoCmd
|
||||||
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
||||||
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
||||||
installation.Product==(int)ProductType.SodistoreGrid?Db.Installations.Count(f => f.Product == (int)ProductType.SodistoreGrid) + installation.Name:
|
installation.Product==(int)ProductType.SodistoreGrid?Db.Installations.Count(f => f.Product == (int)ProductType.SodistoreGrid) + installation.Name:
|
||||||
|
installation.Product==(int)ProductType.SodistorePro?Db.Installations.Count(f => f.Product == (int)ProductType.SodistorePro) + installation.Name:
|
||||||
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
||||||
|
|
||||||
var contentString = $$"""
|
var contentString = $$"""
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ public static class InstallationMethods
|
||||||
private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
|
private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
|
||||||
private static readonly String SodioHomeBucketNameSalt = "e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa";
|
private static readonly String SodioHomeBucketNameSalt = "e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa";
|
||||||
private static readonly String SodistoreGridBucketNameSalt = "5109c126-e141-43ab-8658-f3c44c838ae8";
|
private static readonly String SodistoreGridBucketNameSalt = "5109c126-e141-43ab-8658-f3c44c838ae8";
|
||||||
|
private static readonly String SodistoreProBucketNameSalt = "325c9373-9025-4a8d-bf5a-f9eedf1f155c";
|
||||||
|
|
||||||
public static String BucketName(this Installation installation)
|
public static String BucketName(this Installation installation)
|
||||||
{
|
{
|
||||||
|
|
@ -29,6 +30,11 @@ public static class InstallationMethods
|
||||||
return $"{installation.S3BucketId}-{SodistoreGridBucketNameSalt}";
|
return $"{installation.S3BucketId}-{SodistoreGridBucketNameSalt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (installation.Product == (int)ProductType.SodistorePro)
|
||||||
|
{
|
||||||
|
return $"{installation.S3BucketId}-{SodistoreProBucketNameSalt}";
|
||||||
|
}
|
||||||
|
|
||||||
return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
|
return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ public static class SessionMethods
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodioHome || installation.Product == (int)ProductType.SodistoreGrid)
|
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodioHome || installation.Product == (int)ProductType.SodistoreGrid || installation.Product == (int)ProductType.SodistorePro)
|
||||||
{
|
{
|
||||||
return user is not null
|
return user is not null
|
||||||
&& user.UserType != 0
|
&& user.UserType != 0
|
||||||
|
|
@ -295,7 +295,7 @@ public static class SessionMethods
|
||||||
.Apply(Db.Update);
|
.Apply(Db.Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodistoreGrid)
|
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodistoreGrid || installation.Product == (int)ProductType.SodistorePro)
|
||||||
{
|
{
|
||||||
|
|
||||||
return user is not null
|
return user is not null
|
||||||
|
|
|
||||||
|
|
@ -243,22 +243,22 @@ public static class UserMethods
|
||||||
var (subject, body) = (user.Language ?? "en") switch
|
var (subject, body) = (user.Language ?? "en") switch
|
||||||
{
|
{
|
||||||
"de" => (
|
"de" => (
|
||||||
"Passwort Ihres Inesco Energy Kontos zurücksetzen",
|
"Passwort Ihres inesco energy Kontos zurücksetzen",
|
||||||
$"Sehr geehrte/r {user.Name}\n" +
|
$"Sehr geehrte/r {user.Name}\n" +
|
||||||
$"Um Ihr Passwort zurückzusetzen, öffnen Sie bitte diesen Link: {resetLink}?token={encodedToken}"
|
$"Um Ihr Passwort zurückzusetzen, öffnen Sie bitte diesen Link: {resetLink}?token={encodedToken}"
|
||||||
),
|
),
|
||||||
"fr" => (
|
"fr" => (
|
||||||
"Réinitialisation du mot de passe de votre compte Inesco Energy",
|
"Réinitialisation du mot de passe de votre compte inesco energy",
|
||||||
$"Cher/Chère {user.Name}\n" +
|
$"Cher/Chère {user.Name}\n" +
|
||||||
$"Pour réinitialiser votre mot de passe, veuillez ouvrir ce lien : {resetLink}?token={encodedToken}"
|
$"Pour réinitialiser votre mot de passe, veuillez ouvrir ce lien : {resetLink}?token={encodedToken}"
|
||||||
),
|
),
|
||||||
"it" => (
|
"it" => (
|
||||||
"Reimposta la password del tuo account Inesco Energy",
|
"Reimposta la password del tuo account inesco energy",
|
||||||
$"Gentile {user.Name}\n" +
|
$"Gentile {user.Name}\n" +
|
||||||
$"Per reimpostare la password, apra questo link: {resetLink}?token={encodedToken}"
|
$"Per reimpostare la password, apra questo link: {resetLink}?token={encodedToken}"
|
||||||
),
|
),
|
||||||
_ => (
|
_ => (
|
||||||
"Reset the password of your Inesco Energy Account",
|
"Reset the password of your inesco energy Account",
|
||||||
$"Dear {user.Name}\n" +
|
$"Dear {user.Name}\n" +
|
||||||
$"To reset your password please open this link: {resetLink}?token={encodedToken}"
|
$"To reset your password please open this link: {resetLink}?token={encodedToken}"
|
||||||
)
|
)
|
||||||
|
|
@ -274,24 +274,85 @@ public static class UserMethods
|
||||||
var (subject, body) = (user.Language ?? "en") switch
|
var (subject, body) = (user.Language ?? "en") switch
|
||||||
{
|
{
|
||||||
"de" => (
|
"de" => (
|
||||||
"Ihr neues Inesco Energy Konto",
|
"Ihr neues inesco energy Konto",
|
||||||
$"Sehr geehrte/r {user.Name}\n" +
|
$"Sehr geehrte/r {user.Name}\n" +
|
||||||
$"Um Ihr Passwort festzulegen und sich bei Ihrem Inesco Energy Konto anzumelden, öffnen Sie bitte diesen Link: {resetLink}"
|
$"Um Ihr Passwort festzulegen und sich bei Ihrem inesco energy Konto anzumelden, öffnen Sie bitte diesen Link: {resetLink}"
|
||||||
),
|
),
|
||||||
"fr" => (
|
"fr" => (
|
||||||
"Votre nouveau compte Inesco Energy",
|
"Votre nouveau compte inesco energy",
|
||||||
$"Cher/Chère {user.Name}\n" +
|
$"Cher/Chère {user.Name}\n" +
|
||||||
$"Pour définir votre mot de passe et vous connecter à votre compte Inesco Energy, veuillez ouvrir ce lien : {resetLink}"
|
$"Pour définir votre mot de passe et vous connecter à votre compte inesco energy, veuillez ouvrir ce lien : {resetLink}"
|
||||||
),
|
),
|
||||||
"it" => (
|
"it" => (
|
||||||
"Il tuo nuovo account Inesco Energy",
|
"Il tuo nuovo account inesco energy",
|
||||||
$"Gentile {user.Name}\n" +
|
$"Gentile {user.Name}\n" +
|
||||||
$"Per impostare la password e accedere al suo account Inesco Energy, apra questo link: {resetLink}"
|
$"Per impostare la password e accedere al suo account inesco energy, apra questo link: {resetLink}"
|
||||||
),
|
),
|
||||||
_ => (
|
_ => (
|
||||||
"Your new Inesco Energy Account",
|
"Your new inesco energy Account",
|
||||||
$"Dear {user.Name}\n" +
|
$"Dear {user.Name}\n" +
|
||||||
$"To set your password and log in to your Inesco Energy Account open this link: {resetLink}"
|
$"To set your password and log in to your inesco energy Account open this link: {resetLink}"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
return user.SendEmail(subject, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task SendTicketAssignedEmail(this User user, Ticket ticket)
|
||||||
|
{
|
||||||
|
var ticketLink = $"https://monitor.inesco.energy/tickets/{ticket.Id}";
|
||||||
|
var priority = (TicketPriority)ticket.Priority;
|
||||||
|
var category = (TicketCategory)ticket.Category;
|
||||||
|
|
||||||
|
var (subject, body) = (user.Language ?? "en") switch
|
||||||
|
{
|
||||||
|
"de" => (
|
||||||
|
$"inesco energy – Ticket #{ticket.Id} wurde Ihnen zugewiesen",
|
||||||
|
$"Sehr geehrte/r {user.Name},\n\n" +
|
||||||
|
$"Ein Ticket wurde Ihnen zugewiesen:\n\n" +
|
||||||
|
$"Ticket: #{ticket.Id}\n" +
|
||||||
|
$"Betreff: {ticket.Subject}\n" +
|
||||||
|
$"Priorität: {priority}\n" +
|
||||||
|
$"Kategorie: {category}\n\n" +
|
||||||
|
$"Beschreibung:\n{ticket.Description}\n\n" +
|
||||||
|
$"Öffnen Sie das Ticket hier: {ticketLink}\n\n" +
|
||||||
|
"Mit freundlichen Grüssen\ninesco energy Monitor"
|
||||||
|
),
|
||||||
|
"fr" => (
|
||||||
|
$"inesco energy – Le ticket #{ticket.Id} vous a été attribué",
|
||||||
|
$"Cher/Chère {user.Name},\n\n" +
|
||||||
|
$"Un ticket vous a été attribué :\n\n" +
|
||||||
|
$"Ticket : #{ticket.Id}\n" +
|
||||||
|
$"Objet : {ticket.Subject}\n" +
|
||||||
|
$"Priorité : {priority}\n" +
|
||||||
|
$"Catégorie : {category}\n\n" +
|
||||||
|
$"Description :\n{ticket.Description}\n\n" +
|
||||||
|
$"Ouvrir le ticket : {ticketLink}\n\n" +
|
||||||
|
"Cordialement,\ninesco energy Monitor"
|
||||||
|
),
|
||||||
|
"it" => (
|
||||||
|
$"inesco energy – Il ticket #{ticket.Id} le è stato assegnato",
|
||||||
|
$"Gentile {user.Name},\n\n" +
|
||||||
|
$"Le è stato assegnato un ticket:\n\n" +
|
||||||
|
$"Ticket: #{ticket.Id}\n" +
|
||||||
|
$"Oggetto: {ticket.Subject}\n" +
|
||||||
|
$"Priorità: {priority}\n" +
|
||||||
|
$"Categoria: {category}\n\n" +
|
||||||
|
$"Descrizione:\n{ticket.Description}\n\n" +
|
||||||
|
$"Aprire il ticket: {ticketLink}\n\n" +
|
||||||
|
"Cordiali saluti,\ninesco energy Monitor"
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
$"inesco energy – Ticket #{ticket.Id} has been assigned to you",
|
||||||
|
$"Dear {user.Name},\n\n" +
|
||||||
|
$"A ticket has been assigned to you:\n\n" +
|
||||||
|
$"Ticket: #{ticket.Id}\n" +
|
||||||
|
$"Subject: {ticket.Subject}\n" +
|
||||||
|
$"Priority: {priority}\n" +
|
||||||
|
$"Category: {category}\n\n" +
|
||||||
|
$"Description:\n{ticket.Description}\n\n" +
|
||||||
|
$"Open the ticket: {ticketLink}\n\n" +
|
||||||
|
"Best regards,\ninesco energy Monitor"
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ public class User : TreeNode
|
||||||
public Boolean MustResetPassword { get; set; } = false;
|
public Boolean MustResetPassword { get; set; } = false;
|
||||||
public String? Password { get; set; } = null!;
|
public String? Password { get; set; } = null!;
|
||||||
public String Language { get; set; } = "en";
|
public String Language { get; set; } = "en";
|
||||||
|
public Int32? AcknowledgedTermsVersion { get; set; }
|
||||||
|
|
||||||
[Unique]
|
[Unique]
|
||||||
public override String Name { get; set; } = null!;
|
public override String Name { get; set; } = null!;
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,12 @@ public static partial class Db
|
||||||
Connection.Execute("UPDATE User SET Language = 'fr' WHERE Language = 'french'");
|
Connection.Execute("UPDATE User SET Language = 'fr' WHERE Language = 'french'");
|
||||||
Connection.Execute("UPDATE User SET Language = 'it' WHERE Language = 'italian'");
|
Connection.Execute("UPDATE User SET Language = 'it' WHERE Language = 'italian'");
|
||||||
|
|
||||||
// One-time migration: rebrand to inesco Energy
|
// One-time migration: rebrand to inesco energy
|
||||||
Connection.Execute("UPDATE Folder SET Name = 'inesco Energy' WHERE Name = 'InnovEnergy'");
|
Connection.Execute("UPDATE Folder SET Name = 'inesco energy' WHERE Name = 'InnovEnergy'");
|
||||||
|
Connection.Execute("UPDATE Folder SET Name = 'inesco energy' WHERE Name = 'inesco Energy'");
|
||||||
Connection.Execute("UPDATE Folder SET Name = 'Sodistore Max Installations' WHERE Name = 'SodistoreMax Installations'");
|
Connection.Execute("UPDATE Folder SET Name = 'Sodistore Max Installations' WHERE Name = 'SodistoreMax Installations'");
|
||||||
Connection.Execute("UPDATE User SET Name = 'inesco Energy Master Admin' WHERE Name = 'InnovEnergy Master Admin'");
|
Connection.Execute("UPDATE User SET Name = 'inesco energy Master Admin' WHERE Name = 'InnovEnergy Master Admin'");
|
||||||
|
Connection.Execute("UPDATE User SET Name = 'inesco energy Master Admin' WHERE Name = 'inesco Energy Master Admin'");
|
||||||
|
|
||||||
//UpdateKeys();
|
//UpdateKeys();
|
||||||
CleanupSessions().SupressAwaitWarning();
|
CleanupSessions().SupressAwaitWarning();
|
||||||
|
|
@ -130,6 +132,11 @@ public static partial class Db
|
||||||
fileConnection.CreateTable<TicketAiDiagnosis>();
|
fileConnection.CreateTable<TicketAiDiagnosis>();
|
||||||
fileConnection.CreateTable<TicketTimelineEvent>();
|
fileConnection.CreateTable<TicketTimelineEvent>();
|
||||||
|
|
||||||
|
// Migrate new columns: set defaults for existing rows where NULL or empty
|
||||||
|
fileConnection.Execute("UPDATE Installation SET ExternalEms = 'No' WHERE ExternalEms IS NULL OR ExternalEms = ''");
|
||||||
|
fileConnection.Execute("UPDATE Installation SET InstallationModel = '' WHERE InstallationModel IS NULL");
|
||||||
|
fileConnection.Execute("UPDATE Installation SET PvStringsPerInverter = '' WHERE PvStringsPerInverter IS NULL");
|
||||||
|
|
||||||
return fileConnection;
|
return fileConnection;
|
||||||
//return CopyDbToMemory(fileConnection);
|
//return CopyDbToMemory(fileConnection);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@
|
||||||
"SmtpUsername" : "no-reply@inesco.ch",
|
"SmtpUsername" : "no-reply@inesco.ch",
|
||||||
"SmtpPassword" : "1ci4vi%+bfccIp",
|
"SmtpPassword" : "1ci4vi%+bfccIp",
|
||||||
"SmtpPort" : 587,
|
"SmtpPort" : 587,
|
||||||
"SenderName" : "Inesco Energy",
|
"SenderName" : "inesco energy",
|
||||||
"SenderAddress" : "no-reply@inesco.ch"
|
"SenderAddress" : "no-reply@inesco.ch"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ public class Session : Relation<String, Int64>
|
||||||
public Boolean AccessToSodistoreMax { get; set; } = false;
|
public Boolean AccessToSodistoreMax { get; set; } = false;
|
||||||
public Boolean AccessToSodioHome { get; set; } = false;
|
public Boolean AccessToSodioHome { get; set; } = false;
|
||||||
public Boolean AccessToSodistoreGrid { get; set; } = false;
|
public Boolean AccessToSodistoreGrid { get; set; } = false;
|
||||||
|
public Boolean AccessToSodistorePro { get; set; } = false;
|
||||||
[Ignore] public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ;
|
[Ignore] public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ;
|
||||||
|
|
||||||
// Private backing field
|
// Private backing field
|
||||||
|
|
@ -51,6 +52,7 @@ public class Session : Relation<String, Int64>
|
||||||
AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0;
|
AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0;
|
||||||
AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
|
AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
|
||||||
AccessToSodistoreGrid = user.AccessibleInstallations(product: (int)ProductType.SodistoreGrid).ToList().Count > 0;
|
AccessToSodistoreGrid = user.AccessibleInstallations(product: (int)ProductType.SodistoreGrid).ToList().Count > 0;
|
||||||
|
AccessToSodistorePro = user.AccessibleInstallations(product: (int)ProductType.SodistorePro).ToList().Count > 0;
|
||||||
|
|
||||||
Console.WriteLine("salimax" +user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count);
|
Console.WriteLine("salimax" +user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count);
|
||||||
Console.WriteLine("AccessToSodistoreMax" +user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count);
|
Console.WriteLine("AccessToSodistoreMax" +user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count);
|
||||||
|
|
|
||||||
|
|
@ -1223,7 +1223,7 @@ textarea:focus{outline:none;border-color:#3498db}
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<div class="nav" id="nav"></div>
|
<div class="nav" id="nav"></div>
|
||||||
<div class="thankyou">Vielen Dank für Ihre Zeit — Ihr Beitrag macht einen echten Unterschied für unsere Kunden. 🙏</div>
|
<div class="thankyou">Vielen Dank für Ihre Zeit — Ihr Beitrag macht einen echten Unterschied für unsere Kunden. 🙏</div>
|
||||||
<div style="text-align:center;padding-bottom:24px;font-size:11px;color:#bbb">inesco Energy Monitor</div>
|
<div style="text-align:center;padding-bottom:24px;font-size:11px;color:#bbb">inesco energy Monitor</div>
|
||||||
<script>
|
<script>
|
||||||
var ALARMS = %%ALARMS_JSON%%;
|
var ALARMS = %%ALARMS_JSON%%;
|
||||||
var SUBMIT_URL = "%%SUBMIT_URL%%";
|
var SUBMIT_URL = "%%SUBMIT_URL%%";
|
||||||
|
|
@ -1473,7 +1473,7 @@ render();
|
||||||
<p style="margin-bottom:0;font-size:13px;color:#555">Vielen Dank für Ihre Zeit — Ihr Beitrag macht einen echten Unterschied für unsere Kunden. 🙏</p>
|
<p style="margin-bottom:0;font-size:13px;color:#555">Vielen Dank für Ihre Zeit — Ihr Beitrag macht einen echten Unterschied für unsere Kunden. 🙏</p>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td style="background:#ecf0f1;padding:12px 28px;text-align:center;font-size:11px;color:#888;border-top:1px solid #ddd">
|
<tr><td style="background:#ecf0f1;padding:12px 28px;text-align:center;font-size:11px;color:#888;border-top:1px solid #ddd">
|
||||||
inesco Energy Monitor
|
inesco energy Monitor
|
||||||
</td></tr>
|
</td></tr>
|
||||||
</table></td></tr></table></body></html>
|
</table></td></tr></table></body></html>
|
||||||
""";
|
""";
|
||||||
|
|
@ -1545,7 +1545,7 @@ render();
|
||||||
<p>Hallo <strong>{name}</strong>,</p>
|
<p>Hallo <strong>{name}</strong>,</p>
|
||||||
<p style="margin-top:12px">Kurze Erinnerung — die heutige Alarmprüfung <strong>(Stapel {batch.BatchNumber})</strong> schließt um <strong>8:00 Uhr morgen früh</strong>. Es dauert nur 10 Minuten!</p>
|
<p style="margin-top:12px">Kurze Erinnerung — die heutige Alarmprüfung <strong>(Stapel {batch.BatchNumber})</strong> schließt um <strong>8:00 Uhr morgen früh</strong>. Es dauert nur 10 Minuten!</p>
|
||||||
<p style="margin:20px 0"><a href="{reviewUrl}" style="background:#3498db;color:#fff;text-decoration:none;padding:10px 24px;border-radius:6px;font-size:13px;display:inline-block">Überprüfung abschließen →</a></p>
|
<p style="margin:20px 0"><a href="{reviewUrl}" style="background:#3498db;color:#fff;text-decoration:none;padding:10px 24px;border-radius:6px;font-size:13px;display:inline-block">Überprüfung abschließen →</a></p>
|
||||||
<p style="font-size:11px;color:#bbb">inesco Energy Monitor</p>
|
<p style="font-size:11px;color:#bbb">inesco energy Monitor</p>
|
||||||
</body></html>
|
</body></html>
|
||||||
""";
|
""";
|
||||||
await SendEmailAsync(email, subject, html);
|
await SendEmailAsync(email, subject, html);
|
||||||
|
|
@ -1645,7 +1645,7 @@ render();
|
||||||
<table style="border-collapse:collapse;width:100%">
|
<table style="border-collapse:collapse;width:100%">
|
||||||
{beforeAfterRows}
|
{beforeAfterRows}
|
||||||
</table>
|
</table>
|
||||||
<p style="margin-top:18px;font-size:11px;color:#bbb">inesco Energy Monitor</p>
|
<p style="margin-top:18px;font-size:11px;color:#bbb">inesco energy Monitor</p>
|
||||||
</body></html>
|
</body></html>
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ public static class DailyIngestionService
|
||||||
Console.WriteLine($"[DailyIngestion] Starting ingestion for all SodioHome installations...");
|
Console.WriteLine($"[DailyIngestion] Starting ingestion for all SodioHome installations...");
|
||||||
|
|
||||||
var installations = Db.Installations
|
var installations = Db.Installations
|
||||||
.Where(i => i.Product == (Int32)ProductType.SodioHome && i.Device != 3) // Skip Growatt (device=3)
|
.Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3) // Skip Growatt (device=3)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var installation in installations)
|
foreach (var installation in installations)
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ public static class ReportAggregationService
|
||||||
Console.WriteLine("[ReportAggregation] Running Monday weekly report generation...");
|
Console.WriteLine("[ReportAggregation] Running Monday weekly report generation...");
|
||||||
|
|
||||||
var installations = Db.Installations
|
var installations = Db.Installations
|
||||||
.Where(i => i.Product == (Int32)ProductType.SodioHome && i.Device != 3) // Skip Growatt (device=3)
|
.Where(i => (i.Product == (Int32)ProductType.SodioHome || i.Product == (Int32)ProductType.SodistorePro) && i.Device != 3) // Skip Growatt (device=3)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var generated = 0;
|
var generated = 0;
|
||||||
|
|
@ -364,12 +364,15 @@ public static class ReportAggregationService
|
||||||
var installationName = installation?.Name ?? $"Installation {installationId}";
|
var installationName = installation?.Name ?? $"Installation {installationId}";
|
||||||
var monthName = new DateTime(year, month, 1).ToString("MMMM yyyy");
|
var monthName = new DateTime(year, month, 1).ToString("MMMM yyyy");
|
||||||
|
|
||||||
|
var weatherCity = !string.IsNullOrWhiteSpace(installation?.City) ? installation.City : installation?.Location;
|
||||||
|
var weatherRegion = !string.IsNullOrWhiteSpace(installation?.Canton) ? installation.Canton : installation?.Region;
|
||||||
|
|
||||||
var aiInsight = await GenerateMonthlyAiInsightAsync(
|
var aiInsight = await GenerateMonthlyAiInsightAsync(
|
||||||
installationName, monthName, days.Count,
|
installationName, monthName, days.Count,
|
||||||
totalPv, totalConsump, totalGridIn, totalGridOut,
|
totalPv, totalConsump, totalGridIn, totalGridOut,
|
||||||
totalBattChg, totalBattDis, energySaved, savingsCHF,
|
totalBattChg, totalBattDis, energySaved, savingsCHF,
|
||||||
selfSufficiency, batteryEff, language,
|
selfSufficiency, batteryEff, language,
|
||||||
installation?.Location, installation?.Country, installation?.Region);
|
weatherCity, installation?.Country, weatherRegion);
|
||||||
|
|
||||||
var monthlySummary = new MonthlyReportSummary
|
var monthlySummary = new MonthlyReportSummary
|
||||||
{
|
{
|
||||||
|
|
@ -591,6 +594,8 @@ public static class ReportAggregationService
|
||||||
var installationName = installation?.Name
|
var installationName = installation?.Name
|
||||||
?? $"Installation {report.InstallationId}";
|
?? $"Installation {report.InstallationId}";
|
||||||
var monthName = new DateTime(report.Year, report.Month, 1).ToString("MMMM yyyy");
|
var monthName = new DateTime(report.Year, report.Month, 1).ToString("MMMM yyyy");
|
||||||
|
var weatherCity = !string.IsNullOrWhiteSpace(installation?.City) ? installation.City : installation?.Location;
|
||||||
|
var weatherRegion = !string.IsNullOrWhiteSpace(installation?.Canton) ? installation.Canton : installation?.Region;
|
||||||
return GetOrGenerateInsightAsync("monthly", report.Id, language,
|
return GetOrGenerateInsightAsync("monthly", report.Id, language,
|
||||||
() => GenerateMonthlyAiInsightAsync(
|
() => GenerateMonthlyAiInsightAsync(
|
||||||
installationName, monthName, report.WeekCount,
|
installationName, monthName, report.WeekCount,
|
||||||
|
|
@ -599,7 +604,7 @@ public static class ReportAggregationService
|
||||||
report.TotalBatteryCharged, report.TotalBatteryDischarged,
|
report.TotalBatteryCharged, report.TotalBatteryDischarged,
|
||||||
report.TotalEnergySaved, report.TotalSavingsCHF,
|
report.TotalEnergySaved, report.TotalSavingsCHF,
|
||||||
report.SelfSufficiencyPercent, report.BatteryEfficiencyPercent, language,
|
report.SelfSufficiencyPercent, report.BatteryEfficiencyPercent, language,
|
||||||
installation?.Location, installation?.Country, installation?.Region));
|
weatherCity, installation?.Country, weatherRegion));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Cached-or-generated AI insight for a stored YearlyReportSummary.</summary>
|
/// <summary>Cached-or-generated AI insight for a stored YearlyReportSummary.</summary>
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ public static class ReportEmailService
|
||||||
GridIn: "Netz Ein",
|
GridIn: "Netz Ein",
|
||||||
GridOut: "Netz Aus",
|
GridOut: "Netz Aus",
|
||||||
BattInOut: "Batt. Laden/Entl.",
|
BattInOut: "Batt. Laden/Entl.",
|
||||||
Footer: "Erstellt von <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
Footer: "Erstellt von <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
FooterLink: "Detaillierte Berichte ansehen auf monitor.inesco.energy"
|
FooterLink: "Detaillierte Berichte ansehen auf monitor.inesco.energy"
|
||||||
),
|
),
|
||||||
"fr" => new EmailStrings(
|
"fr" => new EmailStrings(
|
||||||
|
|
@ -151,7 +151,7 @@ public static class ReportEmailService
|
||||||
GridIn: "Réseau Ent.",
|
GridIn: "Réseau Ent.",
|
||||||
GridOut: "Réseau Sor.",
|
GridOut: "Réseau Sor.",
|
||||||
BattInOut: "Batt. Ch./Déch.",
|
BattInOut: "Batt. Ch./Déch.",
|
||||||
Footer: "Généré par <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
Footer: "Généré par <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
FooterLink: "Consultez vos rapports détaillés sur monitor.inesco.energy"
|
FooterLink: "Consultez vos rapports détaillés sur monitor.inesco.energy"
|
||||||
),
|
),
|
||||||
"it" => new EmailStrings(
|
"it" => new EmailStrings(
|
||||||
|
|
@ -182,7 +182,7 @@ public static class ReportEmailService
|
||||||
GridIn: "Rete Ent.",
|
GridIn: "Rete Ent.",
|
||||||
GridOut: "Rete Usc.",
|
GridOut: "Rete Usc.",
|
||||||
BattInOut: "Batt. Car./Sc.",
|
BattInOut: "Batt. Car./Sc.",
|
||||||
Footer: "Generato da <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
Footer: "Generato da <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
FooterLink: "Visualizza i tuoi report dettagliati su monitor.inesco.energy"
|
FooterLink: "Visualizza i tuoi report dettagliati su monitor.inesco.energy"
|
||||||
),
|
),
|
||||||
_ => new EmailStrings(
|
_ => new EmailStrings(
|
||||||
|
|
@ -213,7 +213,7 @@ public static class ReportEmailService
|
||||||
GridIn: "Grid In",
|
GridIn: "Grid In",
|
||||||
GridOut: "Grid Out",
|
GridOut: "Grid Out",
|
||||||
BattInOut: "Batt. Ch./Dis.",
|
BattInOut: "Batt. Ch./Dis.",
|
||||||
Footer: "Generated by <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
Footer: "Generated by <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
FooterLink: "View your detailed reports at monitor.inesco.energy"
|
FooterLink: "View your detailed reports at monitor.inesco.energy"
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -340,7 +340,7 @@ public static class ReportEmailService
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<tr>
|
<tr>
|
||||||
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
||||||
<img src=""{LogoBase64}"" alt=""inesco Energy"" style=""height:36px;margin-bottom:12px"" />
|
<img src=""{LogoBase64}"" alt=""inesco energy"" style=""height:36px;margin-bottom:12px"" />
|
||||||
<div style=""font-size:20px;font-weight:bold"">{s.Title}</div>
|
<div style=""font-size:20px;font-weight:bold"">{s.Title}</div>
|
||||||
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{r.InstallationName}</div>
|
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{r.InstallationName}</div>
|
||||||
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{r.PeriodStart} — {r.PeriodEnd}</div>
|
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{r.PeriodStart} — {r.PeriodEnd}</div>
|
||||||
|
|
@ -546,56 +546,56 @@ public static class ReportEmailService
|
||||||
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
|
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
|
||||||
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
|
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
|
||||||
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
|
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
|
||||||
"Tage aggregiert", "Erstellt von <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"Tage aggregiert", "Erstellt von <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Detaillierte Berichte ansehen auf monitor.inesco.energy"),
|
"Detaillierte Berichte ansehen auf monitor.inesco.energy"),
|
||||||
("de", "yearly") => new AggregatedEmailStrings(
|
("de", "yearly") => new AggregatedEmailStrings(
|
||||||
"Jährlicher Leistungsbericht", "Jährliche Erkenntnisse", "Jährliche Zusammenfassung", "Ihre Ersparnisse dieses Jahr",
|
"Jährlicher Leistungsbericht", "Jährliche Erkenntnisse", "Jährliche Zusammenfassung", "Ihre Ersparnisse dieses Jahr",
|
||||||
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
|
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
|
||||||
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
|
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
|
||||||
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
|
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
|
||||||
"Monate aggregiert", "Erstellt von <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"Monate aggregiert", "Erstellt von <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Detaillierte Berichte ansehen auf monitor.inesco.energy"),
|
"Detaillierte Berichte ansehen auf monitor.inesco.energy"),
|
||||||
("fr", "monthly") => new AggregatedEmailStrings(
|
("fr", "monthly") => new AggregatedEmailStrings(
|
||||||
"Rapport de performance mensuel", "Aperçus du mois", "Résumé du mois", "Vos économies ce mois",
|
"Rapport de performance mensuel", "Aperçus du mois", "Résumé du mois", "Vos économies ce mois",
|
||||||
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
|
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
|
||||||
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
|
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
|
||||||
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
|
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
|
||||||
"jours agrégés", "Généré par <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"jours agrégés", "Généré par <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Consultez vos rapports détaillés sur monitor.inesco.energy"),
|
"Consultez vos rapports détaillés sur monitor.inesco.energy"),
|
||||||
("fr", "yearly") => new AggregatedEmailStrings(
|
("fr", "yearly") => new AggregatedEmailStrings(
|
||||||
"Rapport de performance annuel", "Aperçus de l'année", "Résumé de l'année", "Vos économies cette année",
|
"Rapport de performance annuel", "Aperçus de l'année", "Résumé de l'année", "Vos économies cette année",
|
||||||
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
|
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
|
||||||
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
|
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
|
||||||
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
|
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
|
||||||
"mois agrégés", "Généré par <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"mois agrégés", "Généré par <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Consultez vos rapports détaillés sur monitor.inesco.energy"),
|
"Consultez vos rapports détaillés sur monitor.inesco.energy"),
|
||||||
("it", "monthly") => new AggregatedEmailStrings(
|
("it", "monthly") => new AggregatedEmailStrings(
|
||||||
"Rapporto mensile delle prestazioni", "Approfondimenti mensili", "Riepilogo mensile", "I tuoi risparmi questo mese",
|
"Rapporto mensile delle prestazioni", "Approfondimenti mensili", "Riepilogo mensile", "I tuoi risparmi questo mese",
|
||||||
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
|
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
|
||||||
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
|
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
|
||||||
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
|
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
|
||||||
"giorni aggregati", "Generato da <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"giorni aggregati", "Generato da <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Visualizza i tuoi report dettagliati su monitor.inesco.energy"),
|
"Visualizza i tuoi report dettagliati su monitor.inesco.energy"),
|
||||||
("it", "yearly") => new AggregatedEmailStrings(
|
("it", "yearly") => new AggregatedEmailStrings(
|
||||||
"Rapporto annuale delle prestazioni", "Approfondimenti annuali", "Riepilogo annuale", "I tuoi risparmi quest'anno",
|
"Rapporto annuale delle prestazioni", "Approfondimenti annuali", "Riepilogo annuale", "I tuoi risparmi quest'anno",
|
||||||
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
|
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
|
||||||
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
|
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
|
||||||
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
|
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
|
||||||
"mesi aggregati", "Generato da <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"mesi aggregati", "Generato da <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"Visualizza i tuoi report dettagliati su monitor.inesco.energy"),
|
"Visualizza i tuoi report dettagliati su monitor.inesco.energy"),
|
||||||
(_, "monthly") => new AggregatedEmailStrings(
|
(_, "monthly") => new AggregatedEmailStrings(
|
||||||
"Monthly Performance Report", "Monthly Insights", "Monthly Summary", "Your Savings This Month",
|
"Monthly Performance Report", "Monthly Insights", "Monthly Summary", "Your Savings This Month",
|
||||||
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
|
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
|
||||||
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
|
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
|
||||||
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
|
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
|
||||||
"days aggregated", "Generated by <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"days aggregated", "Generated by <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"View your detailed reports at monitor.inesco.energy"),
|
"View your detailed reports at monitor.inesco.energy"),
|
||||||
_ => new AggregatedEmailStrings(
|
_ => new AggregatedEmailStrings(
|
||||||
"Annual Performance Report", "Annual Insights", "Annual Summary", "Your Savings This Year",
|
"Annual Performance Report", "Annual Insights", "Annual Summary", "Your Savings This Year",
|
||||||
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
|
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
|
||||||
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
|
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
|
||||||
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
|
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
|
||||||
"months aggregated", "Generated by <strong style=\"color:#666\">inesco Energy Monitor</strong>",
|
"months aggregated", "Generated by <strong style=\"color:#666\">inesco energy Monitor</strong>",
|
||||||
"View your detailed reports at monitor.inesco.energy")
|
"View your detailed reports at monitor.inesco.energy")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -637,7 +637,7 @@ public static class ReportEmailService
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<tr>
|
<tr>
|
||||||
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
||||||
<img src=""{LogoBase64}"" alt=""inesco Energy"" style=""height:36px;margin-bottom:12px"" />
|
<img src=""{LogoBase64}"" alt=""inesco energy"" style=""height:36px;margin-bottom:12px"" />
|
||||||
<div style=""font-size:20px;font-weight:bold"">{dailyTitle}</div>
|
<div style=""font-size:20px;font-weight:bold"">{dailyTitle}</div>
|
||||||
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{installationName}</div>
|
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{installationName}</div>
|
||||||
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{record.Date}</div>
|
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{record.Date}</div>
|
||||||
|
|
@ -723,7 +723,7 @@ public static class ReportEmailService
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<tr>
|
<tr>
|
||||||
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
<td style=""background:#2c3e50;padding:24px 30px;color:#ffffff"">
|
||||||
<img src=""{LogoBase64}"" alt=""inesco Energy"" style=""height:36px;margin-bottom:12px"" />
|
<img src=""{LogoBase64}"" alt=""inesco energy"" style=""height:36px;margin-bottom:12px"" />
|
||||||
<div style=""font-size:20px;font-weight:bold"">{s.Title}</div>
|
<div style=""font-size:20px;font-weight:bold"">{s.Title}</div>
|
||||||
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{installationName}</div>
|
<div style=""font-size:14px;margin-top:6px;opacity:0.9"">{installationName}</div>
|
||||||
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{periodStart} — {periodEnd}</div>
|
<div style=""font-size:13px;margin-top:2px;opacity:0.7"">{periodStart} — {periodEnd}</div>
|
||||||
|
|
|
||||||
|
|
@ -179,9 +179,9 @@ public static class WeeklyReportService
|
||||||
|
|
||||||
// 4. Get installation location for weather forecast
|
// 4. Get installation location for weather forecast
|
||||||
var installation = Db.GetInstallationById(installationId);
|
var installation = Db.GetInstallationById(installationId);
|
||||||
var location = installation?.Location;
|
var location = !string.IsNullOrWhiteSpace(installation?.City) ? installation.City : installation?.Location;
|
||||||
var country = installation?.Country;
|
var country = installation?.Country;
|
||||||
var region = installation?.Region;
|
var region = !string.IsNullOrWhiteSpace(installation?.Canton) ? installation.Canton : installation?.Region;
|
||||||
Console.WriteLine($"[WeeklyReportService] Installation {installationId}: Location='{location}', Region='{region}', Country='{country}', HourlyRecords={currentHourlyData.Count}");
|
Console.WriteLine($"[WeeklyReportService] Installation {installationId}: Location='{location}', Region='{region}', Country='{country}', HourlyRecords={currentHourlyData.Count}");
|
||||||
|
|
||||||
return await GenerateReportFromDataAsync(
|
return await GenerateReportFromDataAsync(
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ public static class RabbitMqManager
|
||||||
monitorLink =
|
monitorLink =
|
||||||
$"https://monitor.inesco.energy/sodistoregrid_installations/list/installation/{installation.S3BucketId}/batteryview";
|
$"https://monitor.inesco.energy/sodistoregrid_installations/list/installation/{installation.S3BucketId}/batteryview";
|
||||||
}
|
}
|
||||||
|
else if (installation.Product == (int)ProductType.SodistorePro)
|
||||||
|
{
|
||||||
|
monitorLink =
|
||||||
|
$"https://monitor.inesco.energy/sodistorepro_installations/list/installation/{installation.S3BucketId}/batteryview";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
monitorLink =
|
monitorLink =
|
||||||
|
|
@ -131,7 +136,7 @@ public static class RabbitMqManager
|
||||||
Console.WriteLine("Send replace battery email to the support team for installation "+installationId);
|
Console.WriteLine("Send replace battery email to the support team for installation "+installationId);
|
||||||
string recipient = "support@innov.energy";
|
string recipient = "support@innov.energy";
|
||||||
string subject = $"Battery Alarm from {installation.Name}: 2 or more strings broken";
|
string subject = $"Battery Alarm from {installation.Name}: 2 or more strings broken";
|
||||||
string text = $"Dear inesco Energy Support Team,\n" +
|
string text = $"Dear inesco energy Support Team,\n" +
|
||||||
$"\n"+
|
$"\n"+
|
||||||
$"Installation Name: {installation.Name}\n"+
|
$"Installation Name: {installation.Name}\n"+
|
||||||
$"\n"+
|
$"\n"+
|
||||||
|
|
@ -143,7 +148,7 @@ public static class RabbitMqManager
|
||||||
$"\n"+
|
$"\n"+
|
||||||
$"Thank you for your great support:)";
|
$"Thank you for your great support:)";
|
||||||
//Disable this function now
|
//Disable this function now
|
||||||
//Mailer.Send("inesco Energy Support Team", recipient, subject, text);
|
//Mailer.Send("inesco energy Support Team", recipient, subject, text);
|
||||||
}
|
}
|
||||||
//Create a new error and add it to the database
|
//Create a new error and add it to the database
|
||||||
Db.HandleError(newError, installationId);
|
Db.HandleError(newError, installationId);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ public static class WebsocketManager
|
||||||
(installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) ||
|
(installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) ||
|
||||||
(installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(4)) ||
|
(installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(4)) ||
|
||||||
(installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
(installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
||||||
(installationConnection.Value.Product == (int)ProductType.SodistoreGrid && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2))
|
(installationConnection.Value.Product == (int)ProductType.SodistoreGrid && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
||||||
|
(installationConnection.Value.Product == (int)ProductType.SodistorePro && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(4))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Installation ID is " + installationConnection.Key);
|
Console.WriteLine("Installation ID is " + installationConnection.Key);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>Inesco Energy</title>
|
<title>inesco energy</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,41 @@ function App() {
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const username = searchParams.get('username');
|
const username = searchParams.get('username');
|
||||||
const {
|
const {
|
||||||
|
accessToSalimax,
|
||||||
|
accessToSodiohome,
|
||||||
|
accessToSodistore,
|
||||||
|
accessToSodistoreGrid,
|
||||||
|
accessToSodistorePro,
|
||||||
setAccessToSalimax,
|
setAccessToSalimax,
|
||||||
setAccessToSalidomo,
|
setAccessToSalidomo,
|
||||||
setAccessToSodiohome,
|
setAccessToSodiohome,
|
||||||
setAccessToSodistore,
|
setAccessToSodistore,
|
||||||
setAccessToSodistoreGrid
|
setAccessToSodistoreGrid,
|
||||||
|
setAccessToSodistorePro
|
||||||
} = useContext(ProductIdContext);
|
} = useContext(ProductIdContext);
|
||||||
|
|
||||||
|
const defaultRoute = accessToSodiohome
|
||||||
|
? routes.sodiohome_installations
|
||||||
|
: accessToSodistorePro
|
||||||
|
? routes.sodistorepro_installations
|
||||||
|
: accessToSodistoreGrid
|
||||||
|
? routes.sodistoregrid_installations
|
||||||
|
: accessToSodistore
|
||||||
|
? routes.sodistore_installations
|
||||||
|
: accessToSalimax
|
||||||
|
? routes.installations
|
||||||
|
: routes.salidomo_installations;
|
||||||
|
|
||||||
|
const detectBrowserLanguage = (): string => {
|
||||||
|
const browserLang = navigator.language?.toLowerCase() || '';
|
||||||
|
if (browserLang.startsWith('de')) return 'de';
|
||||||
|
if (browserLang.startsWith('fr')) return 'fr';
|
||||||
|
if (browserLang.startsWith('it')) return 'it';
|
||||||
|
return 'en';
|
||||||
|
};
|
||||||
|
|
||||||
const [language, setLanguage] = useState<string>(
|
const [language, setLanguage] = useState<string>(
|
||||||
() => localStorage.getItem('language') || currentUser?.language || 'en'
|
() => localStorage.getItem('language') || currentUser?.language || detectBrowserLanguage()
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSelectLanguage = (lang: string) => {
|
const onSelectLanguage = (lang: string) => {
|
||||||
|
|
@ -94,11 +120,19 @@ function App() {
|
||||||
const Login = Loader(lazy(() => import('src/components/login')));
|
const Login = Loader(lazy(() => import('src/components/login')));
|
||||||
const Users = Loader(lazy(() => import('src/content/dashboards/Users')));
|
const Users = Loader(lazy(() => import('src/content/dashboards/Users')));
|
||||||
|
|
||||||
const loginToResetPassword = () => {
|
useEffect(() => {
|
||||||
|
if (!username || token) return;
|
||||||
|
|
||||||
axiosConfigWithoutToken
|
axiosConfigWithoutToken
|
||||||
.post('/Login', null, { params: { username, password: '' } })
|
.post('/Login', null, { params: { username, password: '' } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data && response.data.token) {
|
if (response.data && response.data.token) {
|
||||||
|
// Clear the username param from URL to prevent re-login loops
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('username');
|
||||||
|
url.searchParams.delete('reset');
|
||||||
|
window.history.replaceState({}, '', url.pathname);
|
||||||
|
|
||||||
setNewToken(response.data.token);
|
setNewToken(response.data.token);
|
||||||
setUser(response.data.user);
|
setUser(response.data.user);
|
||||||
setAccessToSalimax(response.data.accessToSalimax);
|
setAccessToSalimax(response.data.accessToSalimax);
|
||||||
|
|
@ -106,25 +140,11 @@ function App() {
|
||||||
setAccessToSodiohome(response.data.accessToSodioHome);
|
setAccessToSodiohome(response.data.accessToSodioHome);
|
||||||
setAccessToSodistore(response.data.accessToSodistoreMax);
|
setAccessToSodistore(response.data.accessToSodistoreMax);
|
||||||
setAccessToSodistoreGrid(response.data.accessToSodistoreGrid);
|
setAccessToSodistoreGrid(response.data.accessToSodistoreGrid);
|
||||||
if (response.data.accessToSalimax) {
|
setAccessToSodistorePro(response.data.accessToSodistorePro);
|
||||||
navigate(routes.installations);
|
|
||||||
} else if (response.data.accessToSalidomo) {
|
|
||||||
navigate(routes.salidomo_installations);
|
|
||||||
} else if (response.data.accessToSodistoreMax) {
|
|
||||||
navigate(routes.sodistore_installations);
|
|
||||||
} else if (response.data.accessToSodistoreGrid) {
|
|
||||||
navigate(routes.sodistoregrid_installations);
|
|
||||||
} else {
|
|
||||||
navigate(routes.sodiohome_installations);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
}, [username]);
|
||||||
|
|
||||||
if (username) {
|
|
||||||
loginToResetPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -158,8 +178,14 @@ function App() {
|
||||||
if (token && currentUser?.mustResetPassword) {
|
if (token && currentUser?.mustResetPassword) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<CssBaseline />
|
<IntlProvider
|
||||||
<SetNewPassword></SetNewPassword>
|
messages={getTranslations()}
|
||||||
|
locale={language}
|
||||||
|
defaultLocale="en"
|
||||||
|
>
|
||||||
|
<CssBaseline />
|
||||||
|
<SetNewPassword></SetNewPassword>
|
||||||
|
</IntlProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -177,11 +203,11 @@ function App() {
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path={''}
|
path={''}
|
||||||
element={<Navigate to={routes.installations}></Navigate>}
|
element={<Navigate to={defaultRoute}></Navigate>}
|
||||||
></Route>
|
></Route>
|
||||||
<Route
|
<Route
|
||||||
path={'login'}
|
path={'login'}
|
||||||
element={<Navigate to={routes.installations}></Navigate>}
|
element={<Navigate to={defaultRoute}></Navigate>}
|
||||||
></Route>
|
></Route>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
|
|
@ -228,6 +254,15 @@ function App() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={routes.sodistorepro_installations + '*'}
|
||||||
|
element={
|
||||||
|
<AccessContextProvider>
|
||||||
|
<SodioHomeInstallationTabs product={5} />
|
||||||
|
</AccessContextProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.sodistoregrid_installations + '*'}
|
path={routes.sodistoregrid_installations + '*'}
|
||||||
element={
|
element={
|
||||||
|
|
@ -241,7 +276,7 @@ function App() {
|
||||||
<Route path={routes.users + '*'} element={<Users />} />
|
<Route path={routes.users + '*'} element={<Users />} />
|
||||||
<Route
|
<Route
|
||||||
path={'*'}
|
path={'*'}
|
||||||
element={<Navigate to={routes.installations}></Navigate>}
|
element={<Navigate to={defaultRoute}></Navigate>}
|
||||||
></Route>
|
></Route>
|
||||||
<Route path="ResetPassword" element={<ResetPassword />}></Route>
|
<Route path="ResetPassword" element={<ResetPassword />}></Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"sodistore_installations": "/sodistore_installations/",
|
"sodistore_installations": "/sodistore_installations/",
|
||||||
"sodiohome_installations": "/sodiohome_installations/",
|
"sodiohome_installations": "/sodiohome_installations/",
|
||||||
"sodistoregrid_installations": "/sodistoregrid_installations/",
|
"sodistoregrid_installations": "/sodistoregrid_installations/",
|
||||||
|
"sodistorepro_installations": "/sodistorepro_installations/",
|
||||||
"installation": "installation/",
|
"installation": "installation/",
|
||||||
"login": "/login/",
|
"login": "/login/",
|
||||||
"forgotPassword": "/forgotPassword/",
|
"forgotPassword": "/forgotPassword/",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Box
|
||||||
|
} from '@mui/material';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
export const CURRENT_TERMS_VERSION = 2;
|
||||||
|
|
||||||
|
interface AcknowledgementDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onAcknowledge: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AcknowledgementDialog: React.FC<AcknowledgementDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onAcknowledge
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_dialog_title"
|
||||||
|
defaultMessage="Welcome to inesco energy"
|
||||||
|
/>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box mb={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_data_heading"
|
||||||
|
defaultMessage="Your Data"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_data_body"
|
||||||
|
defaultMessage="Your installation data is securely stored in Switzerland. We do not share your data with third parties."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box my={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_ai_heading"
|
||||||
|
defaultMessage="AI-Powered Insights"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_ai_body"
|
||||||
|
defaultMessage="We use an AI service hosted in the EU to provide diagnostics and insights for your installations. AI-generated results are recommendations and should be verified by qualified personnel."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box mt={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_cookies_heading"
|
||||||
|
defaultMessage="Browser Storage"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_cookies_body"
|
||||||
|
defaultMessage="This platform stores login and preference settings in your browser to keep you signed in and remember your language choice."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={onAcknowledge}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="terms_acknowledge_button"
|
||||||
|
defaultMessage="I understand"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AcknowledgementDialog;
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Box
|
||||||
|
} from '@mui/material';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
interface DataPrivacyDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataPrivacyDialog: React.FC<DataPrivacyDialogProps> = ({
|
||||||
|
open,
|
||||||
|
onClose
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
|
<DialogTitle>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_dialog_title"
|
||||||
|
defaultMessage="Data & Privacy"
|
||||||
|
/>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Box mb={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_data_heading"
|
||||||
|
defaultMessage="Where is my data stored?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_data_body"
|
||||||
|
defaultMessage="Your installation data is stored on servers in Switzerland. Only authorized inesco energy personnel can access your data for support purposes."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box my={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_ai_heading"
|
||||||
|
defaultMessage="How is AI used?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_ai_body"
|
||||||
|
defaultMessage="We use an AI service hosted in the European Union to analyze your installation data and provide diagnostic insights. The AI processes technical data such as battery readings and error codes. AI recommendations should always be verified by qualified personnel."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box my={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_browser_heading"
|
||||||
|
defaultMessage="What does my browser store?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_browser_body"
|
||||||
|
defaultMessage="Your browser stores your login session to keep you signed in, and your language and theme preferences. No tracking or advertising cookies are used."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box mt={2}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_access_heading"
|
||||||
|
defaultMessage="Who has access to my data?"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_access_body"
|
||||||
|
defaultMessage="Your data is not shared with third parties. It is used solely to operate the platform and provide you with insights about your installations."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" onClick={onClose}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="privacy_close_button"
|
||||||
|
defaultMessage="Close"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataPrivacyDialog;
|
||||||
|
|
@ -18,7 +18,7 @@ function Footer() {
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
© 2025 - Inesco Energy AG
|
© 2025 - inesco energy AG
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
|
|
@ -33,7 +33,7 @@ function Footer() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
Inesco Energy AG
|
inesco energy AG
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ function Logo() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipWrapper title="inesco Energy" arrow>
|
<TooltipWrapper title="inesco energy" arrow>
|
||||||
<LogoWrapper to="/overview">
|
<LogoWrapper to="/overview">
|
||||||
<Badge
|
<Badge
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ function Login() {
|
||||||
setAccessToSalidomo,
|
setAccessToSalidomo,
|
||||||
setAccessToSodiohome,
|
setAccessToSodiohome,
|
||||||
setAccessToSodistore,
|
setAccessToSodistore,
|
||||||
setAccessToSodistoreGrid
|
setAccessToSodistoreGrid,
|
||||||
|
setAccessToSodistorePro
|
||||||
} = useContext(ProductIdContext);
|
} = useContext(ProductIdContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
@ -86,16 +87,19 @@ function Login() {
|
||||||
setAccessToSodiohome(response.data.accessToSodioHome);
|
setAccessToSodiohome(response.data.accessToSodioHome);
|
||||||
setAccessToSodistore(response.data.accessToSodistoreMax);
|
setAccessToSodistore(response.data.accessToSodistoreMax);
|
||||||
setAccessToSodistoreGrid(response.data.accessToSodistoreGrid);
|
setAccessToSodistoreGrid(response.data.accessToSodistoreGrid);
|
||||||
if (response.data.accessToSalimax) {
|
setAccessToSodistorePro(response.data.accessToSodistorePro);
|
||||||
navigate(routes.installations);
|
if (response.data.accessToSodioHome) {
|
||||||
} else if (response.data.accessToSalidomo) {
|
navigate(routes.sodiohome_installations);
|
||||||
navigate(routes.salidomo_installations);
|
} else if (response.data.accessToSodistorePro) {
|
||||||
} else if (response.data.accessToSodistoreMax) {
|
navigate(routes.sodistorepro_installations);
|
||||||
navigate(routes.sodistore_installations);
|
|
||||||
} else if (response.data.accessToSodistoreGrid) {
|
} else if (response.data.accessToSodistoreGrid) {
|
||||||
navigate(routes.sodistoregrid_installations);
|
navigate(routes.sodistoregrid_installations);
|
||||||
|
} else if (response.data.accessToSodistoreMax) {
|
||||||
|
navigate(routes.sodistore_installations);
|
||||||
|
} else if (response.data.accessToSalimax) {
|
||||||
|
navigate(routes.installations);
|
||||||
} else {
|
} else {
|
||||||
navigate(routes.sodiohome_installations);
|
navigate(routes.salidomo_installations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -240,12 +240,13 @@ function BatteryViewSodioHome(props: BatteryViewSodioHomeProps) {
|
||||||
align="center"
|
align="center"
|
||||||
sx={{ fontWeight: 'bold' }}
|
sx={{ fontWeight: 'bold' }}
|
||||||
>
|
>
|
||||||
<Link
|
{/* Detailed battery view commented out */}
|
||||||
|
{/*<Link
|
||||||
style={{ color: 'black' }}
|
style={{ color: 'black' }}
|
||||||
to={routes.detailed_view + BatteryId}
|
to={routes.detailed_view + BatteryId}
|
||||||
>
|
>*/}
|
||||||
{'Battery Cluster ' + BatteryId}
|
{'Battery Cluster ' + BatteryId}
|
||||||
</Link>
|
{/*</Link>*/}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -151,22 +151,25 @@ function MainStatsSodioHome(props: MainStatsSodioHomeProps) {
|
||||||
pathsToSearch.push('Node' + i);
|
pathsToSearch.push('Node' + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const total = pathsToSearch.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
pathsToSearch.forEach((devicePath) => {
|
pathsToSearch.forEach((devicePath) => {
|
||||||
if (
|
if (
|
||||||
Object.hasOwnProperty.call(chartData[category].data, devicePath) &&
|
Object.hasOwnProperty.call(chartData[category].data, devicePath) &&
|
||||||
chartData[category].data[devicePath].data.length != 0
|
chartData[category].data[devicePath].data.length != 0
|
||||||
) {
|
) {
|
||||||
|
// Spread color indices evenly across the palette for better contrast
|
||||||
|
const colorIndex = total <= 1 ? 0 : Math.round(i * 9 / (total - 1));
|
||||||
series.push({
|
series.push({
|
||||||
...chartData[category].data[devicePath],
|
...chartData[category].data[devicePath],
|
||||||
color:
|
color:
|
||||||
color === 'blue'
|
color === 'blue'
|
||||||
? blueColors[i]
|
? blueColors[colorIndex]
|
||||||
: color === 'red'
|
: color === 'red'
|
||||||
? redColors[i]
|
? redColors[colorIndex]
|
||||||
: color === 'green'
|
: color === 'green'
|
||||||
? greenColors[i]
|
? greenColors[colorIndex]
|
||||||
: orangeColors[i]
|
: orangeColors[colorIndex]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ function Information(props: InformationProps) {
|
||||||
|
|
||||||
const canEdit = currentUser.userType == UserType.admin;
|
const canEdit = currentUser.userType == UserType.admin;
|
||||||
const isPartner = currentUser.userType == UserType.partner;
|
const isPartner = currentUser.userType == UserType.partner;
|
||||||
const isSodistore = formValues.product === 3 || formValues.product === 4;
|
const isSodistore = formValues.product === 3 || formValues.product === 4 || formValues.product === 5;
|
||||||
|
|
||||||
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
const [networkProviders, setNetworkProviders] = useState<string[]>([]);
|
||||||
const [loadingProviders, setLoadingProviders] = useState(false);
|
const [loadingProviders, setLoadingProviders] = useState(false);
|
||||||
|
|
@ -426,12 +426,18 @@ function Information(props: InformationProps) {
|
||||||
label="S3 Bucket Name"
|
label="S3 Bucket Name"
|
||||||
name="s3bucketname"
|
name="s3bucketname"
|
||||||
value={
|
value={
|
||||||
formValues.product === 0 || formValues.product == 3
|
formValues.product === 0 || formValues.product === 3
|
||||||
? formValues.s3BucketId +
|
? formValues.s3BucketId +
|
||||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||||
: formValues.product == 4
|
: formValues.product === 4
|
||||||
? formValues.s3BucketId +
|
? formValues.s3BucketId +
|
||||||
'-5109c126-e141-43ab-8658-f3c44c838ae8'
|
'-5109c126-e141-43ab-8658-f3c44c838ae8'
|
||||||
|
: formValues.product === 5
|
||||||
|
? formValues.s3BucketId +
|
||||||
|
'-325c9373-9025-4a8d-bf5a-f9eedf1f155c'
|
||||||
|
: formValues.product === 1
|
||||||
|
? formValues.s3BucketId +
|
||||||
|
'-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'
|
||||||
: formValues.s3BucketId +
|
: formValues.s3BucketId +
|
||||||
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
'-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,128 @@
|
||||||
|
export const SODIOHOME_DEVICE_TYPES = [
|
||||||
|
{ id: 3, name: 'Growatt' },
|
||||||
|
{ id: 4, name: 'inesco 12K - WR Hybrid' }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const getDeviceTypeName = (deviceId: number): string =>
|
||||||
|
SODIOHOME_DEVICE_TYPES.find(d => d.id === deviceId)?.name ?? '';
|
||||||
|
|
||||||
|
// [inverter][cluster] = batteryCount
|
||||||
|
export type PresetConfig = number[][];
|
||||||
|
|
||||||
|
// 3D array: [inverter][cluster][batteryIndex] = serialNumber
|
||||||
|
export type BatterySnTree = string[][][];
|
||||||
|
|
||||||
|
export const INSTALLATION_PRESETS: Record<string, PresetConfig> = {
|
||||||
|
'sodistore home 9': [[1, 1]],
|
||||||
|
'sodistore home 18': [[2, 2]],
|
||||||
|
'sodistore home 27': [[2, 2], [1, 1]],
|
||||||
|
'sodistore home 36': [[2, 2], [2, 2]],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildSodistoreProPreset = (inverterCount: number): PresetConfig =>
|
||||||
|
Array.from({ length: inverterCount }, () => [2, 2]);
|
||||||
|
|
||||||
|
export const parseSodistoreProInverterCount = (model: string): number => {
|
||||||
|
const n = parseInt(model, 10);
|
||||||
|
return isNaN(n) || n < 1 ? 1 : n;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildEmptyTree = (preset: PresetConfig): BatterySnTree => {
|
||||||
|
return preset.map((inv) =>
|
||||||
|
inv.map((batteryCount) => Array.from({ length: batteryCount }, () => ''))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseBatterySnTree = (
|
||||||
|
raw: string,
|
||||||
|
preset: PresetConfig
|
||||||
|
): BatterySnTree => {
|
||||||
|
if (!raw || raw.trim() === '') {
|
||||||
|
return buildEmptyTree(preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isStructured = raw.includes('/') || raw.includes('|');
|
||||||
|
|
||||||
|
if (isStructured) {
|
||||||
|
const inverters = raw.split('/');
|
||||||
|
return preset.map((invPreset, invIdx) => {
|
||||||
|
const clusterStr = inverters[invIdx] || '';
|
||||||
|
const clusters = clusterStr ? clusterStr.split('|') : [];
|
||||||
|
return invPreset.map((batteryCount, clIdx) => {
|
||||||
|
const batteries = clusters[clIdx]
|
||||||
|
? clusters[clIdx].split(',').map((s) => s.trim())
|
||||||
|
: [];
|
||||||
|
return Array.from({ length: batteryCount }, (_, i) => batteries[i] || '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy flat format: distribute by preset layout
|
||||||
|
const allSns = raw
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s !== '');
|
||||||
|
let idx = 0;
|
||||||
|
return preset.map((inv) =>
|
||||||
|
inv.map((batteryCount) =>
|
||||||
|
Array.from({ length: batteryCount }, () => allSns[idx++] || '')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serializeBatterySnTree = (tree: BatterySnTree): string => {
|
||||||
|
return tree
|
||||||
|
.map((inv) => inv.map((cluster) => cluster.join(',')).join('|'))
|
||||||
|
.join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remapTree = (
|
||||||
|
oldTree: BatterySnTree,
|
||||||
|
newPreset: PresetConfig
|
||||||
|
): BatterySnTree => {
|
||||||
|
return newPreset.map((inv, invIdx) =>
|
||||||
|
inv.map((batteryCount, clIdx) =>
|
||||||
|
Array.from(
|
||||||
|
{ length: batteryCount },
|
||||||
|
(_, batIdx) => oldTree[invIdx]?.[clIdx]?.[batIdx] || ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const computeFlatValues = (
|
||||||
|
preset: PresetConfig,
|
||||||
|
tree: BatterySnTree
|
||||||
|
) => {
|
||||||
|
const totalBatteries = preset.flat().reduce((a, b) => a + b, 0);
|
||||||
|
const totalClusters = preset.reduce((sum, inv) => sum + inv.length, 0);
|
||||||
|
return {
|
||||||
|
batteryNumber: totalBatteries,
|
||||||
|
batteryClusterNumber: totalClusters,
|
||||||
|
batterySerialNumbers: serializeBatterySnTree(tree),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wouldLoseData = (
|
||||||
|
oldTree: BatterySnTree,
|
||||||
|
newPreset: PresetConfig
|
||||||
|
): boolean => {
|
||||||
|
for (let invIdx = 0; invIdx < oldTree.length; invIdx++) {
|
||||||
|
for (let clIdx = 0; clIdx < (oldTree[invIdx] || []).length; clIdx++) {
|
||||||
|
for (
|
||||||
|
let batIdx = 0;
|
||||||
|
batIdx < (oldTree[invIdx][clIdx] || []).length;
|
||||||
|
batIdx++
|
||||||
|
) {
|
||||||
|
const sn = oldTree[invIdx][clIdx][batIdx];
|
||||||
|
if (sn && sn.trim() !== '') {
|
||||||
|
if (invIdx >= newPreset.length) return true;
|
||||||
|
if (clIdx >= (newPreset[invIdx] || []).length) return true;
|
||||||
|
const newBatCount = newPreset[invIdx]?.[clIdx] ?? 0;
|
||||||
|
if (batIdx >= newBatCount) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
@ -131,7 +131,7 @@ export const fetchAggregatedDataJson = (
|
||||||
} else if (r.status === 200) {
|
} else if (r.status === 200) {
|
||||||
const jsontext = await r.text();
|
const jsontext = await r.text();
|
||||||
|
|
||||||
if (product === 2) {
|
if (product === 2 || product === 5) {
|
||||||
return parseSinexcelAggregatedData(jsontext);
|
return parseSinexcelAggregatedData(jsontext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -458,6 +458,10 @@ function InstallationTabs(props: InstallationTabsProps) {
|
||||||
<Navigate
|
<Navigate
|
||||||
to={routes.sodistoregrid_installations + routes.list}
|
to={routes.sodistoregrid_installations + routes.list}
|
||||||
/>
|
/>
|
||||||
|
) : props.product === 5 ? (
|
||||||
|
<Navigate
|
||||||
|
to={routes.sodistorepro_installations + routes.list}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={routes.sodistore_installations + routes.list}
|
to={routes.sodistore_installations + routes.list}
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ function Log(props: LogProps) {
|
||||||
if (source === 'KnowledgeBase')
|
if (source === 'KnowledgeBase')
|
||||||
return <Chip label="Knowledge Base" size="small" sx={{ bgcolor: '#1976d2', color: '#fff', fontWeight: 'bold' }} />;
|
return <Chip label="Knowledge Base" size="small" sx={{ bgcolor: '#1976d2', color: '#fff', fontWeight: 'bold' }} />;
|
||||||
if (source === 'MistralAI')
|
if (source === 'MistralAI')
|
||||||
return <Chip label="Mistral AI" size="small" sx={{ bgcolor: '#7b1fa2', color: '#fff', fontWeight: 'bold' }} />;
|
return <Chip label="AI" size="small" sx={{ bgcolor: '#7b1fa2', color: '#fff', fontWeight: 'bold' }} />;
|
||||||
if (source === 'MistralFailed')
|
if (source === 'MistralFailed')
|
||||||
return <Chip label="Mistral failed" size="small" color="error" />;
|
return <Chip label="AI failed" size="small" color="error" />;
|
||||||
return <Chip label="Not available" size="small" color="default" />;
|
return <Chip label="Not available" size="small" color="default" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -288,7 +288,7 @@ function Log(props: LogProps) {
|
||||||
onChange={e => { setDemoAlarm(e.target.value); setDemoResult(null); }}
|
onChange={e => { setDemoAlarm(e.target.value); setDemoResult(null); }}
|
||||||
sx={{ minWidth: 260 }}
|
sx={{ minWidth: 260 }}
|
||||||
>
|
>
|
||||||
<ListSubheader>Sinexcel</ListSubheader>
|
<ListSubheader>inesco 12K - WR Hybrid</ListSubheader>
|
||||||
{DEMO_ALARMS.sinexcel.map(a => (
|
{DEMO_ALARMS.sinexcel.map(a => (
|
||||||
<MenuItem key={a} value={a}>{a}</MenuItem>
|
<MenuItem key={a} value={a}>{a}</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,15 @@ function UserAccess(props: UserAccessProps) {
|
||||||
|
|
||||||
const fetchAvailableInstallations = useCallback(async () => {
|
const fetchAvailableInstallations = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const [res0, res1, res2, res3] = await Promise.all([
|
const [res0, res1, res2, res3, res4, res5] = await Promise.all([
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`)
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`),
|
||||||
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=4`),
|
||||||
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`)
|
||||||
]);
|
]);
|
||||||
setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data]);
|
setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data, ...res4.data, ...res5.data]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response && err.response.status === 401) removeToken();
|
if (err.response && err.response.status === 401) removeToken();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ interface OverviewProps {
|
||||||
s3Credentials: I_S3Credentials;
|
s3Credentials: I_S3Credentials;
|
||||||
id: number;
|
id: number;
|
||||||
device?: number;
|
device?: number;
|
||||||
|
product?: number;
|
||||||
connected?: boolean;
|
connected?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -565,7 +566,7 @@ function Overview(props: OverviewProps) {
|
||||||
>
|
>
|
||||||
<FormattedMessage id="24_hours" defaultMessage="24-hours" />
|
<FormattedMessage id="24_hours" defaultMessage="24-hours" />
|
||||||
</Button>
|
</Button>
|
||||||
{props.device !== 3 && (
|
{props.device !== 3 && props.product !== 2 && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleWeekData}
|
onClick={handleWeekData}
|
||||||
|
|
@ -817,7 +818,7 @@ function Overview(props: OverviewProps) {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
color: '#ff9900'
|
color: '#ff9900'
|
||||||
},
|
},
|
||||||
...(product !== 2 ? [{
|
...((product !== 2 && product !== 5) ? [{
|
||||||
name: 'Net Energy',
|
name: 'Net Energy',
|
||||||
color: '#e65100',
|
color: '#e65100',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
|
@ -840,7 +841,7 @@ function Overview(props: OverviewProps) {
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
spacing={3}
|
spacing={3}
|
||||||
>
|
>
|
||||||
{!(aggregatedData && product === 2) && (
|
{!(aggregatedData && (product === 2 || product === 5)) && (
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -933,7 +934,7 @@ function Overview(props: OverviewProps) {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
<Grid item md={(aggregatedData && product === 2) ? 12 : 6} xs={12}>
|
<Grid item md={(aggregatedData && (product === 2 || product === 5)) ? 12 : 6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
|
|
@ -1001,14 +1002,14 @@ function Overview(props: OverviewProps) {
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
...getChartOptions(
|
...getChartOptions(
|
||||||
product === 2
|
(product === 2 || product === 5)
|
||||||
? aggregatedDataArray[aggregatedChartState]
|
? aggregatedDataArray[aggregatedChartState]
|
||||||
.chartOverview.dcPowerWithoutHeating
|
.chartOverview.dcPowerWithoutHeating
|
||||||
: aggregatedDataArray[aggregatedChartState]
|
: aggregatedDataArray[aggregatedChartState]
|
||||||
.chartOverview.dcPower,
|
.chartOverview.dcPower,
|
||||||
'weekly',
|
'weekly',
|
||||||
aggregatedDataArray[aggregatedChartState].datelist,
|
aggregatedDataArray[aggregatedChartState].datelist,
|
||||||
product === 2
|
(product === 2 || product === 5)
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
series={[
|
series={[
|
||||||
|
|
@ -1017,7 +1018,7 @@ function Overview(props: OverviewProps) {
|
||||||
.chartData.dcChargingPower,
|
.chartData.dcChargingPower,
|
||||||
color: '#008FFB'
|
color: '#008FFB'
|
||||||
},
|
},
|
||||||
...(product !== 2 ? [{
|
...((product !== 2 && product !== 5) ? [{
|
||||||
...aggregatedDataArray[aggregatedChartState]
|
...aggregatedDataArray[aggregatedChartState]
|
||||||
.chartData.heatingPower,
|
.chartData.heatingPower,
|
||||||
color: '#ff9900'
|
color: '#ff9900'
|
||||||
|
|
@ -1073,7 +1074,7 @@ function Overview(props: OverviewProps) {
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
spacing={3}
|
spacing={3}
|
||||||
>
|
>
|
||||||
{product !== 2 && (
|
{(product !== 2 && product !== 5) && (
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -1136,7 +1137,7 @@ function Overview(props: OverviewProps) {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{product !== 2 && (
|
{(product !== 2 && product !== 5) && (
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -1392,7 +1393,7 @@ function Overview(props: OverviewProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{aggregatedData && product === 2 && (
|
{aggregatedData && (product === 2 || product === 5) && (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
direction="row"
|
direction="row"
|
||||||
|
|
@ -1457,7 +1458,7 @@ function Overview(props: OverviewProps) {
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
spacing={3}
|
spacing={3}
|
||||||
>
|
>
|
||||||
<Grid item md={product === 2 ? 12 : 6} xs={12}>
|
<Grid item md={(product === 2 || product === 5) ? 12 : 6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
|
|
@ -1518,7 +1519,7 @@ function Overview(props: OverviewProps) {
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
{product !== 2 && (
|
{(product !== 2 && product !== 5) && (
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,18 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import routes from '../../../Resources/routes.json';
|
import routes from '../../../Resources/routes.json';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
|
import { getDeviceTypeName } from '../Information/installationSetupUtils';
|
||||||
|
|
||||||
interface FlatInstallationViewProps {
|
interface FlatInstallationViewProps {
|
||||||
installations: I_Installation[];
|
installations: I_Installation[];
|
||||||
|
product?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
|
const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations;
|
||||||
//
|
//
|
||||||
const sortedInstallations = [...props.installations].sort((a, b) => {
|
const sortedInstallations = [...props.installations].sort((a, b) => {
|
||||||
// Compare the status field of each installation and sort them based on the status.
|
// Compare the status field of each installation and sort them based on the status.
|
||||||
|
|
@ -50,7 +53,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
setSelectedInstallation(-1);
|
setSelectedInstallation(-1);
|
||||||
|
|
||||||
navigate(
|
navigate(
|
||||||
routes.sodiohome_installations +
|
baseRoute +
|
||||||
routes.list +
|
routes.list +
|
||||||
routes.installation +
|
routes.installation +
|
||||||
`${installationID}` +
|
`${installationID}` +
|
||||||
|
|
@ -81,9 +84,9 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
sx={{
|
sx={{
|
||||||
display:
|
display:
|
||||||
currentLocation.pathname ===
|
currentLocation.pathname ===
|
||||||
routes.sodiohome_installations + 'list' ||
|
baseRoute + 'list' ||
|
||||||
currentLocation.pathname ===
|
currentLocation.pathname ===
|
||||||
routes.sodiohome_installations + routes.list
|
baseRoute + routes.list
|
||||||
? 'block'
|
? 'block'
|
||||||
: 'none'
|
: 'none'
|
||||||
}}
|
}}
|
||||||
|
|
@ -96,14 +99,14 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FormattedMessage id="name" defaultMessage="Name" />
|
<FormattedMessage id="name" defaultMessage="Name" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
<FormattedMessage id="location" defaultMessage="Location" />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FormattedMessage id="installationSN" defaultMessage="Installation SN" />
|
<FormattedMessage id="installationSN" defaultMessage="Installation SN" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FormattedMessage id="region" defaultMessage="Region" />
|
<FormattedMessage id="DeviceType" defaultMessage="Device Type" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<FormattedMessage id="canton" defaultMessage="Canton" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
<FormattedMessage id="country" defaultMessage="Country" />
|
||||||
|
|
@ -146,19 +149,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
fontWeight="bold"
|
|
||||||
color="text.primary"
|
|
||||||
gutterBottom
|
|
||||||
noWrap
|
|
||||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
|
||||||
>
|
|
||||||
{installation.location}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
|
|
@ -181,7 +171,20 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
noWrap
|
noWrap
|
||||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||||
>
|
>
|
||||||
{installation.region}
|
{getDeviceTypeName(installation.device)}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="text.primary"
|
||||||
|
gutterBottom
|
||||||
|
noWrap
|
||||||
|
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||||
|
>
|
||||||
|
{installation.canton || ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,9 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
const s3Bucket =
|
const s3Bucket =
|
||||||
props.current_installation.s3BucketId.toString() +
|
props.current_installation.s3BucketId.toString() +
|
||||||
'-' +
|
'-' +
|
||||||
'e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa';
|
(props.current_installation.product === 5
|
||||||
|
? '325c9373-9025-4a8d-bf5a-f9eedf1f155c'
|
||||||
|
: 'e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa');
|
||||||
|
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser } = context;
|
||||||
|
|
@ -603,6 +605,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
s3Credentials={s3Credentials}
|
s3Credentials={s3Credentials}
|
||||||
id={props.current_installation.id}
|
id={props.current_installation.id}
|
||||||
device={props.current_installation.device}
|
device={props.current_installation.device}
|
||||||
|
product={props.current_installation.product}
|
||||||
connected={connected}
|
connected={connected}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ import SodioHomeInstallation from './Installation';
|
||||||
|
|
||||||
interface installationSearchProps {
|
interface installationSearchProps {
|
||||||
installations: I_Installation[];
|
installations: I_Installation[];
|
||||||
|
product?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InstallationSearch(props: installationSearchProps) {
|
function InstallationSearch(props: installationSearchProps) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const currentLocation = useLocation();
|
const currentLocation = useLocation();
|
||||||
|
const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations;
|
||||||
// const [filteredData, setFilteredData] = useState(props.installations);
|
// const [filteredData, setFilteredData] = useState(props.installations);
|
||||||
|
|
||||||
const indexedData = useMemo(() => {
|
const indexedData = useMemo(() => {
|
||||||
|
|
@ -46,9 +48,9 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
sx={{
|
sx={{
|
||||||
display:
|
display:
|
||||||
currentLocation.pathname ===
|
currentLocation.pathname ===
|
||||||
routes.sodiohome_installations + 'list' ||
|
baseRoute + 'list' ||
|
||||||
currentLocation.pathname ===
|
currentLocation.pathname ===
|
||||||
routes.sodiohome_installations + routes.list
|
baseRoute + routes.list
|
||||||
? 'block'
|
? 'block'
|
||||||
: 'none'
|
: 'none'
|
||||||
}}
|
}}
|
||||||
|
|
@ -79,7 +81,7 @@ function InstallationSearch(props: installationSearchProps) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<FlatInstallationView installations={filteredData} />
|
<FlatInstallationView installations={filteredData} product={props.product} />
|
||||||
<Routes>
|
<Routes>
|
||||||
{filteredData.map((installation) => {
|
{filteredData.map((installation) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -17,29 +17,32 @@ import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { INSTALLATION_PRESETS, SODIOHOME_DEVICE_TYPES } from '../Information/installationSetupUtils';
|
||||||
|
|
||||||
interface SodistorehomeInstallationFormPros {
|
interface SodistorehomeInstallationFormPros {
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
parentid: number;
|
parentid: number;
|
||||||
|
product?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) {
|
function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
const isSodistorePro = props.product === 5;
|
||||||
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
const [formValues, setFormValues] = useState<Partial<I_Installation>>({
|
||||||
name: '',
|
name: '',
|
||||||
region: '',
|
|
||||||
location: '',
|
|
||||||
country: '',
|
|
||||||
vpnIp: '',
|
vpnIp: '',
|
||||||
|
installationModel: '',
|
||||||
|
externalEms: 'No',
|
||||||
|
...(isSodistorePro ? { device: 4 } : {}),
|
||||||
});
|
});
|
||||||
const requiredFields = ['name', 'location', 'country', 'vpnIp'];
|
const [inverterCount, setInverterCount] = useState('');
|
||||||
|
const requiredFields = ['name', 'vpnIp', ...(isSodistorePro ? [] : ['installationModel'])];
|
||||||
|
|
||||||
const DeviceTypes = [
|
const DeviceTypes = isSodistorePro
|
||||||
{ id: 3, name: 'Growatt' },
|
? [{ id: 4, name: 'inesco 12K - WR Hybrid' }]
|
||||||
{ id: 4, name: 'Sinexcel' }
|
: SODIOHOME_DEVICE_TYPES;
|
||||||
];
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
const { createInstallation, loading, setLoading, error, setError } =
|
const { createInstallation, loading, setLoading, error, setError } =
|
||||||
installationContext;
|
installationContext;
|
||||||
|
|
@ -55,7 +58,10 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
formValues.parentId = props.parentid;
|
formValues.parentId = props.parentid;
|
||||||
formValues.product = 2;
|
formValues.product = props.product ?? 2;
|
||||||
|
if (isSodistorePro) {
|
||||||
|
formValues.installationModel = inverterCount;
|
||||||
|
}
|
||||||
const responseData = await createInstallation(formValues);
|
const responseData = await createInstallation(formValues);
|
||||||
props.submit();
|
props.submit();
|
||||||
};
|
};
|
||||||
|
|
@ -69,6 +75,9 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isSodistorePro && (!inverterCount || parseInt(inverterCount, 10) < 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -124,42 +133,6 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
error={formValues.name === ''}
|
error={formValues.name === ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={<FormattedMessage id="region" defaultMessage="Region" />}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.region === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="location" defaultMessage="Location" />
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.location === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage id="country" defaultMessage="Country" />
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
error={formValues.country === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label={<FormattedMessage id="VpnIp" defaultMessage="VpnIp" />}
|
label={<FormattedMessage id="VpnIp" defaultMessage="VpnIp" />}
|
||||||
|
|
@ -171,6 +144,67 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isSodistorePro ? (
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="numberOfInverters"
|
||||||
|
defaultMessage="Number of Inverters"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="inverterCount"
|
||||||
|
type="text"
|
||||||
|
value={inverterCount}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
if (val === '' || (/^\d+$/.test(val) && parseInt(val, 10) <= 20)) {
|
||||||
|
setInverterCount(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
error={!inverterCount || parseInt(inverterCount, 10) < 1}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<FormControl
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.installationModel === ''}
|
||||||
|
sx={{
|
||||||
|
marginTop: 1,
|
||||||
|
marginBottom: 1,
|
||||||
|
width: 390
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputLabel
|
||||||
|
sx={{
|
||||||
|
fontSize: 14,
|
||||||
|
backgroundColor: 'white'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="installationModel"
|
||||||
|
defaultMessage="Installation Model"
|
||||||
|
/>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="installationModel"
|
||||||
|
value={formValues.installationModel || ''}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{Object.keys(INSTALLATION_PRESETS).map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
{name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isSodistorePro && (
|
||||||
<div>
|
<div>
|
||||||
<FormControl
|
<FormControl
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
@ -204,6 +238,7 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros)
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import TreeView from '../Tree/treeView';
|
||||||
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
|
||||||
import { UserType } from '../../../interfaces/UserTypes';
|
import { UserType } from '../../../interfaces/UserTypes';
|
||||||
import SodioHomeInstallation from './Installation';
|
import SodioHomeInstallation from './Installation';
|
||||||
|
import AcknowledgementDialog, { CURRENT_TERMS_VERSION } from '../../../components/AcknowledgementDialog';
|
||||||
|
import axiosConfig from '../../../Resources/axiosConfig';
|
||||||
|
|
||||||
interface SodioHomeInstallationTabsProps {
|
interface SodioHomeInstallationTabsProps {
|
||||||
product: number;
|
product: number;
|
||||||
|
|
@ -21,7 +23,25 @@ interface SodioHomeInstallationTabsProps {
|
||||||
function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
const { currentUser } = context;
|
const { currentUser, setUser } = context;
|
||||||
|
|
||||||
|
const showTermsDialog =
|
||||||
|
currentUser?.acknowledgedTermsVersion == null ||
|
||||||
|
currentUser.acknowledgedTermsVersion < CURRENT_TERMS_VERSION;
|
||||||
|
|
||||||
|
const handleAcknowledgeTerms = () => {
|
||||||
|
axiosConfig
|
||||||
|
.put('/AcknowledgeTerms', undefined, {
|
||||||
|
params: { version: CURRENT_TERMS_VERSION }
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const updatedUser = {
|
||||||
|
...currentUser,
|
||||||
|
acknowledgedTermsVersion: CURRENT_TERMS_VERSION
|
||||||
|
};
|
||||||
|
setUser(updatedUser);
|
||||||
|
});
|
||||||
|
};
|
||||||
const tabList = [
|
const tabList = [
|
||||||
'live',
|
'live',
|
||||||
'overview',
|
'overview',
|
||||||
|
|
@ -40,12 +60,15 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const {
|
const {
|
||||||
sodiohomeInstallations,
|
sodiohomeInstallations,
|
||||||
|
sodistoreProInstallations,
|
||||||
fetchAllInstallations,
|
fetchAllInstallations,
|
||||||
socket,
|
socket,
|
||||||
openSocket,
|
openSocket,
|
||||||
closeSocket
|
closeSocket
|
||||||
} = useContext(InstallationsContext);
|
} = useContext(InstallationsContext);
|
||||||
const { product, setProduct } = useContext(ProductIdContext);
|
const { product, setProduct } = useContext(ProductIdContext);
|
||||||
|
const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations;
|
||||||
|
const installations = props.product === 5 ? sodistoreProInstallations : sodiohomeInstallations;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let path = location.pathname.split('/');
|
let path = location.pathname.split('/');
|
||||||
|
|
@ -237,11 +260,11 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
!location.pathname.includes('folder');
|
!location.pathname.includes('folder');
|
||||||
|
|
||||||
// Determine if current installation is Growatt (device=3) to hide report tab
|
// Determine if current installation is Growatt (device=3) to hide report tab
|
||||||
const currentInstallation = sodiohomeInstallations.find((i) =>
|
const currentInstallation = installations.find((i) =>
|
||||||
location.pathname.includes(`/${i.id}/`)
|
location.pathname.includes(`/${i.id}/`)
|
||||||
);
|
);
|
||||||
const isGrowatt = currentInstallation?.device === 3
|
const isGrowatt = currentInstallation?.device === 3
|
||||||
|| (sodiohomeInstallations.length === 1 && sodiohomeInstallations[0].device === 3);
|
|| (installations.length === 1 && installations[0].device === 3);
|
||||||
|
|
||||||
const tabs = inInstallationView && currentUser.userType == UserType.admin
|
const tabs = inInstallationView && currentUser.userType == UserType.admin
|
||||||
? [
|
? [
|
||||||
|
|
@ -413,8 +436,12 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return sodiohomeInstallations.length > 1 ? (
|
return installations.length > 1 ? (
|
||||||
<>
|
<>
|
||||||
|
<AcknowledgementDialog
|
||||||
|
open={showTermsDialog}
|
||||||
|
onAcknowledge={handleAcknowledgeTerms}
|
||||||
|
/>
|
||||||
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
|
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|
@ -459,7 +486,8 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box p={4}>
|
<Box p={4}>
|
||||||
<InstallationSearch
|
<InstallationSearch
|
||||||
installations={sodiohomeInstallations}
|
installations={installations}
|
||||||
|
product={props.product}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -472,7 +500,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
path={'*'}
|
path={'*'}
|
||||||
element={
|
element={
|
||||||
<Navigate
|
<Navigate
|
||||||
to={routes.sodiohome_installations + routes.list}
|
to={baseRoute + routes.list}
|
||||||
></Navigate>
|
></Navigate>
|
||||||
}
|
}
|
||||||
></Route>
|
></Route>
|
||||||
|
|
@ -481,9 +509,12 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
</Card>
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
) : sodiohomeInstallations.length === 1 ? (
|
) : installations.length === 1 ? (
|
||||||
<>
|
<>
|
||||||
{' '}
|
<AcknowledgementDialog
|
||||||
|
open={showTermsDialog}
|
||||||
|
onAcknowledge={handleAcknowledgeTerms}
|
||||||
|
/>
|
||||||
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
|
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
|
|
@ -523,7 +554,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box p={4}>
|
<Box p={4}>
|
||||||
<SodioHomeInstallation
|
<SodioHomeInstallation
|
||||||
current_installation={sodiohomeInstallations[0]}
|
current_installation={installations[0]}
|
||||||
type="installation"
|
type="installation"
|
||||||
></SodioHomeInstallation>
|
></SodioHomeInstallation>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ const productOptions = [
|
||||||
{ value: 1, label: 'Salidomo' },
|
{ value: 1, label: 'Salidomo' },
|
||||||
{ value: 2, label: 'Sodistore Home' },
|
{ value: 2, label: 'Sodistore Home' },
|
||||||
{ value: 3, label: 'Sodistore Max' },
|
{ value: 3, label: 'Sodistore Max' },
|
||||||
{ value: 4, label: 'Sodistore Grid' }
|
{ value: 4, label: 'Sodistore Grid' },
|
||||||
|
{ value: 5, label: 'Sodistore Pro' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const deviceOptionsByProduct: Record<number, { value: number; label: string }[]> = {
|
const deviceOptionsByProduct: Record<number, { value: number; label: string }[]> = {
|
||||||
|
|
@ -47,7 +48,7 @@ const deviceOptionsByProduct: Record<number, { value: number; label: string }[]>
|
||||||
],
|
],
|
||||||
2: [
|
2: [
|
||||||
{ value: 3, label: 'Growatt' },
|
{ value: 3, label: 'Growatt' },
|
||||||
{ value: 4, label: 'Sinexcel' }
|
{ value: 4, label: 'inesco 12K - WR Hybrid' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -742,7 +742,8 @@ function TicketDetailPage() {
|
||||||
1: routes.salidomo_installations,
|
1: routes.salidomo_installations,
|
||||||
2: routes.sodiohome_installations,
|
2: routes.sodiohome_installations,
|
||||||
3: routes.sodistore_installations,
|
3: routes.sodistore_installations,
|
||||||
4: routes.sodistoregrid_installations
|
4: routes.sodistoregrid_installations,
|
||||||
|
5: routes.sodistorepro_installations
|
||||||
};
|
};
|
||||||
const prefix = productRoutes[detail.installationProduct] ?? routes.installations;
|
const prefix = productRoutes[detail.installationProduct] ?? routes.installations;
|
||||||
navigate(
|
navigate(
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
? routes.salidomo_installations
|
? routes.salidomo_installations
|
||||||
: installation.product == 2
|
: installation.product == 2
|
||||||
? routes.sodiohome_installations
|
? routes.sodiohome_installations
|
||||||
|
: installation.product == 4
|
||||||
|
? routes.sodistoregrid_installations
|
||||||
|
: installation.product == 5
|
||||||
|
? routes.sodistorepro_installations
|
||||||
: routes.sodistore_installations;
|
: routes.sodistore_installations;
|
||||||
|
|
||||||
let folder_path =
|
let folder_path =
|
||||||
|
|
@ -69,6 +73,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
? routes.salidomo_installations
|
? routes.salidomo_installations
|
||||||
: product == 2
|
: product == 2
|
||||||
? routes.sodiohome_installations
|
? routes.sodiohome_installations
|
||||||
|
: product == 4
|
||||||
|
? routes.sodistoregrid_installations
|
||||||
|
: product == 5
|
||||||
|
? routes.sodistorepro_installations
|
||||||
: routes.sodistore_installations;
|
: routes.sodistore_installations;
|
||||||
|
|
||||||
if (installation.type != 'Folder') {
|
if (installation.type != 'Folder') {
|
||||||
|
|
@ -209,6 +217,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
currentLocation.pathname === routes.installations + routes.tree ||
|
currentLocation.pathname === routes.installations + routes.tree ||
|
||||||
currentLocation.pathname ===
|
currentLocation.pathname ===
|
||||||
routes.sodiohome_installations + routes.tree ||
|
routes.sodiohome_installations + routes.tree ||
|
||||||
|
currentLocation.pathname ===
|
||||||
|
routes.sodistoregrid_installations + routes.tree ||
|
||||||
|
currentLocation.pathname ===
|
||||||
|
routes.sodistorepro_installations + routes.tree ||
|
||||||
currentLocation.pathname.includes('folder')
|
currentLocation.pathname.includes('folder')
|
||||||
? 'block'
|
? 'block'
|
||||||
: 'none',
|
: 'none',
|
||||||
|
|
|
||||||
|
|
@ -59,17 +59,18 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
fetchAllInstallations
|
fetchAllInstallations
|
||||||
} = installationContext;
|
} = installationContext;
|
||||||
|
|
||||||
const [product, setProduct] = useState('Salimax');
|
const [product, setProduct] = useState('SodistoreHome');
|
||||||
|
|
||||||
const handleChangeInstallationChoice = (e) => {
|
const handleChangeInstallationChoice = (e) => {
|
||||||
setProduct(e.target.value); // Directly update the product state
|
setProduct(e.target.value); // Directly update the product state
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductTypes = ['Salimax', 'Salidomo', 'SodistoreHome', 'SodistoreMax', 'SodistoreGrid'];
|
const ProductTypes = ['SodistoreHome', 'SodistorePro', 'SodistoreGrid', 'SodistoreMax', 'Salimax', 'Salidomo'];
|
||||||
const ProductDisplayNames: Record<string, string> = {
|
const ProductDisplayNames: Record<string, string> = {
|
||||||
'SodistoreHome': 'Sodistore Home',
|
'SodistoreHome': 'Sodistore Home',
|
||||||
'SodistoreMax': 'Sodistore Max',
|
'SodistoreMax': 'Sodistore Max',
|
||||||
'SodistoreGrid': 'Sodistore Grid'
|
'SodistoreGrid': 'Sodistore Grid',
|
||||||
|
'SodistorePro': 'Sodistore Pro'
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
@ -345,11 +346,12 @@ function TreeInformation(props: TreeInformationProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{openModalInstallation && product == 'SodistoreHome' && (
|
{openModalInstallation && (product == 'SodistoreHome' || product == 'SodistorePro') && (
|
||||||
<SodiostorehomeInstallationForm
|
<SodiostorehomeInstallationForm
|
||||||
cancel={handleFormCancel}
|
cancel={handleFormCancel}
|
||||||
submit={handleInstallationFormSubmit}
|
submit={handleInstallationFormSubmit}
|
||||||
parentid={props.folder.id}
|
parentid={props.folder.id}
|
||||||
|
product={product == 'SodistorePro' ? 5 : undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,22 +72,24 @@ function userForm(props: userFormProps) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// fetch product 0
|
const [res0, res1, res2, res3, res4, res5] = await Promise.all([
|
||||||
const res0 = await axiosConfig.get(
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
||||||
`/GetAllInstallationsFromProduct?product=0`
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
||||||
);
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
||||||
const installations0 = res0.data;
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`),
|
||||||
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=4`),
|
||||||
|
axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`)
|
||||||
|
]);
|
||||||
|
|
||||||
// fetch product 1
|
const combined = [
|
||||||
const res1 = await axiosConfig.get(
|
...res0.data,
|
||||||
`/GetAllInstallationsFromProduct?product=3`
|
...res1.data,
|
||||||
);
|
...res2.data,
|
||||||
const installations1 = res1.data;
|
...res3.data,
|
||||||
|
...res4.data,
|
||||||
|
...res5.data
|
||||||
|
];
|
||||||
|
|
||||||
// aggregate
|
|
||||||
const combined = [...installations0, ...installations1];
|
|
||||||
|
|
||||||
// update
|
|
||||||
setInstallations(combined);
|
setInstallations(combined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response && err.response.status === 401) {
|
if (err.response && err.response.status === 401) {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ function Status500() {
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Box textAlign="center">
|
<Box textAlign="center">
|
||||||
<TypographyPrimary variant="h1" sx={{ my: 2 }}>
|
<TypographyPrimary variant="h1" sx={{ my: 2 }}>
|
||||||
inesco Energy{' '}
|
inesco energy{' '}
|
||||||
</TypographyPrimary>
|
</TypographyPrimary>
|
||||||
<TypographySecondary
|
<TypographySecondary
|
||||||
variant="h4"
|
variant="h4"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ const InstallationsContextProvider = ({
|
||||||
const [sodistoreGridInstallations, setSodistoreGridInstallations] = useState<
|
const [sodistoreGridInstallations, setSodistoreGridInstallations] = useState<
|
||||||
I_Installation[]
|
I_Installation[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [sodistoreProInstallations, setSodistoreProInstallations] = useState<
|
||||||
|
I_Installation[]
|
||||||
|
>([]);
|
||||||
const [foldersAndInstallations, setFoldersAndInstallations] = useState([]);
|
const [foldersAndInstallations, setFoldersAndInstallations] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
|
@ -105,10 +108,24 @@ const InstallationsContextProvider = ({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updatedSodistorePro = sodistoreProInstallations.map(
|
||||||
|
(installation) => {
|
||||||
|
const update = pendingUpdates.current[installation.id];
|
||||||
|
return update
|
||||||
|
? {
|
||||||
|
...installation,
|
||||||
|
status: update.status,
|
||||||
|
testingMode: update.testingMode
|
||||||
|
}
|
||||||
|
: installation;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
setSalidomoInstallations(updatedSalidomo);
|
setSalidomoInstallations(updatedSalidomo);
|
||||||
setSalimax_Or_Sodistore_Installations(updatedSalimax);
|
setSalimax_Or_Sodistore_Installations(updatedSalimax);
|
||||||
setSodiohomeInstallations(updatedSodiohome);
|
setSodiohomeInstallations(updatedSodiohome);
|
||||||
setSodistoreGridInstallations(updatedSodistoreGrid);
|
setSodistoreGridInstallations(updatedSodistoreGrid);
|
||||||
|
setSodistoreProInstallations(updatedSodistorePro);
|
||||||
|
|
||||||
// Clear the pending updates after applying
|
// Clear the pending updates after applying
|
||||||
pendingUpdates.current = {};
|
pendingUpdates.current = {};
|
||||||
|
|
@ -116,7 +133,8 @@ const InstallationsContextProvider = ({
|
||||||
salidomoInstallations,
|
salidomoInstallations,
|
||||||
salimax_or_sodistore_Installations,
|
salimax_or_sodistore_Installations,
|
||||||
sodiohomeInstallations,
|
sodiohomeInstallations,
|
||||||
sodistoreGridInstallations
|
sodistoreGridInstallations,
|
||||||
|
sodistoreProInstallations
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -193,6 +211,8 @@ const InstallationsContextProvider = ({
|
||||||
|
|
||||||
if (product === 2) {
|
if (product === 2) {
|
||||||
setSodiohomeInstallations(res.data);
|
setSodiohomeInstallations(res.data);
|
||||||
|
} else if (product === 5) {
|
||||||
|
setSodistoreProInstallations(res.data);
|
||||||
} else if (product === 1) {
|
} else if (product === 1) {
|
||||||
setSalidomoInstallations(res.data);
|
setSalidomoInstallations(res.data);
|
||||||
} else if (product === 0 || product === 3) {
|
} else if (product === 0 || product === 3) {
|
||||||
|
|
@ -418,6 +438,7 @@ const InstallationsContextProvider = ({
|
||||||
salidomoInstallations,
|
salidomoInstallations,
|
||||||
sodiohomeInstallations,
|
sodiohomeInstallations,
|
||||||
sodistoreGridInstallations,
|
sodistoreGridInstallations,
|
||||||
|
sodistoreProInstallations,
|
||||||
foldersAndInstallations,
|
foldersAndInstallations,
|
||||||
fetchAllInstallations,
|
fetchAllInstallations,
|
||||||
fetchAllFoldersAndInstallations,
|
fetchAllFoldersAndInstallations,
|
||||||
|
|
@ -445,6 +466,7 @@ const InstallationsContextProvider = ({
|
||||||
salidomoInstallations,
|
salidomoInstallations,
|
||||||
sodiohomeInstallations,
|
sodiohomeInstallations,
|
||||||
sodistoreGridInstallations,
|
sodistoreGridInstallations,
|
||||||
|
sodistoreProInstallations,
|
||||||
foldersAndInstallations,
|
foldersAndInstallations,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ interface ProductIdContextType {
|
||||||
accessToSodiohome: boolean;
|
accessToSodiohome: boolean;
|
||||||
accessToSodistore: boolean;
|
accessToSodistore: boolean;
|
||||||
accessToSodistoreGrid: boolean;
|
accessToSodistoreGrid: boolean;
|
||||||
|
accessToSodistorePro: boolean;
|
||||||
setAccessToSalimax: (access: boolean) => void;
|
setAccessToSalimax: (access: boolean) => void;
|
||||||
setAccessToSalidomo: (access: boolean) => void;
|
setAccessToSalidomo: (access: boolean) => void;
|
||||||
setAccessToSodiohome: (access: boolean) => void;
|
setAccessToSodiohome: (access: boolean) => void;
|
||||||
setAccessToSodistore: (access: boolean) => void;
|
setAccessToSodistore: (access: boolean) => void;
|
||||||
setAccessToSodistoreGrid: (access: boolean) => void;
|
setAccessToSodistoreGrid: (access: boolean) => void;
|
||||||
|
setAccessToSodistorePro: (access: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the context.
|
// Create the context.
|
||||||
|
|
@ -49,6 +51,10 @@ export const ProductIdContextProvider = ({
|
||||||
const storedValue = localStorage.getItem('accessToSodistoreGrid');
|
const storedValue = localStorage.getItem('accessToSodistoreGrid');
|
||||||
return storedValue === 'true';
|
return storedValue === 'true';
|
||||||
});
|
});
|
||||||
|
const [accessToSodistorePro, setAccessToSodistorePro] = useState(() => {
|
||||||
|
const storedValue = localStorage.getItem('accessToSodistorePro');
|
||||||
|
return storedValue === 'true';
|
||||||
|
});
|
||||||
// const [product, setProduct] = useState(location.includes('salidomo') ? 1 : 0);
|
// const [product, setProduct] = useState(location.includes('salidomo') ? 1 : 0);
|
||||||
// const [product, setProduct] = useState<number>(
|
// const [product, setProduct] = useState<number>(
|
||||||
// productMapping[Object.keys(productMapping).find((key) => location.includes(key)) || ''] || -1
|
// productMapping[Object.keys(productMapping).find((key) => location.includes(key)) || ''] || -1
|
||||||
|
|
@ -56,6 +62,8 @@ export const ProductIdContextProvider = ({
|
||||||
const [product, setProduct] = useState<number>(() => {
|
const [product, setProduct] = useState<number>(() => {
|
||||||
if (location.includes('salidomo')) {
|
if (location.includes('salidomo')) {
|
||||||
return 1;
|
return 1;
|
||||||
|
} else if (location.includes('sodistorepro')) {
|
||||||
|
return 5;
|
||||||
} else if (location.includes('sodiohome')) {
|
} else if (location.includes('sodiohome')) {
|
||||||
return 2;
|
return 2;
|
||||||
} else if (location.includes('sodistoregrid')) {
|
} else if (location.includes('sodistoregrid')) {
|
||||||
|
|
@ -92,6 +100,10 @@ export const ProductIdContextProvider = ({
|
||||||
setAccessToSodistoreGrid(access);
|
setAccessToSodistoreGrid(access);
|
||||||
localStorage.setItem('accessToSodistoreGrid', JSON.stringify(access));
|
localStorage.setItem('accessToSodistoreGrid', JSON.stringify(access));
|
||||||
};
|
};
|
||||||
|
const changeAccessSodistorePro = (access: boolean) => {
|
||||||
|
setAccessToSodistorePro(access);
|
||||||
|
localStorage.setItem('accessToSodistorePro', JSON.stringify(access));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductIdContext.Provider
|
<ProductIdContext.Provider
|
||||||
|
|
@ -103,11 +115,13 @@ export const ProductIdContextProvider = ({
|
||||||
accessToSodiohome,
|
accessToSodiohome,
|
||||||
accessToSodistore,
|
accessToSodistore,
|
||||||
accessToSodistoreGrid,
|
accessToSodistoreGrid,
|
||||||
|
accessToSodistorePro,
|
||||||
setAccessToSalimax: changeAccessSalimax,
|
setAccessToSalimax: changeAccessSalimax,
|
||||||
setAccessToSalidomo: changeAccessSalidomo,
|
setAccessToSalidomo: changeAccessSalidomo,
|
||||||
setAccessToSodiohome: changeAccessSodiohome,
|
setAccessToSodiohome: changeAccessSodiohome,
|
||||||
setAccessToSodistore: changeAccessSodistore,
|
setAccessToSodistore: changeAccessSodistore,
|
||||||
setAccessToSodistoreGrid: changeAccessSodistoreGrid
|
setAccessToSodistoreGrid: changeAccessSodistoreGrid,
|
||||||
|
setAccessToSodistorePro: changeAccessSodistorePro
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
}> => {
|
}> => {
|
||||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
const MAX_NUMBER = 9999999;
|
const MAX_NUMBER = 9999999;
|
||||||
const isSodioHome = product === 2;
|
const isSodioHome = product === 2 || product === 5;
|
||||||
const categories = isSodioHome
|
const categories = isSodioHome
|
||||||
? ['Soc', 'Power', 'Voltage', 'Current', 'Soh']
|
? ['Soc', 'Power', 'Voltage', 'Current', 'Soh']
|
||||||
: ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
|
: ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
|
||||||
|
|
@ -169,7 +169,7 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
);
|
);
|
||||||
|
|
||||||
const adjustedTimestamp =
|
const adjustedTimestamp =
|
||||||
product == 0 || product == 2 || product == 3 || product == 4
|
product == 0 || product == 2 || product == 3 || product == 4 || product == 5
|
||||||
? new Date(timestampArray[i] * 1000)
|
? new Date(timestampArray[i] * 1000)
|
||||||
: new Date(timestampArray[i] * 100000);
|
: new Date(timestampArray[i] * 100000);
|
||||||
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||||
|
|
@ -226,26 +226,52 @@ export const transformInputToBatteryViewDataJson = async (
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
pathsToSave.forEach((path) => {
|
pathsToSave.forEach((path) => {
|
||||||
if (pathsToSave.indexOf(path) >= old_length) {
|
if (pathsToSave.indexOf(path) >= old_length) {
|
||||||
chartData[category].data[path] = { name: path, data: [] };
|
const displayIndex = pathsToSave.indexOf(path);
|
||||||
|
chartData[category].data[path] = { name: 'Battery Cluster ' + (displayIndex + 1), data: [] };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map category names to InverterRecord field suffixes
|
const hasDevices = !!inv?.Devices;
|
||||||
const categoryFieldMap = {
|
|
||||||
|
// Sinexcel field suffixes differ from Growatt for Voltage/Current
|
||||||
|
const categoryFieldMapGrowatt = {
|
||||||
Soc: 'Soc',
|
Soc: 'Soc',
|
||||||
Power: 'Power',
|
Power: 'Power',
|
||||||
Voltage: 'Voltage',
|
Voltage: 'Voltage',
|
||||||
Current: 'Current',
|
Current: 'Current',
|
||||||
Soh: 'Soh'
|
Soh: 'Soh'
|
||||||
};
|
};
|
||||||
|
const categoryFieldMapSinexcel = {
|
||||||
|
Soc: 'Soc',
|
||||||
|
Power: 'Power',
|
||||||
|
Voltage: 'PackTotalVoltage',
|
||||||
|
Current: 'PackTotalCurrent',
|
||||||
|
Soh: 'Soh'
|
||||||
|
};
|
||||||
|
|
||||||
for (let j = 0; j < pathsToSave.length; j++) {
|
for (let j = 0; j < pathsToSave.length; j++) {
|
||||||
const batteryIndex = j + 1; // Battery1, Battery2, ...
|
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
const fieldName = `Battery${batteryIndex}${categoryFieldMap[category]}`;
|
let value: number | undefined;
|
||||||
const value = inv[fieldName];
|
|
||||||
|
if (hasDevices) {
|
||||||
|
// Sinexcel: nested under Devices — 0→D1/B1, 1→D1/B2, 2→D2/B1, ...
|
||||||
|
const deviceId = String(Math.floor(j / 2) + 1);
|
||||||
|
const bi = (j % 2) + 1;
|
||||||
|
const device = inv.Devices[deviceId];
|
||||||
|
const fieldName = `Battery${bi}${categoryFieldMapSinexcel[category]}`;
|
||||||
|
value = device?.[fieldName];
|
||||||
|
// Fallback for Soc
|
||||||
|
if ((value === undefined || value === null) && category === 'Soc') {
|
||||||
|
value = device?.[`Battery${bi}SocSecondvalue`];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Growatt: flat Battery1Soc, Battery2Voltage, ...
|
||||||
|
const batteryIndex = j + 1;
|
||||||
|
const fieldName = `Battery${batteryIndex}${categoryFieldMapGrowatt[category]}`;
|
||||||
|
value = inv[fieldName];
|
||||||
|
}
|
||||||
|
|
||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
if (value < chartOverview[category].min) {
|
if (value < chartOverview[category].min) {
|
||||||
|
|
@ -393,7 +419,7 @@ export const transformInputToDailyDataJson = async (
|
||||||
// custom fallback logic to handle differences between Growatt and Sinexcel.
|
// custom fallback logic to handle differences between Growatt and Sinexcel.
|
||||||
// Growatt has: Battery1AmbientTemperature, GridPower, PvPower
|
// Growatt has: Battery1AmbientTemperature, GridPower, PvPower
|
||||||
// Sinexcel has: Battery1Temperature, TotalGridPower (meter may be offline), PvPower1-4
|
// Sinexcel has: Battery1Temperature, TotalGridPower (meter may be offline), PvPower1-4
|
||||||
const pathsToSearch = product == 2
|
const pathsToSearch = (product == 2 || product == 5)
|
||||||
? [
|
? [
|
||||||
'SODIOHOME_SOC',
|
'SODIOHOME_SOC',
|
||||||
'SODIOHOME_TEMPERATURE',
|
'SODIOHOME_TEMPERATURE',
|
||||||
|
|
@ -516,8 +542,8 @@ export const transformInputToDailyDataJson = async (
|
||||||
|
|
||||||
let value: number | undefined = undefined;
|
let value: number | undefined = undefined;
|
||||||
|
|
||||||
if (product === 2) {
|
if (product === 2 || product === 5) {
|
||||||
// SodioHome: use top-level aggregated values (Sinexcel multi-inverter)
|
// SodioHome/SodistorePro: use top-level aggregated values (Sinexcel multi-inverter)
|
||||||
const inv = result?.InverterRecord;
|
const inv = result?.InverterRecord;
|
||||||
if (inv) {
|
if (inv) {
|
||||||
switch (category_index) {
|
switch (category_index) {
|
||||||
|
|
@ -735,7 +761,7 @@ export const transformInputToAggregatedDataJson = async (
|
||||||
const timestampPromises = [];
|
const timestampPromises = [];
|
||||||
|
|
||||||
while (currentDay.isBefore(end_date)) {
|
while (currentDay.isBefore(end_date)) {
|
||||||
const dateFormat = product === 2
|
const dateFormat = (product === 2 || product === 5)
|
||||||
? currentDay.format('DDMMYYYY')
|
? currentDay.format('DDMMYYYY')
|
||||||
: currentDay.format('YYYY-MM-DD');
|
: currentDay.format('YYYY-MM-DD');
|
||||||
timestampPromises.push(
|
timestampPromises.push(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ export interface I_Installation extends I_S3Credentials {
|
||||||
location: string;
|
location: string;
|
||||||
region: string;
|
region: string;
|
||||||
country: string;
|
country: string;
|
||||||
|
street?: string;
|
||||||
|
postCode?: string;
|
||||||
|
city?: string;
|
||||||
|
canton?: string;
|
||||||
|
distributionPartner?: string;
|
||||||
|
inverterFirmwareVersion?: string;
|
||||||
|
batteryFirmwareVersion?: string;
|
||||||
installationName: string;
|
installationName: string;
|
||||||
vpnIp: string;
|
vpnIp: string;
|
||||||
orderNumbers: string[] | string;
|
orderNumbers: string[] | string;
|
||||||
|
|
@ -18,6 +25,11 @@ export interface I_Installation extends I_S3Credentials {
|
||||||
batteryClusterNumber: number;
|
batteryClusterNumber: number;
|
||||||
batteryNumber: number;
|
batteryNumber: number;
|
||||||
batterySerialNumbers: string;
|
batterySerialNumbers: string;
|
||||||
|
pvStringsPerInverter: string;
|
||||||
|
installationModel: string;
|
||||||
|
externalEms: string;
|
||||||
|
couplingType: string;
|
||||||
|
|
||||||
parentId: number;
|
parentId: number;
|
||||||
s3WriteKey: string;
|
s3WriteKey: string;
|
||||||
s3WriteSecret: string;
|
s3WriteSecret: string;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export type InnovEnergyUser = {
|
||||||
type: string;
|
type: string;
|
||||||
folderIds?: number[];
|
folderIds?: number[];
|
||||||
mustResetPassword: boolean;
|
mustResetPassword: boolean;
|
||||||
|
acknowledgedTermsVersion?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface I_UserWithInheritedAccess {
|
export interface I_UserWithInheritedAccess {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,13 @@
|
||||||
"alarms": "Alarme",
|
"alarms": "Alarme",
|
||||||
"applyChanges": "Änderungen speichern",
|
"applyChanges": "Änderungen speichern",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
|
"street": "Strasse",
|
||||||
|
"postCode": "PLZ",
|
||||||
|
"city": "Ort",
|
||||||
|
"canton": "Kanton",
|
||||||
|
"distributionPartner": "Vertriebspartner",
|
||||||
|
"inverterFirmwareVersion": "Wechselrichter-Firmware-Version",
|
||||||
|
"batteryFirmwareVersion": "Batterie-Firmware-Version",
|
||||||
"networkProvider": "Netzbetreiber",
|
"networkProvider": "Netzbetreiber",
|
||||||
"createNewFolder": "Neuer Ordner",
|
"createNewFolder": "Neuer Ordner",
|
||||||
"createNewUser": "Neuer Benutzer",
|
"createNewUser": "Neuer Benutzer",
|
||||||
|
|
@ -73,6 +80,27 @@
|
||||||
"live": "Live Daten",
|
"live": "Live Daten",
|
||||||
"deleteInstallation": "Installation löschen",
|
"deleteInstallation": "Installation löschen",
|
||||||
"confirmDeleteInstallation": "Möchten Sie diese Installation löschen?",
|
"confirmDeleteInstallation": "Möchten Sie diese Installation löschen?",
|
||||||
|
"installationModel": "Installationsmodell",
|
||||||
|
"externalEms": "Externes EMS",
|
||||||
|
"externalEmsOther": "Externes EMS (angeben)",
|
||||||
|
"emsNo": "Nein",
|
||||||
|
"emsOther": "Andere",
|
||||||
|
"generalInfo": "Allgemeine Informationen",
|
||||||
|
"installationSetup": "Installationseinrichtung",
|
||||||
|
"couplingType": "AC/DC-Kopplung",
|
||||||
|
"couplingAC": "AC-gekoppelt",
|
||||||
|
"couplingDC": "DC-gekoppelt",
|
||||||
|
"selectModel": "Modell auswählen...",
|
||||||
|
"inverterN": "Wechselrichter {n}",
|
||||||
|
"clusterN": "Cluster {n}",
|
||||||
|
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} Cluster, {filledBat}/{totalBat} Batterien",
|
||||||
|
"batteriesSummary": "{filled}/{total} Batterien",
|
||||||
|
"inverterNSerialNumber": "Wechselrichter {n} Seriennummer",
|
||||||
|
"dataloggerNSerialNumber": "Datenlogger {n} Seriennummer",
|
||||||
|
"pvStringsOnInverterN": "Anzahl PV-Strings an Wechselrichter {n}",
|
||||||
|
"batteryNSerialNumber": "Batterie {n} Seriennummer",
|
||||||
|
"adminSection": "Admin",
|
||||||
|
"confirmPresetSwitch": "Der Wechsel zu einer kleineren Konfiguration entfernt einige Batterie-Seriennummern. Fortfahren?",
|
||||||
"bucketLabel": "Bucket",
|
"bucketLabel": "Bucket",
|
||||||
"deleteInstallationWarning": "Bitte notieren Sie den Bucket-Namen oben. Das Löschen der S3-Daten kann mehrere Minuten dauern. Überprüfen Sie nach dem Löschen in Exoscale, ob der Bucket entfernt wurde. Falls nicht, leeren und löschen Sie den Bucket manuell.",
|
"deleteInstallationWarning": "Bitte notieren Sie den Bucket-Namen oben. Das Löschen der S3-Daten kann mehrere Minuten dauern. Überprüfen Sie nach dem Löschen in Exoscale, ob der Bucket entfernt wurde. Falls nicht, leeren und löschen Sie den Bucket manuell.",
|
||||||
"errorOccured": "Ein Fehler ist aufgetreten",
|
"errorOccured": "Ein Fehler ist aufgetreten",
|
||||||
|
|
@ -85,6 +113,7 @@
|
||||||
"noUsersWithDirectAccessToThis": "Keine Benutzer mit direktem Zugriff",
|
"noUsersWithDirectAccessToThis": "Keine Benutzer mit direktem Zugriff",
|
||||||
"selectUsers": "Benutzer auswählen",
|
"selectUsers": "Benutzer auswählen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
|
"continue": "Fortfahren",
|
||||||
"addNewFolder": "Neuen Ordner hinzufügen",
|
"addNewFolder": "Neuen Ordner hinzufügen",
|
||||||
"addNewInstallation": "Neue Installation hinzufügen",
|
"addNewInstallation": "Neue Installation hinzufügen",
|
||||||
"deleteFolder": "Ordner löschen",
|
"deleteFolder": "Ordner löschen",
|
||||||
|
|
@ -185,7 +214,7 @@
|
||||||
"demo_test_button": "KI-Diagnose",
|
"demo_test_button": "KI-Diagnose",
|
||||||
"demo_hide_button": "KI-Diagnose ausblenden",
|
"demo_hide_button": "KI-Diagnose ausblenden",
|
||||||
"demo_panel_title": "KI-Diagnose",
|
"demo_panel_title": "KI-Diagnose",
|
||||||
"demo_custom_group": "Benutzerdefiniert (kann Mistral KI verwenden)",
|
"demo_custom_group": "Benutzerdefiniert (kann KI verwenden)",
|
||||||
"demo_custom_option": "Benutzerdefinierten Alarm eingeben…",
|
"demo_custom_option": "Benutzerdefinierten Alarm eingeben…",
|
||||||
"demo_custom_placeholder": "z.B. UnknownBatteryFault",
|
"demo_custom_placeholder": "z.B. UnknownBatteryFault",
|
||||||
"demo_diagnose_button": "Diagnostizieren",
|
"demo_diagnose_button": "Diagnostizieren",
|
||||||
|
|
@ -614,5 +643,26 @@
|
||||||
"timelineAiDiagnosisCompletedDesc": "KI-Diagnose abgeschlossen.",
|
"timelineAiDiagnosisCompletedDesc": "KI-Diagnose abgeschlossen.",
|
||||||
"timelineAiDiagnosisFailedDesc": "KI-Diagnose fehlgeschlagen.",
|
"timelineAiDiagnosisFailedDesc": "KI-Diagnose fehlgeschlagen.",
|
||||||
"timelineEscalatedDesc": "Ticket eskaliert.",
|
"timelineEscalatedDesc": "Ticket eskaliert.",
|
||||||
"timelineResolutionAddedDesc": "Lösung hinzugefügt von {name}."
|
"timelineResolutionAddedDesc": "Lösung hinzugefügt von {name}.",
|
||||||
|
"terms_dialog_title": "Willkommen bei inesco energy",
|
||||||
|
"terms_data_heading": "Ihre Daten",
|
||||||
|
"terms_data_body": "Ihre Installationsdaten werden sicher in der Schweiz gespeichert. Wir geben Ihre Daten nicht an Dritte weiter.",
|
||||||
|
"terms_ai_heading": "KI-gestützte Einblicke",
|
||||||
|
"terms_ai_body": "Wir nutzen einen in der EU gehosteten KI-Dienst, um Diagnosen und Einblicke für Ihre Installationen bereitzustellen. KI-generierte Ergebnisse sind Empfehlungen und sollten von qualifiziertem Personal überprüft werden.",
|
||||||
|
"terms_cookies_heading": "Browser-Speicher",
|
||||||
|
"terms_cookies_body": "Diese Plattform speichert Anmelde- und Einstellungsdaten in Ihrem Browser, um Sie angemeldet zu halten und Ihre Sprachauswahl zu speichern.",
|
||||||
|
"terms_acknowledge_button": "Ich verstehe",
|
||||||
|
"privacy_menu_item": "Daten & Datenschutz",
|
||||||
|
"privacy_dialog_title": "Daten & Datenschutz",
|
||||||
|
"privacy_data_heading": "Wo werden meine Daten gespeichert?",
|
||||||
|
"privacy_data_body": "Ihre Installationsdaten werden auf Servern in der Schweiz gespeichert. Nur autorisiertes Personal von inesco energy kann zu Supportzwecken auf Ihre Daten zugreifen.",
|
||||||
|
"privacy_ai_heading": "Wie wird KI eingesetzt?",
|
||||||
|
"privacy_ai_body": "Wir nutzen einen in der Europäischen Union gehosteten KI-Dienst, um Ihre Installationsdaten zu analysieren und diagnostische Einblicke zu liefern. Die KI verarbeitet technische Daten wie Batteriemesswerte und Fehlercodes. KI-Empfehlungen sollten stets von qualifiziertem Personal überprüft werden.",
|
||||||
|
"privacy_browser_heading": "Was speichert mein Browser?",
|
||||||
|
"privacy_browser_body": "Ihr Browser speichert Ihre Anmeldesitzung, um Sie angemeldet zu halten, sowie Ihre Sprach- und Designeinstellungen. Es werden keine Tracking- oder Werbe-Cookies verwendet.",
|
||||||
|
"privacy_access_heading": "Wer hat Zugriff auf meine Daten?",
|
||||||
|
"privacy_access_body": "Ihre Daten werden nicht an Dritte weitergegeben. Sie werden ausschliesslich für den Betrieb der Plattform und zur Bereitstellung von Einblicken in Ihre Installationen verwendet.",
|
||||||
|
"privacy_close_button": "Schliessen",
|
||||||
|
"sodistorepro": "Sodistore Pro",
|
||||||
|
"numberOfInverters": "Anzahl der Wechselrichter"
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
"allInstallations": "All installations",
|
"allInstallations": "All installations",
|
||||||
"applyChanges": "Apply changes",
|
"applyChanges": "Apply changes",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
"street": "Street",
|
||||||
|
"postCode": "Postcode",
|
||||||
|
"city": "City",
|
||||||
|
"canton": "Canton",
|
||||||
|
"distributionPartner": "Distribution Partner",
|
||||||
|
"inverterFirmwareVersion": "Inverter Firmware Version",
|
||||||
|
"batteryFirmwareVersion": "Battery Firmware Version",
|
||||||
"networkProvider": "Network Provider",
|
"networkProvider": "Network Provider",
|
||||||
"customerName": "Customer name",
|
"customerName": "Customer name",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
|
|
@ -55,6 +62,27 @@
|
||||||
"live": "Live View",
|
"live": "Live View",
|
||||||
"deleteInstallation": "Delete Installation",
|
"deleteInstallation": "Delete Installation",
|
||||||
"confirmDeleteInstallation": "Do you want to delete this installation?",
|
"confirmDeleteInstallation": "Do you want to delete this installation?",
|
||||||
|
"installationModel": "Installation Model",
|
||||||
|
"externalEms": "External EMS",
|
||||||
|
"externalEmsOther": "External EMS (specify)",
|
||||||
|
"emsNo": "No",
|
||||||
|
"emsOther": "Other",
|
||||||
|
"generalInfo": "General Info",
|
||||||
|
"installationSetup": "Installation Setup",
|
||||||
|
"couplingType": "AC/DC Coupling",
|
||||||
|
"couplingAC": "AC-coupled",
|
||||||
|
"couplingDC": "DC-coupled",
|
||||||
|
"selectModel": "Select model...",
|
||||||
|
"inverterN": "Inverter {n}",
|
||||||
|
"clusterN": "Cluster {n}",
|
||||||
|
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries",
|
||||||
|
"batteriesSummary": "{filled}/{total} batteries",
|
||||||
|
"inverterNSerialNumber": "Inverter {n} Serial Number",
|
||||||
|
"dataloggerNSerialNumber": "Datalogger {n} Serial Number",
|
||||||
|
"pvStringsOnInverterN": "Number of PV Strings on Inverter {n}",
|
||||||
|
"batteryNSerialNumber": "Battery {n} Serial Number",
|
||||||
|
"adminSection": "Admin",
|
||||||
|
"confirmPresetSwitch": "Switching to a smaller configuration will remove some battery serial number entries. Continue?",
|
||||||
"bucketLabel": "Bucket",
|
"bucketLabel": "Bucket",
|
||||||
"deleteInstallationWarning": "Please note the bucket name above. Purging S3 data may take several minutes. After deletion, verify in Exoscale that the bucket has been removed. If not, purge and delete the bucket manually.",
|
"deleteInstallationWarning": "Please note the bucket name above. Purging S3 data may take several minutes. After deletion, verify in Exoscale that the bucket has been removed. If not, purge and delete the bucket manually.",
|
||||||
"errorOccured": "An error has occurred",
|
"errorOccured": "An error has occurred",
|
||||||
|
|
@ -67,6 +95,7 @@
|
||||||
"noUsersWithDirectAccessToThis": "No users with direct access to this ",
|
"noUsersWithDirectAccessToThis": "No users with direct access to this ",
|
||||||
"selectUsers": "Select Users",
|
"selectUsers": "Select Users",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"continue": "Continue",
|
||||||
"addNewFolder": "Add new Folder",
|
"addNewFolder": "Add new Folder",
|
||||||
"addNewInstallation": "Add new Installation",
|
"addNewInstallation": "Add new Installation",
|
||||||
"deleteFolder": "Delete Folder",
|
"deleteFolder": "Delete Folder",
|
||||||
|
|
@ -167,7 +196,7 @@
|
||||||
"demo_test_button": "AI Diagnosis",
|
"demo_test_button": "AI Diagnosis",
|
||||||
"demo_hide_button": "Hide AI Diagnosis",
|
"demo_hide_button": "Hide AI Diagnosis",
|
||||||
"demo_panel_title": "AI Diagnosis",
|
"demo_panel_title": "AI Diagnosis",
|
||||||
"demo_custom_group": "Custom (may use Mistral AI)",
|
"demo_custom_group": "Custom (may use AI)",
|
||||||
"demo_custom_option": "Type custom alarm below…",
|
"demo_custom_option": "Type custom alarm below…",
|
||||||
"demo_custom_placeholder": "e.g. UnknownBatteryFault",
|
"demo_custom_placeholder": "e.g. UnknownBatteryFault",
|
||||||
"demo_diagnose_button": "Diagnose",
|
"demo_diagnose_button": "Diagnose",
|
||||||
|
|
@ -362,5 +391,26 @@
|
||||||
"timelineAiDiagnosisCompletedDesc": "AI diagnosis completed.",
|
"timelineAiDiagnosisCompletedDesc": "AI diagnosis completed.",
|
||||||
"timelineAiDiagnosisFailedDesc": "AI diagnosis failed.",
|
"timelineAiDiagnosisFailedDesc": "AI diagnosis failed.",
|
||||||
"timelineEscalatedDesc": "Ticket escalated.",
|
"timelineEscalatedDesc": "Ticket escalated.",
|
||||||
"timelineResolutionAddedDesc": "Resolution added by {name}."
|
"timelineResolutionAddedDesc": "Resolution added by {name}.",
|
||||||
|
"terms_dialog_title": "Welcome to inesco energy",
|
||||||
|
"terms_data_heading": "Your Data",
|
||||||
|
"terms_data_body": "Your installation data is securely stored in Switzerland. We do not share your data with third parties.",
|
||||||
|
"terms_ai_heading": "AI-Powered Insights",
|
||||||
|
"terms_ai_body": "We use an AI service hosted in the EU to provide diagnostics and insights for your installations. AI-generated results are recommendations and should be verified by qualified personnel.",
|
||||||
|
"terms_cookies_heading": "Browser Storage",
|
||||||
|
"terms_cookies_body": "This platform stores login and preference settings in your browser to keep you signed in and remember your language choice.",
|
||||||
|
"terms_acknowledge_button": "I understand",
|
||||||
|
"privacy_menu_item": "Data & Privacy",
|
||||||
|
"privacy_dialog_title": "Data & Privacy",
|
||||||
|
"privacy_data_heading": "Where is my data stored?",
|
||||||
|
"privacy_data_body": "Your installation data is stored on servers in Switzerland. Only authorized inesco energy personnel can access your data for support purposes.",
|
||||||
|
"privacy_ai_heading": "How is AI used?",
|
||||||
|
"privacy_ai_body": "We use an AI service hosted in the European Union to analyze your installation data and provide diagnostic insights. The AI processes technical data such as battery readings and error codes. AI recommendations should always be verified by qualified personnel.",
|
||||||
|
"privacy_browser_heading": "What does my browser store?",
|
||||||
|
"privacy_browser_body": "Your browser stores your login session to keep you signed in, and your language and theme preferences. No tracking or advertising cookies are used.",
|
||||||
|
"privacy_access_heading": "Who has access to my data?",
|
||||||
|
"privacy_access_body": "Your data is not shared with third parties. It is used solely to operate the platform and provide you with insights about your installations.",
|
||||||
|
"privacy_close_button": "Close",
|
||||||
|
"sodistorepro": "Sodistore Pro",
|
||||||
|
"numberOfInverters": "Number of Inverters"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@
|
||||||
"alarms": "Alarmes",
|
"alarms": "Alarmes",
|
||||||
"applyChanges": "Appliquer",
|
"applyChanges": "Appliquer",
|
||||||
"country": "Pays",
|
"country": "Pays",
|
||||||
|
"street": "Rue",
|
||||||
|
"postCode": "Code postal",
|
||||||
|
"city": "Ville",
|
||||||
|
"canton": "Canton",
|
||||||
|
"distributionPartner": "Partenaire de distribution",
|
||||||
|
"inverterFirmwareVersion": "Version firmware onduleur",
|
||||||
|
"batteryFirmwareVersion": "Version firmware batterie",
|
||||||
"networkProvider": "Gestionnaire de réseau",
|
"networkProvider": "Gestionnaire de réseau",
|
||||||
"createNewFolder": "Nouveau dossier",
|
"createNewFolder": "Nouveau dossier",
|
||||||
"createNewUser": "Nouvel utilisateur",
|
"createNewUser": "Nouvel utilisateur",
|
||||||
|
|
@ -67,6 +74,27 @@
|
||||||
"live": "Diffusion en direct",
|
"live": "Diffusion en direct",
|
||||||
"deleteInstallation": "Supprimer l'installation",
|
"deleteInstallation": "Supprimer l'installation",
|
||||||
"confirmDeleteInstallation": "Voulez-vous supprimer cette installation ?",
|
"confirmDeleteInstallation": "Voulez-vous supprimer cette installation ?",
|
||||||
|
"installationModel": "Modèle d'installation",
|
||||||
|
"externalEms": "EMS externe",
|
||||||
|
"externalEmsOther": "EMS externe (préciser)",
|
||||||
|
"emsNo": "Non",
|
||||||
|
"emsOther": "Autre",
|
||||||
|
"generalInfo": "Informations générales",
|
||||||
|
"installationSetup": "Configuration de l'installation",
|
||||||
|
"couplingType": "Couplage AC/DC",
|
||||||
|
"couplingAC": "Couplage AC",
|
||||||
|
"couplingDC": "Couplage DC",
|
||||||
|
"selectModel": "Sélectionner le modèle...",
|
||||||
|
"inverterN": "Onduleur {n}",
|
||||||
|
"clusterN": "Cluster {n}",
|
||||||
|
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} clusters, {filledBat}/{totalBat} batteries",
|
||||||
|
"batteriesSummary": "{filled}/{total} batteries",
|
||||||
|
"inverterNSerialNumber": "Numéro de série onduleur {n}",
|
||||||
|
"dataloggerNSerialNumber": "Numéro de série datalogger {n}",
|
||||||
|
"pvStringsOnInverterN": "Nombre de chaînes PV sur onduleur {n}",
|
||||||
|
"batteryNSerialNumber": "Numéro de série batterie {n}",
|
||||||
|
"adminSection": "Admin",
|
||||||
|
"confirmPresetSwitch": "Le passage à une configuration plus petite supprimera certains numéros de série de batteries. Continuer ?",
|
||||||
"bucketLabel": "Bucket",
|
"bucketLabel": "Bucket",
|
||||||
"deleteInstallationWarning": "Veuillez noter le nom du bucket ci-dessus. La purge des données S3 peut prendre plusieurs minutes. Après la suppression, vérifiez dans Exoscale que le bucket a bien été supprimé. Sinon, purgez et supprimez le bucket manuellement.",
|
"deleteInstallationWarning": "Veuillez noter le nom du bucket ci-dessus. La purge des données S3 peut prendre plusieurs minutes. Après la suppression, vérifiez dans Exoscale que le bucket a bien été supprimé. Sinon, purgez et supprimez le bucket manuellement.",
|
||||||
"errorOccured": "Une erreur s'est produite",
|
"errorOccured": "Une erreur s'est produite",
|
||||||
|
|
@ -79,6 +107,7 @@
|
||||||
"noUsersWithDirectAccessToThis": "Aucun utilisateur ayant un accès direct",
|
"noUsersWithDirectAccessToThis": "Aucun utilisateur ayant un accès direct",
|
||||||
"selectUsers": "Sélectionnez les utilisateurs",
|
"selectUsers": "Sélectionnez les utilisateurs",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
|
"continue": "Continuer",
|
||||||
"addNewFolder": "Ajouter un nouveau dossier",
|
"addNewFolder": "Ajouter un nouveau dossier",
|
||||||
"addNewInstallation": "Ajouter une nouvelle installation",
|
"addNewInstallation": "Ajouter une nouvelle installation",
|
||||||
"deleteFolder": "Supprimer le dossier",
|
"deleteFolder": "Supprimer le dossier",
|
||||||
|
|
@ -179,7 +208,7 @@
|
||||||
"demo_test_button": "Diagnostic IA",
|
"demo_test_button": "Diagnostic IA",
|
||||||
"demo_hide_button": "Masquer le diagnostic IA",
|
"demo_hide_button": "Masquer le diagnostic IA",
|
||||||
"demo_panel_title": "Diagnostic IA",
|
"demo_panel_title": "Diagnostic IA",
|
||||||
"demo_custom_group": "Personnalisé (peut utiliser Mistral IA)",
|
"demo_custom_group": "Personnalisé (peut utiliser IA)",
|
||||||
"demo_custom_option": "Saisir une alarme personnalisée…",
|
"demo_custom_option": "Saisir une alarme personnalisée…",
|
||||||
"demo_custom_placeholder": "ex. UnknownBatteryFault",
|
"demo_custom_placeholder": "ex. UnknownBatteryFault",
|
||||||
"demo_diagnose_button": "Diagnostiquer",
|
"demo_diagnose_button": "Diagnostiquer",
|
||||||
|
|
@ -614,5 +643,26 @@
|
||||||
"timelineAiDiagnosisCompletedDesc": "Diagnostic IA terminé.",
|
"timelineAiDiagnosisCompletedDesc": "Diagnostic IA terminé.",
|
||||||
"timelineAiDiagnosisFailedDesc": "Diagnostic IA échoué.",
|
"timelineAiDiagnosisFailedDesc": "Diagnostic IA échoué.",
|
||||||
"timelineEscalatedDesc": "Ticket escaladé.",
|
"timelineEscalatedDesc": "Ticket escaladé.",
|
||||||
"timelineResolutionAddedDesc": "Résolution ajoutée par {name}."
|
"timelineResolutionAddedDesc": "Résolution ajoutée par {name}.",
|
||||||
|
"terms_dialog_title": "Bienvenue chez inesco energy",
|
||||||
|
"terms_data_heading": "Vos données",
|
||||||
|
"terms_data_body": "Les données de votre installation sont stockées en toute sécurité en Suisse. Nous ne partageons pas vos données avec des tiers.",
|
||||||
|
"terms_ai_heading": "Analyses basées sur l'IA",
|
||||||
|
"terms_ai_body": "Nous utilisons un service d'IA hébergé dans l'UE pour fournir des diagnostics et des analyses pour vos installations. Les résultats générés par l'IA sont des recommandations et doivent être vérifiés par du personnel qualifié.",
|
||||||
|
"terms_cookies_heading": "Stockage du navigateur",
|
||||||
|
"terms_cookies_body": "Cette plateforme enregistre vos paramètres de connexion et de préférences dans votre navigateur pour maintenir votre session et mémoriser votre choix de langue.",
|
||||||
|
"terms_acknowledge_button": "Je comprends",
|
||||||
|
"privacy_menu_item": "Données et confidentialité",
|
||||||
|
"privacy_dialog_title": "Données et confidentialité",
|
||||||
|
"privacy_data_heading": "Où sont stockées mes données ?",
|
||||||
|
"privacy_data_body": "Les données de votre installation sont stockées sur des serveurs en Suisse. Seul le personnel autorisé d'inesco energy peut accéder à vos données à des fins d'assistance.",
|
||||||
|
"privacy_ai_heading": "Comment l'IA est-elle utilisée ?",
|
||||||
|
"privacy_ai_body": "Nous utilisons un service d'IA hébergé dans l'Union européenne pour analyser les données de votre installation et fournir des informations diagnostiques. L'IA traite des données techniques telles que les relevés de batterie et les codes d'erreur. Les recommandations de l'IA doivent toujours être vérifiées par du personnel qualifié.",
|
||||||
|
"privacy_browser_heading": "Que stocke mon navigateur ?",
|
||||||
|
"privacy_browser_body": "Votre navigateur stocke votre session de connexion pour vous maintenir connecté, ainsi que vos préférences de langue et de thème. Aucun cookie de suivi ou publicitaire n'est utilisé.",
|
||||||
|
"privacy_access_heading": "Qui a accès à mes données ?",
|
||||||
|
"privacy_access_body": "Vos données ne sont pas partagées avec des tiers. Elles sont utilisées uniquement pour le fonctionnement de la plateforme et pour vous fournir des informations sur vos installations.",
|
||||||
|
"privacy_close_button": "Fermer",
|
||||||
|
"sodistorepro": "Sodistore Pro",
|
||||||
|
"numberOfInverters": "Nombre d'onduleurs"
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
"allInstallations": "Tutte le installazioni",
|
"allInstallations": "Tutte le installazioni",
|
||||||
"applyChanges": "Applica modifiche",
|
"applyChanges": "Applica modifiche",
|
||||||
"country": "Paese",
|
"country": "Paese",
|
||||||
|
"street": "Via",
|
||||||
|
"postCode": "CAP",
|
||||||
|
"city": "Città",
|
||||||
|
"canton": "Cantone",
|
||||||
|
"distributionPartner": "Partner di distribuzione",
|
||||||
|
"inverterFirmwareVersion": "Versione firmware inverter",
|
||||||
|
"batteryFirmwareVersion": "Versione firmware batteria",
|
||||||
"networkProvider": "Gestore di rete",
|
"networkProvider": "Gestore di rete",
|
||||||
"customerName": "Nome cliente",
|
"customerName": "Nome cliente",
|
||||||
"english": "Inglese",
|
"english": "Inglese",
|
||||||
|
|
@ -55,6 +62,27 @@
|
||||||
"live": "Vista in diretta",
|
"live": "Vista in diretta",
|
||||||
"deleteInstallation": "Elimina installazione",
|
"deleteInstallation": "Elimina installazione",
|
||||||
"confirmDeleteInstallation": "Vuoi eliminare questa installazione?",
|
"confirmDeleteInstallation": "Vuoi eliminare questa installazione?",
|
||||||
|
"installationModel": "Modello di installazione",
|
||||||
|
"externalEms": "EMS esterno",
|
||||||
|
"externalEmsOther": "EMS esterno (specificare)",
|
||||||
|
"emsNo": "No",
|
||||||
|
"emsOther": "Altro",
|
||||||
|
"generalInfo": "Informazioni generali",
|
||||||
|
"installationSetup": "Configurazione installazione",
|
||||||
|
"couplingType": "Accoppiamento AC/DC",
|
||||||
|
"couplingAC": "Accoppiamento AC",
|
||||||
|
"couplingDC": "Accoppiamento DC",
|
||||||
|
"selectModel": "Seleziona modello...",
|
||||||
|
"inverterN": "Inverter {n}",
|
||||||
|
"clusterN": "Cluster {n}",
|
||||||
|
"clustersBatteriesSummary": "{filledClusters}/{totalClusters} cluster, {filledBat}/{totalBat} batterie",
|
||||||
|
"batteriesSummary": "{filled}/{total} batterie",
|
||||||
|
"inverterNSerialNumber": "Numero di serie inverter {n}",
|
||||||
|
"dataloggerNSerialNumber": "Numero di serie datalogger {n}",
|
||||||
|
"pvStringsOnInverterN": "Numero di stringhe PV sull'inverter {n}",
|
||||||
|
"batteryNSerialNumber": "Numero di serie batteria {n}",
|
||||||
|
"adminSection": "Admin",
|
||||||
|
"confirmPresetSwitch": "Il passaggio a una configurazione più piccola rimuoverà alcuni numeri di serie delle batterie. Continuare?",
|
||||||
"bucketLabel": "Bucket",
|
"bucketLabel": "Bucket",
|
||||||
"deleteInstallationWarning": "Prendi nota del nome del bucket qui sopra. L'eliminazione dei dati S3 potrebbe richiedere diversi minuti. Dopo l'eliminazione, verifica in Exoscale che il bucket sia stato rimosso. In caso contrario, svuota ed elimina il bucket manualmente.",
|
"deleteInstallationWarning": "Prendi nota del nome del bucket qui sopra. L'eliminazione dei dati S3 potrebbe richiedere diversi minuti. Dopo l'eliminazione, verifica in Exoscale che il bucket sia stato rimosso. In caso contrario, svuota ed elimina il bucket manualmente.",
|
||||||
"errorOccured": "Si è verificato un errore",
|
"errorOccured": "Si è verificato un errore",
|
||||||
|
|
@ -67,6 +95,7 @@
|
||||||
"noUsersWithDirectAccessToThis": "Nessun utente con accesso diretto a questo",
|
"noUsersWithDirectAccessToThis": "Nessun utente con accesso diretto a questo",
|
||||||
"selectUsers": "Seleziona utenti",
|
"selectUsers": "Seleziona utenti",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
|
"continue": "Continua",
|
||||||
"addNewFolder": "Aggiungi nuova cartella",
|
"addNewFolder": "Aggiungi nuova cartella",
|
||||||
"addNewInstallation": "Aggiungi nuova installazione",
|
"addNewInstallation": "Aggiungi nuova installazione",
|
||||||
"deleteFolder": "Elimina cartella",
|
"deleteFolder": "Elimina cartella",
|
||||||
|
|
@ -190,7 +219,7 @@
|
||||||
"demo_test_button": "Diagnosi IA",
|
"demo_test_button": "Diagnosi IA",
|
||||||
"demo_hide_button": "Nascondi diagnosi IA",
|
"demo_hide_button": "Nascondi diagnosi IA",
|
||||||
"demo_panel_title": "Diagnosi IA",
|
"demo_panel_title": "Diagnosi IA",
|
||||||
"demo_custom_group": "Personalizzato (potrebbe usare Mistral IA)",
|
"demo_custom_group": "Personalizzato (potrebbe usare IA)",
|
||||||
"demo_custom_option": "Inserisci allarme personalizzato…",
|
"demo_custom_option": "Inserisci allarme personalizzato…",
|
||||||
"demo_custom_placeholder": "es. UnknownBatteryFault",
|
"demo_custom_placeholder": "es. UnknownBatteryFault",
|
||||||
"demo_diagnose_button": "Diagnostica",
|
"demo_diagnose_button": "Diagnostica",
|
||||||
|
|
@ -614,5 +643,26 @@
|
||||||
"timelineAiDiagnosisCompletedDesc": "Diagnosi IA completata.",
|
"timelineAiDiagnosisCompletedDesc": "Diagnosi IA completata.",
|
||||||
"timelineAiDiagnosisFailedDesc": "Diagnosi IA fallita.",
|
"timelineAiDiagnosisFailedDesc": "Diagnosi IA fallita.",
|
||||||
"timelineEscalatedDesc": "Ticket escalato.",
|
"timelineEscalatedDesc": "Ticket escalato.",
|
||||||
"timelineResolutionAddedDesc": "Risoluzione aggiunta da {name}."
|
"timelineResolutionAddedDesc": "Risoluzione aggiunta da {name}.",
|
||||||
|
"terms_dialog_title": "Benvenuto su inesco energy",
|
||||||
|
"terms_data_heading": "I tuoi dati",
|
||||||
|
"terms_data_body": "I dati della tua installazione sono archiviati in modo sicuro in Svizzera. Non condividiamo i tuoi dati con terze parti.",
|
||||||
|
"terms_ai_heading": "Analisi basate sull'IA",
|
||||||
|
"terms_ai_body": "Utilizziamo un servizio di IA ospitato nell'UE per fornire diagnostica e analisi per le tue installazioni. I risultati generati dall'IA sono raccomandazioni e devono essere verificati da personale qualificato.",
|
||||||
|
"terms_cookies_heading": "Archiviazione del browser",
|
||||||
|
"terms_cookies_body": "Questa piattaforma memorizza le impostazioni di accesso e le preferenze nel browser per mantenerti connesso e ricordare la tua scelta linguistica.",
|
||||||
|
"terms_acknowledge_button": "Ho capito",
|
||||||
|
"privacy_menu_item": "Dati e privacy",
|
||||||
|
"privacy_dialog_title": "Dati e privacy",
|
||||||
|
"privacy_data_heading": "Dove vengono archiviati i miei dati?",
|
||||||
|
"privacy_data_body": "I dati della tua installazione sono archiviati su server in Svizzera. Solo il personale autorizzato di inesco energy può accedere ai tuoi dati per scopi di assistenza.",
|
||||||
|
"privacy_ai_heading": "Come viene utilizzata l'IA?",
|
||||||
|
"privacy_ai_body": "Utilizziamo un servizio di IA ospitato nell'Unione Europea per analizzare i dati della tua installazione e fornire informazioni diagnostiche. L'IA elabora dati tecnici come le letture delle batterie e i codici di errore. Le raccomandazioni dell'IA devono sempre essere verificate da personale qualificato.",
|
||||||
|
"privacy_browser_heading": "Cosa memorizza il mio browser?",
|
||||||
|
"privacy_browser_body": "Il tuo browser memorizza la sessione di accesso per mantenerti connesso e le tue preferenze di lingua e tema. Non vengono utilizzati cookie di tracciamento o pubblicitari.",
|
||||||
|
"privacy_access_heading": "Chi ha accesso ai miei dati?",
|
||||||
|
"privacy_access_body": "I tuoi dati non vengono condivisi con terze parti. Vengono utilizzati esclusivamente per il funzionamento della piattaforma e per fornirti informazioni sulle tue installazioni.",
|
||||||
|
"privacy_close_button": "Chiudi",
|
||||||
|
"sodistorepro": "Sodistore Pro",
|
||||||
|
"numberOfInverters": "Numero di inverter"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ import {
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useContext, useRef, useState } from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';
|
import ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';
|
||||||
|
import ShieldOutlinedIcon from '@mui/icons-material/ShieldOutlined';
|
||||||
import { ThemeContext } from '../../../../theme/ThemeProvider';
|
import { ThemeContext } from '../../../../theme/ThemeProvider';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import '../../../../App.css';
|
import '../../../../App.css';
|
||||||
|
import DataPrivacyDialog from '../../../../components/DataPrivacyDialog';
|
||||||
|
|
||||||
interface HeaderButtonsProps {
|
interface HeaderButtonsProps {
|
||||||
language: string;
|
language: string;
|
||||||
|
|
@ -79,6 +81,7 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
||||||
const setThemeName = themeContext;
|
const setThemeName = themeContext;
|
||||||
|
|
||||||
const [darkState, setDarkState] = useState(false);
|
const [darkState, setDarkState] = useState(false);
|
||||||
|
const [privacyOpen, setPrivacyOpen] = useState(false);
|
||||||
|
|
||||||
const handleThemeChange = () => {
|
const handleThemeChange = () => {
|
||||||
setDarkState(!darkState);
|
setDarkState(!darkState);
|
||||||
|
|
@ -132,6 +135,20 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
classes={{ root: 'MuiListItem-indicators' }}
|
||||||
|
onClick={() => setPrivacyOpen(true)}
|
||||||
|
>
|
||||||
|
<ListItemText
|
||||||
|
primaryTypographyProps={{ noWrap: true }}
|
||||||
|
primary={
|
||||||
|
<Box display="flex" alignItems="center">
|
||||||
|
<ShieldOutlinedIcon fontSize="small" sx={{ mr: 0.5 }} />
|
||||||
|
<FormattedMessage id="privacy_menu_item" defaultMessage="Data & Privacy" />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</ListWrapper>
|
</ListWrapper>
|
||||||
<div
|
<div
|
||||||
|
|
@ -152,6 +169,10 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
<DataPrivacyDialog
|
||||||
|
open={privacyOpen}
|
||||||
|
onClose={() => setPrivacyOpen(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,8 @@ function SidebarMenu() {
|
||||||
accessToSodistore,
|
accessToSodistore,
|
||||||
accessToSalidomo,
|
accessToSalidomo,
|
||||||
accessToSodiohome,
|
accessToSodiohome,
|
||||||
accessToSodistoreGrid
|
accessToSodistoreGrid,
|
||||||
|
accessToSodistorePro
|
||||||
} = useContext(ProductIdContext);
|
} = useContext(ProductIdContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -185,37 +186,20 @@ function SidebarMenu() {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SubMenuWrapper>
|
<SubMenuWrapper>
|
||||||
{accessToSalimax && (
|
{accessToSodiohome && (
|
||||||
<List component="div">
|
<List component="div">
|
||||||
<ListItem component="div">
|
<ListItem component="div">
|
||||||
<Button
|
<Button
|
||||||
disableRipple
|
disableRipple
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
to="/installations"
|
to="/sodiohome_installations"
|
||||||
startIcon={<BrightnessLowTwoToneIcon />}
|
|
||||||
>
|
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
|
||||||
<FormattedMessage id="salimax" defaultMessage="Salimax" />
|
|
||||||
</Box>
|
|
||||||
</Button>
|
|
||||||
</ListItem>
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
{accessToSodistore && (
|
|
||||||
<List component="div">
|
|
||||||
<ListItem component="div">
|
|
||||||
<Button
|
|
||||||
disableRipple
|
|
||||||
component={RouterLink}
|
|
||||||
onClick={closeSidebar}
|
|
||||||
to="/sodistore_installations"
|
|
||||||
startIcon={<BrightnessLowTwoToneIcon />}
|
startIcon={<BrightnessLowTwoToneIcon />}
|
||||||
>
|
>
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="sodistore"
|
id="sodiohome"
|
||||||
defaultMessage="Sodistore Max"
|
defaultMessage="Sodistore Home"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -223,20 +207,20 @@ function SidebarMenu() {
|
||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{accessToSalidomo && (
|
{accessToSodistorePro && (
|
||||||
<List component="div">
|
<List component="div">
|
||||||
<ListItem component="div">
|
<ListItem component="div">
|
||||||
<Button
|
<Button
|
||||||
disableRipple
|
disableRipple
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
to="/salidomo_installations"
|
to="/sodistorepro_installations"
|
||||||
startIcon={<BrightnessLowTwoToneIcon />}
|
startIcon={<BrightnessLowTwoToneIcon />}
|
||||||
>
|
>
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="salidomo"
|
id="sodistorepro"
|
||||||
defaultMessage="Salidomo"
|
defaultMessage="Sodistore Pro"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -265,20 +249,59 @@ function SidebarMenu() {
|
||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{accessToSodiohome && (
|
{accessToSodistore && (
|
||||||
<List component="div">
|
<List component="div">
|
||||||
<ListItem component="div">
|
<ListItem component="div">
|
||||||
<Button
|
<Button
|
||||||
disableRipple
|
disableRipple
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
to="/sodiohome_installations"
|
to="/sodistore_installations"
|
||||||
startIcon={<BrightnessLowTwoToneIcon />}
|
startIcon={<BrightnessLowTwoToneIcon />}
|
||||||
>
|
>
|
||||||
<Box sx={{ marginTop: '3px' }}>
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="sodiohome"
|
id="sodistore"
|
||||||
defaultMessage="Sodistore Home"
|
defaultMessage="Sodistore Max"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{accessToSalimax && (
|
||||||
|
<List component="div">
|
||||||
|
<ListItem component="div">
|
||||||
|
<Button
|
||||||
|
disableRipple
|
||||||
|
component={RouterLink}
|
||||||
|
onClick={closeSidebar}
|
||||||
|
to="/installations"
|
||||||
|
startIcon={<BrightnessLowTwoToneIcon />}
|
||||||
|
>
|
||||||
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
|
<FormattedMessage id="salimax" defaultMessage="Salimax" />
|
||||||
|
</Box>
|
||||||
|
</Button>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{accessToSalidomo && (
|
||||||
|
<List component="div">
|
||||||
|
<ListItem component="div">
|
||||||
|
<Button
|
||||||
|
disableRipple
|
||||||
|
component={RouterLink}
|
||||||
|
onClick={closeSidebar}
|
||||||
|
to="/salidomo_installations"
|
||||||
|
startIcon={<BrightnessLowTwoToneIcon />}
|
||||||
|
>
|
||||||
|
<Box sx={{ marginTop: '3px' }}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="salidomo"
|
||||||
|
defaultMessage="Salidomo"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue