From 730f337502683509bdeea41a144bec4e624e2780 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Mon, 23 Mar 2026 13:19:14 +0100 Subject: [PATCH] restructure sodistore home Information tab --- csharp/App/Backend/DataTypes/Installation.cs | 5 +- csharp/App/Backend/Database/Db.cs | 5 + .../Information/InformationSodistoreHome.tsx | 881 ++++++++++-------- .../Information/installationSetupUtils.ts | 112 +++ .../SodistorehomeInstallationForm.tsx | 41 +- .../src/interfaces/InstallationTypes.tsx | 4 + typescript/frontend-marios2/src/lang/de.json | 19 + typescript/frontend-marios2/src/lang/en.json | 19 + typescript/frontend-marios2/src/lang/fr.json | 19 + typescript/frontend-marios2/src/lang/it.json | 19 + 10 files changed, 752 insertions(+), 372 deletions(-) create mode 100644 typescript/frontend-marios2/src/content/dashboards/Information/installationSetupUtils.ts diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs index c1b612192..bdf63571d 100644 --- a/csharp/App/Backend/DataTypes/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -49,7 +49,10 @@ public class Installation : TreeNode public int BatteryClusterNumber { get; set; } = 0; public int BatteryNumber { get; set; } = 0; public string BatterySerialNumbers { get; set; } = ""; - + public string PvStringsPerInverter { get; set; } = ""; + public string InstallationModel { get; set; } = ""; + public string ExternalEms { get; set; } = "No"; + [Ignore] public String OrderNumbers { get; set; } public String VrmLink { get; set; } = ""; diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index 73a22eb0a..e8a197206 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -130,6 +130,11 @@ public static partial class Db fileConnection.CreateTable(); fileConnection.CreateTable(); + // 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 CopyDbToMemory(fileConnection); } diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx index 0b3308884..3cb43e526 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSodistoreHome.tsx @@ -1,8 +1,12 @@ import { + Accordion, + AccordionDetails, + AccordionSummary, Alert, Autocomplete, Box, CardContent, + Chip, CircularProgress, Container, FormControl, @@ -16,10 +20,11 @@ import { Typography, useTheme } from '@mui/material'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import Button from '@mui/material/Button'; import { Close as CloseIcon } from '@mui/icons-material'; -import React, { useContext, useState, useEffect, useRef } from 'react'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import React, { useContext, useState, useEffect } from 'react'; import { I_S3Credentials } from '../../../interfaces/S3Types'; import { I_Installation } from '../../../interfaces/InstallationTypes'; import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; @@ -28,6 +33,16 @@ import routes from '../../../Resources/routes.json'; import { useNavigate } from 'react-router-dom'; import { UserType } from '../../../interfaces/UserTypes'; import axiosConfig from '../../../Resources/axiosConfig'; +import { + INSTALLATION_PRESETS, + PresetConfig, + BatterySnTree, + parseBatterySnTree, + buildEmptyTree, + remapTree, + computeFlatValues, + wouldLoseData, +} from './installationSetupUtils'; interface InformationSodistorehomeProps { values: I_Installation; @@ -43,15 +58,13 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { const context = useContext(UserContext); const { currentUser } = context; const theme = useTheme(); + const intl = useIntl(); const [formValues, setFormValues] = useState(props.values); const requiredFields = ['name', 'region', 'location', 'country']; const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = useState(false); + const [pendingPreset, setPendingPreset] = useState(null); const navigate = useNavigate(); - const [batteryNumber, setBatteryNumber] = useState(0); - const [batterySerialNumbers, setBatterySerialNumbers] = useState( - [] - ); // Parse inverter/datalogger serial numbers from various legacy formats: // Slash-separated: "SN001/SN002" @@ -71,51 +84,179 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { } } - // Slash-separated format: "SN001/SN002" + // Slash-separated format: "SN001/SN002" — preserve empty positions if (value.includes('/')) { - return value.split('/').filter((s) => s.trim() !== ''); + return value.split('/'); } // Single value return [value.trim()]; }; - const [inverterNumber, setInverterNumber] = useState(() => { - const parts = parseSerialNumbers(props.values.inverterSN); - return parts.length > 0 ? parts.length : 1; - }); - const [inverterSerialNumbers, setInverterSerialNumbers] = useState( - () => { - const parts = parseSerialNumbers(props.values.inverterSN); - return parts.length > 0 ? parts : ['']; - } - ); - const [dataloggerSerialNumbers, setDataloggerSerialNumbers] = useState( - () => { - const parts = parseSerialNumbers(props.values.dataloggerSN); - return parts.length > 0 ? parts : ['']; - } - ); - const DeviceTypes = [ { id: 3, name: 'Growatt' }, { id: 4, name: 'Sinexcel' } ]; - const batterySnRefs = useRef<(HTMLInputElement | null)[]>([]); + // Preset state — initializes from persisted installationModel, empty for legacy + const [selectedPreset, setSelectedPreset] = useState( + props.values.installationModel || '' + ); + const presetConfig: PresetConfig | null = INSTALLATION_PRESETS[selectedPreset] || null; - // Initialize battery data from props - useEffect(() => { - if (props.values.batteryNumber) { - setBatteryNumber(props.values.batteryNumber); + const [batterySnTree, setBatterySnTree] = useState(() => { + if (presetConfig) { + return parseBatterySnTree(props.values.batterySerialNumbers || '', presetConfig); } - if (props.values.batterySerialNumbers) { - const serialNumbers = props.values.batterySerialNumbers - .split(',') - .filter((sn) => sn.trim() !== ''); - setBatterySerialNumbers(serialNumbers); + return []; + }); + + const [inverterSerialNumbers, setInverterSerialNumbers] = useState(() => + parseSerialNumbers(props.values.inverterSN) + ); + const [dataloggerSerialNumbers, setDataloggerSerialNumbers] = useState(() => + parseSerialNumbers(props.values.dataloggerSN) + ); + + // PV strings per inverter — persisted as comma-separated, default "1" per inverter + const [pvStringsPerInverter, setPvStringsPerInverter] = useState(() => { + const stored = props.values.pvStringsPerInverter; + if (stored && stored.trim() !== '') { + return stored.split(',').map((s) => s.trim()); } - }, []); + const invCount = presetConfig?.length + || parseSerialNumbers(props.values.inverterSN).length + || 1; + return Array.from({ length: invCount }, () => '1'); + }); + + const handlePresetChange = (newPreset: string) => { + const newConfig = INSTALLATION_PRESETS[newPreset]; + if (!newConfig) return; + + // Check for data loss — either from existing tree or legacy flat data + const treeToCheck = batterySnTree.length > 0 + ? batterySnTree + : props.values.batterySerialNumbers + ? parseBatterySnTree(props.values.batterySerialNumbers, newConfig) + : []; + if (treeToCheck.length > 0 && wouldLoseData(treeToCheck, newConfig)) { + setPendingPreset(newPreset); + return; + } + + applyPreset(newPreset); + }; + + const handlePresetChangeConfirm = () => { + if (pendingPreset) { + applyPreset(pendingPreset); + setPendingPreset(null); + } + }; + + const handlePresetChangeCancel = () => { + setPendingPreset(null); + }; + + const applyPreset = (newPreset: string) => { + const newConfig = INSTALLATION_PRESETS[newPreset]; + if (!newConfig) return; + + setSelectedPreset(newPreset); + + let newTree: BatterySnTree; + if (presetConfig && batterySnTree.length > 0) { + // Switching between presets — remap existing tree + newTree = remapTree(batterySnTree, newConfig); + } else if (props.values.batterySerialNumbers) { + // First preset selection on legacy installation — parse existing flat data + newTree = parseBatterySnTree(props.values.batterySerialNumbers, newConfig); + } else { + newTree = buildEmptyTree(newConfig); + } + setBatterySnTree(newTree); + + const newInvCount = newConfig.length; + const newInvSNs = Array.from({ length: newInvCount }, (_, i) => + inverterSerialNumbers[i] || '' + ); + const newDlSNs = Array.from({ length: newInvCount }, (_, i) => + dataloggerSerialNumbers[i] || '' + ); + const newPvStrings = Array.from({ length: newInvCount }, (_, i) => + pvStringsPerInverter[i] || '1' + ); + setInverterSerialNumbers(newInvSNs); + setDataloggerSerialNumbers(newDlSNs); + setPvStringsPerInverter(newPvStrings); + + const flat = computeFlatValues(newConfig, newTree); + setFormValues({ + ...formValues, + ...flat, + installationModel: newPreset, + inverterSN: newInvSNs.join('/'), + dataloggerSN: newDlSNs.join('/'), + pvStringsPerInverter: newPvStrings.join(','), + }); + }; + + const handleInverterSnChange = (invIdx: number, value: string) => { + const updated = [...inverterSerialNumbers]; + updated[invIdx] = value; + setInverterSerialNumbers(updated); + setFormValues({ + ...formValues, + inverterSN: updated.join('/'), + }); + }; + + const handleDataloggerSnChange = (invIdx: number, value: string) => { + const updated = [...dataloggerSerialNumbers]; + updated[invIdx] = value; + setDataloggerSerialNumbers(updated); + setFormValues({ + ...formValues, + dataloggerSN: updated.join('/'), + }); + }; + + const handlePvStringsChange = (invIdx: number, value: string) => { + if (value !== '' && !/^\d+$/.test(value)) return; + const updated = [...pvStringsPerInverter]; + updated[invIdx] = value; + setPvStringsPerInverter(updated); + setFormValues({ + ...formValues, + pvStringsPerInverter: updated.join(','), + }); + }; + + const handleBatterySnTreeChange = ( + invIdx: number, + clIdx: number, + batIdx: number, + value: string + ) => { + const newTree = batterySnTree.map((inv, i) => + i === invIdx + ? inv.map((cl, j) => + j === clIdx + ? cl.map((bat, k) => (k === batIdx ? value : bat)) + : cl + ) + : inv + ); + setBatterySnTree(newTree); + if (presetConfig) { + const flat = computeFlatValues(presetConfig, newTree); + setFormValues({ + ...formValues, + ...flat, + }); + } + }; const installationContext = useContext(InstallationsContext); const { @@ -137,107 +278,6 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { }); }; - const handleBatteryNumberChange = (e) => { - const inputValue = e.target.value; - // Only allow numeric input - if (inputValue === '' || /^\d+$/.test(inputValue)) { - const value = inputValue === '' ? 0 : parseInt(inputValue); - setBatteryNumber(value); - - if (value > 0) { - // Resize array: preserve existing serial numbers, add empty for new slots - const newSerialNumbers = Array.from({ length: value }, (_, index) => { - return batterySerialNumbers[index] || ''; - }); - setBatterySerialNumbers(newSerialNumbers); - - setFormValues({ - ...formValues, - batteryNumber: value, - batterySerialNumbers: newSerialNumbers.filter((sn) => sn !== '').join(',') - }); - } else { - // Field is empty (user is mid-edit) — don't clear serial numbers - setFormValues({ - ...formValues, - batteryNumber: 0 - }); - } - } - }; - - const handleBatterySerialNumberChange = (index: number, value: string) => { - const updatedSerialNumbers = [...batterySerialNumbers]; - updatedSerialNumbers[index] = value; - setBatterySerialNumbers(updatedSerialNumbers); - setFormValues({ - ...formValues, - batterySerialNumbers: updatedSerialNumbers.filter((sn) => sn !== '').join(',') - }); - }; - - const handleBatterySnKeyDown = (e: React.KeyboardEvent, index: number) => { - if (e.key === 'Enter') { - e.preventDefault(); - const nextIndex = index + 1; - if (nextIndex < batteryNumber && batterySnRefs.current[nextIndex]) { - batterySnRefs.current[nextIndex].focus(); - } - } - }; - const handleInverterNumberChange = (e) => { - const inputValue = e.target.value; - // Allow empty while user is mid-edit - if (inputValue === '') { - setInverterNumber(''); - return; - } - if (/^\d+$/.test(inputValue)) { - const value = Math.max(1, parseInt(inputValue)); - setInverterNumber(value); - - const newInverterSNs = Array.from({ length: value }, (_, i) => - inverterSerialNumbers[i] || '' - ); - const newDataloggerSNs = Array.from({ length: value }, (_, i) => - dataloggerSerialNumbers[i] || '' - ); - setInverterSerialNumbers(newInverterSNs); - setDataloggerSerialNumbers(newDataloggerSNs); - setFormValues({ - ...formValues, - inverterSN: newInverterSNs.filter((s) => s !== '').join('/'), - dataloggerSN: newDataloggerSNs.filter((s) => s !== '').join('/') - }); - } - }; - - const handleInverterNumberBlur = () => { - if (inverterNumber === '' || inverterNumber < 1) { - setInverterNumber(1); - } - }; - - const handleInverterSerialNumberChange = (index: number, value: string) => { - const updated = [...inverterSerialNumbers]; - updated[index] = value; - setInverterSerialNumbers(updated); - setFormValues({ - ...formValues, - inverterSN: updated.filter((s) => s !== '').join('/') - }); - }; - - const handleDataloggerSerialNumberChange = (index: number, value: string) => { - const updated = [...dataloggerSerialNumbers]; - updated[index] = value; - setDataloggerSerialNumbers(updated); - setFormValues({ - ...formValues, - dataloggerSN: updated.filter((s) => s !== '').join('/') - }); - }; - const handleSubmit = () => { setLoading(true); setError(false); @@ -389,6 +429,75 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { )} + {pendingPreset !== null && ( + + + + + + +
+ + +
+
+
+ )} + + + + +
- } + label={} name="name" value={formValues.name} onChange={handleChange} @@ -425,9 +533,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
- } + label={} name="region" value={formValues.region} onChange={handleChange} @@ -440,12 +546,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
- } + label={} name="location" value={formValues.location} onChange={handleChange} @@ -458,9 +559,7 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) {
- } + label={} name="country" value={formValues.country} onChange={handleChange} @@ -472,87 +571,22 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { />
-
- - setFormValues({ - ...formValues, - networkProvider: (val as string) || '' - }) - } - onInputChange={(_e, val) => - setFormValues({ - ...formValues, - networkProvider: val || '' - }) - } - disabled={!canEdit && !isPartner} - loading={loadingProviders} - renderInput={(params) => ( - - } - variant="outlined" - fullWidth - InputProps={{ - ...params.InputProps, - endAdornment: ( - <> - {loadingProviders ? ( - - ) : null} - {params.InputProps.endAdornment} - - ) - }} - /> - )} - /> -
- - {canEdit && ( + {formValues.installationModel && (
- } - name="vpnIp" - value={formValues.vpnIp} - onChange={handleChange} + label={} + value={formValues.installationModel} variant="outlined" fullWidth + inputProps={{ readOnly: true }} />
)}
- - - + + + { + const val = e.target.value as string; + setFormValues({ ...formValues, externalEms: val }); + }} + inputProps={{ readOnly: !canEdit && !isPartner }} + > + + Solar Manager + Smart Fox + Loxone + + + +
- {typeof inverterNumber === 'number' && inverterNumber > 0 && - inverterSerialNumbers.map((sn, index) => ( -
- - handleInverterSerialNumberChange(index, e.target.value) - } - variant="outlined" - fullWidth - inputProps={{ readOnly: !canEdit }} - /> -
- ))} - - {typeof inverterNumber === 'number' && inverterNumber > 0 && - dataloggerSerialNumbers.map((sn, index) => ( -
- - handleDataloggerSerialNumberChange(index, e.target.value) - } - variant="outlined" - fullWidth - inputProps={{ readOnly: !canEdit }} - /> -
- ))} - -
- - } - name="batteryClusterNumber" - value={formValues.batteryClusterNumber} - onChange={handleChange} - variant="outlined" - fullWidth - inputProps={{ readOnly: !canEdit }} - /> -
- -
- - } - name="batteryNumber" - type="text" - value={batteryNumber === 0 ? '' : batteryNumber} - onChange={handleBatteryNumberChange} - variant="outlined" - fullWidth - placeholder={canEdit ? 'Enter number of batteries' : ''} - inputProps={{ readOnly: !canEdit }} - /> -
- - {batteryNumber > 0 && - batterySerialNumbers.map((serialNumber, index) => ( -
- - handleBatterySerialNumberChange(index, e.target.value) - } - onKeyDown={(e) => handleBatterySnKeyDown(e, index)} - inputRef={(el) => (batterySnRefs.current[index] = el)} - variant="outlined" - fullWidth - placeholder={canEdit ? 'Scan or enter serial number' : ''} - inputProps={{ readOnly: !canEdit }} - /> -
- ))} - + {formValues.externalEms && + !['No', 'Solar Manager', 'Smart Fox', 'Loxone'].includes(formValues.externalEms) && ( +
+ } + name="externalEms" + value={formValues.externalEms === 'Other' ? '' : formValues.externalEms} + onChange={handleChange} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit && !isPartner }} + /> +
)}
- } + label={} name="information" value={formValues.information} onChange={handleChange} @@ -716,35 +690,202 @@ function InformationSodistorehome(props: InformationSodistorehomeProps) { />
- {canEdit && ( + {(canEdit || isPartner) && ( <> + + + +
- + + + Installation Model + + +
+
+ } + name="serialNumber" + value={formValues.serialNumber} + onChange={handleChange} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit }} + /> +
+ + {presetConfig && presetConfig.map((clusters, invIdx) => { + const invSn = inverterSerialNumbers[invIdx] || ''; + const totalBat = clusters.reduce((a, b) => a + b, 0); + const filledBat = (batterySnTree[invIdx] || []).flat().filter((s) => s !== '').length; + const filledClusters = (batterySnTree[invIdx] || []).filter( + (cl) => cl.some((s) => s !== '') + ).length; + + return ( + + } + sx={{ + '& .MuiAccordionSummary-content': { flexGrow: 0, justifyContent: 'flex-start' }, + justifyContent: 'flex-start', + '& .MuiAccordionSummary-expandIconWrapper': { ml: 1 } + }} + > + + + + + + + handleInverterSnChange(invIdx, e.target.value)} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit }} + /> + handleDataloggerSnChange(invIdx, e.target.value)} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit }} + /> + handlePvStringsChange(invIdx, e.target.value)} + variant="outlined" + fullWidth + inputProps={{ readOnly: !canEdit }} + /> + + {clusters.map((batteryCount, clIdx) => { + const filledInCluster = (batterySnTree[invIdx]?.[clIdx] || []).filter((s) => s !== '').length; + return ( + + } + sx={{ + '& .MuiAccordionSummary-content': { flexGrow: 0, justifyContent: 'flex-start' }, + justifyContent: 'flex-start', + '& .MuiAccordionSummary-expandIconWrapper': { ml: 1 } + }} + > + + + + + + + {Array.from({ length: batteryCount }, (_, batIdx) => ( + + handleBatterySnTreeChange(invIdx, clIdx, batIdx, e.target.value) + } + variant="outlined" + fullWidth + placeholder={canEdit ? 'Scan or enter serial number' : ''} + inputProps={{ readOnly: !canEdit }} + /> + ))} + + + ); })} + + + ); + })} + + )} + + {canEdit && ( + <> + + + + +
+ } + name="vpnIp" + value={formValues.vpnIp} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+
+ +
-
= { + '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 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; +}; diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx index 89bffedd4..764189e54 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistorehomeInstallationForm.tsx @@ -17,6 +17,7 @@ import { Close as CloseIcon } from '@mui/icons-material'; import { I_Installation } from 'src/interfaces/InstallationTypes'; import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'; import { FormattedMessage } from 'react-intl'; +import { INSTALLATION_PRESETS } from '../Information/installationSetupUtils'; interface SodistorehomeInstallationFormPros { cancel: () => void; @@ -33,8 +34,10 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) location: '', country: '', vpnIp: '', + installationModel: '', + externalEms: 'No', }); - const requiredFields = ['name', 'location', 'country', 'vpnIp']; + const requiredFields = ['name', 'location', 'country', 'vpnIp', 'installationModel']; const DeviceTypes = [ { id: 3, name: 'Growatt' }, @@ -171,6 +174,42 @@ function SodistorehomeInstallationForm(props: SodistorehomeInstallationFormPros) />
+
+ + + + + + +
+