many many
This commit is contained in:
parent
706e0674fb
commit
c94eb235a9
|
|
@ -20,8 +20,9 @@ export function formatPowerForGraph(value, magnitude): { value: number } {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = negative === false ? value : -value;
|
||||||
return {
|
return {
|
||||||
value: negative === false ? value : -value
|
value: Math.round(result * 100) / 100
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,85 @@
|
||||||
import { Step } from 'react-joyride';
|
import { Step } from 'react-joyride';
|
||||||
import { IntlShape } from 'react-intl';
|
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(
|
const tabContentKey: Record<string, string> = {
|
||||||
intl: IntlShape,
|
list: 'tourListContent',
|
||||||
target: string,
|
tree: 'tourTreeContent',
|
||||||
titleId: string,
|
live: 'tourLiveContent',
|
||||||
contentId: string,
|
overview: 'tourOverviewContent',
|
||||||
placement: Step['placement'] = 'bottom',
|
batteryview: 'tourBatteryviewContent',
|
||||||
disableBeacon = false
|
pvview: 'tourPvviewContent',
|
||||||
): Step {
|
log: 'tourLogContent',
|
||||||
return {
|
information: 'tourInformationContent',
|
||||||
target,
|
report: 'tourReportContent',
|
||||||
title: intl.formatMessage({ id: titleId }),
|
manage: 'tourManageContent',
|
||||||
content: intl.formatMessage({ id: contentId }),
|
configuration: 'tourConfigurationContent',
|
||||||
placement,
|
history: 'tourHistoryContent',
|
||||||
...(disableBeacon ? { disableBeacon: true } : {})
|
installationTickets: 'tourInstallationTicketsContent'
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Tab key → i18n key mapping ---
|
|
||||||
|
|
||||||
const tabConfig: Record<string, { titleId: string; contentId: string }> = {
|
|
||||||
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' }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Steps to skip inside a specific installation (already covered in the list-page tour)
|
/**
|
||||||
const listPageOnlyTabs = new Set(['list', 'tree']);
|
* Build tour steps dynamically from the DOM.
|
||||||
|
* Scans for all rendered `#tour-tab-*` elements and creates a step for each.
|
||||||
// --- Build tour steps from tab value list ---
|
* The step title is the tab's own rendered text (matching across languages).
|
||||||
|
*/
|
||||||
function buildTourSteps(intl: IntlShape, tabValues: string[], includeInstallationHint = false, isInsideInstallation = false): Step[] {
|
export function buildDynamicTourSteps(intl: IntlShape, isInsideInstallation: boolean): Step[] {
|
||||||
const steps: Step[] = [];
|
const steps: Step[] = [];
|
||||||
|
|
||||||
|
// Language selector step (only on list/tree pages, not inside an installation)
|
||||||
if (!isInsideInstallation) {
|
if (!isInsideInstallation) {
|
||||||
steps.push(makeStep(intl, '[data-tour="language-selector"]', 'tourLanguageTitle', 'tourLanguageContent', 'bottom', true));
|
const langEl = document.querySelector('[data-tour="language-selector"]');
|
||||||
}
|
if (langEl) {
|
||||||
for (const value of tabValues) {
|
steps.push({
|
||||||
if (isInsideInstallation && listPageOnlyTabs.has(value)) continue;
|
target: '[data-tour="language-selector"]',
|
||||||
const cfg = tabConfig[value];
|
title: intl.formatMessage({ id: 'tourLanguageTitle' }),
|
||||||
if (cfg) {
|
content: intl.formatMessage({ id: 'tourLanguageContent' }),
|
||||||
steps.push(makeStep(intl, `#tour-tab-${value}`, cfg.titleId, cfg.contentId, 'bottom', steps.length === 0));
|
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<HTMLElement>('[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;
|
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);
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,8 @@ function Installation(props: singleInstallationProps) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.log(`Timestamp: ${timestamp}`);
|
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]);
|
setValues(res[timestamp]);
|
||||||
await timeout(2000);
|
await timeout(2000);
|
||||||
|
|
@ -381,7 +382,7 @@ function Installation(props: singleInstallationProps) {
|
||||||
{loading &&
|
{loading &&
|
||||||
currentTab != 'information' &&
|
currentTab != 'information' &&
|
||||||
currentTab != 'history' &&
|
currentTab != 'history' &&
|
||||||
currentTab != 'manage' &&
|
// currentTab != 'manage' &&
|
||||||
currentTab != 'log' &&
|
currentTab != 'log' &&
|
||||||
currentTab != 'installationTickets' && (
|
currentTab != 'installationTickets' && (
|
||||||
<Container
|
<Container
|
||||||
|
|
@ -538,7 +539,7 @@ function Installation(props: singleInstallationProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{/* {currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
path={routes.manage}
|
path={routes.manage}
|
||||||
element={
|
element={
|
||||||
|
|
@ -550,7 +551,7 @@ function Installation(props: singleInstallationProps) {
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ function InstallationTabs(props: InstallationTabsProps) {
|
||||||
const tabList = [
|
const tabList = [
|
||||||
'live',
|
'live',
|
||||||
'overview',
|
'overview',
|
||||||
'manage',
|
// 'manage',
|
||||||
'batteryview',
|
'batteryview',
|
||||||
'log',
|
'log',
|
||||||
'information',
|
'information',
|
||||||
|
|
@ -125,15 +125,15 @@ function InstallationTabs(props: InstallationTabsProps) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
@ -259,15 +259,15 @@ function InstallationTabs(props: InstallationTabsProps) {
|
||||||
value: 'pvview',
|
value: 'pvview',
|
||||||
label: <FormattedMessage id="pvview" defaultMessage="Pv View" />
|
label: <FormattedMessage id="pvview" defaultMessage="Pv View" />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
|
|
||||||
|
|
@ -81,11 +81,12 @@ function UserAccess(props: UserAccessProps) {
|
||||||
|
|
||||||
const sortedInstallations = useMemo(() => {
|
const sortedInstallations = useMemo(() => {
|
||||||
const orderMap = new Map(PRODUCT_GROUP_ORDER.map((p, i) => [p, i]));
|
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 oa = orderMap.get(a.product) ?? 99;
|
||||||
const ob = orderMap.get(b.product) ?? 99;
|
const ob = orderMap.get(b.product) ?? 99;
|
||||||
return oa !== ob ? oa - ob : a.name.localeCompare(b.name);
|
return oa !== ob ? oa - ob : a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
return sorted;
|
||||||
}, [availableInstallations]);
|
}, [availableInstallations]);
|
||||||
|
|
||||||
// Direct grants for this user
|
// Direct grants for this user
|
||||||
|
|
@ -128,15 +129,14 @@ function UserAccess(props: UserAccessProps) {
|
||||||
|
|
||||||
const fetchAvailableInstallations = useCallback(async () => {
|
const fetchAvailableInstallations = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const [res0, res1, res2, res3, res4, res5] = await Promise.all([
|
const products = [0, 1, 2, 3, 4, 5];
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
const responses = await Promise.all(
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
products.map((p) => axiosConfig.get(`/GetAllInstallationsFromProduct?product=${p}`))
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
);
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`),
|
const all = responses.flatMap((res, idx) =>
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=4`),
|
res.data.map((inst: I_Installation) => ({ ...inst, product: products[idx] }))
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`)
|
);
|
||||||
]);
|
setAvailableInstallations(all);
|
||||||
setAvailableInstallations([...res0.data, ...res1.data, ...res2.data, ...res3.data, ...res4.data, ...res5.data]);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response && err.response.status === 401) removeToken();
|
if (err.response && err.response.status === 401) removeToken();
|
||||||
}
|
}
|
||||||
|
|
@ -293,6 +293,22 @@ function UserAccess(props: UserAccessProps) {
|
||||||
value={selectedInstallations}
|
value={selectedInstallations}
|
||||||
onChange={(_event, newValue) => setSelectedInstallations(newValue)}
|
onChange={(_event, newValue) => setSelectedInstallations(newValue)}
|
||||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
|
renderGroup={(params) => (
|
||||||
|
<li key={params.key}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 13,
|
||||||
|
padding: '4px 16px',
|
||||||
|
backgroundColor: theme.colors.alpha.black[5],
|
||||||
|
color: theme.colors.alpha.black[70]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{params.group}
|
||||||
|
</Typography>
|
||||||
|
<ul style={{ padding: 0 }}>{params.children}</ul>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export const getChartOptions = (
|
||||||
colors: ['#3498db', '#2ecc71', '#282828'],
|
colors: ['#3498db', '#2ecc71', '#282828'],
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
|
tickAmount: 8,
|
||||||
labels: {
|
labels: {
|
||||||
datetimeFormatter: {
|
datetimeFormatter: {
|
||||||
year: 'yyyy',
|
year: 'yyyy',
|
||||||
|
|
@ -51,6 +52,7 @@ export const getChartOptions = (
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
seriesName: 'Grid Power',
|
seriesName: 'Grid Power',
|
||||||
|
tickAmount: 6,
|
||||||
min:
|
min:
|
||||||
chartInfo.min >= 0
|
chartInfo.min >= 0
|
||||||
? 0
|
? 0
|
||||||
|
|
@ -88,6 +90,7 @@ export const getChartOptions = (
|
||||||
{
|
{
|
||||||
seriesName: 'Grid Power',
|
seriesName: 'Grid Power',
|
||||||
show: false,
|
show: false,
|
||||||
|
tickAmount: 6,
|
||||||
min:
|
min:
|
||||||
chartInfo.min >= 0
|
chartInfo.min >= 0
|
||||||
? 0
|
? 0
|
||||||
|
|
@ -104,15 +107,6 @@ export const getChartOptions = (
|
||||||
: chartInfo.max <= 0
|
: chartInfo.max <= 0
|
||||||
? 0
|
? 0
|
||||||
: undefined,
|
: undefined,
|
||||||
title: {
|
|
||||||
text: chartInfo.unit,
|
|
||||||
style: {
|
|
||||||
fontSize: '12px'
|
|
||||||
},
|
|
||||||
offsetY: -190,
|
|
||||||
offsetX: 25,
|
|
||||||
rotate: 0
|
|
||||||
},
|
|
||||||
labels: {
|
labels: {
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
return formatPowerForGraph(
|
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,
|
opposite: true,
|
||||||
|
tickAmount: 5,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
title: {
|
title: {
|
||||||
|
|
@ -140,12 +162,13 @@ export const getChartOptions = (
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
return formatPowerForGraph(value, 0).value.toString();
|
return Math.round(value).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: {
|
: {
|
||||||
|
tickAmount: chartInfo.unit === '(%)' ? 5 : 6,
|
||||||
min:
|
min:
|
||||||
chartInfo.min >= 0
|
chartInfo.min >= 0
|
||||||
? 0
|
? 0
|
||||||
|
|
@ -173,6 +196,9 @@ export const getChartOptions = (
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
|
if (chartInfo.unit === '(%)') {
|
||||||
|
return Math.round(value).toString();
|
||||||
|
}
|
||||||
return formatPowerForGraph(
|
return formatPowerForGraph(
|
||||||
value,
|
value,
|
||||||
chartInfo.magnitude
|
chartInfo.magnitude
|
||||||
|
|
@ -189,7 +215,7 @@ export const getChartOptions = (
|
||||||
y: {
|
y: {
|
||||||
formatter: function (val, { seriesIndex, w }) {
|
formatter: function (val, { seriesIndex, w }) {
|
||||||
const seriesName = w.config.series[seriesIndex].name;
|
const seriesName = w.config.series[seriesIndex].name;
|
||||||
if (seriesName === 'State Of Charge') {
|
if (seriesName === 'Battery SOC') {
|
||||||
return val.toFixed(2) + ' %';
|
return val.toFixed(2) + ' %';
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
|
@ -255,6 +281,7 @@ export const getChartOptions = (
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
|
tickAmount: 6,
|
||||||
min:
|
min:
|
||||||
chartInfo.min >= 0
|
chartInfo.min >= 0
|
||||||
? 0
|
? 0
|
||||||
|
|
|
||||||
|
|
@ -735,6 +735,11 @@ function Overview(props: OverviewProps) {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
color: '#ff9900'
|
color: '#ff9900'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
...dailyDataArray[chartState].chartData.ACLoad,
|
||||||
|
type: 'line',
|
||||||
|
color: '#2ecc71'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...dailyDataArray[chartState].chartData.soc,
|
...dailyDataArray[chartState].chartData.soc,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.log('NUMBER OF FILES=' + Object.keys(res).length);
|
console.log('NUMBER OF FILES=' + Object.keys(res).length);
|
||||||
|
|
||||||
console.log('res=', res);
|
console.log('res= [S3 credentials hidden]');
|
||||||
|
|
||||||
while (continueFetching.current) {
|
while (continueFetching.current) {
|
||||||
for (const timestamp of Object.keys(res)) {
|
for (const timestamp of Object.keys(res)) {
|
||||||
|
|
@ -114,7 +114,8 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.log(`Timestamp: ${timestamp}`);
|
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
|
// Set values asynchronously with delay
|
||||||
setValues(res[timestamp]);
|
setValues(res[timestamp]);
|
||||||
|
|
@ -323,7 +324,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
</div>
|
</div>
|
||||||
{loading &&
|
{loading &&
|
||||||
currentTab != 'information' &&
|
currentTab != 'information' &&
|
||||||
currentTab != 'manage' &&
|
// currentTab != 'manage' &&
|
||||||
currentTab != 'history' &&
|
currentTab != 'history' &&
|
||||||
currentTab != 'log' &&
|
currentTab != 'log' &&
|
||||||
currentTab != 'installationTickets' && (
|
currentTab != 'installationTickets' && (
|
||||||
|
|
@ -416,7 +417,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{/* {currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
path={routes.manage}
|
path={routes.manage}
|
||||||
element={
|
element={
|
||||||
|
|
@ -428,7 +429,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
||||||
const tabList = [
|
const tabList = [
|
||||||
'batteryview',
|
'batteryview',
|
||||||
'information',
|
'information',
|
||||||
'manage',
|
// 'manage',
|
||||||
'overview',
|
'overview',
|
||||||
'log',
|
'log',
|
||||||
'history',
|
'history',
|
||||||
|
|
@ -113,15 +113,15 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
|
|
@ -198,15 +198,15 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) {
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,8 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.log(`Timestamp: ${timestamp}`);
|
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]);
|
setValues(res[timestamp]);
|
||||||
await timeout(2000);
|
await timeout(2000);
|
||||||
|
|
@ -473,7 +474,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
</div>
|
</div>
|
||||||
{loading &&
|
{loading &&
|
||||||
currentTab != 'information' &&
|
currentTab != 'information' &&
|
||||||
currentTab != 'manage' &&
|
// currentTab != 'manage' &&
|
||||||
currentTab != 'history' &&
|
currentTab != 'history' &&
|
||||||
currentTab != 'log' &&
|
currentTab != 'log' &&
|
||||||
currentTab != 'report' &&
|
currentTab != 'report' &&
|
||||||
|
|
@ -584,7 +585,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentUser.userType == UserType.admin && (
|
{/* {currentUser.userType == UserType.admin && (
|
||||||
<Route
|
<Route
|
||||||
path={routes.manage}
|
path={routes.manage}
|
||||||
element={
|
element={
|
||||||
|
|
@ -596,7 +597,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
|
||||||
</AccessContextProvider>
|
</AccessContextProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={routes.overview}
|
path={routes.overview}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
'overview',
|
'overview',
|
||||||
'batteryview',
|
'batteryview',
|
||||||
'information',
|
'information',
|
||||||
'manage',
|
// 'manage',
|
||||||
'log',
|
'log',
|
||||||
'history',
|
'history',
|
||||||
'configuration',
|
'configuration',
|
||||||
|
|
@ -142,15 +142,15 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
|
|
@ -297,15 +297,15 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) {
|
||||||
value: 'log',
|
value: 'log',
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
value: 'manage',
|
// value: 'manage',
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id="manage"
|
// id="manage"
|
||||||
defaultMessage="Access Management"
|
// defaultMessage="Access Management"
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
value: 'information',
|
value: 'information',
|
||||||
label: (
|
label: (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
Autocomplete,
|
||||||
Box,
|
Box,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -10,6 +11,7 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Button from '@mui/material/Button';
|
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 { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const PRODUCT_GROUP_ORDER: number[] = [2, 5, 4, 3, 0, 1];
|
||||||
|
const PRODUCT_NAMES: Record<number, string> = {
|
||||||
|
0: 'Salimax',
|
||||||
|
1: 'Salidomo',
|
||||||
|
2: 'Sodistore Home',
|
||||||
|
3: 'Sodistore Max',
|
||||||
|
4: 'Sodistore Grid',
|
||||||
|
5: 'Sodistore Pro'
|
||||||
|
};
|
||||||
|
|
||||||
interface userFormProps {
|
interface userFormProps {
|
||||||
cancel: () => void;
|
cancel: () => void;
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
|
|
@ -32,7 +44,6 @@ function userForm(props: userFormProps) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [errormessage, setErrorMessage] = useState(intl.formatMessage({ id: 'errorOccured' }));
|
const [errormessage, setErrorMessage] = useState(intl.formatMessage({ id: 'errorOccured' }));
|
||||||
const [openInstallation, setOpenInstallation] = useState(false);
|
|
||||||
const [openFolder, setOpenFolder] = useState(false);
|
const [openFolder, setOpenFolder] = useState(false);
|
||||||
const [formValues, setFormValues] = useState<Partial<InnovEnergyUser>>({
|
const [formValues, setFormValues] = useState<Partial<InnovEnergyUser>>({
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -41,9 +52,7 @@ function userForm(props: userFormProps) {
|
||||||
});
|
});
|
||||||
const requiredFields = ['name', 'email'];
|
const requiredFields = ['name', 'email'];
|
||||||
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
const [selectedFolderNames, setSelectedFolderNames] = useState<string[]>([]);
|
||||||
const [selectedInstallationNames, setSelectedInstallationNames] = useState<
|
const [selectedInstallations, setSelectedInstallations] = useState<I_Installation[]>([]);
|
||||||
string[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
const UserTypes = ['Client', 'Partner', 'Admin'];
|
const UserTypes = ['Client', 'Partner', 'Admin'];
|
||||||
|
|
||||||
|
|
@ -72,23 +81,13 @@ function userForm(props: userFormProps) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [res0, res1, res2, res3, res4, res5] = await Promise.all([
|
const products = [0, 1, 2, 3, 4, 5];
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=0`),
|
const responses = await Promise.all(
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=1`),
|
products.map((p) => axiosConfig.get(`/GetAllInstallationsFromProduct?product=${p}`))
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=2`),
|
);
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=3`),
|
const combined = responses.flatMap((res, idx) =>
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=4`),
|
res.data.map((inst: I_Installation) => ({ ...inst, product: products[idx] }))
|
||||||
axiosConfig.get(`/GetAllInstallationsFromProduct?product=5`)
|
);
|
||||||
]);
|
|
||||||
|
|
||||||
const combined = [
|
|
||||||
...res0.data,
|
|
||||||
...res1.data,
|
|
||||||
...res2.data,
|
|
||||||
...res3.data,
|
|
||||||
...res4.data,
|
|
||||||
...res5.data
|
|
||||||
];
|
|
||||||
|
|
||||||
setInstallations(combined);
|
setInstallations(combined);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -100,6 +99,15 @@ function userForm(props: userFormProps) {
|
||||||
}
|
}
|
||||||
}, [setInstallations]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
fetchFolders();
|
fetchFolders();
|
||||||
fetchInstallations();
|
fetchInstallations();
|
||||||
|
|
@ -116,10 +124,6 @@ function userForm(props: userFormProps) {
|
||||||
setSelectedFolderNames(event.target.value);
|
setSelectedFolderNames(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInstallationChange = (event) => {
|
|
||||||
setSelectedInstallationNames(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isMobile = window.innerWidth <= 1490;
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
|
|
@ -153,11 +157,7 @@ function userForm(props: userFormProps) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const installationName of selectedInstallationNames) {
|
for (const installation of selectedInstallations) {
|
||||||
const installation = installations.find(
|
|
||||||
(installation) => installation.name === installationName
|
|
||||||
);
|
|
||||||
|
|
||||||
await axiosConfig
|
await axiosConfig
|
||||||
.post(
|
.post(
|
||||||
`/GrantUserAccessToInstallation?UserId=${res.data.id}&InstallationId=${installation.id}`
|
`/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 = () => {
|
const handleOpenFolder = () => {
|
||||||
setOpenFolder(true);
|
setOpenFolder(true);
|
||||||
};
|
};
|
||||||
|
|
@ -345,55 +337,43 @@ function userForm(props: userFormProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FormControl fullWidth sx={{ marginTop: 2, width: 390 }}>
|
<Autocomplete<I_Installation, true, false, false>
|
||||||
<InputLabel
|
|
||||||
sx={{
|
|
||||||
fontSize: 14,
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="grantAccessToInstallations"
|
|
||||||
defaultMessage="Grant access to installations"
|
|
||||||
/>
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
multiple
|
multiple
|
||||||
value={selectedInstallationNames}
|
options={sortedInstallations}
|
||||||
onChange={handleInstallationChange}
|
groupBy={(option) => PRODUCT_NAMES[option.product] || 'Unknown'}
|
||||||
open={openInstallation}
|
getOptionLabel={(option) => option.name}
|
||||||
onClose={handleCloseInstallation}
|
value={selectedInstallations}
|
||||||
onOpen={handleOpenInstallation}
|
onChange={(_event, newValue) => setSelectedInstallations(newValue)}
|
||||||
renderValue={(selected) => (
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
<div>
|
renderGroup={(params) => (
|
||||||
{selected.map((installation) => (
|
<li key={params.key}>
|
||||||
<span key={installation}>{installation}, </span>
|
<Typography
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{installations.map((installation) => (
|
|
||||||
<MenuItem key={installation.id} value={installation.name}>
|
|
||||||
{installation.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
sx={{
|
sx={{
|
||||||
marginLeft: '150px',
|
fontWeight: 'bold',
|
||||||
marginTop: '10px',
|
fontSize: 13,
|
||||||
backgroundColor: theme.colors.primary.main,
|
padding: '4px 16px',
|
||||||
color: 'white',
|
backgroundColor: theme.colors.alpha.black[5],
|
||||||
'&:hover': {
|
color: theme.colors.alpha.black[70]
|
||||||
backgroundColor: theme.colors.primary.dark
|
|
||||||
},
|
|
||||||
padding: '6px 8px'
|
|
||||||
}}
|
}}
|
||||||
onClick={handleCloseInstallation}
|
|
||||||
>
|
>
|
||||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
{params.group}
|
||||||
</Button>
|
</Typography>
|
||||||
</Select>
|
<ul style={{ padding: 0 }}>{params.children}</ul>
|
||||||
</FormControl>
|
</li>
|
||||||
|
)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label={intl.formatMessage({ id: 'grantAccessToInstallations' })}
|
||||||
|
placeholder={intl.formatMessage({ id: 'searchInstallations' })}
|
||||||
|
InputLabelProps={{
|
||||||
|
...params.InputLabelProps,
|
||||||
|
sx: { fontSize: 14, backgroundColor: 'white' }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
sx={{ mt: 1 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -452,11 +452,11 @@ export const transformInputToDailyDataJson = async (
|
||||||
];
|
];
|
||||||
|
|
||||||
const chartData: chartDataInterface = {
|
const chartData: chartDataInterface = {
|
||||||
soc: { name: 'State Of Charge', data: [] },
|
soc: { name: 'Battery SOC', data: [] },
|
||||||
temperature: { name: 'Battery Temperature', data: [] },
|
temperature: { name: 'Battery Temperature', data: [] },
|
||||||
dcPower: { name: 'Battery Power', data: [] },
|
dcPower: { name: 'Battery Power', data: [] },
|
||||||
gridPower: { name: 'Grid Power', data: [] },
|
gridPower: { name: 'Grid Power', data: [] },
|
||||||
pvProduction: { name: 'Pv Production', data: [] },
|
pvProduction: { name: 'PV Power', data: [] },
|
||||||
dcBusVoltage: { name: 'DC Bus Voltage', data: [] },
|
dcBusVoltage: { name: 'DC Bus Voltage', data: [] },
|
||||||
ACLoad: { name: 'AC Load', data: [] },
|
ACLoad: { name: 'AC Load', data: [] },
|
||||||
DCLoad: { name: 'DC Load', data: [] }
|
DCLoad: { name: 'DC Load', data: [] }
|
||||||
|
|
@ -530,7 +530,8 @@ export const transformInputToDailyDataJson = async (
|
||||||
Object.keys(results[i]).length - 1
|
Object.keys(results[i]).length - 1
|
||||||
];
|
];
|
||||||
const result = results[i][timestamp];
|
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;
|
let category_index = 0;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
|
|
@ -639,16 +640,19 @@ export const transformInputToDailyDataJson = async (
|
||||||
chartOverview.overview = {
|
chartOverview.overview = {
|
||||||
magnitude: Math.max(
|
magnitude: Math.max(
|
||||||
chartOverview['gridPower'].magnitude,
|
chartOverview['gridPower'].magnitude,
|
||||||
chartOverview['pvProduction'].magnitude
|
chartOverview['pvProduction'].magnitude,
|
||||||
|
chartOverview['ACLoad'].magnitude
|
||||||
),
|
),
|
||||||
unit: '(kW)',
|
unit: '(kW)',
|
||||||
min: Math.min(
|
min: Math.min(
|
||||||
chartOverview['gridPower'].min,
|
chartOverview['gridPower'].min,
|
||||||
chartOverview['pvProduction'].min
|
chartOverview['pvProduction'].min,
|
||||||
|
chartOverview['ACLoad'].min
|
||||||
),
|
),
|
||||||
max: Math.max(
|
max: Math.max(
|
||||||
chartOverview['gridPower'].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);
|
res = await fetchDataJson(timestampToFetch, s3Credentials);
|
||||||
|
|
||||||
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||||
//console.log('Successfully fetched ' + timestampToFetch);
|
console.log('Successfully fetched ' + timestampToFetch);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching data:', err);
|
console.error('Error fetching data:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.warn('Failed to fetch timestamp ' + startUnixTime.ticks);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -771,7 +776,7 @@ export const transformInputToAggregatedDataJson = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(timestampPromises);
|
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;
|
currentDay = start_date;
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,7 @@
|
||||||
"tourConfigurationContent": "Geräteeinstellungen für diese Installation anzeigen und ändern.",
|
"tourConfigurationContent": "Geräteeinstellungen für diese Installation anzeigen und ändern.",
|
||||||
"tourHistoryTitle": "Verlauf",
|
"tourHistoryTitle": "Verlauf",
|
||||||
"tourHistoryContent": "Protokoll der Aktionen an dieser Installation — wer hat was und wann geändert.",
|
"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",
|
"tickets": "Tickets",
|
||||||
"createTicket": "Ticket erstellen",
|
"createTicket": "Ticket erstellen",
|
||||||
"subject": "Betreff",
|
"subject": "Betreff",
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,7 @@
|
||||||
"tourConfigurationContent": "View and modify device settings for this installation.",
|
"tourConfigurationContent": "View and modify device settings for this installation.",
|
||||||
"tourHistoryTitle": "History",
|
"tourHistoryTitle": "History",
|
||||||
"tourHistoryContent": "Audit trail of actions performed on this installation — who changed what and when.",
|
"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",
|
"tickets": "Tickets",
|
||||||
"createTicket": "Create Ticket",
|
"createTicket": "Create Ticket",
|
||||||
"subject": "Subject",
|
"subject": "Subject",
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,7 @@
|
||||||
"tourConfigurationContent": "Afficher et modifier les paramètres de l'appareil pour cette installation.",
|
"tourConfigurationContent": "Afficher et modifier les paramètres de l'appareil pour cette installation.",
|
||||||
"tourHistoryTitle": "Historique",
|
"tourHistoryTitle": "Historique",
|
||||||
"tourHistoryContent": "Journal des actions effectuées sur cette installation — qui a changé quoi et quand.",
|
"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",
|
"tickets": "Tickets",
|
||||||
"createTicket": "Créer un ticket",
|
"createTicket": "Créer un ticket",
|
||||||
"subject": "Objet",
|
"subject": "Objet",
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,7 @@
|
||||||
"tourConfigurationContent": "Visualizza e modifica le impostazioni del dispositivo per questa installazione.",
|
"tourConfigurationContent": "Visualizza e modifica le impostazioni del dispositivo per questa installazione.",
|
||||||
"tourHistoryTitle": "Cronologia",
|
"tourHistoryTitle": "Cronologia",
|
||||||
"tourHistoryContent": "Registro delle azioni eseguite su questa installazione — chi ha cambiato cosa e quando.",
|
"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",
|
"tickets": "Ticket",
|
||||||
"createTicket": "Crea ticket",
|
"createTicket": "Crea ticket",
|
||||||
"subject": "Oggetto",
|
"subject": "Oggetto",
|
||||||
|
|
|
||||||
|
|
@ -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 { alpha, Box, lighten, useTheme } from '@mui/material';
|
||||||
import { Outlet, useLocation } from 'react-router-dom';
|
import { Outlet, useLocation } from 'react-router-dom';
|
||||||
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
|
import Joyride, { CallBackProps, EVENTS, STATUS, Step } from 'react-joyride';
|
||||||
import { useIntl, IntlShape } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useTour } from 'src/contexts/TourContext';
|
import { useTour } from 'src/contexts/TourContext';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { buildDynamicTourSteps } from 'src/config/tourSteps';
|
||||||
import { UserType } from 'src/interfaces/UserTypes';
|
|
||||||
import {
|
|
||||||
buildSodiohomeCustomerTourSteps, buildSodiohomePartnerTourSteps, buildSodiohomeAdminTourSteps,
|
|
||||||
buildSalimaxCustomerTourSteps, buildSalimaxPartnerTourSteps, buildSalimaxAdminTourSteps,
|
|
||||||
buildSodistoregridCustomerTourSteps, buildSodistoregridPartnerTourSteps, buildSodistoregridAdminTourSteps,
|
|
||||||
buildSalidomoCustomerTourSteps, buildSalidomoPartnerTourSteps, buildSalidomoAdminTourSteps
|
|
||||||
} from 'src/config/tourSteps';
|
|
||||||
|
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
|
@ -22,38 +15,11 @@ interface SidebarLayoutProps {
|
||||||
onSelectLanguage: (item: string) => void;
|
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 SidebarLayout = (props: SidebarLayoutProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { runTour, stopTour } = useTour();
|
const { runTour, stopTour } = useTour();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { currentUser } = useContext(UserContext);
|
|
||||||
const [tourSteps, setTourSteps] = useState<Step[]>([]);
|
const [tourSteps, setTourSteps] = useState<Step[]>([]);
|
||||||
const [tourReady, setTourReady] = useState(false);
|
const [tourReady, setTourReady] = useState(false);
|
||||||
|
|
||||||
|
|
@ -64,23 +30,22 @@ const SidebarLayout = (props: SidebarLayoutProps) => {
|
||||||
}
|
}
|
||||||
// Delay to let child components render their tour target elements
|
// Delay to let child components render their tour target elements
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
const userType = currentUser?.userType ?? UserType.client;
|
|
||||||
const isInsideInstallation = location.pathname.includes('/installation/');
|
const isInsideInstallation = location.pathname.includes('/installation/');
|
||||||
const steps = getTourSteps(location.pathname, userType, intl, isInsideInstallation);
|
const steps = buildDynamicTourSteps(intl, isInsideInstallation);
|
||||||
const filtered = steps.filter((step) => {
|
setTourSteps(steps);
|
||||||
if (typeof step.target === 'string') {
|
|
||||||
return document.querySelector(step.target) !== null;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
setTourSteps(filtered);
|
|
||||||
setTourReady(true);
|
setTourReady(true);
|
||||||
}, 300);
|
}, 500);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [runTour, location.pathname, currentUser?.userType, intl]);
|
}, [runTour, location.pathname, intl]);
|
||||||
|
|
||||||
const handleJoyrideCallback = (data: CallBackProps) => {
|
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) {
|
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
|
||||||
stopTour();
|
stopTour();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue