From 57ee8be5200f1dd573c8f6b341f70abc416e9248 Mon Sep 17 00:00:00 2001 From: Yinyin Liu Date: Mon, 9 Mar 2026 14:51:04 +0100 Subject: [PATCH] added Guide Tour button on Monitor to help the user navigate different tabs --- typescript/frontend-marios2/package-lock.json | 240 +++++++++++++++++- typescript/frontend-marios2/package.json | 1 + typescript/frontend-marios2/src/App.tsx | 41 +-- .../frontend-marios2/src/config/tourSteps.ts | 117 +++++++++ .../dashboards/Installations/index.tsx | 2 + .../SalidomoInstallations/index.tsx | 2 + .../SodiohomeInstallations/index.tsx | 2 + .../src/contexts/TourContext.tsx | 32 +++ typescript/frontend-marios2/src/lang/de.json | 30 ++- typescript/frontend-marios2/src/lang/en.json | 30 ++- typescript/frontend-marios2/src/lang/fr.json | 30 ++- typescript/frontend-marios2/src/lang/it.json | 30 ++- .../SidebarLayout/Header/Menu/index.tsx | 1 + .../layouts/SidebarLayout/Header/index.tsx | 11 + .../src/layouts/SidebarLayout/index.tsx | 92 ++++++- 15 files changed, 636 insertions(+), 25 deletions(-) create mode 100644 typescript/frontend-marios2/src/config/tourSteps.ts create mode 100644 typescript/frontend-marios2/src/contexts/TourContext.tsx diff --git a/typescript/frontend-marios2/package-lock.json b/typescript/frontend-marios2/package-lock.json index 8658486f9..4eaee6681 100644 --- a/typescript/frontend-marios2/package-lock.json +++ b/typescript/frontend-marios2/package-lock.json @@ -42,6 +42,7 @@ "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", "react-intl": "^6.4.4", + "react-joyride": "^2.9.3", "react-redux": "^8.1.3", "react-router": "6.3.0", "react-router-dom": "6.3.0", @@ -2876,6 +2877,11 @@ "tslib": "^2.4.0" } }, + "node_modules/@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", @@ -8195,6 +8201,12 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "node_modules/deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8205,7 +8217,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11465,6 +11476,11 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" }, + "node_modules/is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" + }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -15414,6 +15430,16 @@ "node": ">=4" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -17273,6 +17299,41 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "node_modules/react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "dependencies": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-floater/node_modules/@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" + }, + "node_modules/react-floater/node_modules/is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" + }, + "node_modules/react-floater/node_modules/tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + }, "node_modules/react-flow-renderer": { "version": "10.3.17", "resolved": "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.17.tgz", @@ -17392,6 +17453,15 @@ "react": ">=16.3.0" } }, + "node_modules/react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "peerDependencies": { + "@types/react": ">=0.0.0 <=99", + "react": ">=0.0.0 <=99" + } + }, "node_modules/react-intl": { "version": "6.6.8", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", @@ -17423,6 +17493,44 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-joyride": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz", + "integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "react": "15 - 18", + "react-dom": "15 - 18" + } + }, + "node_modules/react-joyride/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-joyride/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/react-redux": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", @@ -18263,6 +18371,16 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + }, + "node_modules/scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -19771,6 +19889,15 @@ "node": ">=8" } }, + "node_modules/tree-changes": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz", + "integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==", + "dependencies": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.1" + } + }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -23174,6 +23301,11 @@ "tslib": "^2.4.0" } }, + "@gilbarbara/deep-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz", + "integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==" + }, "@humanwhocodes/config-array": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", @@ -27055,6 +27187,11 @@ "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", "dev": true }, + "deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -27064,8 +27201,7 @@ "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "default-gateway": { "version": "6.0.3", @@ -29458,6 +29594,11 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" }, + "is-lite": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz", + "integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==" + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -32446,6 +32587,11 @@ } } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -33627,6 +33773,39 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "react-floater": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz", + "integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==", + "requires": { + "deepmerge": "^4.3.1", + "is-lite": "^0.8.2", + "popper.js": "^1.16.0", + "prop-types": "^15.8.1", + "tree-changes": "^0.9.1" + }, + "dependencies": { + "@gilbarbara/deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==" + }, + "is-lite": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz", + "integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==" + }, + "tree-changes": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz", + "integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==", + "requires": { + "@gilbarbara/deep-equal": "^0.1.1", + "is-lite": "^0.8.2" + } + } + } + }, "react-flow-renderer": { "version": "10.3.17", "resolved": "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.17.tgz", @@ -33725,6 +33904,12 @@ "react": ">=16.3.0" } }, + "react-innertext": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz", + "integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==", + "requires": {} + }, "react-intl": { "version": "6.6.8", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.8.tgz", @@ -33747,6 +33932,36 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-joyride": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz", + "integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "deep-diff": "^1.0.2", + "deepmerge": "^4.3.1", + "is-lite": "^1.2.1", + "react-floater": "^0.7.9", + "react-innertext": "^1.1.5", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.1.0", + "tree-changes": "^0.11.2", + "type-fest": "^4.27.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" + } + } + }, "react-redux": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", @@ -34334,6 +34549,16 @@ } } }, + "scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + }, + "scrollparent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz", + "integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -35520,6 +35745,15 @@ "punycode": "^2.1.1" } }, + "tree-changes": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz", + "integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==", + "requires": { + "@gilbarbara/deep-equal": "^0.3.1", + "is-lite": "^1.2.1" + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", diff --git a/typescript/frontend-marios2/package.json b/typescript/frontend-marios2/package.json index e5a5a5d2f..297b606cd 100644 --- a/typescript/frontend-marios2/package.json +++ b/typescript/frontend-marios2/package.json @@ -38,6 +38,7 @@ "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", "react-intl": "^6.4.4", + "react-joyride": "^2.9.3", "react-redux": "^8.1.3", "react-router": "6.3.0", "react-router-dom": "6.3.0", diff --git a/typescript/frontend-marios2/src/App.tsx b/typescript/frontend-marios2/src/App.tsx index 7ba5ece03..3ff590030 100644 --- a/typescript/frontend-marios2/src/App.tsx +++ b/typescript/frontend-marios2/src/App.tsx @@ -22,6 +22,7 @@ import AccessContextProvider from './contexts/AccessContextProvider'; import SalidomoInstallationTabs from './content/dashboards/SalidomoInstallations'; import SodioHomeInstallationTabs from './content/dashboards/SodiohomeInstallations'; import { ProductIdContext } from './contexts/ProductIdContextProvider'; +import { TourProvider } from './contexts/TourContext'; function App() { const context = useContext(UserContext); @@ -127,22 +128,28 @@ function App() { if (!token) { return ( - - - } - > - }> - } - > - } - > - + + + + } + > + }> + } + > + } + > + + ); } @@ -163,6 +170,7 @@ function App() { locale={language} defaultLocale="en" > + @@ -237,6 +245,7 @@ function App() { + ); diff --git a/typescript/frontend-marios2/src/config/tourSteps.ts b/typescript/frontend-marios2/src/config/tourSteps.ts new file mode 100644 index 000000000..f3684278b --- /dev/null +++ b/typescript/frontend-marios2/src/config/tourSteps.ts @@ -0,0 +1,117 @@ +import { Step } from 'react-joyride'; +import { IntlShape } from 'react-intl'; + +// --- Build a single step with i18n --- + +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' } +}; + +// 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[] { + const steps: Step[] = []; + 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)); + } + } + if (includeInstallationHint && !isInsideInstallation) { + steps.push(makeStep(intl, '#tour-tab-list', 'tourExploreTitle', 'tourExploreContent')); + } + 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/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index 7ffc7d20f..81121533a 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -412,6 +412,7 @@ function InstallationTabs(props: InstallationTabsProps) { ? routes[tab.value] : navigateToTabPath(location.pathname, routes[tab.value]) } + id={`tour-tab-${tab.value}`} /> ))} @@ -480,6 +481,7 @@ function InstallationTabs(props: InstallationTabsProps) { component={Link} label={tab.label} to={routes[tab.value]} + id={`tour-tab-${tab.value}`} /> ))} diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx index f4bd4b5f2..e84691bac 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx @@ -288,6 +288,7 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { ? routes[tab.value] : navigateToTabPath(location.pathname, routes[tab.value]) } + id={`tour-tab-${tab.value}`} /> ))} @@ -349,6 +350,7 @@ function SalidomoInstallationTabs(props: InstallationTabsProps) { component={Link} label={tab.label} to={routes[tab.value]} + id={`tour-tab-${tab.value}`} /> ))} diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx index 5b2e9f0ba..f772ca628 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/index.tsx @@ -416,6 +416,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { icon={tab.icon} component={Link} label={tab.label} + id={`tour-tab-${tab.value}`} to={ tab.value === 'list' || tab.value === 'tree' ? routes[tab.value] @@ -482,6 +483,7 @@ function SodioHomeInstallationTabs(props: SodioHomeInstallationTabsProps) { component={Link} label={tab.label} to={routes[tab.value]} + id={`tour-tab-${tab.value}`} /> ))} diff --git a/typescript/frontend-marios2/src/contexts/TourContext.tsx b/typescript/frontend-marios2/src/contexts/TourContext.tsx new file mode 100644 index 000000000..e43783589 --- /dev/null +++ b/typescript/frontend-marios2/src/contexts/TourContext.tsx @@ -0,0 +1,32 @@ +import { createContext, useContext, useState, useCallback, ReactNode } from 'react'; + +interface TourContextType { + runTour: boolean; + startTour: () => void; + stopTour: () => void; +} + +const TourContext = createContext({ + runTour: false, + startTour: () => {}, + stopTour: () => {} +}); + +export const useTour = () => useContext(TourContext); + +interface TourProviderProps { + children: ReactNode; +} + +export function TourProvider({ children }: TourProviderProps) { + const [runTour, setRunTour] = useState(false); + + const startTour = useCallback(() => setRunTour(true), []); + const stopTour = useCallback(() => setRunTour(false), []); + + return ( + + {children} + + ); +} diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json index a6a64d8f7..83c4c746a 100644 --- a/typescript/frontend-marios2/src/lang/de.json +++ b/typescript/frontend-marios2/src/lang/de.json @@ -469,5 +469,33 @@ "powerW": "Leistung (W)", "enterPowerValue": "Positiven oder negativen Leistungswert eingeben", "startDateTime": "Startdatum und -zeit (Startzeit < Stoppzeit)", - "stopDateTime": "Stoppdatum und -zeit (Startzeit < Stoppzeit)" + "stopDateTime": "Stoppdatum und -zeit (Startzeit < Stoppzeit)", + "tourLanguageTitle": "Sprache", + "tourLanguageContent": "Wählen Sie Ihre bevorzugte Sprache. Die Oberfläche unterstützt Englisch, Deutsch, Französisch und Italienisch.", + "tourExploreTitle": "Installation erkunden", + "tourExploreContent": "Klicken Sie auf eine Installation, um sie zu öffnen. Klicken Sie darin erneut auf die Tour-Schaltfläche für eine detaillierte Anleitung aller verfügbaren Tabs.", + "tourListTitle": "Installationsliste", + "tourListContent": "Suchen und durchsuchen Sie alle Ihre Installationen. Klicken Sie auf eine Installation, um deren Dashboard zu öffnen.", + "tourTreeTitle": "Ordneransicht", + "tourTreeContent": "Ihre Installationen nach Ordnern organisiert. Erweitern Sie Ordner, um Installationen nach Standort zu finden.", + "tourLiveTitle": "Live-Daten", + "tourLiveContent": "Echtzeitdaten Ihres Systems — Batteriestatus, Energiefluss und Systemstatus, kontinuierlich aktualisiert.", + "tourOverviewTitle": "Überblick", + "tourOverviewContent": "Visuelle Zusammenfassung mit Diagrammen — Produktion, Verbrauch und Batterieladung im Zeitverlauf. Verwenden Sie die Datumssteuerung, um einen bestimmten Tag oder Zeitraum anzuzeigen.", + "tourBatteryviewTitle": "Batterieansicht", + "tourBatteryviewContent": "Detaillierte Batterieüberwachung — Ladezustand (%), Energiefluss (kW), Spannung und Strom pro Batterieeinheit.", + "tourPvviewTitle": "PV-Ansicht", + "tourPvviewContent": "Solaranlagen-Überwachung — Produktionsdaten Ihrer Photovoltaikanlage.", + "tourLogTitle": "Protokoll", + "tourLogContent": "Geräte-Ereignisprotokolle — Systemereignisse, Warnungen und Fehler im Zeitverlauf.", + "tourInformationTitle": "Systeminformationen", + "tourInformationContent": "Installationsdetails — Standort, Geräteseriennummern und Firmware-Versionen. Nutzen Sie dies als Referenz bei Kontakt mit dem Support.", + "tourReportTitle": "Energieberichte", + "tourReportContent": "Energiedaten in kWh anzeigen. Wechseln Sie zwischen wöchentlichen (Montag–Sonntag), monatlichen und jährlichen Berichten, um zu sehen, wie viel Energie produziert, verbraucht oder gespeichert wurde.", + "tourManageTitle": "Zugriffsverwaltung", + "tourManageContent": "Verwalten Sie, welche Benutzer Zugriff auf diese Installation haben, und legen Sie deren Berechtigungen fest.", + "tourConfigurationTitle": "Konfiguration", + "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." } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json index 6067ffbe6..c9c909b62 100644 --- a/typescript/frontend-marios2/src/lang/en.json +++ b/typescript/frontend-marios2/src/lang/en.json @@ -217,5 +217,33 @@ "powerW": "Power (W)", "enterPowerValue": "Enter a positive or negative power value", "startDateTime": "Start Date and Time (Start Time < Stop Time)", - "stopDateTime": "Stop Date and Time (Start Time < Stop Time)" + "stopDateTime": "Stop Date and Time (Start Time < Stop Time)", + "tourLanguageTitle": "Language", + "tourLanguageContent": "Choose your preferred language. The interface supports English, German, French, and Italian.", + "tourExploreTitle": "Explore an Installation", + "tourExploreContent": "Click any installation to open it. Once inside, click the tour button again for a detailed guide of all available tabs.", + "tourListTitle": "Installation List", + "tourListContent": "Search and browse all your installations. Click any installation to open its dashboard.", + "tourTreeTitle": "Folder View", + "tourTreeContent": "Your installations organised in folders. Expand folders to find installations by site or location.", + "tourLiveTitle": "Live Data", + "tourLiveContent": "Real-time data from your system — battery state, power flow, and system status, updated continuously.", + "tourOverviewTitle": "Overview", + "tourOverviewContent": "Visual summary with charts — production, consumption, and battery charge over time. Use the date controls to view a specific day or custom range.", + "tourBatteryviewTitle": "Battery View", + "tourBatteryviewContent": "Detailed battery monitoring — state of charge (%), power flow (kW), voltage, and current per battery unit.", + "tourPvviewTitle": "PV View", + "tourPvviewContent": "Solar panel monitoring — see production data from your photovoltaic system.", + "tourLogTitle": "Log", + "tourLogContent": "Device event logs — view system events, warnings, and errors over time.", + "tourInformationTitle": "System Information", + "tourInformationContent": "Installation details — location, device serial numbers, and firmware versions. Use this as reference if you contact support.", + "tourReportTitle": "Energy Reports", + "tourReportContent": "View energy data in kWh. Switch between weekly (Monday–Sunday), monthly, and yearly reports to see how much energy was produced, consumed, or stored.", + "tourManageTitle": "Access Management", + "tourManageContent": "Manage which users have access to this installation and set their permissions.", + "tourConfigurationTitle": "Configuration", + "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." } diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json index c7ceb83e5..1fcba5814 100644 --- a/typescript/frontend-marios2/src/lang/fr.json +++ b/typescript/frontend-marios2/src/lang/fr.json @@ -469,5 +469,33 @@ "powerW": "Puissance (W)", "enterPowerValue": "Entrez une valeur de puissance positive ou négative", "startDateTime": "Date et heure de début (Début < Fin)", - "stopDateTime": "Date et heure de fin (Début < Fin)" + "stopDateTime": "Date et heure de fin (Début < Fin)", + "tourLanguageTitle": "Langue", + "tourLanguageContent": "Choisissez votre langue préférée. L'interface est disponible en anglais, allemand, français et italien.", + "tourExploreTitle": "Explorer une installation", + "tourExploreContent": "Cliquez sur une installation pour l'ouvrir. Une fois à l'intérieur, cliquez à nouveau sur le bouton de visite pour un guide détaillé de tous les onglets disponibles.", + "tourListTitle": "Liste des installations", + "tourListContent": "Recherchez et parcourez toutes vos installations. Cliquez sur une installation pour ouvrir son tableau de bord.", + "tourTreeTitle": "Vue par dossiers", + "tourTreeContent": "Vos installations organisées en dossiers. Développez les dossiers pour trouver les installations par site ou emplacement.", + "tourLiveTitle": "Données en direct", + "tourLiveContent": "Données en temps réel de votre système — état de la batterie, flux d'énergie et état du système, mis à jour en continu.", + "tourOverviewTitle": "Aperçu", + "tourOverviewContent": "Résumé visuel avec graphiques — production, consommation et charge de la batterie au fil du temps. Utilisez les contrôles de date pour afficher un jour spécifique ou une plage personnalisée.", + "tourBatteryviewTitle": "Vue batterie", + "tourBatteryviewContent": "Surveillance détaillée de la batterie — état de charge (%), flux d'énergie (kW), tension et courant par unité de batterie.", + "tourPvviewTitle": "Vue PV", + "tourPvviewContent": "Surveillance des panneaux solaires — consultez les données de production de votre système photovoltaïque.", + "tourLogTitle": "Journal", + "tourLogContent": "Journaux d'événements — événements système, avertissements et erreurs au fil du temps.", + "tourInformationTitle": "Informations système", + "tourInformationContent": "Détails de l'installation — emplacement, numéros de série des appareils et versions du firmware. Utilisez ceci comme référence si vous contactez le support.", + "tourReportTitle": "Rapports énergétiques", + "tourReportContent": "Afficher les données énergétiques en kWh. Basculez entre les rapports hebdomadaires (lundi–dimanche), mensuels et annuels pour voir combien d'énergie a été produite, consommée ou stockée.", + "tourManageTitle": "Gestion des accès", + "tourManageContent": "Gérez quels utilisateurs ont accès à cette installation et définissez leurs autorisations.", + "tourConfigurationTitle": "Configuration", + "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." } \ No newline at end of file diff --git a/typescript/frontend-marios2/src/lang/it.json b/typescript/frontend-marios2/src/lang/it.json index 46a0ee780..0ed40875f 100644 --- a/typescript/frontend-marios2/src/lang/it.json +++ b/typescript/frontend-marios2/src/lang/it.json @@ -469,5 +469,33 @@ "powerW": "Potenza (W)", "enterPowerValue": "Inserire un valore di potenza positivo o negativo", "startDateTime": "Data e ora di inizio (Inizio < Fine)", - "stopDateTime": "Data e ora di fine (Inizio < Fine)" + "stopDateTime": "Data e ora di fine (Inizio < Fine)", + "tourLanguageTitle": "Lingua", + "tourLanguageContent": "Scegli la tua lingua preferita. L'interfaccia supporta inglese, tedesco, francese e italiano.", + "tourExploreTitle": "Esplora un'installazione", + "tourExploreContent": "Clicca su un'installazione per aprirla. Una volta dentro, clicca nuovamente sul pulsante del tour per una guida dettagliata di tutte le schede disponibili.", + "tourListTitle": "Elenco installazioni", + "tourListContent": "Cerca e sfoglia tutte le tue installazioni. Clicca su un'installazione per aprire la sua dashboard.", + "tourTreeTitle": "Vista cartelle", + "tourTreeContent": "Le tue installazioni organizzate in cartelle. Espandi le cartelle per trovare le installazioni per sito o posizione.", + "tourLiveTitle": "Dati in tempo reale", + "tourLiveContent": "Dati in tempo reale dal tuo sistema — stato della batteria, flusso di energia e stato del sistema, aggiornati continuamente.", + "tourOverviewTitle": "Panoramica", + "tourOverviewContent": "Riepilogo visivo con grafici — produzione, consumo e carica della batteria nel tempo. Usa i controlli della data per visualizzare un giorno specifico o un intervallo personalizzato.", + "tourBatteryviewTitle": "Vista batteria", + "tourBatteryviewContent": "Monitoraggio dettagliato della batteria — stato di carica (%), flusso di energia (kW), tensione e corrente per unità di batteria.", + "tourPvviewTitle": "Vista PV", + "tourPvviewContent": "Monitoraggio dei pannelli solari — visualizza i dati di produzione del tuo impianto fotovoltaico.", + "tourLogTitle": "Registro", + "tourLogContent": "Registri degli eventi — eventi di sistema, avvisi ed errori nel tempo.", + "tourInformationTitle": "Informazioni di sistema", + "tourInformationContent": "Dettagli dell'installazione — posizione, numeri di serie dei dispositivi e versioni firmware. Usa come riferimento se contatti l'assistenza.", + "tourReportTitle": "Rapporti energetici", + "tourReportContent": "Visualizza i dati energetici in kWh. Passa tra rapporti settimanali (lunedì–domenica), mensili e annuali per vedere quanta energia è stata prodotta, consumata o immagazzinata.", + "tourManageTitle": "Gestione accessi", + "tourManageContent": "Gestisci quali utenti hanno accesso a questa installazione e imposta i loro permessi.", + "tourConfigurationTitle": "Configurazione", + "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." } diff --git a/typescript/frontend-marios2/src/layouts/SidebarLayout/Header/Menu/index.tsx b/typescript/frontend-marios2/src/layouts/SidebarLayout/Header/Menu/index.tsx index 32ceaf848..7d85b6c90 100644 --- a/typescript/frontend-marios2/src/layouts/SidebarLayout/Header/Menu/index.tsx +++ b/typescript/frontend-marios2/src/layouts/SidebarLayout/Header/Menu/index.tsx @@ -112,6 +112,7 @@ function HeaderMenu(props: HeaderButtonsProps) { sx={{ color: isMobile ? 'white' : '' }} + data-tour="language-selector" > ` @@ -44,6 +46,7 @@ interface HeaderProps { function Header(props: HeaderProps) { const { sidebarToggle, toggleSidebar } = useContext(SidebarContext); + const { startTour } = useTour(); const theme = useTheme(); const isMobile = window.innerWidth <= 1280; @@ -96,6 +99,14 @@ function Header(props: HeaderProps) { > + + + + + 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); + + useEffect(() => { + if (!runTour) { + setTourReady(false); + return; + } + // 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); + setTourReady(true); + }, 300); + return () => clearTimeout(timer); + }, [runTour, location.pathname, currentUser?.userType, intl]); + + const handleJoyrideCallback = (data: CallBackProps) => { + const { status } = data; + if (status === STATUS.FINISHED || status === STATUS.SKIPPED) { + stopTour(); + } + }; return ( <> +