From c94eb235a936b43b827f399c9dd672825af76657 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Tue, 31 Mar 2026 19:23:17 +0200 Subject: [PATCH] many many --- .../src/Resources/formatPower.tsx | 3 +- .../frontend-marios2/src/config/tourSteps.ts | 176 +++++++----------- .../dashboards/Installations/Installation.tsx | 9 +- .../dashboards/Installations/index.tsx | 38 ++-- .../dashboards/ManageAccess/UserAccess.tsx | 36 +++- .../dashboards/Overview/chartOptions.tsx | 55 ++++-- .../content/dashboards/Overview/overview.tsx | 5 + .../SalidomoInstallations/Installation.tsx | 11 +- .../SalidomoInstallations/index.tsx | 38 ++-- .../SodiohomeInstallations/Installation.tsx | 9 +- .../SodiohomeInstallations/index.tsx | 38 ++-- .../src/content/dashboards/Users/userForm.tsx | 154 +++++++-------- .../frontend-marios2/src/interfaces/Chart.tsx | 21 ++- typescript/frontend-marios2/src/lang/de.json | 1 + typescript/frontend-marios2/src/lang/en.json | 1 + typescript/frontend-marios2/src/lang/fr.json | 1 + typescript/frontend-marios2/src/lang/it.json | 1 + .../src/layouts/SidebarLayout/index.tsx | 65 ++----- 18 files changed, 318 insertions(+), 344 deletions(-) diff --git a/typescript/frontend-marios2/src/Resources/formatPower.tsx b/typescript/frontend-marios2/src/Resources/formatPower.tsx index 4f5a3ede0..28df5755a 100644 --- a/typescript/frontend-marios2/src/Resources/formatPower.tsx +++ b/typescript/frontend-marios2/src/Resources/formatPower.tsx @@ -20,8 +20,9 @@ export function formatPowerForGraph(value, magnitude): { value: number } { } } + const result = negative === false ? value : -value; return { - value: negative === false ? value : -value + value: Math.round(result * 100) / 100 }; } diff --git a/typescript/frontend-marios2/src/config/tourSteps.ts b/typescript/frontend-marios2/src/config/tourSteps.ts index f3684278b..0721a075a 100644 --- a/typescript/frontend-marios2/src/config/tourSteps.ts +++ b/typescript/frontend-marios2/src/config/tourSteps.ts @@ -1,117 +1,85 @@ import { Step } from 'react-joyride'; import { IntlShape } from 'react-intl'; -// --- Build a single step with i18n --- +// --- Tab key → i18n content description mapping --- +// Only the *content* (description) needs i18n keys. +// The *title* is read directly from the rendered tab element's text, +// so it always matches the tab label in the current language. -function makeStep( - intl: IntlShape, - target: string, - titleId: string, - contentId: string, - placement: Step['placement'] = 'bottom', - disableBeacon = false -): Step { - return { - target, - title: intl.formatMessage({ id: titleId }), - content: intl.formatMessage({ id: contentId }), - placement, - ...(disableBeacon ? { disableBeacon: true } : {}) - }; -} - -// --- Tab key → i18n key mapping --- - -const tabConfig: Record = { - list: { titleId: 'tourListTitle', contentId: 'tourListContent' }, - tree: { titleId: 'tourTreeTitle', contentId: 'tourTreeContent' }, - live: { titleId: 'tourLiveTitle', contentId: 'tourLiveContent' }, - overview: { titleId: 'tourOverviewTitle', contentId: 'tourOverviewContent' }, - batteryview: { titleId: 'tourBatteryviewTitle', contentId: 'tourBatteryviewContent' }, - pvview: { titleId: 'tourPvviewTitle', contentId: 'tourPvviewContent' }, - log: { titleId: 'tourLogTitle', contentId: 'tourLogContent' }, - information: { titleId: 'tourInformationTitle', contentId: 'tourInformationContent' }, - report: { titleId: 'tourReportTitle', contentId: 'tourReportContent' }, - manage: { titleId: 'tourManageTitle', contentId: 'tourManageContent' }, - configuration: { titleId: 'tourConfigurationTitle', contentId: 'tourConfigurationContent' }, - history: { titleId: 'tourHistoryTitle', contentId: 'tourHistoryContent' } +const tabContentKey: Record = { + list: 'tourListContent', + tree: 'tourTreeContent', + live: 'tourLiveContent', + overview: 'tourOverviewContent', + batteryview: 'tourBatteryviewContent', + pvview: 'tourPvviewContent', + log: 'tourLogContent', + information: 'tourInformationContent', + report: 'tourReportContent', + manage: 'tourManageContent', + configuration: 'tourConfigurationContent', + history: 'tourHistoryContent', + installationTickets: 'tourInstallationTicketsContent' }; -// Steps to skip inside a specific installation (already covered in the list-page tour) -const listPageOnlyTabs = new Set(['list', 'tree']); - -// --- Build tour steps from tab value list --- - -function buildTourSteps(intl: IntlShape, tabValues: string[], includeInstallationHint = false, isInsideInstallation = false): Step[] { +/** + * Build tour steps dynamically from the DOM. + * Scans for all rendered `#tour-tab-*` elements and creates a step for each. + * The step title is the tab's own rendered text (matching across languages). + */ +export function buildDynamicTourSteps(intl: IntlShape, isInsideInstallation: boolean): Step[] { const steps: Step[] = []; + + // Language selector step (only on list/tree pages, not inside an installation) if (!isInsideInstallation) { - steps.push(makeStep(intl, '[data-tour="language-selector"]', 'tourLanguageTitle', 'tourLanguageContent', 'bottom', true)); - } - for (const value of tabValues) { - if (isInsideInstallation && listPageOnlyTabs.has(value)) continue; - const cfg = tabConfig[value]; - if (cfg) { - steps.push(makeStep(intl, `#tour-tab-${value}`, cfg.titleId, cfg.contentId, 'bottom', steps.length === 0)); + const langEl = document.querySelector('[data-tour="language-selector"]'); + if (langEl) { + steps.push({ + target: '[data-tour="language-selector"]', + title: intl.formatMessage({ id: 'tourLanguageTitle' }), + content: intl.formatMessage({ id: 'tourLanguageContent' }), + placement: 'bottom', + disableBeacon: true + }); } } - if (includeInstallationHint && !isInsideInstallation) { - steps.push(makeStep(intl, '#tour-tab-list', 'tourExploreTitle', 'tourExploreContent')); + + // Collect all tour-tab elements in DOM order + const tabEls = document.querySelectorAll('[id^="tour-tab-"]'); + tabEls.forEach((el) => { + const rect = el.getBoundingClientRect(); + if (rect.width === 0 || rect.height === 0) return; + + const tabValue = el.id.replace('tour-tab-', ''); + + // Skip list/tree tabs when inside an installation (already covered on list page) + if (isInsideInstallation && (tabValue === 'list' || tabValue === 'tree')) return; + + // Use the tab's own rendered text as title (matches current language) + const title = el.textContent?.trim() || tabValue; + const contentKey = tabContentKey[tabValue]; + const content = contentKey + ? intl.formatMessage({ id: contentKey }) + : ''; + + steps.push({ + target: `#tour-tab-${tabValue}`, + title, + content, + placement: 'bottom' as const, + ...(steps.length === 0 ? { disableBeacon: true } : {}) + }); + }); + + // "Explore" hint at the end when on list/tree page + if (!isInsideInstallation && document.querySelector('#tour-tab-list')) { + steps.push({ + target: '#tour-tab-list', + title: intl.formatMessage({ id: 'tourExploreTitle' }), + content: intl.formatMessage({ id: 'tourExploreContent' }), + placement: 'bottom' + }); } + return steps; } - -// --- Sodistore Home (product 2) --- - -export const buildSodiohomeCustomerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'live', 'overview', 'information', 'report' -], false, inside); - -export const buildSodiohomePartnerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'log', 'information', 'report' -], true, inside); - -export const buildSodiohomeAdminTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'log', 'manage', 'information', 'configuration', 'history', 'report' -], true, inside); - -// --- Salimax (product 0) / Sodistore Max (product 3) --- - -export const buildSalimaxCustomerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'live', 'overview', 'information' -], false, inside); - -export const buildSalimaxPartnerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'pvview', 'information' -], true, inside); - -export const buildSalimaxAdminTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'manage', 'log', 'information', 'configuration', 'history', 'pvview' -], true, inside); - -// --- Sodistore Grid (product 4) — same as Salimax but no PV View --- - -export const buildSodistoregridCustomerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'live', 'overview', 'information' -], false, inside); - -export const buildSodistoregridPartnerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'information' -], true, inside); - -export const buildSodistoregridAdminTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'live', 'overview', 'batteryview', 'manage', 'log', 'information', 'configuration', 'history' -], true, inside); - -// --- Salidomo (product 1) --- - -export const buildSalidomoCustomerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'batteryview', 'overview', 'information' -], false, inside); - -export const buildSalidomoPartnerTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'batteryview', 'overview', 'information' -], true, inside); - -export const buildSalidomoAdminTourSteps = (intl: IntlShape, inside = false) => buildTourSteps(intl, [ - 'list', 'tree', 'batteryview', 'overview', 'log', 'manage', 'information', 'history' -], true, inside); diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index d13f4e139..56f8c8f99 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -150,7 +150,8 @@ function Installation(props: singleInstallationProps) { return false; } console.log(`Timestamp: ${timestamp}`); - console.log(res[timestamp]); + const { Config: { S3: { Key, Secret, ...s3Rest } = {} as any, ...configRest } = {} as any, ...dataRest } = res[timestamp] || {}; + console.log({ ...dataRest, Config: { ...configRest, S3: { ...s3Rest, Key: '***', Secret: '***' } } }); setValues(res[timestamp]); await timeout(2000); @@ -381,7 +382,7 @@ function Installation(props: singleInstallationProps) { {loading && currentTab != 'information' && currentTab != 'history' && - currentTab != 'manage' && + // currentTab != 'manage' && currentTab != 'log' && currentTab != 'installationTickets' && ( )} - {currentUser.userType == UserType.admin && ( + {/* {currentUser.userType == UserType.admin && ( } /> - )} + )} */} {currentUser.userType == UserType.admin && ( - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'log', label: @@ -259,15 +259,15 @@ function InstallationTabs(props: InstallationTabsProps) { value: 'pvview', label: }, - { - value: 'manage', - label: ( - - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'log', label: diff --git a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx index ab85972af..90801853d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/ManageAccess/UserAccess.tsx @@ -81,11 +81,12 @@ function UserAccess(props: UserAccessProps) { const sortedInstallations = useMemo(() => { const orderMap = new Map(PRODUCT_GROUP_ORDER.map((p, i) => [p, i])); - return [...availableInstallations].sort((a, b) => { + const sorted = [...availableInstallations].sort((a, b) => { const oa = orderMap.get(a.product) ?? 99; const ob = orderMap.get(b.product) ?? 99; return oa !== ob ? oa - ob : a.name.localeCompare(b.name); }); + return sorted; }, [availableInstallations]); // Direct grants for this user @@ -128,15 +129,14 @@ function UserAccess(props: UserAccessProps) { const fetchAvailableInstallations = useCallback(async () => { try { - 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=4`), - axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`) - ]); - setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data, ...res4.data, ...res5.data]); + const products = [0, 1, 2, 3, 4, 5]; + const responses = await Promise.all( + products.map((p) => axiosConfig.get(`/GetAllInstallationsFromProduct?product=${p}`)) + ); + const all = responses.flatMap((res, idx) => + res.data.map((inst: I_Installation) => ({ ...inst, product: products[idx] })) + ); + setAvailableInstallations(all); } catch (err) { if (err.response && err.response.status === 401) removeToken(); } @@ -293,6 +293,22 @@ function UserAccess(props: UserAccessProps) { value={selectedInstallations} onChange={(_event, newValue) => setSelectedInstallations(newValue)} isOptionEqualToValue={(option, value) => option.id === value.id} + renderGroup={(params) => ( +
  • + + {params.group} + +
      {params.children}
    +
  • + )} renderInput={(params) => ( = 0 ? 0 @@ -88,6 +90,7 @@ export const getChartOptions = ( { seriesName: 'Grid Power', show: false, + tickAmount: 6, min: chartInfo.min >= 0 ? 0 @@ -104,15 +107,6 @@ export const getChartOptions = ( : chartInfo.max <= 0 ? 0 : undefined, - title: { - text: chartInfo.unit, - style: { - fontSize: '12px' - }, - offsetY: -190, - offsetX: 25, - rotate: 0 - }, labels: { formatter: function (value: number) { return formatPowerForGraph( @@ -122,11 +116,39 @@ export const getChartOptions = ( } } }, - { - seriesName: 'State Of Charge', + seriesName: 'Grid Power', + show: false, + tickAmount: 6, + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil( + chartInfo.min / findPower(chartInfo.min).value + ) * findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil( + chartInfo.max / findPower(chartInfo.max).value + ) * findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + labels: { + formatter: function (value: number) { + return formatPowerForGraph( + value, + chartInfo.magnitude + ).value.toString(); + } + } + }, + { + seriesName: 'Battery SOC', opposite: true, - + tickAmount: 5, min: 0, max: 100, title: { @@ -140,12 +162,13 @@ export const getChartOptions = ( }, labels: { formatter: function (value: number) { - return formatPowerForGraph(value, 0).value.toString(); + return Math.round(value).toString(); } } } ] : { + tickAmount: chartInfo.unit === '(%)' ? 5 : 6, min: chartInfo.min >= 0 ? 0 @@ -173,6 +196,9 @@ export const getChartOptions = ( }, labels: { formatter: function (value: number) { + if (chartInfo.unit === '(%)') { + return Math.round(value).toString(); + } return formatPowerForGraph( value, chartInfo.magnitude @@ -189,7 +215,7 @@ export const getChartOptions = ( y: { formatter: function (val, { seriesIndex, w }) { const seriesName = w.config.series[seriesIndex].name; - if (seriesName === 'State Of Charge') { + if (seriesName === 'Battery SOC') { return val.toFixed(2) + ' %'; } else { return ( @@ -255,6 +281,7 @@ export const getChartOptions = ( } }, yaxis: { + tickAmount: 6, min: chartInfo.min >= 0 ? 0 diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index b02aef438..0d0fa4a4f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -735,6 +735,11 @@ function Overview(props: OverviewProps) { type: 'line', color: '#ff9900' }, + { + ...dailyDataArray[chartState].chartData.ACLoad, + type: 'line', + color: '#2ecc71' + }, { ...dailyDataArray[chartState].chartData.soc, type: 'line', diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index b8a678512..ca3b6f621 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -105,7 +105,7 @@ function SalidomoInstallation(props: singleInstallationProps) { setLoading(false); console.log('NUMBER OF FILES=' + Object.keys(res).length); - console.log('res=', res); + console.log('res= [S3 credentials hidden]'); while (continueFetching.current) { for (const timestamp of Object.keys(res)) { @@ -114,7 +114,8 @@ function SalidomoInstallation(props: singleInstallationProps) { return false; } console.log(`Timestamp: ${timestamp}`); - console.log('object is', res); + const { Config: { S3: { Key, Secret, ...s3Rest } = {} as any, ...configRest } = {} as any, ...dataRest } = res[timestamp] || {}; + console.log('object is', { ...dataRest, Config: { ...configRest, S3: { ...s3Rest, Key: '***', Secret: '***' } } }); // Set values asynchronously with delay setValues(res[timestamp]); @@ -323,7 +324,7 @@ function SalidomoInstallation(props: singleInstallationProps) { {loading && currentTab != 'information' && - currentTab != 'manage' && + // currentTab != 'manage' && currentTab != 'history' && currentTab != 'log' && currentTab != 'installationTickets' && ( @@ -416,7 +417,7 @@ function SalidomoInstallation(props: singleInstallationProps) { /> )} - {currentUser.userType == UserType.admin && ( + {/* {currentUser.userType == UserType.admin && ( } /> - )} + )} */} {currentUser.userType == UserType.admin && ( }, - { - value: 'manage', - label: ( - - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'information', @@ -198,15 +198,15 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { label: }, - { - value: 'manage', - label: ( - - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'information', diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx index e46776e68..80ea47d89 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx @@ -183,7 +183,8 @@ function SodioHomeInstallation(props: singleInstallationProps) { return false; } console.log(`Timestamp: ${timestamp}`); - console.log(res[timestamp]); + const { Config: { S3: { Key, Secret, ...s3Rest } = {} as any, ...configRest } = {} as any, ...dataRest } = res[timestamp] || {}; + console.log({ ...dataRest, Config: { ...configRest, S3: { ...s3Rest, Key: '***', Secret: '***' } } }); setValues(res[timestamp]); await timeout(2000); @@ -473,7 +474,7 @@ function SodioHomeInstallation(props: singleInstallationProps) { {loading && currentTab != 'information' && - currentTab != 'manage' && + // currentTab != 'manage' && currentTab != 'history' && currentTab != 'log' && currentTab != 'report' && @@ -584,7 +585,7 @@ function SodioHomeInstallation(props: singleInstallationProps) { /> )} - {currentUser.userType == UserType.admin && ( + {/* {currentUser.userType == UserType.admin && ( } /> - )} + )} */} }, - { - value: 'manage', - label: ( - - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'information', label: ( @@ -297,15 +297,15 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { value: 'log', label: }, - { - value: 'manage', - label: ( - - ) - }, + // { + // value: 'manage', + // label: ( + // + // ) + // }, { value: 'information', label: ( diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/userForm.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/userForm.tsx index 1d849e3f5..d3e3082af 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Users/userForm.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Users/userForm.tsx @@ -1,6 +1,7 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { Alert, + Autocomplete, Box, CircularProgress, FormControl, @@ -10,6 +11,7 @@ import { Modal, Select, TextField, + Typography, useTheme } from '@mui/material'; import Button from '@mui/material/Button'; @@ -20,6 +22,16 @@ import { TokenContext } from 'src/contexts/tokenContext'; import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes'; import { FormattedMessage, useIntl } from 'react-intl'; +const PRODUCT_GROUP_ORDER: number[] = [2, 5, 4, 3, 0, 1]; +const PRODUCT_NAMES: Record = { + 0: 'Salimax', + 1: 'Salidomo', + 2: 'Sodistore Home', + 3: 'Sodistore Max', + 4: 'Sodistore Grid', + 5: 'Sodistore Pro' +}; + interface userFormProps { cancel: () => void; submit: () => void; @@ -32,7 +44,6 @@ function userForm(props: userFormProps) { const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [errormessage, setErrorMessage] = useState(intl.formatMessage({ id: 'errorOccured' })); - const [openInstallation, setOpenInstallation] = useState(false); const [openFolder, setOpenFolder] = useState(false); const [formValues, setFormValues] = useState>({ name: '', @@ -41,9 +52,7 @@ function userForm(props: userFormProps) { }); const requiredFields = ['name', 'email']; const [selectedFolderNames, setSelectedFolderNames] = useState([]); - const [selectedInstallationNames, setSelectedInstallationNames] = useState< - string[] - >([]); + const [selectedInstallations, setSelectedInstallations] = useState([]); const UserTypes = ['Client', 'Partner', 'Admin']; @@ -72,23 +81,13 @@ function userForm(props: userFormProps) { setLoading(true); try { - 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=4`), - axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`) - ]); - - const combined = [ - ...res0.data, - ...res1.data, - ...res2.data, - ...res3.data, - ...res4.data, - ...res5.data - ]; + const products = [0, 1, 2, 3, 4, 5]; + const responses = await Promise.all( + products.map((p) => axiosConfig.get(`/GetAllInstallationsFromProduct?product=${p}`)) + ); + const combined = responses.flatMap((res, idx) => + res.data.map((inst: I_Installation) => ({ ...inst, product: products[idx] })) + ); setInstallations(combined); } catch (err) { @@ -100,6 +99,15 @@ function userForm(props: userFormProps) { } }, [setInstallations]); + const sortedInstallations = useMemo(() => { + const orderMap = new Map(PRODUCT_GROUP_ORDER.map((p, i) => [p, i])); + return [...installations].sort((a, b) => { + const oa = orderMap.get(a.product) ?? 99; + const ob = orderMap.get(b.product) ?? 99; + return oa !== ob ? oa - ob : a.name.localeCompare(b.name); + }); + }, [installations]); + useEffect(() => { fetchFolders(); fetchInstallations(); @@ -116,10 +124,6 @@ function userForm(props: userFormProps) { setSelectedFolderNames(event.target.value); }; - const handleInstallationChange = (event) => { - setSelectedInstallationNames(event.target.value); - }; - const isMobile = window.innerWidth <= 1490; const handleSubmit = async (e) => { @@ -153,11 +157,7 @@ function userForm(props: userFormProps) { }); } - for (const installationName of selectedInstallationNames) { - const installation = installations.find( - (installation) => installation.name === installationName - ); - + for (const installation of selectedInstallations) { await axiosConfig .post( `/GrantUserAccessToInstallation?UserId=${res.data.id}&InstallationId=${installation.id}` @@ -207,14 +207,6 @@ function userForm(props: userFormProps) { }); }; - const handleOpenInstallation = () => { - setOpenInstallation(true); - }; - - const handleCloseInstallation = () => { - setOpenInstallation(false); - }; - const handleOpenFolder = () => { setOpenFolder(true); }; @@ -345,55 +337,43 @@ function userForm(props: userFormProps) {
    - - - - - - + /> + )} + sx={{ mt: 1 }} + />
    diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index 6c5840cb5..791374c64 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -452,11 +452,11 @@ export const transformInputToDailyDataJson = async ( ]; const chartData: chartDataInterface = { - soc: { name: 'State Of Charge', data: [] }, + soc: { name: 'Battery SOC', data: [] }, temperature: { name: 'Battery Temperature', data: [] }, dcPower: { name: 'Battery Power', data: [] }, gridPower: { name: 'Grid Power', data: [] }, - pvProduction: { name: 'Pv Production', data: [] }, + pvProduction: { name: 'PV Power', data: [] }, dcBusVoltage: { name: 'DC Bus Voltage', data: [] }, ACLoad: { name: 'AC Load', data: [] }, DCLoad: { name: 'DC Load', data: [] } @@ -530,7 +530,8 @@ export const transformInputToDailyDataJson = async ( Object.keys(results[i]).length - 1 ]; const result = results[i][timestamp]; - //console.log(result); + const { Config: { S3: { Key: _k, Secret: _s, ...s3Rest } = {} as any, ...configRest } = {} as any, ...dataRest } = result || {}; + console.log('Overview data:', { ...dataRest, Config: { ...configRest, S3: { ...s3Rest, Key: '***', Secret: '***' } } }); let category_index = 0; // eslint-disable-next-line @typescript-eslint/no-loop-func pathsToSearch.forEach((path) => { @@ -639,16 +640,19 @@ export const transformInputToDailyDataJson = async ( chartOverview.overview = { magnitude: Math.max( chartOverview['gridPower'].magnitude, - chartOverview['pvProduction'].magnitude + chartOverview['pvProduction'].magnitude, + chartOverview['ACLoad'].magnitude ), unit: '(kW)', min: Math.min( chartOverview['gridPower'].min, - chartOverview['pvProduction'].min + chartOverview['pvProduction'].min, + chartOverview['ACLoad'].min ), max: Math.max( chartOverview['gridPower'].max, - chartOverview['pvProduction'].max + chartOverview['pvProduction'].max, + chartOverview['ACLoad'].max ) }; @@ -673,13 +677,14 @@ const fetchJsonDataForOneTime = async ( res = await fetchDataJson(timestampToFetch, s3Credentials); if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) { - //console.log('Successfully fetched ' + timestampToFetch); + console.log('Successfully fetched ' + timestampToFetch); return res; } } catch (err) { console.error('Error fetching data:', err); } } + console.warn('Failed to fetch timestamp ' + startUnixTime.ticks); return null; }; @@ -771,7 +776,7 @@ export const transformInputToAggregatedDataJson = async ( } const results = await Promise.all(timestampPromises); - console.log("Fetched aggregated daily results:", results); + console.log("Fetched aggregated daily results: [count=" + results.length + "]"); currentDay = start_date; for (let i = 0; i < results.length; i++) { diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index 60c57090f..798497264 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -544,6 +544,7 @@ "tourConfigurationContent": "Geräteeinstellungen für diese Installation anzeigen und ändern.", "tourHistoryTitle": "Verlauf", "tourHistoryContent": "Protokoll der Aktionen an dieser Installation — wer hat was und wann geändert.", + "tourInstallationTicketsContent": "Support-Tickets für diese Installation anzeigen und verwalten — Probleme melden, Fortschritt verfolgen und KI-gestützte Diagnosen einsehen.", "tickets": "Tickets", "createTicket": "Ticket erstellen", "subject": "Betreff", diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index 5072864e0..884144b49 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -292,6 +292,7 @@ "tourConfigurationContent": "View and modify device settings for this installation.", "tourHistoryTitle": "History", "tourHistoryContent": "Audit trail of actions performed on this installation — who changed what and when.", + "tourInstallationTicketsContent": "View and manage support tickets for this installation — report issues, track progress, and see AI-powered diagnostics.", "tickets": "Tickets", "createTicket": "Create Ticket", "subject": "Subject", diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index 81df22eca..b045b028b 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -544,6 +544,7 @@ "tourConfigurationContent": "Afficher et modifier les paramètres de l'appareil pour cette installation.", "tourHistoryTitle": "Historique", "tourHistoryContent": "Journal des actions effectuées sur cette installation — qui a changé quoi et quand.", + "tourInstallationTicketsContent": "Consultez et gérez les tickets de support pour cette installation — signalez des problèmes, suivez la progression et consultez les diagnostics IA.", "tickets": "Tickets", "createTicket": "Créer un ticket", "subject": "Objet", diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index a778bdd0a..2cb285c98 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -544,6 +544,7 @@ "tourConfigurationContent": "Visualizza e modifica le impostazioni del dispositivo per questa installazione.", "tourHistoryTitle": "Cronologia", "tourHistoryContent": "Registro delle azioni eseguite su questa installazione — chi ha cambiato cosa e quando.", + "tourInstallationTicketsContent": "Visualizza e gestisci i ticket di supporto per questa installazione — segnala problemi, monitora i progressi e consulta le diagnosi IA.", "tickets": "Ticket", "createTicket": "Crea ticket", "subject": "Oggetto", diff --git a/typescript/frontend-marios2/src/layouts/SidebarLayout/index.tsx b/typescript/frontend-marios2/src/layouts/SidebarLayout/index.tsx index 5ed022bf7..07a3ecd92 100644 --- a/typescript/frontend-marios2/src/layouts/SidebarLayout/index.tsx +++ b/typescript/frontend-marios2/src/layouts/SidebarLayout/index.tsx @@ -1,17 +1,10 @@ -import { ReactNode, useContext, useEffect, useState } from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import { alpha, Box, lighten, useTheme } from '@mui/material'; import { Outlet, useLocation } from 'react-router-dom'; -import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride'; -import { useIntl, IntlShape } from 'react-intl'; +import Joyride, { CallBackProps, EVENTS, STATUS, Step } from 'react-joyride'; +import { useIntl } from 'react-intl'; import { useTour } from 'src/contexts/TourContext'; -import { UserContext } from 'src/contexts/userContext'; -import { UserType } from 'src/interfaces/UserTypes'; -import { - buildSodiohomeCustomerTourSteps, buildSodiohomePartnerTourSteps, buildSodiohomeAdminTourSteps, - buildSalimaxCustomerTourSteps, buildSalimaxPartnerTourSteps, buildSalimaxAdminTourSteps, - buildSodistoregridCustomerTourSteps, buildSodistoregridPartnerTourSteps, buildSodistoregridAdminTourSteps, - buildSalidomoCustomerTourSteps, buildSalidomoPartnerTourSteps, buildSalidomoAdminTourSteps -} from 'src/config/tourSteps'; +import { buildDynamicTourSteps } from 'src/config/tourSteps'; import Sidebar from './Sidebar'; import Header from './Header'; @@ -22,38 +15,11 @@ interface SidebarLayoutProps { onSelectLanguage: (item: string) => void; } -function getTourSteps(pathname: string, userType: UserType, intl: IntlShape, isInsideInstallation: boolean): Step[] { - const role = userType === UserType.admin ? 'admin' - : userType === UserType.partner ? 'partner' - : 'customer'; - - if (pathname.includes('/sodiohome_installations')) { - if (role === 'admin') return buildSodiohomeAdminTourSteps(intl, isInsideInstallation); - if (role === 'partner') return buildSodiohomePartnerTourSteps(intl, isInsideInstallation); - return buildSodiohomeCustomerTourSteps(intl, isInsideInstallation); - } - if (pathname.includes('/salidomo_installations')) { - if (role === 'admin') return buildSalidomoAdminTourSteps(intl, isInsideInstallation); - if (role === 'partner') return buildSalidomoPartnerTourSteps(intl, isInsideInstallation); - return buildSalidomoCustomerTourSteps(intl, isInsideInstallation); - } - if (pathname.includes('/sodistoregrid_installations')) { - if (role === 'admin') return buildSodistoregridAdminTourSteps(intl, isInsideInstallation); - if (role === 'partner') return buildSodistoregridPartnerTourSteps(intl, isInsideInstallation); - return buildSodistoregridCustomerTourSteps(intl, isInsideInstallation); - } - // Salimax (/installations/) and Sodistore Max (/sodistore_installations/) - if (role === 'admin') return buildSalimaxAdminTourSteps(intl, isInsideInstallation); - if (role === 'partner') return buildSalimaxPartnerTourSteps(intl, isInsideInstallation); - return buildSalimaxCustomerTourSteps(intl, isInsideInstallation); -} - const SidebarLayout = (props: SidebarLayoutProps) => { const theme = useTheme(); const intl = useIntl(); const { runTour, stopTour } = useTour(); const location = useLocation(); - const { currentUser } = useContext(UserContext); const [tourSteps, setTourSteps] = useState([]); const [tourReady, setTourReady] = useState(false); @@ -64,23 +30,22 @@ const SidebarLayout = (props: SidebarLayoutProps) => { } // Delay to let child components render their tour target elements const timer = setTimeout(() => { - const userType = currentUser?.userType ?? UserType.client; const isInsideInstallation = location.pathname.includes('/installation/'); - const steps = getTourSteps(location.pathname, userType, intl, isInsideInstallation); - const filtered = steps.filter((step) => { - if (typeof step.target === 'string') { - return document.querySelector(step.target) !== null; - } - return true; - }); - setTourSteps(filtered); + const steps = buildDynamicTourSteps(intl, isInsideInstallation); + setTourSteps(steps); setTourReady(true); - }, 300); + }, 500); return () => clearTimeout(timer); - }, [runTour, location.pathname, currentUser?.userType, intl]); + }, [runTour, location.pathname, intl]); const handleJoyrideCallback = (data: CallBackProps) => { - const { status } = data; + const { status, step, type } = data; + if (type === EVENTS.STEP_BEFORE && step?.target) { + const el = document.querySelector(step.target as string); + if (el) { + el.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); + } + } if (status === STATUS.FINISHED || status === STATUS.SKIPPED) { stopTour(); }