diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 4a9aa699a..4f74271a2 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -202,6 +202,8 @@ public class Controller : ControllerBase bucketPath = "s3://" + installation.S3BucketId + "-e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa/" + startTimestamp; else if (installation.Product == (int)ProductType.SodistoreGrid) 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 bucketPath = "s3://" + installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/" + startTimestamp; Console.WriteLine("Fetching data for "+startTimestamp); @@ -815,9 +817,10 @@ public class Controller : ControllerBase if (installation is null || !user.HasAccessTo(installation)) 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 && - installation.Product != (int)ProductType.SodiStoreMax) + installation.Product != (int)ProductType.SodiStoreMax && + installation.Product != (int)ProductType.SodistorePro) return BadRequest("AI diagnostics not available for this product."); var result = await DiagnosticService.DiagnoseAsync(installationId, errorDescription, user.Language ?? "en"); diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index 0345e3292..0902d74a0 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -8,7 +8,8 @@ public enum ProductType Salidomo = 1, SodioHome =2, SodiStoreMax=3, - SodistoreGrid=4 + SodistoreGrid=4, + SodistorePro=5 } public enum StatusType diff --git a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs index 888ac27ae..44915ab37 100644 --- a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs +++ b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs @@ -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: 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.SodistorePro?Db.Installations.Count(f => f.Product == (int)ProductType.SodistorePro) + 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: 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.SodistorePro?Db.Installations.Count(f => f.Product == (int)ProductType.SodistorePro) + installation.Name: Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name; var contentString = $$""" diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs index 0bd36b6f6..074b62638 100644 --- a/csharp/App/Backend/DataTypes/Methods/Installation.cs +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -11,6 +11,7 @@ public static class InstallationMethods private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"; private static readonly String SodioHomeBucketNameSalt = "e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa"; 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) { @@ -29,6 +30,11 @@ public static class InstallationMethods return $"{installation.S3BucketId}-{SodistoreGridBucketNameSalt}"; } + if (installation.Product == (int)ProductType.SodistorePro) + { + return $"{installation.S3BucketId}-{SodistoreProBucketNameSalt}"; + } + return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}"; } diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 5a7c4e95d..b57b7ce13 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -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 && user.UserType != 0 @@ -295,7 +295,7 @@ public static class SessionMethods .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 diff --git a/csharp/App/Backend/Relations/Session.cs b/csharp/App/Backend/Relations/Session.cs index 9b54bca5f..c9aeda1a2 100644 --- a/csharp/App/Backend/Relations/Session.cs +++ b/csharp/App/Backend/Relations/Session.cs @@ -17,6 +17,7 @@ public class Session : Relation public Boolean AccessToSodistoreMax { get; set; } = false; public Boolean AccessToSodioHome { get; set; } = false; public Boolean AccessToSodistoreGrid { get; set; } = false; + public Boolean AccessToSodistorePro { get; set; } = false; [Ignore] public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ; // Private backing field @@ -51,6 +52,7 @@ public class Session : Relation AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0; AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).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("AccessToSodistoreMax" +user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count); diff --git a/csharp/App/Backend/Services/DailyIngestionService.cs b/csharp/App/Backend/Services/DailyIngestionService.cs index 28380c63f..920b18a52 100644 --- a/csharp/App/Backend/Services/DailyIngestionService.cs +++ b/csharp/App/Backend/Services/DailyIngestionService.cs @@ -50,7 +50,7 @@ public static class DailyIngestionService Console.WriteLine($"[DailyIngestion] Starting ingestion for all SodioHome 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(); foreach (var installation in installations) diff --git a/csharp/App/Backend/Services/ReportAggregationService.cs b/csharp/App/Backend/Services/ReportAggregationService.cs index 50614e28c..f6f2041b4 100644 --- a/csharp/App/Backend/Services/ReportAggregationService.cs +++ b/csharp/App/Backend/Services/ReportAggregationService.cs @@ -106,7 +106,7 @@ public static class ReportAggregationService Console.WriteLine("[ReportAggregation] Running Monday weekly report generation..."); 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(); var generated = 0; diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index 3ff62a7d7..18056b68b 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -105,6 +105,11 @@ public static class RabbitMqManager monitorLink = $"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 { monitorLink = diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index 8d054b8b0..73a7c6589 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -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.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.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); diff --git a/typescript/frontend-marios2/src/App.tsx b/typescript/frontend-marios2/src/App.tsx index 19e054b97..368465a5f 100644 --- a/typescript/frontend-marios2/src/App.tsx +++ b/typescript/frontend-marios2/src/App.tsx @@ -38,7 +38,8 @@ function App() { setAccessToSalidomo, setAccessToSodiohome, setAccessToSodistore, - setAccessToSodistoreGrid + setAccessToSodistoreGrid, + setAccessToSodistorePro } = useContext(ProductIdContext); const [language, setLanguage] = useState( @@ -106,6 +107,7 @@ function App() { setAccessToSodiohome(response.data.accessToSodioHome); setAccessToSodistore(response.data.accessToSodistoreMax); setAccessToSodistoreGrid(response.data.accessToSodistoreGrid); + setAccessToSodistorePro(response.data.accessToSodistorePro); if (response.data.accessToSalimax) { navigate(routes.installations); } else if (response.data.accessToSalidomo) { @@ -114,6 +116,8 @@ function App() { navigate(routes.sodistore_installations); } else if (response.data.accessToSodistoreGrid) { navigate(routes.sodistoregrid_installations); + } else if (response.data.accessToSodistorePro) { + navigate(routes.sodistorepro_installations); } else { navigate(routes.sodiohome_installations); } @@ -228,6 +232,15 @@ function App() { } /> + + + + } + /> + ( props.values.installationModel || '' ); - const presetConfig: PresetConfig | null = INSTALLATION_PRESETS[selectedPreset] || null; + const [inverterCount, setInverterCount] = useState( + isSodistorePro && props.values.installationModel + ? props.values.installationModel + : '' + ); + const presetConfig: PresetConfig | null = isSodistorePro + ? (inverterCount && parseInt(inverterCount, 10) > 0 + ? buildSodistoreProPreset(parseInt(inverterCount, 10)) + : null) + : (INSTALLATION_PRESETS[selectedPreset] || null); const [batterySnTree, setBatterySnTree] = useState(() => { if (presetConfig) { @@ -200,6 +213,38 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { }); }; + const handleInverterCountChange = (value: string) => { + if (value !== '' && !/^\d+$/.test(value)) return; + if (value !== '' && parseInt(value, 10) > 20) return; + setInverterCount(value); + const count = parseInt(value, 10); + if (isNaN(count) || count < 1) { + setBatterySnTree([]); + setFormValues({ ...formValues, installationModel: value }); + return; + } + const newConfig = buildSodistoreProPreset(count); + const newTree = batterySnTree.length > 0 + ? remapTree(batterySnTree, newConfig) + : buildEmptyTree(newConfig); + setBatterySnTree(newTree); + const newInvSNs = Array.from({ length: count }, (_, i) => inverterSerialNumbers[i] || ''); + const newDlSNs = Array.from({ length: count }, (_, i) => dataloggerSerialNumbers[i] || ''); + const newPvStrings = Array.from({ length: count }, (_, i) => pvStringsPerInverter[i] || '1'); + setInverterSerialNumbers(newInvSNs); + setDataloggerSerialNumbers(newDlSNs); + setPvStringsPerInverter(newPvStrings); + const flat = computeFlatValues(newConfig, newTree); + setFormValues({ + ...formValues, + ...flat, + installationModel: value, + inverterSN: newInvSNs.join('/'), + dataloggerSN: newDlSNs.join('/'), + pvStringsPerInverter: newPvStrings.join(','), + }); + }; + const handleInverterSnChange = (invIdx: number, value: string) => { const updated = [...inverterSerialNumbers]; updated[invIdx] = value; @@ -608,6 +653,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { )} + {!isSodistorePro && (
@@ -627,6 +673,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
+ )}
+ {isSodistorePro ? ( +
+ } + type="text" + value={inverterCount} + onChange={(e) => handleInverterCountChange(e.target.value)} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit }} + /> +
+ ) : (
+ )}
diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/installationSetupUtils.ts b/typescript/frontend-marios2/src/content/dashboards/Information/installationSetupUtils.ts index daf7c966a..6c4f4beb1 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/installationSetupUtils.ts +++ b/typescript/frontend-marios2/src/content/dashboards/Information/installationSetupUtils.ts @@ -19,6 +19,14 @@ export const INSTALLATION_PRESETS: Record = { '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 }, () => '')) diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx index 9f69c4fe5..0fd655380 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx @@ -131,7 +131,7 @@ export const fetchAggregatedDataJson = ( } else if (r.status === 200) { const jsontext = await r.text(); - if (product === 2) { + if (product === 2 || product === 5) { return parseSinexcelAggregatedData(jsontext); } diff --git a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx index 3f035d2ae..4021a2cad 100644 --- a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx @@ -107,13 +107,15 @@ function UserAccess(props: UserAccessProps) { const fetchAvailableInstallations = useCallback(async () => { 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=1`), 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) { if (err.response && err.response.status === 401) removeToken(); } diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index 023be7bd5..e3c88ae09 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -817,7 +817,7 @@ function Overview(props: OverviewProps) { type: 'bar', color: '#ff9900' }, - ...(product !== 2 ? [{ + ...((product !== 2 && product !== 5) ? [{ name: 'Net Energy', color: '#e65100', type: 'line', @@ -840,7 +840,7 @@ function Overview(props: OverviewProps) { alignItems="stretch" spacing={3} > - {!(aggregatedData && product === 2) && ( + {!(aggregatedData && (product === 2 || product === 5)) && ( )} - + - {product !== 2 && ( + {(product !== 2 && product !== 5) && ( )} - {product !== 2 && ( + {(product !== 2 && product !== 5) && ( - {aggregatedData && product === 2 && ( + {aggregatedData && (product === 2 || product === 5) && ( - + - {product !== 2 && ( + {(product !== 2 && product !== 5) && ( { const navigate = useNavigate(); const [selectedInstallation, setSelectedInstallation] = useState(-1); const currentLocation = useLocation(); + const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations; // const sortedInstallations = [...props.installations].sort((a, b) => { // Compare the status field of each installation and sort them based on the status. @@ -51,7 +53,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { setSelectedInstallation(-1); navigate( - routes.sodiohome_installations + + baseRoute + routes.list + routes.installation + `${installationID}` + @@ -82,9 +84,9 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { sx={{ display: currentLocation.pathname === - routes.sodiohome_installations + 'list' || + baseRoute + 'list' || currentLocation.pathname === - routes.sodiohome_installations + routes.list + baseRoute + routes.list ? 'block' : 'none' }} diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index 91c3bc9d3..00631620d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -50,7 +50,9 @@ function SodioHomeInstallation(props: singleInstallationProps) { const s3Bucket = 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 { currentUser } = context; diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx index 521f4622e..eac127311 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/InstallationSearch.tsx @@ -10,12 +10,14 @@ import SodioHomeInstallation from './Installation'; interface installationSearchProps { installations: I_Installation[]; + product?: number; } function InstallationSearch(props: installationSearchProps) { const intl = useIntl(); const [searchTerm, setSearchTerm] = useState(''); const currentLocation = useLocation(); + const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations; // const [filteredData, setFilteredData] = useState(props.installations); const indexedData = useMemo(() => { @@ -46,9 +48,9 @@ function InstallationSearch(props: installationSearchProps) { sx={{ display: currentLocation.pathname === - routes.sodiohome_installations + 'list' || + baseRoute + 'list' || currentLocation.pathname === - routes.sodiohome_installations + routes.list + baseRoute + routes.list ? 'block' : 'none' }} @@ -79,7 +81,7 @@ function InstallationSearch(props: installationSearchProps) { - + {filteredData.map((installation) => { return ( diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx index 038f7bb42..88e2814e3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx @@ -23,20 +23,26 @@ interface SodistorehomeInstallationFormPros { cancel: () => void; submit: () => void; parentid: number; + product?: number; } function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) { const theme = useTheme(); const [open, setOpen] = useState(true); + const isSodistorePro = props.product === 5; const [formValues, setFormValues] = useState>({ name: '', vpnIp: '', installationModel: '', externalEms: 'No', + ...(isSodistorePro ? { device: 4 } : {}), }); - const requiredFields = ['name', 'vpnIp', 'installationModel']; + const [inverterCount, setInverterCount] = useState(''); + const requiredFields = ['name', 'vpnIp', ...(isSodistorePro ? [] : ['installationModel'])]; - const DeviceTypes = SODIOHOME_DEVICE_TYPES; + const DeviceTypes = isSodistorePro + ? [{ id: 4, name: 'inesco 12K - WR Hybrid' }] + : SODIOHOME_DEVICE_TYPES; const installationContext = useContext(InstallationsContext); const { createInstallation, loading, setLoading, error, setError } = installationContext; @@ -52,7 +58,10 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) const handleSubmit = async (e) => { setLoading(true); formValues.parentId = props.parentid; - formValues.product = 2; + formValues.product = props.product ?? 2; + if (isSodistorePro) { + formValues.installationModel = inverterCount; + } const responseData = await createInstallation(formValues); props.submit(); }; @@ -66,6 +75,9 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) return false; } } + if (isSodistorePro && (!inverterCount || parseInt(inverterCount, 10) < 1)) { + return false; + } return true; }; @@ -132,6 +144,29 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) />
+ {isSodistorePro ? ( +
+ + } + 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} + /> +
+ ) : (
+ )} + {!isSodistorePro && (
+ )}
{ let path = location.pathname.split('/'); @@ -484,6 +485,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { @@ -496,7 +498,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { path={'*'} element={ } > diff --git a/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx index 60c35f955..181791aab 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tickets/TicketDetail.tsx @@ -742,7 +742,8 @@ function TicketDetailPage() { 1: routes.salidomo_installations, 2: routes.sodiohome_installations, 3: routes.sodistore_installations, - 4: routes.sodistoregrid_installations + 4: routes.sodistoregrid_installations, + 5: routes.sodistorepro_installations }; const prefix = productRoutes[detail.installationProduct] ?? routes.installations; navigate( diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx index 469d9658d..b125a9f40 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx @@ -60,6 +60,8 @@ function CustomTreeItem(props: CustomTreeItemProps) { ? routes.salidomo_installations : installation.product == 2 ? routes.sodiohome_installations + : installation.product == 5 + ? routes.sodistorepro_installations : routes.sodistore_installations; let folder_path = @@ -69,6 +71,8 @@ function CustomTreeItem(props: CustomTreeItemProps) { ? routes.salidomo_installations : product == 2 ? routes.sodiohome_installations + : product == 5 + ? routes.sodistorepro_installations : routes.sodistore_installations; if (installation.type != 'Folder') { diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx index 3e8c936f6..8a07848b3 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx @@ -65,11 +65,12 @@ function TreeInformation(props: TreeInformationProps) { setProduct(e.target.value); // Directly update the product state }; - const ProductTypes = ['Salimax', 'Salidomo', 'SodistoreHome', 'SodistoreMax', 'SodistoreGrid']; + const ProductTypes = ['Salimax', 'Salidomo', 'SodistoreHome', 'SodistoreMax', 'SodistoreGrid', 'SodistorePro']; const ProductDisplayNames: Record = { 'SodistoreHome': 'Sodistore Home', 'SodistoreMax': 'Sodistore Max', - 'SodistoreGrid': 'Sodistore Grid' + 'SodistoreGrid': 'Sodistore Grid', + 'SodistorePro': 'Sodistore Pro' }; const isMobile = window.innerWidth <= 1490; @@ -345,11 +346,12 @@ function TreeInformation(props: TreeInformationProps) { /> )} - {openModalInstallation && product == 'SodistoreHome' && ( + {openModalInstallation && (product == 'SodistoreHome' || product == 'SodistorePro') && ( )} diff --git a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx index a663f4ae3..926689b19 100644 --- a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx +++ b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx @@ -191,7 +191,7 @@ const InstallationsContextProvider = ({ `/GetAllInstallationsFromProduct?product=${product}` ); - if (product === 2) { + if (product === 2 || product === 5) { setSodiohomeInstallations(res.data); } else if (product === 1) { setSalidomoInstallations(res.data); diff --git a/typescript/frontend-marios2/src/contexts/ProductIdContextProvider.tsx b/typescript/frontend-marios2/src/contexts/ProductIdContextProvider.tsx index 0b14ffb7c..cfe6720a9 100644 --- a/typescript/frontend-marios2/src/contexts/ProductIdContextProvider.tsx +++ b/typescript/frontend-marios2/src/contexts/ProductIdContextProvider.tsx @@ -10,11 +10,13 @@ interface ProductIdContextType { accessToSodiohome: boolean; accessToSodistore: boolean; accessToSodistoreGrid: boolean; + accessToSodistorePro: boolean; setAccessToSalimax: (access: boolean) => void; setAccessToSalidomo: (access: boolean) => void; setAccessToSodiohome: (access: boolean) => void; setAccessToSodistore: (access: boolean) => void; setAccessToSodistoreGrid: (access: boolean) => void; + setAccessToSodistorePro: (access: boolean) => void; } // Create the context. @@ -49,6 +51,10 @@ export const ProductIdContextProvider = ({ const storedValue = localStorage.getItem('accessToSodistoreGrid'); 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( // productMapping[Object.keys(productMapping).find((key) => location.includes(key)) || ''] || -1 @@ -56,6 +62,8 @@ export const ProductIdContextProvider = ({ const [product, setProduct] = useState(() => { if (location.includes('salidomo')) { return 1; + } else if (location.includes('sodistorepro')) { + return 5; } else if (location.includes('sodiohome')) { return 2; } else if (location.includes('sodistoregrid')) { @@ -92,6 +100,10 @@ export const ProductIdContextProvider = ({ setAccessToSodistoreGrid(access); localStorage.setItem('accessToSodistoreGrid', JSON.stringify(access)); }; + const changeAccessSodistorePro = (access: boolean) => { + setAccessToSodistorePro(access); + localStorage.setItem('accessToSodistorePro', JSON.stringify(access)); + }; return ( {children} diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index 7a331de8e..754611b06 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -86,7 +86,7 @@ export const transformInputToBatteryViewDataJson = async ( }> => { const prefixes = ['', 'k', 'M', 'G', 'T']; const MAX_NUMBER = 9999999; - const isSodioHome = product === 2; + const isSodioHome = product === 2 || product === 5; const categories = isSodioHome ? ['Soc', 'Power', 'Voltage', 'Current', 'Soh'] : ['Soc', 'Temperature', 'Power', 'Voltage', 'Current']; @@ -169,7 +169,7 @@ export const transformInputToBatteryViewDataJson = async ( ); 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] * 100000); //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset @@ -393,7 +393,7 @@ export const transformInputToDailyDataJson = async ( // custom fallback logic to handle differences between Growatt and Sinexcel. // Growatt has: Battery1AmbientTemperature, GridPower, PvPower // Sinexcel has: Battery1Temperature, TotalGridPower (meter may be offline), PvPower1-4 - const pathsToSearch = product == 2 + const pathsToSearch = (product == 2 || product == 5) ? [ 'SODIOHOME_SOC', 'SODIOHOME_TEMPERATURE', @@ -516,8 +516,8 @@ export const transformInputToDailyDataJson = async ( let value: number | undefined = undefined; - if (product === 2) { - // SodioHome: use top-level aggregated values (Sinexcel multi-inverter) + if (product === 2 || product === 5) { + // SodioHome/SodistorePro: use top-level aggregated values (Sinexcel multi-inverter) const inv = result?.InverterRecord; if (inv) { switch (category_index) { @@ -735,7 +735,7 @@ export const transformInputToAggregatedDataJson = async ( const timestampPromises = []; while (currentDay.isBefore(end_date)) { - const dateFormat = product === 2 + const dateFormat = (product === 2 || product === 5) ? currentDay.format('DDMMYYYY') : currentDay.format('YYYY-MM-DD'); timestampPromises.push( diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index 9bf46ca08..333a65ef5 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -648,5 +648,7 @@ "terms_cookies_body": "Browser-Speicher wird für Anmeldesitzungen und Benutzereinstellungen verwendet. Dies ist für die korrekte Funktion der Plattform erforderlich.", "terms_usage_heading": "Nutzungsbedingungen", "terms_usage_body": "Durch die Nutzung dieser Plattform erkennen Sie die allgemeinen Nutzungsbedingungen von inesco Energy an. Bei Fragen wenden Sie sich bitte an Ihren Systemadministrator.", - "terms_acknowledge_button": "Ich verstehe" + "terms_acknowledge_button": "Ich verstehe", + "sodistorepro": "Sodistore Pro", + "numberOfInverters": "Anzahl der Wechselrichter" } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index b8e9aa4a1..44bd3d72a 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -396,5 +396,7 @@ "terms_cookies_body": "Browser storage is used for login sessions and user preferences. This is required for the platform to function correctly.", "terms_usage_heading": "Terms of Use", "terms_usage_body": "By using this platform, you acknowledge the general terms of use of inesco Energy. For questions, please contact your system administrator.", - "terms_acknowledge_button": "I understand" + "terms_acknowledge_button": "I understand", + "sodistorepro": "Sodistore Pro", + "numberOfInverters": "Number of Inverters" } diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index 8357a50d6..97a5cedf7 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -648,5 +648,7 @@ "terms_cookies_body": "Le stockage du navigateur est utilisé pour les sessions de connexion et les préférences utilisateur. Ceci est nécessaire au bon fonctionnement de la plateforme.", "terms_usage_heading": "Conditions d'utilisation", "terms_usage_body": "En utilisant cette plateforme, vous reconnaissez les conditions générales d'utilisation d'inesco Energy. Pour toute question, veuillez contacter votre administrateur système.", - "terms_acknowledge_button": "Je comprends" + "terms_acknowledge_button": "Je comprends", + "sodistorepro": "Sodistore Pro", + "numberOfInverters": "Nombre d'onduleurs" } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index 9759bb9a0..e68e4696d 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -648,5 +648,7 @@ "terms_cookies_body": "L'archiviazione del browser viene utilizzata per le sessioni di accesso e le preferenze utente. Questo è necessario per il corretto funzionamento della piattaforma.", "terms_usage_heading": "Condizioni d'uso", "terms_usage_body": "Utilizzando questa piattaforma, si riconoscono le condizioni generali d'uso di inesco Energy. Per domande, contattare l'amministratore di sistema.", - "terms_acknowledge_button": "Ho capito" + "terms_acknowledge_button": "Ho capito", + "sodistorepro": "Sodistore Pro", + "numberOfInverters": "Numero di inverter" } diff --git a/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx b/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx index 3a25cf5ef..402a44b25 100644 --- a/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx +++ b/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx @@ -170,7 +170,8 @@ function SidebarMenu() { accessToSodistore, accessToSalidomo, accessToSodiohome, - accessToSodistoreGrid + accessToSodistoreGrid, + accessToSodistorePro } = useContext(ProductIdContext); return ( @@ -285,6 +286,27 @@ function SidebarMenu() { )} + + {accessToSodistorePro && ( + + + + + + )}