From 988b714d5741a452d4564a9c8660b05a5226f8c5 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Fri, 6 Feb 2026 15:02:56 +0100 Subject: [PATCH] improved Configurtaion page responsiveness in frontend --- .../SodiohomeInstallations/Installation.tsx | 28 +++++-- .../SodistoreHomeConfiguration.tsx | 81 ++++++++++++++++++- 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index d8bae8c68..0733b2fb6 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -182,11 +182,13 @@ function SodioHomeInstallation(props: singleInstallationProps) { }; const fetchDataForOneTime = async () => { - var timeperiodToSearch = 200; + var timeperiodToSearch = 300; // 5 minutes to cover ~4 upload cycles let res; let timestampToFetch; - for (var i = timeperiodToSearch; i > 0; i -= 2) { + // Search from NOW backward to find the most recent data + // Step by 10 seconds - balances between finding files quickly and reducing 404s + for (var i = 0; i < timeperiodToSearch; i += 10) { timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i)); try { res = await fetchDataJson(timestampToFetch, s3Credentials, false); @@ -199,7 +201,7 @@ function SodioHomeInstallation(props: singleInstallationProps) { } } - if (i <= 0) { + if (i >= timeperiodToSearch) { setConnected(false); setLoading(false); return false; @@ -207,8 +209,10 @@ function SodioHomeInstallation(props: singleInstallationProps) { setConnected(true); setLoading(false); - const timestamp = Object.keys(res)[Object.keys(res).length - 1]; - setValues(res[timestamp]); + // Sort timestamps numerically to ensure we get the most recent data point + const timestamps = Object.keys(res).sort((a, b) => Number(b) - Number(a)); + const latestTimestamp = timestamps[0]; + setValues(res[latestTimestamp]); // setValues( // extractValues({ // time: UnixTime.fromTicks(parseInt(timestamp, 10)), @@ -251,9 +255,19 @@ function SodioHomeInstallation(props: singleInstallationProps) { } } } - // Fetch only one time in configuration tab + // Fetch periodically in configuration tab (every 30 seconds to detect S3 updates) if (currentTab == 'configuration') { - fetchDataForOneTime(); + fetchDataForOneTime(); // Initial fetch + + const configRefreshInterval = setInterval(() => { + console.log('Refreshing configuration data from S3...'); + fetchDataForOneTime(); + }, 15000); // Refresh every 15 seconds + + return () => { + continueFetching.current = false; + clearInterval(configRefreshInterval); + }; } return () => { diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx index 6aa7ad22a..cad3fcbbc 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/SodistoreHomeConfiguration.tsx @@ -16,7 +16,7 @@ import { useTheme } from '@mui/material'; -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import Button from '@mui/material/Button'; import { Close as CloseIcon } from '@mui/icons-material'; @@ -105,6 +105,78 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true", }); + // Storage key for pending config (optimistic update) + const pendingConfigKey = `pendingConfig_${props.id}`; + + // Helper to get current S3 values + const getS3Values = () => ({ + minimumSoC: props.values.Config.MinSoc, + maximumDischargingCurrent: props.values.Config.MaximumChargingCurrent, + maximumChargingCurrent: props.values.Config.MaximumDischargingCurrent, + operatingPriority: OperatingPriorityOptions.indexOf( + props.values.Config.OperatingPriority + ), + batteriesCount: props.values.Config.BatteriesCount, + clusterNumber: props.values.Config.ClusterNumber ?? 1, + PvNumber: props.values.Config.PvNumber ?? 0, + timeChargeandDischargePower: props.values.Config?.TimeChargeandDischargePower ?? 0, + startTimeChargeandDischargeDayandTime: + props.values.Config?.StartTimeChargeandDischargeDayandTime + ? dayjs(props.values.Config.StartTimeChargeandDischargeDayandTime).toDate() + : null, + stopTimeChargeandDischargeDayandTime: + props.values.Config?.StopTimeChargeandDischargeDayandTime + ? dayjs(props.values.Config.StopTimeChargeandDischargeDayandTime).toDate() + : null, + controlPermission: String(props.values.Config.ControlPermission).toLowerCase() === "true", + }); + + // Sync form values when props.values changes + // Logic: Use localStorage only briefly after submit to prevent flicker, then trust S3 + useEffect(() => { + const s3Values = getS3Values(); + const pendingConfigStr = localStorage.getItem(pendingConfigKey); + + if (pendingConfigStr) { + try { + const pendingConfig = JSON.parse(pendingConfigStr); + const submittedAt = pendingConfig.submittedAt || 0; + const timeSinceSubmit = Date.now() - submittedAt; + + // Within 150 seconds of submit: use localStorage (waiting for S3 sync) + // This covers two full S3 upload cycles (75 sec × 2) to ensure new file is available + if (timeSinceSubmit < 150000) { + // Check if S3 now matches - if so, sync is complete + const s3MatchesPending = + s3Values.controlPermission === pendingConfig.values.controlPermission && + s3Values.minimumSoC === pendingConfig.values.minimumSoC && + s3Values.operatingPriority === pendingConfig.values.operatingPriority; + + if (s3MatchesPending) { + // S3 synced! Clear localStorage and use S3 from now on + console.log('S3 synced with submitted config'); + localStorage.removeItem(pendingConfigKey); + setFormValues(s3Values); + } else { + // Still waiting for sync, keep showing submitted values + console.log('Waiting for S3 sync, showing submitted values'); + setFormValues(pendingConfig.values); + } + return; + } + + // Timeout expired: clear localStorage, trust S3 completely + console.log('Timeout expired, trusting S3 data'); + localStorage.removeItem(pendingConfigKey); + } catch (e) { + localStorage.removeItem(pendingConfigKey); + } + } + + // No localStorage or expired: always use S3 (source of truth) + setFormValues(s3Values); + }, [props.values]); + const handleOperatingPriorityChange = (event) => { setFormValues({ ...formValues, @@ -173,6 +245,13 @@ function SodistoreHomeConfiguration(props: SodistoreHomeConfigurationProps) { if (res) { setUpdated(true); setLoading(false); + + // Save submitted values to localStorage for optimistic UI update + // This ensures the form shows correct values even before S3 syncs (up to 75 sec delay) + localStorage.setItem(pendingConfigKey, JSON.stringify({ + values: formValues, + submittedAt: Date.now() + })); } };