diff --git a/app/package-lock.json b/app/package-lock.json
index 8be748e..5d92f68 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -48,6 +48,7 @@
"zustand": "^5.0.0-rc.2"
},
"devDependencies": {
+ "@types/html2canvas": "^1.0.0",
"@types/node": "^22.9.1",
"@types/three": "^0.169.0",
"axios": "^1.8.4",
@@ -2020,7 +2021,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -2032,7 +2033,7 @@
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
@@ -4135,6 +4136,26 @@
"url": "https://github.com/sponsors/gregberge"
}
},
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@testing-library/jest-dom": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
@@ -4246,25 +4267,25 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
- "dev": true
+ "devOptional": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true
+ "devOptional": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true
+ "devOptional": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true
+ "devOptional": true
},
"node_modules/@turf/along": {
"version": "7.2.0",
@@ -6470,6 +6491,17 @@
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
"integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="
},
+ "node_modules/@types/html2canvas": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz",
+ "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==",
+ "deprecated": "This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "html2canvas": "*"
+ }
+ },
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
@@ -9007,7 +9039,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "dev": true
+ "devOptional": true
},
"node_modules/cross-env": {
"version": "7.0.3",
@@ -9884,7 +9916,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=0.3.1"
}
@@ -15247,7 +15279,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true
+ "devOptional": true
},
"node_modules/makeerror": {
"version": "1.0.12",
@@ -20715,7 +20747,7 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -20758,7 +20790,7 @@
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"acorn": "^8.11.0"
},
@@ -20770,7 +20802,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true
+ "devOptional": true
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
@@ -21266,7 +21298,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true
+ "devOptional": true
},
"node_modules/v8-to-istanbul": {
"version": "8.1.1",
@@ -22325,7 +22357,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
+ "devOptional": true,
"engines": {
"node": ">=6"
}
diff --git a/app/package.json b/app/package.json
index ce5c7d3..e555d5f 100644
--- a/app/package.json
+++ b/app/package.json
@@ -70,6 +70,7 @@
]
},
"devDependencies": {
+ "@types/html2canvas": "^1.0.0",
"@types/node": "^22.9.1",
"@types/three": "^0.169.0",
"axios": "^1.8.4",
diff --git a/app/src/components/layout/3D-cards/CardsScene.tsx b/app/src/components/layout/3D-cards/CardsScene.tsx
deleted file mode 100644
index a23cdd0..0000000
--- a/app/src/components/layout/3D-cards/CardsScene.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Canvas } from "@react-three/fiber";
-import Throughput from "./cards/Throughput";
-import ReturnOfInvestment from "./cards/ReturnOfInvestment";
-import ProductionCapacity from "./cards/ProductionCapacity";
-import { OrbitControls } from "@react-three/drei";
-import StateWorking from "./cards/StateWorking";
-
-const CardsScene = () => {
- return (
-
- {/*
*/}
-
- );
-};
-
-export default CardsScene;
diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx
index b1aad0b..e0b56d4 100644
--- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx
+++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx
@@ -6,7 +6,7 @@ import useToggleStore from "../../../store/useUIToggleStore";
import Assets from "./Assets";
import useModuleStore from "../../../store/useModuleStore";
import Widgets from "./visualization/widgets/Widgets";
-import Templates from "./visualization/Templates";
+import Templates from "../../../modules/visualization/template/Templates";
import Search from "../../ui/inputs/Search";
const SideBarLeft: React.FC = () => {
diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx
index d0873c5..7b532b8 100644
--- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx
+++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets2D.tsx
@@ -45,7 +45,10 @@ const ChartWidget: React.FC = ({ type, index, title }) => {
),
title,
panel: "top",
- data: sampleData,
+ Data: {
+ measurements:{},
+ duration:'1h'
+ },
});
}}
onDragEnd={() => setDraggedAsset(null)}
diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx
index 09c481a..b5ae0bb 100644
--- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx
+++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx
@@ -5,11 +5,11 @@ import {
GlobeIcon,
WalletIcon,
} from "../../../../icons/3dChartIcons";
-import SimpleCard from "../../../../ui/realTimeVis/floating/SimpleCard";
+import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
-import WarehouseThroughput from "../../../../ui/realTimeVis/floating/WarehouseThroughput";
-import ProductivityDashboard from "../../../../ui/realTimeVis/floating/ProductivityDashboard";
-import FleetEfficiency from "../../../../ui/realTimeVis/floating/FleetEfficiency";
+import WarehouseThroughput from "../../../../../modules/visualization/widgets/floating/cards/WarehouseThroughput";
+import ProductivityDashboard from "../../../../../modules/visualization/widgets/floating/cards/ProductivityDashboard";
+import FleetEfficiency from "../../../../../modules/visualization/widgets/floating/cards/FleetEfficiency";
interface Widget {
id: string;
@@ -33,7 +33,7 @@ const WidgetsFloating = () => {
};
return (
-
+
{/* {widgets.map((widget) => (
{
{" "}
diff --git a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
index 4ffea3b..568783c 100644
--- a/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/GlobalProperties.tsx
@@ -98,7 +98,7 @@ const GlobalProperties: React.FC = () => {
// setPlaneValue({ height: value * 100, width: value * 100 });
}
function updatedGrid(value: number) {
- console.log(" (value * 100) / 4 : ", (value * 100) / 4);
+ // console.log(" (value * 100) / 4 : ", (value * 100) / 4);
setGridValue({ size: value * 100, divisions: (value * 100) / 4 });
setPlaneValue({ height: value * 100, width: value * 100 });
}
diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx
index 6492252..c36b656 100644
--- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx
+++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx
@@ -32,7 +32,7 @@ const ZoneProperties: React.FC = () => {
if (response.message === "updated successfully") {
setEdit(false);
} else {
- console.log(response);
+ // console.log(response);
}
} catch (error) {
@@ -63,7 +63,7 @@ const ZoneProperties: React.FC = () => {
)
);
}else{
- console.log(response?.message);
+ // console.log(response?.message);
}
}
function handleVectorChange(key: "zoneViewPortTarget" | "zoneViewPortPosition", newValue: [number, number, number]) {
diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx
index 4b54c88..234b936 100644
--- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx
+++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx
@@ -3,7 +3,7 @@ import { useWidgetStore } from "../../../../../store/useWidgetStore";
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
import { WalletIcon } from "../../../../icons/3dChartIcons";
-import SimpleCard from "../../../../ui/realTimeVis/floating/SimpleCard";
+import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard";
interface Widget {
id: string;
diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx
index 5938aa3..0f125ad 100644
--- a/app/src/components/ui/Tools.tsx
+++ b/app/src/components/ui/Tools.tsx
@@ -15,7 +15,7 @@ import {
} from "../icons/ExportToolsIcons";
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
-import { handleSaveTemplate } from "../../modules/visualization/handleSaveTemplate";
+import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate";
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
import useTemplateStore from "../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../store/useZoneStore";
diff --git a/app/src/components/ui/componets/functions/convertAutoToNumeric.ts b/app/src/components/ui/componets/functions/convertAutoToNumeric.ts
deleted file mode 100644
index 3addac2..0000000
--- a/app/src/components/ui/componets/functions/convertAutoToNumeric.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { getActiveProperties } from "./getActiveProperties";
-
-export const convertAutoToNumeric = (
- canvasRect: DOMRect,
- position: {
- top: number | "auto";
- left: number | "auto";
- right: number | "auto";
- bottom: number | "auto";
- }
-): { top: number; left: number; right: number; bottom: number } => {
- const { width, height } = canvasRect;
-
- // Determine which properties are active
- const [activeProp1, activeProp2] = getActiveProperties(position);
-
- let top = typeof position.top !== "string" ? position.top : 0;
- let left = typeof position.left !== "string" ? position.left : 0;
- let right = typeof position.right !== "string" ? position.right : 0;
- let bottom = typeof position.bottom !== "string" ? position.bottom : 0;
-
- // Calculate missing properties based on active properties
- if (activeProp1 === "top") {
- bottom = height - top;
- } else if (activeProp1 === "bottom") {
- top = height - bottom;
- }
-
- if (activeProp2 === "left") {
- right = width - left;
- } else if (activeProp2 === "right") {
- left = width - right;
- }
-
- return {
- top,
- left,
- right,
- bottom,
- };
-};
diff --git a/app/src/components/ui/realTimeVis/charts/ProgressCard1.tsx b/app/src/components/ui/realTimeVis/charts/ProgressCard1.tsx
deleted file mode 100644
index 8fe8777..0000000
--- a/app/src/components/ui/realTimeVis/charts/ProgressCard1.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import { StockIncreseIcon } from "../../../icons/RealTimeVisulationIcons";
-import React, { useEffect, useMemo, useState } from "react";
-import { Line } from "react-chartjs-2";
-import io from "socket.io-client";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-const ProgressCard1 = ({ id,title,}: {
- id: string,
- title: string;
-}) => {
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState
({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState(title)
- const [value, setValue] = useState('')
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
-
-
- useEffect(() => {
- const socket = io(`http://${iotApiUrl}`);
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lastInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lastOutput", (response) => {
- const responseData = response.input1;
- setValue(responseData);
-
- });
-
- return () => {
- socket.off("lastOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return(
-
-
{name}
-
-
-
- {value}
- Units
-
-
- {
- measurements ? `${measurements?.input1?.fields}` : 'description'}
-
-
-
-
-
-
- )
-};
-
-export default ProgressCard1;
\ No newline at end of file
diff --git a/app/src/components/ui/realTimeVis/charts/ProgressCard2.tsx b/app/src/components/ui/realTimeVis/charts/ProgressCard2.tsx
deleted file mode 100644
index 083bbed..0000000
--- a/app/src/components/ui/realTimeVis/charts/ProgressCard2.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { StockIncreseIcon } from "../../../icons/RealTimeVisulationIcons";
-import React, { useEffect, useMemo, useState } from "react";
-import { Line } from "react-chartjs-2";
-import io from "socket.io-client";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-const ProgressCard2 = ({ id,title,}: {
- id: string,
- title: string;
-}) => {
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState(title)
- const [value1, setValue1] = useState('')
- const [value2, setValue2] = useState('')
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
-
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lastInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lastOutput", (response) => {
- const responseData1 = response.input1;
- const responseData2 = response.input2;
- setValue1(responseData1);
- setValue2(responseData2);
- });
-
- return () => {
- socket.off("lastOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return(
-
-
{name}
-
-
-
-
- {value1}
- Units
-
-
- {
- measurements ? `${measurements?.input1?.fields}` : 'description'}
-
-
-
-
-
-
-
-
-
- {value2}
- Units
-
-
- {
- measurements ? `${measurements?.input2?.fields}` : 'description'}
-
-
-
-
-
-
-
- )
-};
-
-export default ProgressCard2;
\ No newline at end of file
diff --git a/app/src/modules/builder/agv/agv.tsx b/app/src/modules/builder/agv/agv.tsx
index 5b8a2e4..c2e7783 100644
--- a/app/src/modules/builder/agv/agv.tsx
+++ b/app/src/modules/builder/agv/agv.tsx
@@ -1,111 +1,108 @@
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { Line } from "@react-three/drei";
import {
- useNavMesh,
- usePlayAgv,
- useSimulationStates,
+ useNavMesh,
+ usePlayAgv,
+ useSimulationStates,
} from "../../../store/store";
import PathNavigator from "./pathNavigator";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
+import { useAnimationPlaySpeed, usePlayButtonStore, useResetButtonStore } from "../../../store/usePlayButtonStore";
type PathPoints = {
- modelUuid: string;
- modelSpeed: number;
- bufferTime: number;
- points: { x: number; y: number; z: number }[];
- hitCount: number;
+ modelUuid: string;
+ modelSpeed: number;
+ bufferTime: number;
+ points: { x: number; y: number; z: number }[];
+ hitCount: number;
};
interface ProcessContainerProps {
- processes: any[];
- agvRef: any;
- MaterialRef: any;
+ processes: any[];
+ agvRef: any;
+ MaterialRef: any;
}
const Agv: React.FC = ({
- processes,
- agvRef,
- MaterialRef,
+ processes,
+ agvRef,
+ MaterialRef,
}) => {
- const [pathPoints, setPathPoints] = useState([]);
- const { simulationStates } = useSimulationStates();
- const { navMesh } = useNavMesh();
- const { isPlaying } = usePlayButtonStore();
- const { PlayAgv, setPlayAgv } = usePlayAgv();
+ const [pathPoints, setPathPoints] = useState([]);
+ const { simulationStates } = useSimulationStates();
+ const { navMesh } = useNavMesh();
+ const { isPlaying } = usePlayButtonStore();
+ const { isReset, setReset } = useResetButtonStore();
+ const { speed } = useAnimationPlaySpeed();
+ const globalSpeed = useRef(1);
+ useEffect(() => { globalSpeed.current = speed }, [speed])
+ useEffect(() => {
+ if (!isPlaying || isReset) {
+ agvRef.current = [];
+ }
+ }, [isPlaying, isReset])
- useEffect(() => {
- if (simulationStates.length > 0) {
- const agvModels = simulationStates.filter(
- (val) => val.modelName === "agv" && val.type === "Vehicle"
- );
+ useEffect(() => {
+ if (simulationStates.length > 0) {
+ const agvModels = simulationStates.filter(
+ (val) => val.modelName === "agv" && val.type === "Vehicle"
+ );
- const newPathPoints = agvModels
- .filter(
- (model: any) =>
- model.points &&
- model.points.actions &&
- typeof model.points.actions.start === "object" &&
- typeof model.points.actions.end === "object" &&
- "x" in model.points.actions.start &&
- "y" in model.points.actions.start &&
- "x" in model.points.actions.end &&
- "y" in model.points.actions.end
- )
- .map((model: any) => ({
- modelUuid: model.modeluuid,
- modelSpeed: model.points.speed,
- bufferTime: model.points.actions.buffer,
- hitCount: model.points.actions.hitCount,
- points: [
- {
- x: model.position[0],
- y: model.position[1],
- z: model.position[2],
- },
- {
- x: model.points.actions.start.x,
- y: 0,
- z: model.points.actions.start.y,
- },
- {
- x: model.points.actions.end.x,
- y: 0,
- z: model.points.actions.end.y,
- },
- ],
- }));
+ const newPathPoints = agvModels
+ .filter(
+ (model: any) =>
+ model.points &&
+ model.points.actions &&
+ typeof model.points.actions.start === "object" &&
+ typeof model.points.actions.end === "object" &&
+ "x" in model.points.actions.start &&
+ "y" in model.points.actions.start &&
+ "x" in model.points.actions.end &&
+ "y" in model.points.actions.end
+ )
+ .map((model: any) => ({
+ modelUuid: model.modeluuid,
+ modelSpeed: model.points.speed,
+ bufferTime: model.points.actions.buffer,
+ hitCount: model.points.actions.hitCount,
+ points: [
+ { x: model.position[0], y: model.position[1], z: model.position[2], },
+ { x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y, },
+ { x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y, },
+ ],
+ }));
- setPathPoints(newPathPoints);
- }
- }, [simulationStates]);
+ setPathPoints(newPathPoints);
+ }
+ }, [simulationStates]);
- return (
- <>
- {pathPoints.map((pair, i) => (
-
-
+ return (
+ <>
+ {pathPoints.map((pair, i) => (
+
+
- {pair.points.slice(1).map((point, idx) => (
-
-
-
-
- ))}
-
- ))}
- >
- );
+ {pair.points.slice(1).map((point, idx) => (
+
+
+
+
+ ))}
+
+ ))}
+ >
+ );
};
export default Agv;
diff --git a/app/src/modules/builder/agv/pathNavigator.tsx b/app/src/modules/builder/agv/pathNavigator.tsx
index d68da57..9a6fae7 100644
--- a/app/src/modules/builder/agv/pathNavigator.tsx
+++ b/app/src/modules/builder/agv/pathNavigator.tsx
@@ -3,15 +3,15 @@ import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { NavMeshQuery } from "@recast-navigation/core";
import { Line } from "@react-three/drei";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
+import { useAnimationPlaySpeed, usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store";
-import crate from "../../../assets/gltf-glb/crate_box.glb";
interface PathNavigatorProps {
navMesh: any;
pathPoints: any;
id: string;
speed: number;
+ globalSpeed: number,
bufferTime: number;
hitCount: number;
processes: any[];
@@ -31,6 +31,7 @@ export default function PathNavigator({
pathPoints,
id,
speed,
+ globalSpeed,
bufferTime,
hitCount,
processes,
@@ -38,30 +39,12 @@ export default function PathNavigator({
MaterialRef,
}: PathNavigatorProps) {
const [currentPhase, setCurrentPhase] = useState("initial");
- //
-
const [path, setPath] = useState<[number, number, number][]>([]);
- const PickUpDrop = useRef([]);
-
- // const [currentPhase, setCurrentPhase] = useState<"initial" | "loop">(
- // "initial"
- // );
- const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>(
- []
- );
-
- const [pickupDropPath, setPickupDropPath] = useState<
- [number, number, number][]
- >([]);
- const [dropPickupPath, setDropPickupPath] = useState<
- [number, number, number][]
- >([]);
- const [initialPosition, setInitialPosition] = useState(
- null
- );
- const [initialRotation, setInitialRotation] = useState(
- null
- );
+ const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]);
+ const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]);
+ const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]);
+ const [initialPosition, setInitialPosition] = useState(null);
+ const [initialRotation, setInitialRotation] = useState(null);
const [boxVisible, setBoxVisible] = useState(false);
const distancesRef = useRef([]);
@@ -70,11 +53,20 @@ export default function PathNavigator({
const isWaiting = useRef(false);
const timeoutRef = useRef(null);
const hasStarted = useRef(false);
+ const hasReachedPickup = useRef(false);
const { scene } = useThree();
const { isPlaying } = usePlayButtonStore();
const { PlayAgv, setPlayAgv } = usePlayAgv();
+ const boxRef = useRef(null);
+
+ const baseMaterials = useMemo(() => ({
+ Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
+ Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
+ Default: new THREE.MeshStandardMaterial({ color: 0xcccccc })
+ }), []);
+
useEffect(() => {
const object = scene.getObjectByProperty("uuid", id);
if (object) {
@@ -105,12 +97,16 @@ export default function PathNavigator({
setPath([]);
setCurrentPhase("initial");
+ setToPickupPath([]);
setPickupDropPath([]);
setDropPickupPath([]);
+ setBoxVisible(false);
distancesRef.current = [];
totalDistanceRef.current = 0;
progressRef.current = 0;
isWaiting.current = false;
+ hasStarted.current = false;
+ hasReachedPickup.current = false;
if (initialPosition && initialRotation) {
const object = scene.getObjectByProperty("uuid", id);
@@ -130,27 +126,16 @@ export default function PathNavigator({
const [pickup, drop] = pathPoints.slice(-2);
- PickUpDrop.current = pathPoints.slice(-2);
-
const object = scene.getObjectByProperty("uuid", id);
-
if (!object) return;
- const currentPosition = {
- x: object.position.x,
- y: object.position.y,
- z: object.position.z,
- };
+ const currentPosition = object.position;
const toPickupPath = computePath(currentPosition, pickup);
const pickupToDropPath = computePath(pickup, drop);
const dropToPickupPath = computePath(drop, pickup);
- if (
- toPickupPath.length &&
- pickupToDropPath.length &&
- dropToPickupPath.length
- ) {
+ if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) {
setPickupDropPath(pickupToDropPath);
setDropPickupPath(dropToPickupPath);
setToPickupPath(toPickupPath);
@@ -177,25 +162,9 @@ export default function PathNavigator({
isWaiting.current = false;
}, [path]);
- // Add these refs outside the useFrame if not already present:
- const startPointReached = useRef(false);
-
- const baseMaterials = useMemo(
- () => ({
- Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
- Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
- // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
- Default: new THREE.MeshStandardMaterial(),
- }),
- []
- );
-
- // Example usage:
- const targetModelUUID = id; // Replace with your target ID
- const matchedProcess = findProcessByTargetModelUUID(
- processes,
- targetModelUUID
- );
+ function logAgvStatus(id: string, status: string) {
+ // console.log(`AGV ${id}: ${status}`);
+ }
function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) {
for (const process of processes) {
@@ -206,75 +175,55 @@ export default function PathNavigator({
(target: any) => target.modelUUID === targetModelUUID
)
) {
- //
- return process.id; // Return the process if a match is found
+ return process.id;
}
}
}
}
- return null; // Return null if no match is found
+ return null;
}
- useFrame((_, delta) => {});
- const boxRef = useRef(null);
-
useEffect(() => {
- if (!scene || !boxRef || !processes) return;
+ if (!scene || !boxRef || !processes || !MaterialRef.current) return;
- // 1. Find the existing object by UUID
const existingObject = scene.getObjectByProperty("uuid", id);
if (!existingObject) return;
- // 2. Find the process that targets this object's modelUUID
+ if (boxRef.current?.parent) {
+ boxRef.current.parent.remove(boxRef.current);
+ boxRef.current = null;
+ }
+
if (boxVisible) {
const matchedProcess = findProcessByTargetModelUUID(processes, id);
-
- // 3. Determine the material (from materialref) if a process is matched
- let materialType = "Default";
+ let materialType: "Box" | "Crate" | "Default" = "Default";
if (matchedProcess) {
const materialEntry = MaterialRef.current.find((item: any) =>
item.objects.some((obj: any) => obj.processId === matchedProcess)
);
- console.log("materialEntry: ", materialEntry);
if (materialEntry) {
- materialType = materialEntry.material; // "Box" or "Crate"
+ materialType = materialEntry.material;
}
}
- // 4. Create the box mesh with the assigned material
const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
- const boxMaterial = baseMaterials[materialType as MaterialType];
- const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
+ const boxMesh = new THREE.Mesh(boxGeometry, baseMaterials[materialType]);
boxMesh.position.y = 1;
boxMesh.name = `box-${id}`;
-
- // 5. Add to scene and cleanup
existingObject.add(boxMesh);
boxRef.current = boxMesh;
}
- if (!startPointReached.current && boxVisible) {
- setBoxVisible(false);
- }
return () => {
if (boxRef.current?.parent) {
boxRef.current.parent.remove(boxRef.current);
}
- boxRef.current = null;
};
- }, [
- processes,
- MaterialRef,
- boxVisible,
- findProcessByTargetModelUUID,
- startPointReached,
- ]);
+ }, [processes, MaterialRef, boxVisible, scene, id, baseMaterials]);
useFrame((_, delta) => {
- const currentAgv = (agvRef.current || []).find(
- (agv: AGVData) => agv.vehicleId === id
- );
+ const currentAgv = (agvRef.current || []).find((agv: AGVData) => agv.vehicleId === id);
if (!scene || !id || !isPlaying) return;
@@ -283,7 +232,6 @@ export default function PathNavigator({
if (isPlaying && !hasStarted.current) {
hasStarted.current = false;
- startPointReached.current = false;
progressRef.current = 0;
isWaiting.current = false;
if (timeoutRef.current) {
@@ -295,10 +243,9 @@ export default function PathNavigator({
const isAgvReady = () => {
if (!agvRef.current || agvRef.current.length === 0) return false;
if (!currentAgv) return false;
- return hitCount === currentAgv.expectedHitCount;
+ return currentAgv.isActive && hitCount >= currentAgv.maxHitCount;
};
- // Step 1: Snap to start point on first play
if (isPlaying && !hasStarted.current && toPickupPath.length > 0) {
setBoxVisible(false);
const startPoint = new THREE.Vector3(...toPickupPath[0]);
@@ -311,146 +258,132 @@ export default function PathNavigator({
}
hasStarted.current = true;
- startPointReached.current = true;
progressRef.current = 0;
-
+ hasReachedPickup.current = false;
setToPickupPath(toPickupPath.slice(-1));
-
+ logAgvStatus(id, "Started from station, heading to pickup");
return;
}
- // Step 2: Wait at start point for AGV readiness
- if (isPlaying && startPointReached.current && path.length === 0) {
- if (!isAgvReady()) {
- return; // Prevent transitioning to the next phase if AGV is not ready
+ if (isPlaying && currentPhase === "initial" && !hasReachedPickup.current) {
+ const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef);
+
+ if (reached) {
+ hasReachedPickup.current = true;
+ if (currentAgv) {
+ currentAgv.status = 'picking';
+ }
+ logAgvStatus(id, "Reached pickup point, Waiting for material");
}
-
- setBoxVisible(true);
-
- setPath([...toPickupPath]); // Start path transition once the AGV is ready
- setCurrentPhase("toDrop");
- progressRef.current = 0;
- console.log("startPointReached: ", startPointReached.current);
- startPointReached.current = false;
-
return;
}
- if (path.length < 2) return;
+ if (isPlaying && currentAgv?.isActive && currentPhase === "initial") {
+ if (!isAgvReady()) return;
+ setTimeout(() => {
+ setBoxVisible(true);
+ setPath([...pickupDropPath]);
+ setCurrentPhase("toDrop");
+ progressRef.current = 0;
+ logAgvStatus(id, "Started from pickup point, heading to drop point");
+ if (currentAgv) {
+ currentAgv.status = 'toDrop';
+ }
+ }, 0)
+ return;
+ }
- progressRef.current += delta * speed;
+ if (isPlaying && currentPhase === "toDrop") {
+ const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef);
+ if (reached && !isWaiting.current) {
+ isWaiting.current = true;
+ logAgvStatus(id, "Reached drop point");
+ if (currentAgv) {
+ currentAgv.status = 'droping';
+ }
+ timeoutRef.current = setTimeout(() => {
+ setPath([...dropPickupPath]);
+ setCurrentPhase("toPickup");
+ progressRef.current = 0;
+ isWaiting.current = false;
+ setBoxVisible(false);
+ if (currentAgv) {
+ currentAgv.status = 'toPickup';
+ }
+ logAgvStatus(id, "Started from droping point, heading to pickup point");
+ }, bufferTime * 1000);
+ }
+ return;
+ }
+
+ if (isPlaying && currentPhase === "toPickup") {
+ const reached = moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef);
+
+ if (reached) {
+ if (currentAgv) {
+ currentAgv.isActive = false;
+ }
+ setCurrentPhase("initial");
+ if (currentAgv) {
+ currentAgv.status = 'picking';
+ }
+ logAgvStatus(id, "Reached pickup point again, cycle complete");
+ }
+ return;
+ }
+
+ moveAlongPath(object, path, distancesRef.current, speed, delta, progressRef);
+ });
+
+ function moveAlongPath(
+ object: THREE.Object3D,
+ path: [number, number, number][],
+ distances: number[],
+ speed: number,
+ delta: number,
+ progressRef: React.MutableRefObject
+ ): boolean {
+ if (path.length < 2) return false;
+
+ progressRef.current += delta * (speed * globalSpeed);
let covered = progressRef.current;
let accumulated = 0;
let index = 0;
- if (distancesRef.current.length !== path.length - 1) {
- distancesRef.current = [];
- totalDistanceRef.current = 0;
+ for (; index < distances.length; index++) {
+ const dist = distances[index];
+ if (accumulated + dist >= covered) break;
+ accumulated += dist;
+ }
- for (let i = 0; i < path.length - 1; i++) {
- const start = new THREE.Vector3(...path[i]);
- const end = new THREE.Vector3(...path[i + 1]);
- const distance = start.distanceTo(end);
- distancesRef.current.push(distance);
- totalDistanceRef.current += distance;
+ if (index >= path.length - 1) {
+ if (path.length > 1) {
+ const lastDirection = new THREE.Vector3(...path[path.length - 1])
+ .sub(new THREE.Vector3(...path[path.length - 2]))
+ .normalize();
+ object.rotation.y = Math.atan2(lastDirection.x, lastDirection.z);
}
+ return true;
}
- while (
- index < distancesRef.current.length &&
- covered > accumulated + distancesRef.current[index]
- ) {
- accumulated += distancesRef.current[index];
- index++;
- }
-
- // AGV has completed its path
- if (index >= distancesRef.current.length) {
- progressRef.current = totalDistanceRef.current;
-
- timeoutRef.current = setTimeout(() => {
- if (!isAgvReady()) {
- isWaiting.current = false;
- return;
- }
-
- let nextPath = [];
- let nextPhase = currentPhase;
-
- if (currentPhase === "toDrop") {
- nextPath = dropPickupPath;
- nextPhase = "toPickup";
- setBoxVisible(false);
- } else if (currentPhase === "toPickup") {
- // When returning to start point (toPickup phase completed)
- // Set position to toPickupPath[1] instead of [0]
- if (toPickupPath.length > 1) {
- object.position.copy(new THREE.Vector3(...toPickupPath[1]));
- // Also set rotation towards the next point if available
- if (toPickupPath.length > 2) {
- const nextPoint = new THREE.Vector3(...toPickupPath[2]);
- const direction = nextPoint
- .clone()
- .sub(object.position)
- .normalize();
- object.rotation.y = Math.atan2(direction.x, direction.z);
- }
- }
-
- // Reset the path and mark start point as reached
- setPath([]);
- startPointReached.current = true;
- progressRef.current = 0;
- distancesRef.current = [];
-
- // Stop the AGV by setting isplaying to false
- if (currentAgv) {
- currentAgv.isplaying = false;
- }
-
- // Clear timeout and return to prevent further movement
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- timeoutRef.current = null;
- }
- return;
- } else {
- nextPath = pickupDropPath;
- nextPhase = "toDrop";
- setBoxVisible(true);
- }
-
- setPath([...nextPath]);
- setCurrentPhase(nextPhase);
- progressRef.current = 0;
- isWaiting.current = false;
- distancesRef.current = [];
-
- // Reset hit count for the next cycle
- if (agvRef.current) {
- agvRef.current = agvRef.current.map((agv: AGVData) =>
- agv.vehicleId === id ? { ...agv, hitCount: 0 } : agv
- );
- }
- }, bufferTime * 1000);
-
- return;
- }
-
- // Step 4: Interpolate position and rotation
const start = new THREE.Vector3(...path[index]);
const end = new THREE.Vector3(...path[index + 1]);
- const dist = distancesRef.current[index];
-
- if (!dist || dist === 0) return;
+ const dist = distances[index];
const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1);
object.position.copy(start.clone().lerp(end, t));
- const direction = new THREE.Vector3().subVectors(end, start).normalize();
- object.rotation.y = Math.atan2(direction.x, direction.z);
- });
+ if (dist > 0.1) {
+ const targetDirection = end.clone().sub(start).normalize();
+ const targetRotationY = Math.atan2(targetDirection.x, targetDirection.z);
+
+ const rotationSpeed = Math.min(5 * delta, 1);
+ object.rotation.y = THREE.MathUtils.lerp(object.rotation.y, targetRotationY, rotationSpeed);
+ }
+
+ return false;
+ }
useEffect(() => {
return () => {
diff --git a/app/src/modules/collaboration/collabCams.tsx b/app/src/modules/collaboration/collabCams.tsx
index 95a9833..38ea548 100644
--- a/app/src/modules/collaboration/collabCams.tsx
+++ b/app/src/modules/collaboration/collabCams.tsx
@@ -10,190 +10,227 @@ import { useNavigate } from "react-router-dom";
import { Html } from "@react-three/drei";
import CollabUserIcon from "./collabUserIcon";
import { getAvatarColor } from "./users/functions/getAvatarColor";
+import useModuleStore from "../../store/useModuleStore";
const CamModelsGroup = () => {
- const navigate = useNavigate();
- const groupRef = useRef(null);
- const email = localStorage.getItem("email");
- const { activeUsers, setActiveUsers } = useActiveUsers();
- const { socket } = useSocketStore();
+ const navigate = useNavigate();
+ const groupRef = useRef(null);
+ const email = localStorage.getItem("email");
+ const { setActiveUsers } = useActiveUsers();
+ const { socket } = useSocketStore();
+ const { activeModule } = useModuleStore();
- const loader = new GLTFLoader();
- const dracoLoader = new DRACOLoader();
- dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
- loader.setDRACOLoader(dracoLoader);
+ const loader = new GLTFLoader();
+ const dracoLoader = new DRACOLoader();
+ dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
+ loader.setDRACOLoader(dracoLoader);
- const [cams, setCams] = useState([]);
- const [models, setModels] = useState>({});
+ const [cams, setCams] = useState([]);
+ const [models, setModels] = useState<
+ Record<
+ string,
+ { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }
+ >
+ >({});
- const dedupeCams = (cams: any[]) => {
- const seen = new Set();
- return cams.filter((cam) => {
- if (seen.has(cam.uuid)) return false;
- seen.add(cam.uuid);
- return true;
- });
- };
+ const dedupeCams = (cams: any[]) => {
+ const seen = new Set();
+ return cams.filter((cam) => {
+ if (seen.has(cam.uuid)) return false;
+ seen.add(cam.uuid);
+ return true;
+ });
+ };
- const dedupeUsers = (users: any[]) => {
- const seen = new Set();
- return users.filter((user) => {
- if (seen.has(user._id)) return false;
- seen.add(user._id);
- return true;
- });
- };
+ const dedupeUsers = (users: any[]) => {
+ const seen = new Set();
+ return users.filter((user) => {
+ if (seen.has(user._id)) return false;
+ seen.add(user._id);
+ return true;
+ });
+ };
- useEffect(() => {
- if (!email) navigate("/");
+ useEffect(() => {
+ if (!email) navigate("/");
- if (!socket) return;
- const organization = email!.split("@")[1].split(".")[0];
+ if (!socket) return;
+ const organization = email!.split("@")[1].split(".")[0];
- socket.on("userConnectResponse", (data: any) => {
- if (!groupRef.current) return;
- if (data.data.userData.email === email) return;
- if (socket.id === data.socketId || organization !== data.organization) return;
+ socket.on("userConnectResponse", (data: any) => {
+ if (!groupRef.current) return;
+ if (data.data.userData.email === email) return;
+ if (socket.id === data.socketId || organization !== data.organization)
+ return;
- const model = groupRef.current.getObjectByProperty("uuid", data.data.userData._id);
- if (model) {
- groupRef.current.remove(model);
- }
+ const model = groupRef.current.getObjectByProperty(
+ "uuid",
+ data.data.userData._id
+ );
+ if (model) {
+ groupRef.current.remove(model);
+ }
- loader.load(camModel, (gltf) => {
- const newModel = gltf.scene.clone();
- newModel.uuid = data.data.userData._id;
- newModel.position.set(
- data.data.position.x,
- data.data.position.y,
- data.data.position.z
- );
- newModel.rotation.set(
- data.data.rotation.x,
- data.data.rotation.y,
- data.data.rotation.z
- );
- newModel.userData = data.data.userData;
+ loader.load(camModel, (gltf) => {
+ const newModel = gltf.scene.clone();
+ newModel.uuid = data.data.userData._id;
+ newModel.position.set(
+ data.data.position.x,
+ data.data.position.y,
+ data.data.position.z
+ );
+ newModel.rotation.set(
+ data.data.rotation.x,
+ data.data.rotation.y,
+ data.data.rotation.z
+ );
+ newModel.userData = data.data.userData;
- setCams((prev) => dedupeCams([...prev, newModel]));
- setActiveUsers((prev: any) =>
- dedupeUsers([...prev, data.data.userData])
- );
- });
- });
+ setCams((prev) => dedupeCams([...prev, newModel]));
+ setActiveUsers((prev: any) =>
+ dedupeUsers([...prev, data.data.userData])
+ );
+ });
+ });
- socket.on("userDisConnectResponse", (data: any) => {
- if (!groupRef.current) return;
- if (socket.id === data.socketId || organization !== data.organization) return;
+ socket.on("userDisConnectResponse", (data: any) => {
+ if (!groupRef.current) return;
+ if (socket.id === data.socketId || organization !== data.organization)
+ return;
- setCams((prev) =>
- prev.filter((cam) => cam.uuid !== data.data.userData._id)
- );
- setActiveUsers((prev: any) =>
- prev.filter((user: any) => user._id !== data.data.userData._id)
- );
- });
+ setCams((prev) =>
+ prev.filter((cam) => cam.uuid !== data.data.userData._id)
+ );
+ setActiveUsers((prev: any) =>
+ prev.filter((user: any) => user._id !== data.data.userData._id)
+ );
+ });
- socket.on("cameraUpdateResponse", (data: any) => {
- if (
- !groupRef.current ||
- socket.id === data.socketId ||
- organization !== data.organization
- )
- return;
+ socket.on("cameraUpdateResponse", (data: any) => {
+ if (
+ !groupRef.current ||
+ socket.id === data.socketId ||
+ organization !== data.organization
+ )
+ return;
- setModels((prev) => ({
- ...prev,
- [data.data.userId]: {
- targetPosition: new THREE.Vector3(
- data.data.position.x,
- data.data.position.y,
- data.data.position.z
- ),
- targetRotation: new THREE.Euler(
- data.data.rotation.x,
- data.data.rotation.y,
- data.data.rotation.z
- ),
- },
- }));
- });
+ setModels((prev) => ({
+ ...prev,
+ [data.data.userId]: {
+ targetPosition: new THREE.Vector3(
+ data.data.position.x,
+ data.data.position.y,
+ data.data.position.z
+ ),
+ targetRotation: new THREE.Euler(
+ data.data.rotation.x,
+ data.data.rotation.y,
+ data.data.rotation.z
+ ),
+ },
+ }));
+ });
- return () => {
- socket.off("userConnectResponse");
- socket.off("userDisConnectResponse");
- socket.off("cameraUpdateResponse");
- };
- }, [socket]);
+ return () => {
+ socket.off("userConnectResponse");
+ socket.off("userDisConnectResponse");
+ socket.off("cameraUpdateResponse");
+ };
+ }, [socket]);
- useFrame(() => {
- if (!groupRef.current) return;
- Object.keys(models).forEach((uuid) => {
- const model = groupRef.current!.getObjectByProperty("uuid", uuid);
- if (!model) return;
+ useFrame(() => {
+ if (!groupRef.current) return;
+ Object.keys(models).forEach((uuid) => {
+ const model = groupRef.current!.getObjectByProperty("uuid", uuid);
+ if (!model) return;
- const { targetPosition, targetRotation } = models[uuid];
- model.position.lerp(targetPosition, 0.1);
- model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1);
- model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1);
- model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1);
- });
- });
+ const { targetPosition, targetRotation } = models[uuid];
+ model.position.lerp(targetPosition, 0.1);
+ model.rotation.x = THREE.MathUtils.lerp(
+ model.rotation.x,
+ targetRotation.x,
+ 0.1
+ );
+ model.rotation.y = THREE.MathUtils.lerp(
+ model.rotation.y,
+ targetRotation.y,
+ 0.1
+ );
+ model.rotation.z = THREE.MathUtils.lerp(
+ model.rotation.z,
+ targetRotation.z,
+ 0.1
+ );
+ });
+ });
- useEffect(() => {
- if (!groupRef.current) return;
- const organization = email!.split("@")[1].split(".")[0];
+ useEffect(() => {
+ if (!groupRef.current) return;
+ const organization = email!.split("@")[1].split(".")[0];
- getActiveUsersData(organization).then((data) => {
- const filteredData = data.cameraDatas.filter(
- (camera: any) => camera.userData.email !== email
- );
+ getActiveUsersData(organization).then((data) => {
+ const filteredData = data.cameraDatas.filter(
+ (camera: any) => camera.userData.email !== email
+ );
- if (filteredData.length > 0) {
- loader.load(camModel, (gltf) => {
- const newCams = filteredData.map((cam: any) => {
- const newModel = gltf.scene.clone();
- newModel.uuid = cam.userData._id;
- newModel.position.set(cam.position.x, cam.position.y, cam.position.z);
- newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z);
- newModel.userData = cam.userData;
- return newModel;
- });
+ if (filteredData.length > 0) {
+ loader.load(camModel, (gltf) => {
+ const newCams = filteredData.map((cam: any) => {
+ const newModel = gltf.scene.clone();
+ newModel.uuid = cam.userData._id;
+ newModel.position.set(
+ cam.position.x,
+ cam.position.y,
+ cam.position.z
+ );
+ newModel.rotation.set(
+ cam.rotation.x,
+ cam.rotation.y,
+ cam.rotation.z
+ );
+ newModel.userData = cam.userData;
+ return newModel;
+ });
- const users = filteredData.map((cam: any) => cam.userData);
- setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
- setCams((prev) => dedupeCams([...prev, ...newCams]));
- });
- }
- });
- }, []);
+ const users = filteredData.map((cam: any) => cam.userData);
+ setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
+ setCams((prev) => dedupeCams([...prev, ...newCams]));
+ });
+ }
+ });
+ }, []);
- return (
-
- {cams.map((cam, index) => (
-
-
-
-
-
- ))}
-
- );
+ return (
+
+ {cams.map((cam, index) => (
+
+
+
+
+
+ ))}
+
+ );
};
export default CamModelsGroup;
diff --git a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
index c93b660..ea5ea74 100644
--- a/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
+++ b/app/src/modules/scene/IntialLoad/loadInitialFloorItems.ts
@@ -20,7 +20,6 @@ async function loadInitialFloorItems(
const organization = (email!.split("@")[1]).split(".")[0];
const items = await getFloorAssets(organization);
- console.log('items: ', items);
localStorage.setItem("FloorItems", JSON.stringify(items));
await initializeDB();
diff --git a/app/src/modules/scene/controls/selection/selectionControls.tsx b/app/src/modules/scene/controls/selection/selectionControls.tsx
index acaed3a..11c2dfb 100644
--- a/app/src/modules/scene/controls/selection/selectionControls.tsx
+++ b/app/src/modules/scene/controls/selection/selectionControls.tsx
@@ -14,7 +14,6 @@ import CopyPasteControls from "./copyPasteControls";
import MoveControls from "./moveControls";
import RotateControls from "./rotateControls";
import useModuleStore from "../../../../store/useModuleStore";
-import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const SelectionControls: React.FC = () => {
const { camera, controls, gl, scene, pointer } = useThree();
@@ -102,13 +101,12 @@ const SelectionControls: React.FC = () => {
};
const onKeyDown = (event: KeyboardEvent) => {
- const keyCombination = detectModifierKeys(event);
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
- if (keyCombination === "ESCAPE") {
+ if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
}
- if (keyCombination === "DELETE") {
+ if (event.key.toLowerCase() === "delete") {
event.preventDefault();
deleteSelection();
}
diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx
index 3c1bf78..b26dca9 100644
--- a/app/src/modules/scene/scene.tsx
+++ b/app/src/modules/scene/scene.tsx
@@ -18,8 +18,8 @@ import Simulation from "../simulation/simulation";
// import Simulation from "./simulationtemp/simulation";
import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget";
-import Dropped3dWidgets from "../../components/ui/componets/Dropped3dWidget";
-import ZoneAssets from "../../components/ui/componets/zoneAssets";
+import Dropped3dWidgets from "../../modules/visualization/widgets/3d/Dropped3dWidget";
+import ZoneAssets from "../visualization/zoneAssets";
export default function Scene() {
const map = useMemo(
diff --git a/app/src/modules/simulation/process/processAnimator.tsx b/app/src/modules/simulation/process/processAnimator.tsx
index 7b906d2..c1063c3 100644
--- a/app/src/modules/simulation/process/processAnimator.tsx
+++ b/app/src/modules/simulation/process/processAnimator.tsx
@@ -26,6 +26,7 @@ const ProcessAnimator: React.FC = ({
}) => {
const gltf = useLoader(GLTFLoader, crate) as GLTF;
const groupRef = useRef(null);
+ const tempStackedObjectsRef = useRef>({});
const {
animationStates,
@@ -43,27 +44,19 @@ const ProcessAnimator: React.FC = ({
checkAndCountTriggers,
} = useProcessAnimation(processes, setProcesses, agvRef);
- const baseMaterials = useMemo(
- () => ({
- Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
- Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
- // Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
- Default: new THREE.MeshStandardMaterial(),
- }),
- []
- );
+ const baseMaterials = useMemo(() => ({
+ Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
+ Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
+ Default: new THREE.MeshStandardMaterial(),
+ }), []);
useEffect(() => {
// Update material references for all spawned objects
Object.entries(animationStates).forEach(([processId, processState]) => {
Object.keys(processState.spawnedObjects).forEach((objectId) => {
- const entry = {
- processId,
- objectId,
- };
+ const entry = { processId, objectId, };
- const materialType =
- processState.spawnedObjects[objectId]?.currentMaterialType;
+ const materialType = processState.spawnedObjects[objectId]?.currentMaterialType;
if (!materialType) {
return;
@@ -72,17 +65,13 @@ const ProcessAnimator: React.FC = ({
const matRefArray = MaterialRef.current;
// Find existing material group
- const existing = matRefArray.find(
- (entryGroup: { material: string; objects: any[] }) =>
- entryGroup.material === materialType
+ const existing = matRefArray.find((entryGroup: { material: string; objects: any[] }) =>
+ entryGroup.material === materialType
);
if (existing) {
// Check if this processId + objectId already exists
- const alreadyExists = existing.objects.some(
- (o: any) =>
- o.processId === entry.processId && o.objectId === entry.objectId
- );
+ const alreadyExists = existing.objects.some((o: any) => o.processId === entry.processId && o.objectId === entry.objectId);
if (!alreadyExists) {
existing.objects.push(entry);
@@ -102,8 +91,7 @@ const ProcessAnimator: React.FC = ({
useFrame(() => {
// Spawn logic frame
- const currentTime =
- clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
+ const currentTime = clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
@@ -131,10 +119,7 @@ const ProcessAnimator: React.FC = ({
: parseFloat(spawnAction.spawnInterval as string) || 0;
// Check if this is a zero interval spawn and we already spawned an object
- if (
- spawnInterval === 0 &&
- processState.hasSpawnedZeroIntervalObject === true
- ) {
+ if (spawnInterval === 0 && processState.hasSpawnedZeroIntervalObject === true) {
return; // Don't spawn more objects for zero interval
}
@@ -312,35 +297,87 @@ const ProcessAnimator: React.FC = ({
// }
// }
+ // if (isLastPoint) {
+ // if (currentPointData?.actions) {
+ // const hasNonInherit = hasNonInheritActions(
+ // currentPointData.actions
+ // );
+ // if (!hasNonInherit) {
+ // // Remove the object if all actions are inherit
+ // updatedObjects[objectId] = {
+ // ...obj,
+ // visible: false,
+ // state: { ...stateRef, isAnimating: false },
+ // };
+ // return;
+ // }
+ // } else {
+ // // No actions at last point - remove the object
+ // updatedObjects[objectId] = {
+ // ...obj,
+ // visible: false,
+ // state: { ...stateRef, isAnimating: false },
+ // };
+ // return;
+ // }
+ // }
+
if (isLastPoint) {
- if (currentPointData?.actions) {
- const hasNonInherit = hasNonInheritActions(
- currentPointData.actions
- );
- if (!hasNonInherit) {
- // Remove the object if all actions are inherit
+ const isAgvPicking = agvRef.current.some(
+ (agv: any) => agv.processId === process.id && agv.status === "picking"
+ );
+
+ const shouldHide = !currentPointData?.actions || !hasNonInheritActions(currentPointData.actions);
+
+ if (shouldHide) {
+ if (isAgvPicking) {
updatedObjects[objectId] = {
...obj,
visible: false,
- state: { ...stateRef, isAnimating: false },
+ state: {
+ ...stateRef,
+ isAnimating: false,
+ },
+ };
+ } else {
+ tempStackedObjectsRef.current[objectId] = true;
+
+ updatedObjects[objectId] = {
+ ...obj,
+ visible: true,
+ state: {
+ ...stateRef,
+ isAnimating: true,
+ },
};
- return;
}
- } else {
- // No actions at last point - remove the object
+
+ return;
+ }
+ }
+
+ if (tempStackedObjectsRef.current[objectId]) {
+ const isAgvPicking = agvRef.current.some(
+ (agv: any) => agv.processId === process.id && agv.status === "picking"
+ );
+
+ if (isAgvPicking) {
+ delete tempStackedObjectsRef.current[objectId];
+
updatedObjects[objectId] = {
...obj,
visible: false,
- state: { ...stateRef, isAnimating: false },
+ state: {
+ ...stateRef,
+ isAnimating: false,
+ },
};
- return;
}
}
if (!isLastPoint) {
const nextPoint = path[nextPointIdx];
- const distance =
- path[stateRef.currentIndex].distanceTo(nextPoint);
+ const distance = path[stateRef.currentIndex].distanceTo(nextPoint);
const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta;
diff --git a/app/src/modules/simulation/process/processCreator.tsx b/app/src/modules/simulation/process/processCreator.tsx
index cae152b..041b4ec 100644
--- a/app/src/modules/simulation/process/processCreator.tsx
+++ b/app/src/modules/simulation/process/processCreator.tsx
@@ -57,7 +57,7 @@
// pointActions: PointAction[][];
// pointTriggers: PointTrigger[][];
// speed: number;
-// isplaying: boolean;
+// isActive: boolean;
// }
// interface ProcessCreatorProps {
@@ -147,7 +147,7 @@
// pointActions: [],
// pointTriggers: [], // Added point triggers array
// speed: 1,
-// isplaying: false,
+// isActive: false,
// });
// // Enhanced connection checking function
@@ -283,7 +283,7 @@
// pointActions,
// pointTriggers,
// speed: processSpeed,
-// isplaying: false,
+// isActive: false,
// };
// },
// [scene]
@@ -410,7 +410,7 @@
// p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
// )
// .join(","),
-// isplaying: false,
+// isActive: false,
// }));
// }, [convertedPaths]);
@@ -459,6 +459,7 @@ import {
ConveyorEventsSchema,
VehicleEventsSchema,
} from "../../../types/world/worldTypes";
+import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
// Type definitions
export interface PointAction {
@@ -495,7 +496,7 @@ export interface SimulationPath {
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
- isplaying: boolean;
+ isActive: boolean;
}
export interface Process {
@@ -505,7 +506,7 @@ export interface Process {
pointActions: PointAction[][];
pointTriggers: PointTrigger[][];
speed: number;
- isplaying: boolean;
+ isActive: boolean;
}
interface ProcessCreatorProps {
@@ -538,13 +539,13 @@ function convertToSimulationPath(
actions: Array.isArray(point.actions)
? point.actions.map(normalizeAction)
: point.actions
- ? [normalizeAction(point.actions)]
- : [],
+ ? [normalizeAction(point.actions)]
+ : [],
triggers: Array.isArray(point.triggers)
? point.triggers.map(normalizeTrigger)
: point.triggers
- ? [normalizeTrigger(point.triggers)]
- : [],
+ ? [normalizeTrigger(point.triggers)]
+ : [],
connections: {
targets: point.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
@@ -556,7 +557,7 @@ function convertToSimulationPath(
typeof path.speed === "string"
? parseFloat(path.speed) || 1
: path.speed || 1,
- isplaying: false, // Added missing property
+ isActive: false, // Added missing property
};
} else {
// For vehicle paths, handle the case where triggers might not exist
@@ -570,8 +571,8 @@ function convertToSimulationPath(
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: path.points.actions
- ? [normalizeAction(path.points.actions)]
- : [],
+ ? [normalizeAction(path.points.actions)]
+ : [],
triggers: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
@@ -582,7 +583,7 @@ function convertToSimulationPath(
],
pathPosition: path.position,
speed: path.points.speed || 1,
- isplaying: false,
+ isActive: false,
};
}
}
@@ -595,7 +596,7 @@ const createEmptyProcess = (): Process => ({
pointActions: [],
pointTriggers: [], // Added point triggers array
speed: 1,
- isplaying: false,
+ isActive: false,
});
// Enhanced connection checking function
@@ -731,7 +732,7 @@ export function useProcessCreation() {
pointActions,
pointTriggers,
speed: processSpeed,
- isplaying: false,
+ isActive: false,
};
},
[scene]
@@ -824,6 +825,7 @@ const ProcessCreator: React.FC = React.memo(
const { createProcessesFromPaths } = useProcessCreation();
const prevPathsRef = useRef([]);
const prevProcessesRef = useRef([]);
+ const { isPlaying } = usePlayButtonStore();
const convertedPaths = useMemo((): SimulationPath[] => {
if (!simulationStates) return [];
@@ -858,7 +860,7 @@ const ProcessCreator: React.FC = React.memo(
p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
)
.join(","),
- isplaying: false,
+ isActive: false,
}));
}, [convertedPaths]);
diff --git a/app/src/modules/simulation/process/types.ts b/app/src/modules/simulation/process/types.ts
index 086b620..6f935fc 100644
--- a/app/src/modules/simulation/process/types.ts
+++ b/app/src/modules/simulation/process/types.ts
@@ -39,7 +39,7 @@ export interface ProcessPath {
pathRotation: number[];
speed: number;
type: "Conveyor" | "Vehicle";
- isplaying: boolean
+ isActive: boolean
}
export interface ProcessData {
diff --git a/app/src/modules/simulation/process/useProcessAnimations.tsx b/app/src/modules/simulation/process/useProcessAnimations.tsx
index 7525c29..032d013 100644
--- a/app/src/modules/simulation/process/useProcessAnimations.tsx
+++ b/app/src/modules/simulation/process/useProcessAnimations.tsx
@@ -1,540 +1,609 @@
import { useCallback, useEffect, useRef, useState } from "react";
import * as THREE from "three";
import {
- ProcessData,
- ProcessAnimationState,
- SpawnedObject,
- AnimationState,
- ProcessPoint,
- PointAction,
- Trigger,
+ ProcessData,
+ ProcessAnimationState,
+ SpawnedObject,
+ AnimationState,
+ ProcessPoint,
+ PointAction,
+ Trigger,
} from "./types";
import {
- useAnimationPlaySpeed,
- usePauseButtonStore,
- usePlayButtonStore,
- useResetButtonStore,
+ useAnimationPlaySpeed,
+ usePauseButtonStore,
+ usePlayButtonStore,
+ useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store";
// Enhanced ProcessAnimationState with trigger tracking
interface EnhancedProcessAnimationState extends ProcessAnimationState {
- triggerCounts: Record;
- triggerLogs: Array<{
- timestamp: number;
- pointId: string;
- objectId: string;
- triggerId: string;
- }>;
+ triggerCounts: Record;
+ triggerLogs: Array<{
+ timestamp: number;
+ pointId: string;
+ objectId: string;
+ triggerId: string;
+ }>;
}
interface ProcessContainerProps {
- processes: ProcessData[];
- setProcesses: React.Dispatch>;
- agvRef: any;
+ processes: ProcessData[];
+ setProcesses: React.Dispatch>;
+ agvRef: any;
}
interface PlayAgvState {
- playAgv: Record;
- setPlayAgv: (data: any) => void;
+ playAgv: Record;
+ setPlayAgv: (data: any) => void;
}
export const useProcessAnimation = (
- processes: ProcessData[],
- setProcesses: React.Dispatch>,
- agvRef: any
+ processes: ProcessData[],
+ setProcesses: React.Dispatch>,
+ agvRef: any
) => {
- // State and refs initialization
- const { isPlaying, setIsPlaying } = usePlayButtonStore();
- const { isPaused, setIsPaused } = usePauseButtonStore();
- const { isReset, setReset } = useResetButtonStore();
- const debugRef = useRef(false);
- const clockRef = useRef(new THREE.Clock());
- const pauseTimeRef = useRef(0);
- const elapsedBeforePauseRef = useRef(0);
- const animationStatesRef = useRef<
- Record
- >({});
- const { speed } = useAnimationPlaySpeed();
- const prevIsPlaying = useRef(null);
- const [internalResetFlag, setInternalResetFlag] = useState(false);
- const [animationStates, setAnimationStates] = useState<
- Record
- >({});
- const speedRef = useRef(speed);
- const { PlayAgv, setPlayAgv } = usePlayAgv();
+ // State and refs initialization
+ const { isPlaying, setIsPlaying } = usePlayButtonStore();
+ const { isPaused, setIsPaused } = usePauseButtonStore();
+ const { isReset, setReset } = useResetButtonStore();
+ const debugRef = useRef(false);
+ const clockRef = useRef(new THREE.Clock());
+ const pauseTimeRef = useRef(0);
+ const elapsedBeforePauseRef = useRef(0);
+ const animationStatesRef = useRef>({});
+ const { speed } = useAnimationPlaySpeed();
+ const prevIsPlaying = useRef(null);
+ const [internalResetFlag, setInternalResetFlag] = useState(false);
+ const [animationStates, setAnimationStates] = useState>({});
+ const speedRef = useRef(speed);
+ const { PlayAgv, setPlayAgv } = usePlayAgv();
- // Effect hooks
- useEffect(() => {
- speedRef.current = speed;
- }, [speed]);
+ // Effect hooks
+ useEffect(() => {
+ speedRef.current = speed;
+ }, [speed]);
- useEffect(() => {
- if (prevIsPlaying.current !== null) {
- setAnimationStates({});
- }
- prevIsPlaying.current = isPlaying;
- }, [isPlaying]);
-
- useEffect(() => {
- animationStatesRef.current = animationStates;
- }, [animationStates]);
-
- // Reset handler
- useEffect(() => {
- if (isReset) {
- setInternalResetFlag(true);
- setIsPlaying(false);
- setIsPaused(false);
- setAnimationStates({});
- animationStatesRef.current = {};
- clockRef.current = new THREE.Clock();
- elapsedBeforePauseRef.current = 0;
- pauseTimeRef.current = 0;
- setReset(false);
- setTimeout(() => {
- setInternalResetFlag(false);
- setIsPlaying(true);
- }, 0);
- }
- }, [isReset, setReset, setIsPlaying, setIsPaused]);
-
- // Pause handler
- useEffect(() => {
- if (isPaused) {
- pauseTimeRef.current = clockRef.current.getElapsedTime();
- } else if (pauseTimeRef.current > 0) {
- const pausedDuration =
- clockRef.current.getElapsedTime() - pauseTimeRef.current;
- elapsedBeforePauseRef.current += pausedDuration;
- }
- }, [isPaused]);
-
- // Initialize animation states with trigger tracking
- useEffect(() => {
- if (isPlaying && !internalResetFlag) {
- const newStates: Record = {};
-
- processes.forEach((process) => {
- const triggerCounts: Record = {};
-
- // Initialize trigger counts for all On-Hit triggers
- process.paths?.forEach((path) => {
- path.points?.forEach((point) => {
- point.triggers?.forEach((trigger: Trigger) => {
- if (trigger.type === "On-Hit" && trigger.isUsed) {
- triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
- }
- });
- });
- });
-
- newStates[process.id] = {
- spawnedObjects: {},
- nextSpawnTime: 0,
- objectIdCounter: 0,
- isProcessDelaying: false,
- processDelayStartTime: 0,
- processDelayDuration: 0,
- triggerCounts,
- triggerLogs: [],
- };
- });
-
- setAnimationStates(newStates);
- animationStatesRef.current = newStates;
- clockRef.current.start();
- }
- }, [isPlaying, processes, internalResetFlag]);
-
- // Helper functions
- const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
- for (const path of process.paths || []) {
- for (const point of path.points || []) {
- const spawnAction = point.actions?.find(
- (a) => a.isUsed && a.type === "Spawn"
- );
- if (spawnAction) {
- return point;
+ useEffect(() => {
+ if (prevIsPlaying.current !== null || !isPlaying) {
+ setAnimationStates({});
}
- }
- }
- return null;
- };
+ prevIsPlaying.current = isPlaying;
+ }, [isPlaying]);
- const findAnimationPathPoint = (
- process: ProcessData,
- spawnPoint: ProcessPoint
- ): THREE.Vector3 => {
- if (process.animationPath && process.animationPath.length > 0) {
- let pointIndex = 0;
- for (const path of process.paths || []) {
- for (let i = 0; i < (path.points?.length || 0); i++) {
- const point = path.points?.[i];
- if (point && point.uuid === spawnPoint.uuid) {
- if (process.animationPath[pointIndex]) {
- const p = process.animationPath[pointIndex];
- return new THREE.Vector3(p.x, p.y, p.z);
- }
- }
- pointIndex++;
+ useEffect(() => {
+ animationStatesRef.current = animationStates;
+ }, [animationStates]);
+
+ // Reset handler
+ useEffect(() => {
+ if (isReset) {
+ setInternalResetFlag(true);
+ setIsPlaying(false);
+ setIsPaused(false);
+ setAnimationStates({});
+ animationStatesRef.current = {};
+ clockRef.current = new THREE.Clock();
+ elapsedBeforePauseRef.current = 0;
+ pauseTimeRef.current = 0;
+ setReset(false);
+ setTimeout(() => {
+ setInternalResetFlag(false);
+ setIsPlaying(true);
+ }, 0);
}
- }
- }
- return new THREE.Vector3(
- spawnPoint.position[0],
- spawnPoint.position[1],
- spawnPoint.position[2]
- );
- };
+ }, [isReset, setReset, setIsPlaying, setIsPaused]);
- // Optimized object creation
- const createSpawnedObject = useCallback(
- (
- process: ProcessData,
- currentTime: number,
- materialType: string,
- spawnPoint: ProcessPoint,
- baseMaterials: Record
- ): SpawnedObject => {
- const processMaterials = {
- ...baseMaterials,
- ...(process.customMaterials || {}),
- };
+ // Pause handler
+ useEffect(() => {
+ if (isPaused) {
+ pauseTimeRef.current = clockRef.current.getElapsedTime();
+ } else if (pauseTimeRef.current > 0) {
+ const pausedDuration = clockRef.current.getElapsedTime() - pauseTimeRef.current;
+ elapsedBeforePauseRef.current += pausedDuration;
+ }
+ }, [isPaused]);
- const spawnPosition = findAnimationPathPoint(process, spawnPoint);
- const material =
- processMaterials[materialType as keyof typeof processMaterials] ||
- baseMaterials.Default;
+ // Initialize animation states with trigger tracking
+ useEffect(() => {
+ if (isPlaying && !internalResetFlag) {
+ const newStates: Record = {};
- return {
- ref: { current: null },
- state: {
- currentIndex: 0,
- progress: 0,
- isAnimating: true,
- speed: process.speed || 1,
- isDelaying: false,
- delayStartTime: 0,
- currentDelayDuration: 0,
- delayComplete: false,
- currentPathIndex: 0,
- },
- visible: true,
- material: material,
- currentMaterialType: materialType,
- spawnTime: currentTime,
- position: spawnPosition,
- };
- },
- []
- );
+ processes.forEach((process) => {
+ const triggerCounts: Record = {};
- // Material handling
- const handleMaterialSwap = useCallback(
- (
- processId: string,
- objectId: string,
- materialType: string,
- processes: ProcessData[],
- baseMaterials: Record
- ) => {
- setAnimationStates((prev) => {
- const processState = prev[processId];
- if (!processState || !processState.spawnedObjects[objectId])
- return prev;
+ // Initialize trigger counts for all On-Hit triggers
+ process.paths?.forEach((path) => {
+ path.points?.forEach((point) => {
+ point.triggers?.forEach((trigger: Trigger) => {
+ if (trigger.type === "On-Hit" && trigger.isUsed) {
+ triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
+ }
+ });
+ });
+ });
- const process = processes.find((p) => p.id === processId);
- if (!process) return prev;
-
- const processMaterials = {
- ...baseMaterials,
- ...(process.customMaterials || {}),
- };
-
- const newMaterial =
- processMaterials[materialType as keyof typeof processMaterials];
- if (!newMaterial) return prev;
-
- return {
- ...prev,
- [processId]: {
- ...processState,
- spawnedObjects: {
- ...processState.spawnedObjects,
- [objectId]: {
- ...processState.spawnedObjects[objectId],
- material: newMaterial,
- currentMaterialType: materialType,
- },
- },
- },
- };
- });
- },
- []
- );
-
- // Point action handler with trigger counting
- const handlePointActions = useCallback(
- (
- processId: string,
- objectId: string,
- actions: PointAction[] = [],
- currentTime: number,
- processes: ProcessData[],
- baseMaterials: Record
- ): boolean => {
- let shouldStopAnimation = false;
-
- actions.forEach((action) => {
- if (!action.isUsed) return;
-
- switch (action.type) {
- case "Delay":
- setAnimationStates((prev) => {
- const processState = prev[processId];
- if (!processState || processState.isProcessDelaying) return prev;
-
- const delayDuration =
- typeof action.delay === "number"
- ? action.delay
- : parseFloat(action.delay as string) || 0;
-
- if (delayDuration > 0) {
- return {
- ...prev,
- [processId]: {
- ...processState,
- isProcessDelaying: true,
- processDelayStartTime: currentTime,
- processDelayDuration: delayDuration,
- spawnedObjects: {
- ...processState.spawnedObjects,
- [objectId]: {
- ...processState.spawnedObjects[objectId],
- state: {
- ...processState.spawnedObjects[objectId].state,
- isAnimating: false,
- isDelaying: true,
- delayStartTime: currentTime,
- currentDelayDuration: delayDuration,
- delayComplete: false,
- },
- },
- },
- },
+ newStates[process.id] = {
+ spawnedObjects: {},
+ nextSpawnTime: 0,
+ objectIdCounter: 0,
+ isProcessDelaying: false,
+ processDelayStartTime: 0,
+ processDelayDuration: 0,
+ triggerCounts,
+ triggerLogs: [],
};
- }
- return prev;
});
- shouldStopAnimation = true;
- break;
- case "Despawn":
- setAnimationStates((prev) => {
- const processState = prev[processId];
- if (!processState) return prev;
-
- const newSpawnedObjects = { ...processState.spawnedObjects };
- delete newSpawnedObjects[objectId];
-
- return {
- ...prev,
- [processId]: {
- ...processState,
- spawnedObjects: newSpawnedObjects,
- },
- };
- });
- shouldStopAnimation = true;
- break;
-
- case "Swap":
- if (action.material) {
- handleMaterialSwap(
- processId,
- objectId,
- action.material,
- processes,
- baseMaterials
- );
- }
- break;
-
- default:
- break;
+ setAnimationStates(newStates);
+ animationStatesRef.current = newStates;
+ clockRef.current.start();
}
- });
+ }, [isPlaying, processes, internalResetFlag]);
- return shouldStopAnimation;
- },
- [handleMaterialSwap]
- );
+ useEffect(() => {
+ if (isPlaying && !internalResetFlag) {
+ const newStates: Record = {};
- // Trigger counting system
- const checkAndCountTriggers = useCallback(
- (
- processId: string,
- objectId: string,
- currentPointIndex: number,
- processes: ProcessData[],
- currentTime: number
- ) => {
- setAnimationStates((prev) => {
- const processState = prev[processId];
- if (!processState) return prev;
+ // Initialize AGVs for each process first
+ processes.forEach((process) => {
+ // Find all vehicle paths for this process
+ const vehiclePaths = process.paths?.filter(
+ (path) => path.type === "Vehicle"
+ ) || [];
- const process = processes.find((p) => p.id === processId);
- if (!process) return prev;
+ // Initialize AGVs for each vehicle path
+ vehiclePaths.forEach((vehiclePath) => {
+ if (vehiclePath.points?.length > 0) {
+ const vehiclePoint = vehiclePath.points[0];
+ const action = vehiclePoint.actions?.[0];
+ const maxHitCount = action?.hitCount;
- const point = getPointDataForAnimationIndex(process, currentPointIndex);
- if (!point?.triggers) return prev;
+ const vehicleId = vehiclePath.modeluuid;
+ const processId = process.id;
- const onHitTriggers = point.triggers.filter(
- (t: Trigger) => t.type === "On-Hit" && t.isUsed
+ // Check if this AGV already exists
+ const existingAgv = agvRef.current.find(
+ (v: any) => v.vehicleId === vehicleId && v.processId === processId
+ );
+
+ if (!existingAgv) {
+ // Initialize the AGV in a stationed state
+ agvRef.current.push({
+ processId,
+ vehicleId,
+ maxHitCount: maxHitCount || 0,
+ isActive: false,
+ hitCount: 0,
+ status: 'stationed',
+ lastUpdated: 0
+ });
+ }
+ }
+ });
+
+ // Then initialize trigger counts as before
+ const triggerCounts: Record = {};
+ process.paths?.forEach((path) => {
+ path.points?.forEach((point) => {
+ point.triggers?.forEach((trigger: Trigger) => {
+ if (trigger.type === "On-Hit" && trigger.isUsed) {
+ triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
+ }
+ });
+ });
+ });
+
+ newStates[process.id] = {
+ spawnedObjects: {},
+ nextSpawnTime: 0,
+ objectIdCounter: 0,
+ isProcessDelaying: false,
+ processDelayStartTime: 0,
+ processDelayDuration: 0,
+ triggerCounts,
+ triggerLogs: [],
+ };
+ });
+
+ setAnimationStates(newStates);
+ animationStatesRef.current = newStates;
+ clockRef.current.start();
+ }
+ }, [isPlaying, processes, internalResetFlag]);
+
+ // Helper functions
+ const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
+ for (const path of process.paths || []) {
+ for (const point of path.points || []) {
+ const spawnAction = point.actions?.find(
+ (a) => a.isUsed && a.type === "Spawn"
+ );
+ if (spawnAction) {
+ return point;
+ }
+ }
+ }
+ return null;
+ };
+
+ const findAnimationPathPoint = (
+ process: ProcessData,
+ spawnPoint: ProcessPoint
+ ): THREE.Vector3 => {
+ if (process.animationPath && process.animationPath.length > 0) {
+ let pointIndex = 0;
+ for (const path of process.paths || []) {
+ for (let i = 0; i < (path.points?.length || 0); i++) {
+ const point = path.points?.[i];
+ if (point && point.uuid === spawnPoint.uuid) {
+ if (process.animationPath[pointIndex]) {
+ const p = process.animationPath[pointIndex];
+ return new THREE.Vector3(p.x, p.y, p.z);
+ }
+ }
+ pointIndex++;
+ }
+ }
+ }
+ return new THREE.Vector3(
+ spawnPoint.position[0],
+ spawnPoint.position[1],
+ spawnPoint.position[2]
);
- if (onHitTriggers.length === 0) return prev;
+ };
- const newTriggerCounts = { ...processState.triggerCounts };
- const newTriggerLogs = [...processState.triggerLogs];
- let shouldLog = false;
+ // Optimized object creation
+ const createSpawnedObject = useCallback(
+ (
+ process: ProcessData,
+ currentTime: number,
+ materialType: string,
+ spawnPoint: ProcessPoint,
+ baseMaterials: Record
+ ): SpawnedObject => {
+ const processMaterials = {
+ ...baseMaterials,
+ ...(process.customMaterials || {}),
+ };
- // Update counts for all valid triggers
- onHitTriggers.forEach((trigger: Trigger) => {
- const triggerKey = `${point.uuid}-${trigger.uuid}`;
- newTriggerCounts[triggerKey] =
- (newTriggerCounts[triggerKey] || 0) + 1;
- shouldLog = true;
- newTriggerLogs.push({
- timestamp: currentTime,
- pointId: point.uuid,
- objectId,
- triggerId: trigger.uuid,
- });
- });
+ const spawnPosition = findAnimationPathPoint(process, spawnPoint);
+ const material =
+ processMaterials[materialType as keyof typeof processMaterials] ||
+ baseMaterials.Default;
- const processTotalHits = Object.values(newTriggerCounts).reduce(
- (a, b) => a + b,
- 0
- );
+ return {
+ ref: { current: null },
+ state: {
+ currentIndex: 0,
+ progress: 0,
+ isAnimating: true,
+ speed: process.speed || 1,
+ isDelaying: false,
+ delayStartTime: 0,
+ currentDelayDuration: 0,
+ delayComplete: false,
+ currentPathIndex: 0,
+ },
+ visible: true,
+ material: material,
+ currentMaterialType: materialType,
+ spawnTime: currentTime,
+ position: spawnPosition,
+ };
+ },
+ []
+ );
- if (shouldLog) {
- const vehiclePaths = process.paths.filter(
- (path) => path.type === "Vehicle"
- );
+ // Material handling
+ const handleMaterialSwap = useCallback(
+ (
+ processId: string,
+ objectId: string,
+ materialType: string,
+ processes: ProcessData[],
+ baseMaterials: Record
+ ) => {
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState || !processState.spawnedObjects[objectId])
+ return prev;
- vehiclePaths.forEach((vehiclePath) => {
- if (vehiclePath.points?.length > 0) {
- const vehiclePoint = vehiclePath.points[0];
- const action = vehiclePoint.actions?.[0];
- let expectedHitCount = action?.hitCount;
+ const process = processes.find((p) => p.id === processId);
+ if (!process) return prev;
- if (expectedHitCount !== undefined) {
- const vehicleId = vehiclePath.modeluuid;
+ const processMaterials = {
+ ...baseMaterials,
+ ...(process.customMaterials || {}),
+ };
- let vehicleEntry = agvRef.current.find(
- (v: any) =>
- v.vehicleId === vehicleId && v.processId === processId
+ const newMaterial =
+ processMaterials[materialType as keyof typeof processMaterials];
+ if (!newMaterial) return prev;
+
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ spawnedObjects: {
+ ...processState.spawnedObjects,
+ [objectId]: {
+ ...processState.spawnedObjects[objectId],
+ material: newMaterial,
+ currentMaterialType: materialType,
+ },
+ },
+ },
+ };
+ });
+ },
+ []
+ );
+
+ // Point action handler with trigger counting
+ const handlePointActions = useCallback(
+ (
+ processId: string,
+ objectId: string,
+ actions: PointAction[] = [],
+ currentTime: number,
+ processes: ProcessData[],
+ baseMaterials: Record
+ ): boolean => {
+ let shouldStopAnimation = false;
+
+ actions.forEach((action) => {
+ if (!action.isUsed) return;
+
+ switch (action.type) {
+ case "Delay":
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState || processState.isProcessDelaying) return prev;
+
+ const delayDuration =
+ typeof action.delay === "number"
+ ? action.delay
+ : parseFloat(action.delay as string) || 0;
+
+ if (delayDuration > 0) {
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ isProcessDelaying: true,
+ processDelayStartTime: currentTime,
+ processDelayDuration: delayDuration,
+ spawnedObjects: {
+ ...processState.spawnedObjects,
+ [objectId]: {
+ ...processState.spawnedObjects[objectId],
+ state: {
+ ...processState.spawnedObjects[objectId].state,
+ isAnimating: false,
+ isDelaying: true,
+ delayStartTime: currentTime,
+ currentDelayDuration: delayDuration,
+ delayComplete: false,
+ },
+ },
+ },
+ },
+ };
+ }
+ return prev;
+ });
+ shouldStopAnimation = true;
+ break;
+
+ case "Despawn":
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState) return prev;
+
+ const newSpawnedObjects = { ...processState.spawnedObjects };
+ delete newSpawnedObjects[objectId];
+
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ spawnedObjects: newSpawnedObjects,
+ },
+ };
+ });
+ shouldStopAnimation = true;
+ break;
+
+ case "Swap":
+ if (action.material) {
+ handleMaterialSwap(
+ processId,
+ objectId,
+ action.material,
+ processes,
+ baseMaterials
+ );
+ }
+ break;
+
+ default:
+ break;
+ }
+ });
+
+ return shouldStopAnimation;
+ },
+ [handleMaterialSwap]
+ );
+
+ // Trigger counting system
+ const checkAndCountTriggers = useCallback(
+ (
+ processId: string,
+ objectId: string,
+ currentPointIndex: number,
+ processes: ProcessData[],
+ currentTime: number
+ ) => {
+ setAnimationStates((prev) => {
+ const processState = prev[processId];
+ if (!processState) return prev;
+
+ const process = processes.find((p) => p.id === processId);
+ if (!process) return prev;
+
+ const point = getPointDataForAnimationIndex(process, currentPointIndex);
+ if (!point?.triggers) return prev;
+
+ const onHitTriggers = point.triggers.filter(
+ (t: Trigger) => t.type === "On-Hit" && t.isUsed
+ );
+ if (onHitTriggers.length === 0) return prev;
+
+ let newTriggerCounts = { ...processState.triggerCounts };
+ const newTriggerLogs = [...processState.triggerLogs];
+ let shouldLog = false;
+
+ // Find all vehicle paths for this process
+ const vehiclePaths = process.paths.filter(
+ (path) => path.type === "Vehicle"
);
- if (!vehicleEntry) {
- vehicleEntry = {
- processId,
- vehicleId,
- expectedHitCount,
- isplaying: false,
- hitCount: 0, // Initialize hitCount
- };
- agvRef.current.push(vehicleEntry);
+ // Check if any vehicle is active for this process
+ const activeVehicles = vehiclePaths.filter(path => {
+ const vehicleId = path.modeluuid;
+ const vehicleEntry = agvRef.current.find(
+ (v: any) => v.vehicleId === vehicleId && v.processId === processId
+ );
+ return vehicleEntry?.isActive;
+ });
+
+ // Only count triggers if no vehicles are active for this process
+ if (activeVehicles.length === 0) {
+ onHitTriggers.forEach((trigger: Trigger) => {
+ const triggerKey = `${point.uuid}-${trigger.uuid}`;
+ newTriggerCounts[triggerKey] = (newTriggerCounts[triggerKey] || 0) + 1;
+ shouldLog = true;
+ newTriggerLogs.push({
+ timestamp: currentTime,
+ pointId: point.uuid,
+ objectId,
+ triggerId: trigger.uuid,
+ });
+ });
}
- // Increment expectedHitCount if not playing
- if (!vehicleEntry.isplaying) {
- vehicleEntry.expectedHitCount = processTotalHits;
+ let processTotalHits = Object.values(newTriggerCounts).reduce((a, b) => a + b, 0);
+
+ if (shouldLog) {
+ vehiclePaths.forEach((vehiclePath) => {
+ if (vehiclePath.points?.length > 0) {
+ const vehiclePoint = vehiclePath.points[0];
+ const action = vehiclePoint.actions?.[0];
+ const maxHitCount = action?.hitCount;
+
+ if (maxHitCount !== undefined) {
+ const vehicleId = vehiclePath.modeluuid;
+ let vehicleEntry = agvRef.current.find((v: any) => v.vehicleId === vehicleId && v.processId === processId);
+
+ if (!vehicleEntry) {
+ vehicleEntry = {
+ processId,
+ vehicleId,
+ maxHitCount: maxHitCount,
+ isActive: false,
+ hitCount: 0,
+ status: 'stationed'
+ };
+ agvRef.current.push(vehicleEntry);
+ }
+
+ // if (!vehicleEntry.isActive && vehicleEntry.status === 'picking') {
+ if (!vehicleEntry.isActive) {
+ vehicleEntry.hitCount = processTotalHits;
+ vehicleEntry.lastUpdated = currentTime;
+
+ if (vehicleEntry.hitCount >= vehicleEntry.maxHitCount) {
+ vehicleEntry.isActive = true;
+ newTriggerCounts = {};
+ processTotalHits = 0;
+ }
+ }
+ }
+ }
+ });
}
- // Set vehicle's hitCount to the processTotalHits
- vehicleEntry.hitCount = processTotalHits;
- vehicleEntry.lastUpdated = currentTime;
+ return {
+ ...prev,
+ [processId]: {
+ ...processState,
+ triggerCounts: newTriggerCounts,
+ triggerLogs: newTriggerLogs,
+ totalHits: processTotalHits,
+ },
+ };
+ });
+ },
+ []
+ );
- // If hitCount matches expectedHitCount, set isplaying to true
- if (vehicleEntry.hitCount >= vehicleEntry.expectedHitCount) {
- vehicleEntry.isplaying = true;
+ // Utility functions
+ const hasNonInheritActions = useCallback(
+ (actions: PointAction[] = []): boolean => {
+ return actions.some(
+ (action) => action.isUsed && action.type !== "Inherit"
+ );
+ },
+ []
+ );
+
+ const getPointDataForAnimationIndex = useCallback(
+ (process: ProcessData, index: number): ProcessPoint | null => {
+ if (!process.paths) return null;
+
+ let cumulativePoints = 0;
+ for (const path of process.paths) {
+ const pointCount = path.points?.length || 0;
+
+ if (index < cumulativePoints + pointCount) {
+ const pointIndex = index - cumulativePoints;
+ return path.points?.[pointIndex] || null;
}
- }
+
+ cumulativePoints += pointCount;
}
- });
- }
- return {
- ...prev,
- [processId]: {
- ...processState,
- triggerCounts: newTriggerCounts,
- triggerLogs: newTriggerLogs,
- totalHits: processTotalHits,
- },
- };
- });
- },
- []
- );
+ return null;
+ },
+ []
+ );
- // Utility functions
- const hasNonInheritActions = useCallback(
- (actions: PointAction[] = []): boolean => {
- return actions.some(
- (action) => action.isUsed && action.type !== "Inherit"
- );
- },
- []
- );
+ const getTriggerCounts = useCallback((processId: string) => {
+ return animationStatesRef.current[processId]?.triggerCounts || {};
+ }, []);
- const getPointDataForAnimationIndex = useCallback(
- (process: ProcessData, index: number): ProcessPoint | null => {
- if (!process.paths) return null;
+ const getTriggerLogs = useCallback((processId: string) => {
+ return animationStatesRef.current[processId]?.triggerLogs || [];
+ }, []);
- let cumulativePoints = 0;
- for (const path of process.paths) {
- const pointCount = path.points?.length || 0;
-
- if (index < cumulativePoints + pointCount) {
- const pointIndex = index - cumulativePoints;
- return path.points?.[pointIndex] || null;
- }
-
- cumulativePoints += pointCount;
- }
-
- return null;
- },
- []
- );
-
- const getTriggerCounts = useCallback((processId: string) => {
- return animationStatesRef.current[processId]?.triggerCounts || {};
- }, []);
-
- const getTriggerLogs = useCallback((processId: string) => {
- return animationStatesRef.current[processId]?.triggerLogs || [];
- }, []);
-
- return {
- animationStates,
- setAnimationStates,
- clockRef,
- elapsedBeforePauseRef,
- speedRef,
- debugRef,
- findSpawnPoint,
- createSpawnedObject,
- handlePointActions,
- hasNonInheritActions,
- getPointDataForAnimationIndex,
- checkAndCountTriggers,
- getTriggerCounts,
- getTriggerLogs,
- processes,
- };
+ return {
+ animationStates,
+ setAnimationStates,
+ clockRef,
+ elapsedBeforePauseRef,
+ speedRef,
+ debugRef,
+ findSpawnPoint,
+ createSpawnedObject,
+ handlePointActions,
+ hasNonInheritActions,
+ getPointDataForAnimationIndex,
+ checkAndCountTriggers,
+ getTriggerCounts,
+ getTriggerLogs,
+ processes,
+ };
};
diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx
index 5bcf7d1..0741b5f 100644
--- a/app/src/modules/simulation/simulation.tsx
+++ b/app/src/modules/simulation/simulation.tsx
@@ -15,7 +15,6 @@ import Agv from "../builder/agv/agv";
function Simulation() {
const { activeModule } = useModuleStore();
const pathsGroupRef = useRef() as React.MutableRefObject;
- const { simulationStates, setSimulationStates } = useSimulationStates();
const [processes, setProcesses] = useState([]);
const agvRef = useRef([]);
const MaterialRef = useRef([]);
diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/modules/visualization/DisplayZone.tsx
similarity index 87%
rename from app/src/components/ui/componets/DisplayZone.tsx
rename to app/src/modules/visualization/DisplayZone.tsx
index 2b959df..f4033f9 100644
--- a/app/src/components/ui/componets/DisplayZone.tsx
+++ b/app/src/modules/visualization/DisplayZone.tsx
@@ -1,255 +1,257 @@
-import React, { useEffect, useRef, useState, useCallback } from "react";
-import { useWidgetStore, Widget } from "../../../store/useWidgetStore";
-import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons";
-import { InfoIcon } from "../../icons/ExportCommonIcons";
-import {
- useDroppedObjectsStore,
- useFloatingWidget,
-} from "../../../store/useDroppedObjectsStore";
-import { getSelect2dZoneData } from "../../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
-import { getFloatingZoneData } from "../../../services/realTimeVisulization/zoneData/getFloatingData";
-import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData";
-
-// Define the type for `Side`
-type Side = "top" | "bottom" | "left" | "right";
-
-interface HiddenPanels {
- [zoneId: string]: Side[];
-}
-
-interface DisplayZoneProps {
- zonesData: {
- [key: string]: {
- activeSides: Side[];
- panelOrder: Side[];
- lockedPanels: Side[];
- points: [];
- widgets: Widget[];
- zoneId: string;
- zoneViewPortTarget: number[];
- zoneViewPortPosition: number[];
- };
- };
- selectedZone: {
- zoneName: string;
- activeSides: Side[];
- panelOrder: Side[];
- lockedPanels: Side[];
- zoneId: string;
- points: [];
- zoneViewPortTarget: number[];
- zoneViewPortPosition: number[];
- widgets: {
- id: string;
- type: string;
- title: string;
- panel: Side;
- data: any;
- }[];
- };
- setSelectedZone: React.Dispatch<
- React.SetStateAction<{
- zoneName: string;
- activeSides: Side[];
- panelOrder: Side[];
- lockedPanels: Side[];
- points: [];
- zoneId: string;
- zoneViewPortTarget: number[];
- zoneViewPortPosition: number[];
- widgets: {
- id: string;
- type: string;
- title: string;
- panel: Side;
- data: any;
- }[];
- }>
- >;
- hiddenPanels: HiddenPanels; // Updated prop type
- setHiddenPanels: React.Dispatch>; // Updated prop type
-}
-
-const DisplayZone: React.FC = ({
- zonesData,
- selectedZone,
- setSelectedZone,
- hiddenPanels,
-}) => {
- // Ref for the container element
- const containerRef = useRef(null);
- const scrollContainerRef = useRef(null);
-
- // State to track overflow visibility
- const [showLeftArrow, setShowLeftArrow] = useState(false);
- const [showRightArrow, setShowRightArrow] = useState(false);
- const { floatingWidget, setFloatingWidget } = useFloatingWidget()
-
- const { setSelectedChartId } = useWidgetStore()
-
- // Function to calculate overflow state
- const updateOverflowState = useCallback(() => {
- const container = scrollContainerRef.current;
- if (container) {
- const isOverflowing = container.scrollWidth > container.clientWidth;
- const canScrollLeft = container.scrollLeft > 0;
- const canScrollRight =
- container.scrollLeft + container.clientWidth < container.scrollWidth;
-
- setShowLeftArrow(isOverflowing && canScrollLeft);
- setShowRightArrow(isOverflowing && canScrollRight);
- }
- }, []);
-
- useEffect(() => {
- const container = scrollContainerRef.current;
- if (!container) return;
-
- // Initial calculation after the DOM has been rendered
- const observer = new ResizeObserver(updateOverflowState);
- observer.observe(container);
-
- // Update on scroll
- const handleScroll = () => updateOverflowState();
- container.addEventListener("scroll", handleScroll);
-
- // Add mouse wheel listener for horizontal scrolling
- const handleWheel = (event: WheelEvent) => {
- if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
- event.preventDefault();
- container.scrollBy({
- left: event.deltaY * 2,
- behavior: "smooth",
- });
- }
- };
-
- container.addEventListener("wheel", handleWheel, { passive: false });
-
- // Initial check
- updateOverflowState();
-
- return () => {
- observer.disconnect();
- container.removeEventListener("scroll", handleScroll);
- container.removeEventListener("wheel", handleWheel);
- };
- }, [updateOverflowState]);
-
- // Handle scrolling with navigation arrows
- const handleScrollLeft = () => {
- const container = scrollContainerRef.current;
- if (container) {
- container.scrollBy({
- left: -200,
- behavior: "smooth",
- });
- }
- };
-
- const handleScrollRight = () => {
- const container = scrollContainerRef.current;
- if (container) {
- container.scrollBy({
- left: 200,
- behavior: "smooth",
- });
- }
- };
-
- async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
- try {
- if (selectedZone?.zoneId === zoneId) {
- return;
- }
- setSelectedChartId(null);
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- let response = await getSelect2dZoneData(zoneId, organization);
- console.log('response: ', response);
- let res = await getFloatingZoneData(zoneId, organization);
- console.log('res: ', res);
-
- setFloatingWidget(res);
- // Set the selected zone in the store
- useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
- if (Array.isArray(res)) {
- res.forEach((val) => {
- useDroppedObjectsStore.getState().addObject(zoneName, val);
- });
- }
-
- setSelectedZone({
- zoneName,
- activeSides: response.activeSides || [],
- panelOrder: response.panelOrder || [],
- lockedPanels: response.lockedPanels || [],
- widgets: response.widgets || [],
- points: response.points || [],
- zoneId: zoneId,
- zoneViewPortTarget: response.viewPortCenter || {},
- zoneViewPortPosition: response.viewPortposition || {},
- });
- } catch (error) {
-
- }
- }
-
- return (
-
- {/* Left Arrow */}
- {showLeftArrow && (
-
- )}
-
- {/* Scrollable Zones Container */}
-
- {Object.keys(zonesData).length !== 0 ? (
- <>
- {Object.keys(zonesData).map((zoneName, index) => (
-
- handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)
- }
- >
- {zoneName}
-
- ))}
- >
- ) : (
-
-
- No zones? Create one!
-
- )}
-
-
- {/* Right Arrow */}
- {showRightArrow && (
-
- )}
-
- );
-};
-
-export default DisplayZone;
+import React, { useEffect, useRef, useState, useCallback } from "react";
+import { useWidgetStore, Widget } from "../../store/useWidgetStore";
+
+import {
+ useDroppedObjectsStore,
+ useFloatingWidget,
+} from "../../store/useDroppedObjectsStore";
+import { getSelect2dZoneData } from "../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
+import { getFloatingZoneData } from "../../services/realTimeVisulization/zoneData/getFloatingData";
+import { get3dWidgetZoneData } from "../../services/realTimeVisulization/zoneData/get3dWidgetData";
+import {
+ MoveArrowLeft,
+ MoveArrowRight,
+} from "../../components/icons/SimulationIcons";
+import { InfoIcon } from "../../components/icons/ExportCommonIcons";
+
+// Define the type for `Side`
+type Side = "top" | "bottom" | "left" | "right";
+
+interface HiddenPanels {
+ [zoneId: string]: Side[];
+}
+
+interface DisplayZoneProps {
+ zonesData: {
+ [key: string]: {
+ activeSides: Side[];
+ panelOrder: Side[];
+ lockedPanels: Side[];
+ points: [];
+ widgets: Widget[];
+ zoneId: string;
+ zoneViewPortTarget: number[];
+ zoneViewPortPosition: number[];
+ };
+ };
+ selectedZone: {
+ zoneName: string;
+ activeSides: Side[];
+ panelOrder: Side[];
+ lockedPanels: Side[];
+ zoneId: string;
+ points: [];
+ zoneViewPortTarget: number[];
+ zoneViewPortPosition: number[];
+ widgets: {
+ id: string;
+ type: string;
+ title: string;
+ panel: Side;
+ data: any;
+ }[];
+ };
+ setSelectedZone: React.Dispatch<
+ React.SetStateAction<{
+ zoneName: string;
+ activeSides: Side[];
+ panelOrder: Side[];
+ lockedPanels: Side[];
+ points: [];
+ zoneId: string;
+ zoneViewPortTarget: number[];
+ zoneViewPortPosition: number[];
+ widgets: {
+ id: string;
+ type: string;
+ title: string;
+ panel: Side;
+ data: any;
+ }[];
+ }>
+ >;
+ hiddenPanels: HiddenPanels; // Updated prop type
+ setHiddenPanels: React.Dispatch>; // Updated prop type
+}
+
+const DisplayZone: React.FC = ({
+ zonesData,
+ selectedZone,
+ setSelectedZone,
+ hiddenPanels,
+}) => {
+ // Ref for the container element
+ const containerRef = useRef(null);
+ const scrollContainerRef = useRef(null);
+
+ // State to track overflow visibility
+ const [showLeftArrow, setShowLeftArrow] = useState(false);
+ const [showRightArrow, setShowRightArrow] = useState(false);
+ const { floatingWidget, setFloatingWidget } = useFloatingWidget();
+
+ const { setSelectedChartId } = useWidgetStore();
+
+ // Function to calculate overflow state
+ const updateOverflowState = useCallback(() => {
+ const container = scrollContainerRef.current;
+ if (container) {
+ const isOverflowing = container.scrollWidth > container.clientWidth;
+ const canScrollLeft = container.scrollLeft > 0;
+ const canScrollRight =
+ container.scrollLeft + container.clientWidth < container.scrollWidth;
+
+ setShowLeftArrow(isOverflowing && canScrollLeft);
+ setShowRightArrow(isOverflowing && canScrollRight);
+ }
+ }, []);
+
+ useEffect(() => {
+ const container = scrollContainerRef.current;
+ if (!container) return;
+
+ // Initial calculation after the DOM has been rendered
+ const observer = new ResizeObserver(updateOverflowState);
+ observer.observe(container);
+
+ // Update on scroll
+ const handleScroll = () => updateOverflowState();
+ container.addEventListener("scroll", handleScroll);
+
+ // Add mouse wheel listener for horizontal scrolling
+ const handleWheel = (event: WheelEvent) => {
+ if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
+ event.preventDefault();
+ container.scrollBy({
+ left: event.deltaY * 2,
+ behavior: "smooth",
+ });
+ }
+ };
+
+ container.addEventListener("wheel", handleWheel, { passive: false });
+
+ // Initial check
+ updateOverflowState();
+
+ return () => {
+ observer.disconnect();
+ container.removeEventListener("scroll", handleScroll);
+ container.removeEventListener("wheel", handleWheel);
+ };
+ }, [updateOverflowState]);
+
+ // Handle scrolling with navigation arrows
+ const handleScrollLeft = () => {
+ const container = scrollContainerRef.current;
+ if (container) {
+ container.scrollBy({
+ left: -200,
+ behavior: "smooth",
+ });
+ }
+ };
+
+ const handleScrollRight = () => {
+ const container = scrollContainerRef.current;
+ if (container) {
+ container.scrollBy({
+ left: 200,
+ behavior: "smooth",
+ });
+ }
+ };
+
+ async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
+ try {
+ if (selectedZone?.zoneId === zoneId) {
+ return;
+ }
+ setSelectedChartId(null);
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ let response = await getSelect2dZoneData(zoneId, organization);
+ console.log("response: ", response);
+ let res = await getFloatingZoneData(zoneId, organization);
+ console.log("res: ", res);
+
+ setFloatingWidget(res);
+ // Set the selected zone in the store
+ useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
+ if (Array.isArray(res)) {
+ res.forEach((val) => {
+ useDroppedObjectsStore.getState().addObject(zoneName, val);
+ });
+ }
+
+ setSelectedZone({
+ zoneName,
+ activeSides: response.activeSides || [],
+ panelOrder: response.panelOrder || [],
+ lockedPanels: response.lockedPanels || [],
+ widgets: response.widgets || [],
+ points: response.points || [],
+ zoneId: zoneId,
+ zoneViewPortTarget: response.viewPortCenter || {},
+ zoneViewPortPosition: response.viewPortposition || {},
+ });
+ } catch (error) {}
+ }
+
+ return (
+
+ {/* Left Arrow */}
+ {showLeftArrow && (
+
+ )}
+
+ {/* Scrollable Zones Container */}
+
+ {Object.keys(zonesData).length !== 0 ? (
+ <>
+ {Object.keys(zonesData).map((zoneName, index) => (
+
+ handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)
+ }
+ >
+ {zoneName}
+
+ ))}
+ >
+ ) : (
+
+
+ No zones? Create one!
+
+ )}
+
+
+ {/* Right Arrow */}
+ {showRightArrow && (
+
+ )}
+
+ );
+};
+
+export default DisplayZone;
diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx
similarity index 67%
rename from app/src/components/ui/componets/RealTimeVisulization.tsx
rename to app/src/modules/visualization/RealTimeVisulization.tsx
index d70bfc3..6a5145f 100644
--- a/app/src/components/ui/componets/RealTimeVisulization.tsx
+++ b/app/src/modules/visualization/RealTimeVisulization.tsx
@@ -1,34 +1,39 @@
import React, { useEffect, useState, useRef } from "react";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
-import Panel from "./Panel";
-import AddButtons from "./AddButtons";
-import { useSelectedZoneStore } from "../../../store/useZoneStore";
+import { usePlayButtonStore } from "../../store/usePlayButtonStore";
+import Panel from "./widgets/panel/Panel";
+import AddButtons from "./widgets/panel/AddButtons";
+import { useSelectedZoneStore } from "../../store/useZoneStore";
import DisplayZone from "./DisplayZone";
-import Scene from "../../../modules/scene/scene";
-import useModuleStore from "../../../store/useModuleStore";
+import Scene from "../scene/scene";
+import useModuleStore from "../../store/useModuleStore";
-import { useDroppedObjectsStore, useFloatingWidget } from "../../../store/useDroppedObjectsStore";
+import {
+ useDroppedObjectsStore,
+ useFloatingWidget,
+} from "../../store/useDroppedObjectsStore";
import {
useAsset3dWidget,
useSocketStore,
useWidgetSubOption,
useZones,
-} from "../../../store/store";
-import { getZone2dData } from "../../../services/realTimeVisulization/zoneData/getZoneData";
-import { generateUniqueId } from "../../../functions/generateUniqueId";
+} from "../../store/store";
+import { getZone2dData } from "../../services/realTimeVisulization/zoneData/getZoneData";
+import { generateUniqueId } from "../../functions/generateUniqueId";
import { determinePosition } from "./functions/determinePosition";
-import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
-import SocketRealTimeViz from "../../../modules/visualization/realTimeVizSocket.dev";
-import RenderOverlay from "../../templates/Overlay";
-import ConfirmationPopup from "../../layout/confirmationPopup/ConfirmationPopup";
-import DroppedObjects from "./DroppedFloatingWidgets";
-import EditWidgetOption from "../menu/EditWidgetOption";
+import { addingFloatingWidgets } from "../../services/realTimeVisulization/zoneData/addFloatingWidgets";
+import SocketRealTimeViz from "./socket/realTimeVizSocket.dev";
+import RenderOverlay from "../../components/templates/Overlay";
+import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup";
+import DroppedObjects from "./widgets/floating/DroppedFloatingWidgets";
+import EditWidgetOption from "../../components/ui/menu/EditWidgetOption";
import {
useEditWidgetOptionsStore,
useRightClickSelected,
useRightSelected,
-} from "../../../store/useZone3DWidgetStore";
-import Dropped3dWidgets from "./Dropped3dWidget";
+} from "../../store/useZone3DWidgetStore";
+import Dropped3dWidgets from "./widgets/3d/Dropped3dWidget";
+import OuterClick from "../../utils/outerClick";
+import { useWidgetStore } from "../../store/useWidgetStore";
type Side = "top" | "bottom" | "left" | "right";
@@ -74,10 +79,22 @@ const RealTimeVisulization: React.FC = () => {
const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false);
// const [floatingWidgets, setFloatingWidgets] = useState>({});
- const { floatingWidget, setFloatingWidget } = useFloatingWidget()
+ const { floatingWidget, setFloatingWidget } = useFloatingWidget();
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption();
const { visualizationSocket } = useSocketStore();
+ const { setSelectedChartId } = useWidgetStore();
+
+ OuterClick({
+ contextClassName: [
+ "chart-container",
+ "floating",
+ "sidebar-right-wrapper",
+ "card",
+ "dropdown-menu",
+ ],
+ setMenuVisible: () => setSelectedChartId(null),
+ });
useEffect(() => {
async function GetZoneData() {
@@ -85,7 +102,7 @@ const RealTimeVisulization: React.FC = () => {
const organization = email?.split("@")[1]?.split(".")[0];
try {
const response = await getZone2dData(organization);
- console.log('response: ', response);
+ // console.log('response: ', response);
if (!Array.isArray(response)) {
return;
@@ -107,7 +124,7 @@ const RealTimeVisulization: React.FC = () => {
{}
);
setZonesData(formattedData);
- } catch (error) { }
+ } catch (error) {}
}
GetZoneData();
@@ -123,7 +140,7 @@ const RealTimeVisulization: React.FC = () => {
activeSides: selectedZone.activeSides || [],
panelOrder: selectedZone.panelOrder || [],
lockedPanels: selectedZone.lockedPanels || [],
- points:selectedZone.points||[],
+ points: selectedZone.points || [],
zoneId: selectedZone.zoneId || "",
zoneViewPortTarget: selectedZone.zoneViewPortTarget || [],
zoneViewPortPosition: selectedZone.zoneViewPortPosition || [],
@@ -138,82 +155,99 @@ const RealTimeVisulization: React.FC = () => {
const handleDrop = async (event: React.DragEvent) => {
event.preventDefault();
try {
- event.preventDefault();
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
-
+
const data = event.dataTransfer.getData("text/plain");
- // if (widgetSelect !== "") return;
if (widgetSubOption === "3D") return;
if (!data || selectedZone.zoneName === "") return;
-
+
const droppedData = JSON.parse(data);
const canvasElement = document.getElementById("real-time-vis-canvas");
if (!canvasElement) throw new Error("Canvas element not found");
-
- const canvasRect = canvasElement.getBoundingClientRect();
- const relativeX = event.clientX - canvasRect.left;
- const relativeY = event.clientY - canvasRect.top;
-
- const newPosition = determinePosition(canvasRect, relativeX, relativeY);
- console.log("newPosition: ", newPosition);
+
+ // Get canvas dimensions and mouse position
+ const rect = canvasElement.getBoundingClientRect();
+ let relativeX = (event.clientX - rect.left) ;
+ let relativeY = event.clientY - rect.top;
+
+ // Widget dimensions (with defaults)
+ const widgetWidth = droppedData.width || 125; // 250/2 as default
+ const widgetHeight = droppedData.height || 100; // 83/2 as default
+
+ // Clamp to ensure widget stays fully inside canvas
+ const clampedX = Math.max(
+ 0, // Prevent going beyond left edge
+ Math.min(
+ relativeX,
+ rect.width - widgetWidth // Prevent going beyond right edge
+ )
+ );
+
+ console.log('clampedX: ', clampedX);
+ const clampedY = Math.max(
+ 0, // Prevent going beyond top edge
+ Math.min(
+ relativeY,
+ rect.height - widgetHeight // Prevent going beyond bottom edge
+ )
+ );
+
+ // Debug logging (optional)
+ console.log("Drop coordinates:", {
+ rawX: relativeX,
+ rawY: relativeY,
+ clampedX,
+ clampedY,
+ canvasWidth: rect.width,
+ canvasHeight: rect.height,
+ widgetWidth,
+ widgetHeight
+ });
+
+ const finalPosition = determinePosition(rect, clampedX, clampedY);
+
const newObject = {
...droppedData,
id: generateUniqueId(),
- position: determinePosition(canvasRect, relativeX, relativeY),
+ position: finalPosition,
};
-
- // Only set zone if itβs not already in the store (prevents overwriting objects)
- const existingZone =
- useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
+
+ // Zone management
+ const existingZone = useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
if (!existingZone) {
- useDroppedObjectsStore
- .getState()
- .setZone(selectedZone.zoneName, selectedZone.zoneId);
+ useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId);
}
-
- let addFloatingWidget = {
- organization: organization,
+
+ // Socket emission
+ const addFloatingWidget = {
+ organization,
widget: newObject,
zoneId: selectedZone.zoneId,
};
- console.log("newObject: ", newObject);
-
+
if (visualizationSocket) {
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
}
- useDroppedObjectsStore
- .getState()
- .addObject(selectedZone.zoneName, newObject);
-
- //I need to console here objects based on selectedZone.zoneId
- // Console the objects after adding
+
+ // Store update
+ useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, newObject);
+
+ // Post-drop verification
const droppedObjectsStore = useDroppedObjectsStore.getState();
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
-
+
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
- console.log(
- `Objects for Zone ID: ${selectedZone.zoneId}`,
- currentZone.objects
- );
- setFloatingWidget(currentZone.objects)
+ console.log(`Objects for Zone ${selectedZone.zoneId}:`, currentZone.objects);
+ setFloatingWidget(currentZone.objects);
} else {
- console.warn("Zone not found or mismatched zoneId");
+ console.warn("Zone not found or zoneId mismatch");
}
-
- // let response = await addingFloatingWidgets(
- // selectedZone.zoneId,
- // organization,
- // newObject
- // );
- // Add the dropped object to the zone if the API call is successful
- // if (response.message === "FloatWidget created successfully") {
- // }
-
- // Update floating widgets state
-
- } catch (error) { }
-
+
+ } catch (error) {
+ console.error("Error in handleDrop:", error);
+ // Consider adding user feedback here (e.g., toast notification)
+ }
};
useEffect(() => {
@@ -253,7 +287,7 @@ const RealTimeVisulization: React.FC = () => {
console.log("confirm")}
+ onConfirm={() => console.log("Confirmed")}
onCancel={() => setOpenConfirmationPopup(false)}
/>
@@ -286,7 +320,6 @@ const RealTimeVisulization: React.FC = () => {
"Horizontal Move",
"RotateX",
"RotateY",
- "RotateZ",
"Delete",
]}
/>
diff --git a/app/src/modules/visualization/captureVisualization.ts b/app/src/modules/visualization/functions/captureVisualization.ts
similarity index 97%
rename from app/src/modules/visualization/captureVisualization.ts
rename to app/src/modules/visualization/functions/captureVisualization.ts
index e1fac77..d8d3ba2 100644
--- a/app/src/modules/visualization/captureVisualization.ts
+++ b/app/src/modules/visualization/functions/captureVisualization.ts
@@ -1,33 +1,33 @@
-import html2canvas from "html2canvas";
-
-export const captureVisualization = async (): Promise => {
- const container = document.getElementById("real-time-vis-canvas");
- if (!container) {
- console.error("Container element not found");
- return null;
- }
-
- try {
- // Hide any elements you don't want in the screenshot
- const originalVisibility = container.style.visibility;
- container.style.visibility = 'visible';
-
- const canvas = await html2canvas(container, {
- scale: 2, // Higher scale for better quality
- logging: false, // Disable console logging
- useCORS: true, // Handle cross-origin images
- allowTaint: true, // Allow tainted canvas
- backgroundColor: '#ffffff', // Set white background
- removeContainer: true // Clean up temporary containers
- });
-
- // Restore original visibility
- container.style.visibility = originalVisibility;
-
- // Convert to PNG with highest quality
- return canvas.toDataURL('image/png', 1.0);
- } catch (error) {
- console.error("Error capturing visualization:", error);
- return null;
- }
-};
\ No newline at end of file
+import html2canvas from "html2canvas";
+
+export const captureVisualization = async (): Promise => {
+ const container = document.getElementById("real-time-vis-canvas");
+ if (!container) {
+ console.error("Container element not found");
+ return null;
+ }
+
+ try {
+ // Hide any elements you don't want in the screenshot
+ const originalVisibility = container.style.visibility;
+ container.style.visibility = 'visible';
+
+ const canvas = await html2canvas(container, {
+ scale: 2, // Higher scale for better quality
+ logging: false, // Disable console logging
+ useCORS: true, // Handle cross-origin images
+ allowTaint: true, // Allow tainted canvas
+ backgroundColor: '#ffffff', // Set white background
+ removeContainer: true // Clean up temporary containers
+ });
+
+ // Restore original visibility
+ container.style.visibility = originalVisibility;
+
+ // Convert to PNG with highest quality
+ return canvas.toDataURL('image/png', 1.0);
+ } catch (error) {
+ console.error("Error capturing visualization:", error);
+ return null;
+ }
+};
diff --git a/app/src/components/ui/componets/functions/determinePosition.ts b/app/src/modules/visualization/functions/determinePosition.ts
similarity index 99%
rename from app/src/components/ui/componets/functions/determinePosition.ts
rename to app/src/modules/visualization/functions/determinePosition.ts
index 325ad14..05e246a 100644
--- a/app/src/components/ui/componets/functions/determinePosition.ts
+++ b/app/src/modules/visualization/functions/determinePosition.ts
@@ -74,4 +74,4 @@ export function determinePosition(
}
return position;
-}
\ No newline at end of file
+}
diff --git a/app/src/components/ui/componets/functions/getActiveProperties.ts b/app/src/modules/visualization/functions/getActiveProperties.ts
similarity index 100%
rename from app/src/components/ui/componets/functions/getActiveProperties.ts
rename to app/src/modules/visualization/functions/getActiveProperties.ts
diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/functions/handleSaveTemplate.ts
similarity index 92%
rename from app/src/modules/visualization/handleSaveTemplate.ts
rename to app/src/modules/visualization/functions/handleSaveTemplate.ts
index 3b4fc89..0b67e62 100644
--- a/app/src/modules/visualization/handleSaveTemplate.ts
+++ b/app/src/modules/visualization/functions/handleSaveTemplate.ts
@@ -1,97 +1,97 @@
-import { Template } from "../../store/useTemplateStore";
-import { captureVisualization } from "./captureVisualization";
-
-type HandleSaveTemplateProps = {
- addTemplate: (template: Template) => void;
- floatingWidget: []; // Updated type from `[]` to `any[]` for clarity
- widgets3D: []; // Updated type from `[]` to `any[]` for clarity
- selectedZone: {
- panelOrder: string[];
- widgets: any[];
- };
- templates?: Template[];
- visualizationSocket: any;
-};
-
-// Generate a unique ID
-const generateUniqueId = (): string => {
- return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
-};
-
-export const handleSaveTemplate = async ({
- addTemplate,
- floatingWidget,
- widgets3D,
- selectedZone,
- templates = [],
- visualizationSocket,
-}: HandleSaveTemplateProps): Promise => {
- console.log("floatingWidget: ", floatingWidget);
- try {
- // Check if the selected zone has any widgets
- if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) {
- return;
- }
-
- // Check if the template already exists
- const isDuplicate = templates.some(
- (template) =>
- JSON.stringify(template.panelOrder) ===
- JSON.stringify(selectedZone.panelOrder) &&
- JSON.stringify(template.widgets) ===
- JSON.stringify(selectedZone.widgets)
- );
-
- if (isDuplicate) {
- return;
- }
-
- // Capture visualization snapshot
- const snapshot = await captureVisualization();
-
- if (!snapshot) {
- return;
- }
-
- // Create a new template
- const newTemplate: Template = {
- id: generateUniqueId(),
- name: `Template ${new Date().toISOString()}`, // Better name formatting
- panelOrder: selectedZone.panelOrder,
- widgets: selectedZone.widgets,
- snapshot,
- floatingWidget,
- widgets3D,
- };
-
- // Extract organization from email
- const email = localStorage.getItem("email") || "";
- const organization = email.includes("@")
- ? email.split("@")[1]?.split(".")[0]
- : "";
-
- if (!organization) {
- return;
- }
- let saveTemplate = {
- organization: organization,
- template: newTemplate,
- };
- if (visualizationSocket) {
- visualizationSocket.emit("v2:viz-template:add", saveTemplate);
- }
-
- // Save the template
- try {
- addTemplate(newTemplate);
- // const response = await saveTemplateApi(organization, newTemplate);
- //
-
- // Add template only if API call succeeds
- } catch (apiError) {
- //
- }
- } catch (error) {
- //
- }
-};
+import { Template } from "../../../store/useTemplateStore";
+import { captureVisualization } from "./captureVisualization";
+
+type HandleSaveTemplateProps = {
+ addTemplate: (template: Template) => void;
+ floatingWidget: []; // Updated type from `[]` to `any[]` for clarity
+ widgets3D: []; // Updated type from `[]` to `any[]` for clarity
+ selectedZone: {
+ panelOrder: string[];
+ widgets: any[];
+ };
+ templates?: Template[];
+ visualizationSocket: any;
+};
+
+// Generate a unique ID
+const generateUniqueId = (): string => {
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
+};
+
+export const handleSaveTemplate = async ({
+ addTemplate,
+ floatingWidget,
+ widgets3D,
+ selectedZone,
+ templates = [],
+ visualizationSocket,
+}: HandleSaveTemplateProps): Promise => {
+ try {
+ // Check if the selected zone has any widgets
+ if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) {
+ return;
+ }
+
+ // Check if the template already exists
+ const isDuplicate = templates.some(
+ (template) =>
+ JSON.stringify(template.panelOrder) ===
+ JSON.stringify(selectedZone.panelOrder) &&
+ JSON.stringify(template.widgets) ===
+ JSON.stringify(selectedZone.widgets)
+ );
+
+ if (isDuplicate) {
+ return;
+ }
+
+ // Capture visualization snapshot
+ const snapshot = await captureVisualization();
+
+
+ if (!snapshot) {
+ return;
+ }
+
+ // Create a new template
+ const newTemplate: Template = {
+ id: generateUniqueId(),
+ name: `Template ${new Date().toISOString()}`, // Better name formatting
+ panelOrder: selectedZone.panelOrder,
+ widgets: selectedZone.widgets,
+ snapshot,
+ floatingWidget,
+ widgets3D,
+ };
+
+ // Extract organization from email
+ const email = localStorage.getItem("email") || "";
+ const organization = email.includes("@")
+ ? email.split("@")[1]?.split(".")[0]
+ : "";
+
+ if (!organization) {
+ return;
+ }
+ let saveTemplate = {
+ organization: organization,
+ template: newTemplate,
+ };
+ if (visualizationSocket) {
+ visualizationSocket.emit("v2:viz-template:add", saveTemplate);
+ }
+
+ // Save the template
+ try {
+ addTemplate(newTemplate);
+ // const response = await saveTemplateApi(organization, newTemplate);
+ //
+
+ // Add template only if API call succeeds
+ } catch (apiError) {
+ //
+ }
+ } catch (error) {
+ //
+ }
+};
diff --git a/app/src/components/ui/componets/functions/handleWidgetsOuterClick.ts b/app/src/modules/visualization/functions/handleWidgetsOuterClick.ts
similarity index 100%
rename from app/src/components/ui/componets/functions/handleWidgetsOuterClick.ts
rename to app/src/modules/visualization/functions/handleWidgetsOuterClick.ts
diff --git a/app/src/modules/visualization/realTimeVizSocket.dev.tsx b/app/src/modules/visualization/realTimeVizSocket.dev.tsx
deleted file mode 100644
index f1abaaa..0000000
--- a/app/src/modules/visualization/realTimeVizSocket.dev.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { useEffect } from "react";
-import { useSocketStore } from "../../store/store";
-import { useSelectedZoneStore } from "../../store/useZoneStore";
-import { useDroppedObjectsStore } from "../../store/useDroppedObjectsStore";
-import { useZoneWidgetStore } from "../../store/useZone3DWidgetStore";
-import useTemplateStore from "../../store/useTemplateStore";
-
-type WidgetData = {
- id: string;
- type: string;
- position: [number, number, number];
- rotation?: [number, number, number];
- tempPosition?: [number, number, number];
-};
-
-export default function SocketRealTimeViz() {
- const { visualizationSocket } = useSocketStore();
- const { setSelectedZone } = useSelectedZoneStore();
- const deleteObject = useDroppedObjectsStore((state) => state.deleteObject);
- const updateObjectPosition = useDroppedObjectsStore((state) => state.updateObjectPosition);
- const { addWidget } = useZoneWidgetStore()
- const { removeTemplate } = useTemplateStore();
- const { setTemplates } = useTemplateStore();
- const { zoneWidgetData, setZoneWidgetData, updateWidgetPosition, updateWidgetRotation } = useZoneWidgetStore();
-
- useEffect(() => {
- if (visualizationSocket) {
- //add panel response
- visualizationSocket.on("viz-panel:response:updates", (addPanel: any) => {
-
- if (addPanel.success) {
- let addPanelData = addPanel.data.data
- setSelectedZone(addPanelData)
- }
- })
- //delete panel response
- visualizationSocket.on("viz-panel:response:delete", (deletePanel: any) => {
-
- if (deletePanel.success) {
- let deletePanelData = deletePanel.data.data
- setSelectedZone(deletePanelData)
- }
- })
- //clear Panel response
- visualizationSocket.on("viz-panel:response:clear", (clearPanel: any) => {
-
- if (clearPanel.success && clearPanel.message === "PanelWidgets cleared successfully") {
-
- let clearPanelData = clearPanel.data.data
- setSelectedZone(clearPanelData)
- }
- })
- //lock Panel response
- visualizationSocket.on("viz-panel:response:locked", (lockPanel: any) => {
-
- if (lockPanel.success && lockPanel.message === "locked panel updated successfully") {
-
- let lockPanelData = lockPanel.data.data
- setSelectedZone(lockPanelData)
- }
- })
- // add 2dWidget response
- visualizationSocket.on("viz-widget:response:updates", (add2dWidget: any) => {
-
-
- if (add2dWidget.success && add2dWidget.data) {
- setSelectedZone((prev) => {
- const isWidgetAlreadyAdded = prev.widgets.some(
- (widget) => widget.id === add2dWidget.data.widgetData.id
- );
- if (isWidgetAlreadyAdded) return prev; // Prevent duplicate addition
- return {
- ...prev,
- zoneId: add2dWidget.data.zoneId,
- zoneName: add2dWidget.data.zoneName,
- widgets: [...prev.widgets, add2dWidget.data.widgetData], // Append new widget
- };
- });
- }
- });
- //delete 2D Widget response
- visualizationSocket.on("viz-widget:response:delete", (deleteWidget: any) => {
-
-
- if (deleteWidget?.success && deleteWidget.data) {
- setSelectedZone((prevZone: any) => ({
- ...prevZone,
- zoneId: deleteWidget.data.zoneId,
- zoneName: deleteWidget.data.zoneName,
- widgets: deleteWidget.data.widgetDeleteDatas, // Replace with new widget list
- }));
- }
- });
- //add Floating Widget response
- visualizationSocket.on("viz-float:response:updates", (addFloatingWidget: any) => {
-
-
- if (addFloatingWidget.success) {
- if (addFloatingWidget.success && addFloatingWidget.message === "FloatWidget created successfully") {
- const state = useDroppedObjectsStore.getState();
- const zone = state.zones[addFloatingWidget.data.zoneName];
- if (!zone) {
- state.setZone(addFloatingWidget.data.zoneName, addFloatingWidget.data.zoneId);
- }
- const existingObjects = zone ? zone.objects : [];
- const newWidget = addFloatingWidget.data.widget;
- // β
Check if the widget ID already exists before adding
- const isAlreadyAdded = existingObjects.some(obj => obj.id === newWidget.id);
- if (isAlreadyAdded) {
-
- return; // Don't add the widget if it already exists
- }
- // Add widget only if it doesn't exist
- state.addObject(addFloatingWidget.data.zoneName, newWidget);
- }
- if (addFloatingWidget.message === "Widget updated successfully") {
- updateObjectPosition(addFloatingWidget.data.zoneName, addFloatingWidget.data.index, addFloatingWidget.data.position);
- }
- }
- });
- //duplicate Floating Widget response
- visualizationSocket.on("viz-float:response:addDuplicate", (duplicateFloatingWidget: any) => {
-
-
- if (duplicateFloatingWidget.success && duplicateFloatingWidget.message === "duplicate FloatWidget created successfully") {
- useDroppedObjectsStore.setState((state) => {
- const zone = state.zones[duplicateFloatingWidget.data.zoneName];
- if (!zone) return state; // Zone doesn't exist, return state as is
- const existingObjects = zone.objects;
- const newWidget = duplicateFloatingWidget.data.widget;
- // β
Check if the object with the same ID already exists
- const isAlreadyAdded = existingObjects.some(obj => obj.id === newWidget.id);
- if (isAlreadyAdded) {
-
- return state; // Don't update state if it's already there
- }
- return {
- zones: {
- ...state.zones,
- [duplicateFloatingWidget.data.zoneName]: {
- ...zone,
- objects: [...existingObjects, newWidget], // Append only if it's not a duplicate
- },
- },
- };
- });
- }
- });
- //delete Floating Widget response
- visualizationSocket.on("viz-float:response:delete", (deleteFloatingWidget: any) => {
-
-
- if (deleteFloatingWidget.success) {
- deleteObject(deleteFloatingWidget.data.zoneName, deleteFloatingWidget.data.floatWidgetID);
- }
- });
- //add 3D Widget response
- visualizationSocket.on("viz-widget3D:response:updates", (add3DWidget: any) => {
-
-
- if (add3DWidget.success) {
-
- if (add3DWidget.message === "Widget created successfully") {
- addWidget(add3DWidget.data.zoneId, add3DWidget.data.widget);
- }
- }
- });
- //delete 3D Widget response
- visualizationSocket.on("viz-widget3D:response:delete", (delete3DWidget: any) => {
-
- // "3DWidget delete unsuccessfull"
- if (delete3DWidget.success && delete3DWidget.message === "3DWidget delete successfull") {
- const activeZoneWidgets = zoneWidgetData[delete3DWidget.data.zoneId] || [];
- setZoneWidgetData(
- delete3DWidget.data.zoneId,
- activeZoneWidgets.filter((w: WidgetData) => w.id !== delete3DWidget.data.id)
- );
- }
- });
- //update3D widget response
- visualizationSocket.on("viz-widget3D:response:modifyPositionRotation", (update3DWidget: any) => {
-
-
- if (update3DWidget.success && update3DWidget.message === "widget update successfully") {
- updateWidgetPosition(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.position);
- updateWidgetRotation(update3DWidget.data.zoneId, update3DWidget.data.widget.id, update3DWidget.data.widget.rotation);
- }
- });
- // add Template response
- visualizationSocket.on("viz-template:response:add", (addingTemplate: any) => {
-
-
- if (addingTemplate.success) {
- if (addingTemplate.message === "Template saved successfully") {
- setTemplates(addingTemplate.data);
- }
- }
- });
- //load Template response
- visualizationSocket.on("viz-template:response:addTemplateZone", (loadTemplate: any) => {
-
-
- if (loadTemplate.success) {
- if (loadTemplate.message === "Template placed in Zone") {
- let template = loadTemplate.data.template
- setSelectedZone({
- panelOrder: template.panelOrder,
- activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides`
- widgets: template.widgets,
- });
- useDroppedObjectsStore.getState().setZone(template.zoneName, template.zoneId);
-
- if (Array.isArray(template.floatingWidget)) {
- template.floatingWidget.forEach((val: any) => {
- useDroppedObjectsStore.getState().addObject(template.zoneName, val);
- });
- }
- }
-
- }
- });
- //delete Template response
- visualizationSocket.on("viz-template:response:delete", (deleteTemplate: any) => {
-
-
- if (deleteTemplate.success) {
- if (deleteTemplate.message === 'Template deleted successfully') {
- removeTemplate(deleteTemplate.data);
- }
- }
- });
- }
- }, [visualizationSocket])
-
- return (
- <>>
- )
-}
\ No newline at end of file
diff --git a/app/src/modules/visualization/socket/realTimeVizSocket.dev.tsx b/app/src/modules/visualization/socket/realTimeVizSocket.dev.tsx
new file mode 100644
index 0000000..b5291a3
--- /dev/null
+++ b/app/src/modules/visualization/socket/realTimeVizSocket.dev.tsx
@@ -0,0 +1,296 @@
+import { useEffect } from "react";
+import { useSocketStore } from "../../../store/store";
+import { useSelectedZoneStore } from "../../../store/useZoneStore";
+import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
+import { useZoneWidgetStore } from "../../../store/useZone3DWidgetStore";
+import useTemplateStore from "../../../store/useTemplateStore";
+
+type WidgetData = {
+ id: string;
+ type: string;
+ position: [number, number, number];
+ rotation?: [number, number, number];
+ tempPosition?: [number, number, number];
+};
+
+export default function SocketRealTimeViz() {
+ const { visualizationSocket } = useSocketStore();
+ const { setSelectedZone } = useSelectedZoneStore();
+ const deleteObject = useDroppedObjectsStore((state) => state.deleteObject);
+ const updateObjectPosition = useDroppedObjectsStore(
+ (state) => state.updateObjectPosition
+ );
+ const { addWidget } = useZoneWidgetStore();
+ const { removeTemplate } = useTemplateStore();
+ const { setTemplates } = useTemplateStore();
+ const {
+ zoneWidgetData,
+ setZoneWidgetData,
+ updateWidgetPosition,
+ updateWidgetRotation,
+ } = useZoneWidgetStore();
+
+ useEffect(() => {
+ if (visualizationSocket) {
+ //add panel response
+ visualizationSocket.on("viz-panel:response:updates", (addPanel: any) => {
+ if (addPanel.success) {
+ let addPanelData = addPanel.data.data;
+ setSelectedZone(addPanelData);
+ }
+ });
+ //delete panel response
+ visualizationSocket.on(
+ "viz-panel:response:delete",
+ (deletePanel: any) => {
+ if (deletePanel.success) {
+ let deletePanelData = deletePanel.data.data;
+ setSelectedZone(deletePanelData);
+ }
+ }
+ );
+ //clear Panel response
+ visualizationSocket.on("viz-panel:response:clear", (clearPanel: any) => {
+ if (
+ clearPanel.success &&
+ clearPanel.message === "PanelWidgets cleared successfully"
+ ) {
+ let clearPanelData = clearPanel.data.data;
+ setSelectedZone(clearPanelData);
+ }
+ });
+ //lock Panel response
+ visualizationSocket.on("viz-panel:response:locked", (lockPanel: any) => {
+ if (
+ lockPanel.success &&
+ lockPanel.message === "locked panel updated successfully"
+ ) {
+ let lockPanelData = lockPanel.data.data;
+ setSelectedZone(lockPanelData);
+ }
+ });
+ // add 2dWidget response
+ visualizationSocket.on(
+ "viz-widget:response:updates",
+ (add2dWidget: any) => {
+ if (add2dWidget.success && add2dWidget.data) {
+ setSelectedZone((prev) => {
+ const isWidgetAlreadyAdded = prev.widgets.some(
+ (widget) => widget.id === add2dWidget.data.widgetData.id
+ );
+ if (isWidgetAlreadyAdded) return prev; // Prevent duplicate addition
+ return {
+ ...prev,
+ zoneId: add2dWidget.data.zoneId,
+ zoneName: add2dWidget.data.zoneName,
+ widgets: [...prev.widgets, add2dWidget.data.widgetData], // Append new widget
+ };
+ });
+ }
+ }
+ );
+ //delete 2D Widget response
+ visualizationSocket.on(
+ "viz-widget:response:delete",
+ (deleteWidget: any) => {
+ if (deleteWidget?.success && deleteWidget.data) {
+ setSelectedZone((prevZone: any) => ({
+ ...prevZone,
+ zoneId: deleteWidget.data.zoneId,
+ zoneName: deleteWidget.data.zoneName,
+ widgets: deleteWidget.data.widgetDeleteDatas, // Replace with new widget list
+ }));
+ }
+ }
+ );
+ //add Floating Widget response
+ visualizationSocket.on(
+ "viz-float:response:updates",
+ (addFloatingWidget: any) => {
+ if (addFloatingWidget.success) {
+ if (
+ addFloatingWidget.success &&
+ addFloatingWidget.message === "FloatWidget created successfully"
+ ) {
+ const state = useDroppedObjectsStore.getState();
+ const zone = state.zones[addFloatingWidget.data.zoneName];
+ if (!zone) {
+ state.setZone(
+ addFloatingWidget.data.zoneName,
+ addFloatingWidget.data.zoneId
+ );
+ }
+ const existingObjects = zone ? zone.objects : [];
+ const newWidget = addFloatingWidget.data.widget;
+ // β
Check if the widget ID already exists before adding
+ const isAlreadyAdded = existingObjects.some(
+ (obj) => obj.id === newWidget.id
+ );
+ if (isAlreadyAdded) {
+ return; // Don't add the widget if it already exists
+ }
+ // Add widget only if it doesn't exist
+ state.addObject(addFloatingWidget.data.zoneName, newWidget);
+ }
+ if (addFloatingWidget.message === "Widget updated successfully") {
+ updateObjectPosition(
+ addFloatingWidget.data.zoneName,
+ addFloatingWidget.data.index,
+ addFloatingWidget.data.position
+ );
+ }
+ }
+ }
+ );
+ //duplicate Floating Widget response
+ visualizationSocket.on(
+ "viz-float:response:addDuplicate",
+ (duplicateFloatingWidget: any) => {
+ if (
+ duplicateFloatingWidget.success &&
+ duplicateFloatingWidget.message ===
+ "duplicate FloatWidget created successfully"
+ ) {
+ useDroppedObjectsStore.setState((state) => {
+ const zone = state.zones[duplicateFloatingWidget.data.zoneName];
+ if (!zone) return state; // Zone doesn't exist, return state as is
+ const existingObjects = zone.objects;
+ const newWidget = duplicateFloatingWidget.data.widget;
+ // β
Check if the object with the same ID already exists
+ const isAlreadyAdded = existingObjects.some(
+ (obj) => obj.id === newWidget.id
+ );
+ if (isAlreadyAdded) {
+ return state; // Don't update state if it's already there
+ }
+ return {
+ zones: {
+ ...state.zones,
+ [duplicateFloatingWidget.data.zoneName]: {
+ ...zone,
+ objects: [...existingObjects, newWidget], // Append only if it's not a duplicate
+ },
+ },
+ };
+ });
+ }
+ }
+ );
+ //delete Floating Widget response
+ visualizationSocket.on(
+ "viz-float:response:delete",
+ (deleteFloatingWidget: any) => {
+ if (deleteFloatingWidget.success) {
+ deleteObject(
+ deleteFloatingWidget.data.zoneName,
+ deleteFloatingWidget.data.floatWidgetID
+ );
+ }
+ }
+ );
+ //add 3D Widget response
+ visualizationSocket.on(
+ "viz-widget3D:response:updates",
+ (add3DWidget: any) => {
+ if (add3DWidget.success) {
+ if (add3DWidget.message === "Widget created successfully") {
+ addWidget(add3DWidget.data.zoneId, add3DWidget.data.widget);
+ }
+ }
+ }
+ );
+ //delete 3D Widget response
+ visualizationSocket.on(
+ "viz-widget3D:response:delete",
+ (delete3DWidget: any) => {
+ // "3DWidget delete unsuccessfull"
+ if (
+ delete3DWidget.success &&
+ delete3DWidget.message === "3DWidget delete successfull"
+ ) {
+ const activeZoneWidgets =
+ zoneWidgetData[delete3DWidget.data.zoneId] || [];
+ setZoneWidgetData(
+ delete3DWidget.data.zoneId,
+ activeZoneWidgets.filter(
+ (w: WidgetData) => w.id !== delete3DWidget.data.id
+ )
+ );
+ }
+ }
+ );
+ //update3D widget response
+ visualizationSocket.on(
+ "viz-widget3D:response:modifyPositionRotation",
+ (update3DWidget: any) => {
+ if (
+ update3DWidget.success &&
+ update3DWidget.message === "widget update successfully"
+ ) {
+ updateWidgetPosition(
+ update3DWidget.data.zoneId,
+ update3DWidget.data.widget.id,
+ update3DWidget.data.widget.position
+ );
+ updateWidgetRotation(
+ update3DWidget.data.zoneId,
+ update3DWidget.data.widget.id,
+ update3DWidget.data.widget.rotation
+ );
+ }
+ }
+ );
+ // add Template response
+ visualizationSocket.on(
+ "viz-template:response:add",
+ (addingTemplate: any) => {
+ if (addingTemplate.success) {
+ if (addingTemplate.message === "Template saved successfully") {
+ setTemplates(addingTemplate.data);
+ }
+ }
+ }
+ );
+ //load Template response
+ visualizationSocket.on(
+ "viz-template:response:addTemplateZone",
+ (loadTemplate: any) => {
+ if (loadTemplate.success) {
+ if (loadTemplate.message === "Template placed in Zone") {
+ let template = loadTemplate.data.template;
+ setSelectedZone({
+ panelOrder: template.panelOrder,
+ activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides`
+ widgets: template.widgets,
+ });
+ useDroppedObjectsStore
+ .getState()
+ .setZone(template.zoneName, template.zoneId);
+
+ if (Array.isArray(template.floatingWidget)) {
+ template.floatingWidget.forEach((val: any) => {
+ useDroppedObjectsStore
+ .getState()
+ .addObject(template.zoneName, val);
+ });
+ }
+ }
+ }
+ }
+ );
+ //delete Template response
+ visualizationSocket.on(
+ "viz-template:response:delete",
+ (deleteTemplate: any) => {
+ if (deleteTemplate.success) {
+ if (deleteTemplate.message === "Template deleted successfully") {
+ removeTemplate(deleteTemplate.data);
+ }
+ }
+ }
+ );
+ }
+ }, [visualizationSocket]);
+
+ return <>>;
+}
diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/modules/visualization/template/Templates.tsx
similarity index 81%
rename from app/src/components/layout/sidebarLeft/visualization/Templates.tsx
rename to app/src/modules/visualization/template/Templates.tsx
index efcbcaf..8370112 100644
--- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx
+++ b/app/src/modules/visualization/template/Templates.tsx
@@ -1,129 +1,135 @@
-import { useEffect } from "react";
-import { useDroppedObjectsStore } from "../../../../store/useDroppedObjectsStore";
-import useTemplateStore from "../../../../store/useTemplateStore";
-import { useSelectedZoneStore } from "../../../../store/useZoneStore";
-import { getTemplateData } from "../../../../services/realTimeVisulization/zoneData/getTemplate";
-import { useSocketStore } from "../../../../store/store";
-import RenameInput from "../../../ui/inputs/RenameInput";
-
-const Templates = () => {
- const { templates, removeTemplate, setTemplates } = useTemplateStore();
- const { setSelectedZone, selectedZone } = useSelectedZoneStore();
- const { visualizationSocket } = useSocketStore();
-
- useEffect(() => {
- async function templateData() {
- try {
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- let response = await getTemplateData(organization);
- setTemplates(response);
- } catch (error) {
- console.error("Error fetching template data:", error);
- }
- }
-
- templateData();
- }, []);
-
- const handleDeleteTemplate = async (id: string) => {
- try {
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- let deleteTemplate = {
- organization: organization,
- templateID: id,
- };
- if (visualizationSocket) {
- visualizationSocket.emit(
- "v2:viz-template:deleteTemplate",
- deleteTemplate
- );
- }
- removeTemplate(id);
- } catch (error) {
- console.error("Error deleting template:", error);
- }
- };
-
- const handleLoadTemplate = async (template: any) => {
- try {
- if (selectedZone.zoneName === "") return;
-
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
-
- let loadingTemplate = {
- organization: organization,
- zoneId: selectedZone.zoneId,
- templateID: template.id,
- };
-
- if (visualizationSocket) {
- visualizationSocket.emit("v2:viz-template:addToZone", loadingTemplate);
- }
-
- setSelectedZone({
- panelOrder: template.panelOrder,
- activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides`
- widgets: template.widgets,
- });
-
- useDroppedObjectsStore
- .getState()
- .setZone(selectedZone.zoneName, selectedZone.zoneId);
-
- if (Array.isArray(template.floatingWidget)) {
- template.floatingWidget.forEach((val: any) => {
- useDroppedObjectsStore
- .getState()
- .addObject(selectedZone.zoneName, val);
- });
- }
- } catch (error) {
- console.error("Error loading template:", error);
- }
- };
-
- return (
-
- {templates.map((template, index) => (
-
handleLoadTemplate(template)}
- >
- {template?.snapshot && (
-
-

-
- )}
-
-
- {/* {`Template ${index + 1}`} */}
-
-
-
-
-
- ))}
- {templates.length === 0 && (
-
- No saved templates yet. Create one in the visualization view!
-
- )}
-
- );
-};
-
-export default Templates;
+import { useEffect } from "react";
+import useTemplateStore from "../../../store/useTemplateStore";
+import { useSelectedZoneStore } from "../../../store/useZoneStore";
+import { useSocketStore } from "../../../store/store";
+import { getTemplateData } from "../../../services/realTimeVisulization/zoneData/getTemplate";
+import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
+import RenameInput from "../../../components/ui/inputs/RenameInput";
+
+
+
+const Templates = () => {
+ const { templates, removeTemplate, setTemplates } = useTemplateStore();
+ const { setSelectedZone, selectedZone } = useSelectedZoneStore();
+ const { visualizationSocket } = useSocketStore();
+
+ useEffect(() => {
+ async function templateData() {
+ try {
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ let response = await getTemplateData(organization);
+ setTemplates(response);
+ } catch (error) {
+ console.error("Error fetching template data:", error);
+ }
+ }
+
+ templateData();
+ }, []);
+
+ const handleDeleteTemplate = async (
+ e: React.MouseEvent,
+ id: string
+ ) => {
+ try {
+ e.stopPropagation();
+ const email = localStorage.getItem("email") || "";
+
+ const organization = email?.split("@")[1]?.split(".")[0];
+ let deleteTemplate = {
+ organization: organization,
+ templateID: id,
+ };
+ if (visualizationSocket) {
+ visualizationSocket.emit(
+ "v2:viz-template:deleteTemplate",
+ deleteTemplate
+ );
+ }
+ removeTemplate(id);
+ } catch (error) {
+ console.error("Error deleting template:", error);
+ }
+ };
+
+ const handleLoadTemplate = async (template: any) => {
+ try {
+ if (selectedZone.zoneName === "") return;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+
+ let loadingTemplate = {
+ organization: organization,
+ zoneId: selectedZone.zoneId,
+ templateID: template.id,
+ };
+
+ if (visualizationSocket) {
+ visualizationSocket.emit("v2:viz-template:addToZone", loadingTemplate);
+ }
+
+ setSelectedZone({
+ panelOrder: template.panelOrder,
+ activeSides: Array.from(new Set(template.panelOrder)), // No merging with previous `activeSides`
+ widgets: template.widgets,
+ });
+
+ useDroppedObjectsStore
+ .getState()
+ .setZone(selectedZone.zoneName, selectedZone.zoneId);
+
+ if (Array.isArray(template.floatingWidget)) {
+ template.floatingWidget.forEach((val: any) => {
+ useDroppedObjectsStore
+ .getState()
+ .addObject(selectedZone.zoneName, val);
+ });
+ }
+ } catch (error) {
+ console.error("Error loading template:", error);
+ }
+ };
+
+ return (
+
+ {templates.map((template, index) => (
+
+ {template?.snapshot && (
+
handleLoadTemplate(template)}>
+

+
+ )}
+
+
+ {/* {`Template ${index + 1}`} */}
+
+
+
+
+
+ ))}
+ {templates.length === 0 && (
+
+ No saved templates yet. Create one in the visualization view!
+
+ )}
+
+ );
+};
+
+export default Templates;
diff --git a/app/src/components/ui/componets/DraggableWidget.tsx b/app/src/modules/visualization/widgets/2d/DraggableWidget.tsx
similarity index 80%
rename from app/src/components/ui/componets/DraggableWidget.tsx
rename to app/src/modules/visualization/widgets/2d/DraggableWidget.tsx
index e0c8773..3b9e8d7 100644
--- a/app/src/components/ui/componets/DraggableWidget.tsx
+++ b/app/src/modules/visualization/widgets/2d/DraggableWidget.tsx
@@ -1,24 +1,25 @@
-import { useWidgetStore } from "../../../store/useWidgetStore";
-import ProgressCard from "../realTimeVis/charts/ProgressCard";
-import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent";
-import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent";
-import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent";
-import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent";
-import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent";
-import ProgressCard1 from "../realTimeVis/charts/ProgressCard1";
-import ProgressCard2 from "../realTimeVis/charts/ProgressCard2";
+import { useWidgetStore } from "../../../../store/useWidgetStore";
+import ProgressCard from "../2d/charts/ProgressCard";
+import PieGraphComponent from "../2d/charts/PieGraphComponent";
+import BarGraphComponent from "../2d/charts/BarGraphComponent";
+import LineGraphComponent from "../2d/charts/LineGraphComponent";
+import DoughnutGraphComponent from "../2d/charts/DoughnutGraphComponent";
+import PolarAreaGraphComponent from "../2d/charts/PolarAreaGraphComponent";
+import ProgressCard1 from "../2d/charts/ProgressCard1";
+import ProgressCard2 from "../2d/charts/ProgressCard2";
import {
DeleteIcon,
DublicateIcon,
KebabIcon,
-} from "../../icons/ExportCommonIcons";
+} from "../../../../components/icons/ExportCommonIcons";
import { useEffect, useRef, useState } from "react";
-import { duplicateWidgetApi } from "../../../services/realTimeVisulization/zoneData/duplicateWidget";
-import { deleteWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteWidgetApi";
-import { useClickOutside } from "./functions/handleWidgetsOuterClick";
-import { useSocketStore } from "../../../store/store";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
-import OuterClick from "../../../utils/outerClick";
+import { duplicateWidgetApi } from "../../../../services/realTimeVisulization/zoneData/duplicateWidget";
+import { deleteWidgetApi } from "../../../../services/realTimeVisulization/zoneData/deleteWidgetApi";
+import { useClickOutside } from "../../functions/handleWidgetsOuterClick";
+import { useSocketStore } from "../../../../store/store";
+import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
+import OuterClick from "../../../../utils/outerClick";
+import useChartStore from "../../../../store/useChartStore";
type Side = "top" | "bottom" | "left" | "right";
@@ -44,7 +45,7 @@ export const DraggableWidget = ({
zoneName: string;
zoneId: string;
activeSides: Side[];
- points:[];
+ points: [];
panelOrder: Side[];
lockedPanels: Side[];
widgets: Widget[];
@@ -54,7 +55,7 @@ export const DraggableWidget = ({
zoneName: string;
activeSides: Side[];
panelOrder: Side[];
- points:[];
+ points: [];
lockedPanels: Side[];
zoneId: string;
zoneViewPortTarget: number[];
@@ -81,6 +82,16 @@ export const DraggableWidget = ({
const [panelDimensions, setPanelDimensions] = useState<{
[side in Side]?: { width: number; height: number };
}>({});
+ const { measurements, duration, name } = useChartStore();
+ const { isPlaying } = usePlayButtonStore();
+
+ const [canvasDimensions, setCanvasDimensions] = useState({
+ width: 0,
+ height: 0,
+ });
+ useEffect(() => {
+ console.log("changes loggggg", measurements, duration, name);
+ }, [measurements, duration, name])
const handlePointerDown = () => {
if (selectedChartId?.id !== widget.id) {
setSelectedChartId(widget);
@@ -89,17 +100,6 @@ export const DraggableWidget = ({
const chartWidget = useRef(null);
- OuterClick({
- contextClassName: [
- "chart-container",
- "floating",
- "sidebar-right-wrapper",
- "card",
- "dropdown-menu",
- ],
- setMenuVisible: () => setSelectedChartId(null),
- });
-
const deleteSelectedChart = async () => {
try {
const email = localStorage.getItem("email") || "";
@@ -139,43 +139,53 @@ export const DraggableWidget = ({
}
};
+ // Calculate panel size
+ const panelSize = Math.max(
+ Math.min(canvasDimensions.width * 0.25, canvasDimensions.height * 0.25),
+ 170 // Min 170px
+ );
+
const getCurrentWidgetCount = (panel: Side) =>
selectedZone.widgets.filter((w) => w.panel === panel).length;
-
+ // Calculate panel capacity
+
const calculatePanelCapacity = (panel: Side) => {
- const CHART_WIDTH = 150;
- const CHART_HEIGHT = 150;
- const FALLBACK_HORIZONTAL_CAPACITY = 5;
- const FALLBACK_VERTICAL_CAPACITY = 3;
+ const CHART_WIDTH = panelSize;
+ const CHART_HEIGHT = panelSize;
const dimensions = panelDimensions[panel];
if (!dimensions) {
- return panel === "top" || panel === "bottom"
- ? FALLBACK_HORIZONTAL_CAPACITY
- : FALLBACK_VERTICAL_CAPACITY;
+ return panel === "top" || panel === "bottom" ? 5 : 3; // Fallback capacities
}
return panel === "top" || panel === "bottom"
- ? Math.floor(dimensions.width / CHART_WIDTH)
- : Math.floor(dimensions.height / CHART_HEIGHT);
+ ? Math.max(1, Math.floor(dimensions.width / CHART_WIDTH))
+ : Math.max(1, Math.floor(dimensions.height / CHART_HEIGHT));
};
const isPanelFull = (panel: Side) => {
const currentWidgetCount = getCurrentWidgetCount(panel);
const panelCapacity = calculatePanelCapacity(panel);
- return currentWidgetCount >= panelCapacity;
+
+ return currentWidgetCount > panelCapacity;
};
const duplicateWidget = async () => {
try {
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
+ console.log("widget data sent", widget);
+
const duplicatedWidget: Widget = {
...widget,
+ Data: {
+ duration: duration,
+ measurements: { ...measurements }
+ },
id: `${widget.id}-copy-${Date.now()}`,
};
- console.log('duplicatedWidget: ', duplicatedWidget);
+ console.log("duplicatedWidget: ", duplicatedWidget);
let duplicateWidget = {
organization: organization,
@@ -183,6 +193,8 @@ export const DraggableWidget = ({
widget: duplicatedWidget,
};
if (visualizationSocket) {
+ console.log("duplecate widget", duplicateWidget);
+
visualizationSocket.emit("v2:viz-widget:add", duplicateWidget);
}
setSelectedZone((prevZone: any) => ({
@@ -255,12 +267,7 @@ export const DraggableWidget = ({
// useClickOutside(chartWidget, () => {
// setSelectedChartId(null);
// });
- const { isPlaying } = usePlayButtonStore();
- const [canvasDimensions, setCanvasDimensions] = useState({
- width: 0,
- height: 0,
- });
// Track canvas dimensions
// Current: Two identical useEffect hooks for canvas dimensions
@@ -298,9 +305,8 @@ export const DraggableWidget = ({
@@ -402,5 +407,3 @@ export const DraggableWidget = ({
>
);
};
-
-// by using canvasDimensions.height canvasDimensions.width dynamically div value insted of static 6 and 4 calculate according to canvasDimensions.width canvasDimensions.height
diff --git a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx
similarity index 94%
rename from app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx
index f6589a2..4f3cfd9 100644
--- a/app/src/components/ui/realTimeVis/charts/BarGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx
@@ -1,376 +1,377 @@
-// import React, { useEffect, useRef, useMemo, useState } from "react";
-// import { Chart } from "chart.js/auto";
-// import { useThemeStore } from "../../../../store/useThemeStore";
-// import io from "socket.io-client";
-// import { Bar } from 'react-chartjs-2';
-// import useChartStore from "../../../../store/useChartStore";
-
-// // WebSocket Connection
-// // const socket = io("http://localhost:5000"); // Adjust to your backend URL
-
-// interface ChartComponentProps {
-// type: any;
-// title: string;
-// fontFamily?: string;
-// fontSize?: string;
-// fontWeight?: "Light" | "Regular" | "Bold";
-// data: any;
-// }
-
-// const LineGraphComponent = ({
-// type,
-// title,
-// fontFamily,
-// fontSize,
-// fontWeight = "Regular",
-// data,
-// }: ChartComponentProps) => {
-// const canvasRef = useRef
(null);
-// const { themeColor } = useThemeStore();
-// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
-// labels: [],
-// datasets: [],
-// });
-
-// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
-
-// const defaultData = {
-// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
-// datasets: [
-// {
-// label: "Dataset",
-// data: [12, 19, 3, 5, 2, 3],
-// backgroundColor: ["#6f42c1"],
-// borderColor: "#ffffff",
-// borderWidth: 2,
-// },
-// ],
-// };
-
-// // Memoize Theme Colors to Prevent Unnecessary Recalculations
-// const buttonActionColor = useMemo(
-// () => themeColor[0] || "#5c87df",
-// [themeColor]
-// );
-// const buttonAbortColor = useMemo(
-// () => themeColor[1] || "#ffffff",
-// [themeColor]
-// );
-
-// // Memoize Font Weight Mapping
-// const chartFontWeightMap = useMemo(
-// () => ({
-// Light: "lighter" as const,
-// Regular: "normal" as const,
-// Bold: "bold" as const,
-// }),
-// []
-// );
-
-// // Parse and Memoize Font Size
-// const fontSizeValue = useMemo(
-// () => (fontSize ? parseInt(fontSize) : 12),
-// [fontSize]
-// );
-
-// // Determine and Memoize Font Weight
-// const fontWeightValue = useMemo(
-// () => chartFontWeightMap[fontWeight],
-// [fontWeight, chartFontWeightMap]
-// );
-
-// // Memoize Chart Font Style
-// const chartFontStyle = useMemo(
-// () => ({
-// family: fontFamily || "Arial",
-// size: fontSizeValue,
-// weight: fontWeightValue,
-// }),
-// [fontFamily, fontSizeValue, fontWeightValue]
-// );
-
-// // Memoize Chart Data
-// // const data = useMemo(() => propsData, [propsData]);
-
-// // Memoize Chart Options
-// const options = useMemo(
-// () => ({
-// responsive: true,
-// maintainAspectRatio: false,
-// plugins: {
-// title: {
-// display: true,
-// text: title,
-// font: chartFontStyle,
-// },
-// legend: {
-// display: false,
-// },
-// },
-// scales: {
-// x: {
-// ticks: {
-// display: true, // This hides the x-axis labels
-// },
-// },
-// },
-// }),
-// [title, chartFontStyle]
-// );
-
-// const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
-
-// useEffect(() => {
-
-// const socket = io(`http://${iotApiUrl}`);
-
-// if ( measurements.length > 0 ) {
-// var inputes = {
-// measurements: measurements,
-// duration: duration,
-// interval: 1000,
-// }
-
-// // Start stream
-// const startStream = () => {
-// socket.emit("lineInput", inputes);
-// }
-
-// socket.on('connect', startStream);
-
-// socket.on("lineOutput", (response) => {
-// const responceData = response.data;
-// console.log("Received data:", responceData);
-
-// // Extract timestamps and values
-// const labels = responceData.time;
-// const datasets = measurements.map((measurement: any) => {
-// const key = `${measurement.name}.${measurement.fields}`;
-// return {
-// label: key,
-// data: responceData[key]?.values ?? [], // Ensure it exists
-// backgroundColor: "#6f42c1",
-// borderColor: "#ffffff",
-// };
-// });
-
-// setChartData({ labels, datasets });
-// });
-// }
-
-// return () => {
-// socket.off("lineOutput");
-// socket.emit("stop_stream"); // Stop streaming when component unmounts
-// };
-// }, [measurements, duration]);
-
-// // useEffect(() => {
-// // if (!canvasRef.current) return;
-// // const ctx = canvasRef.current.getContext("2d");
-// // if (!ctx) return;
-
-// // const chart = new Chart(ctx, {
-// // type,
-// // data: chartData,
-// // options: options,
-// // });
-
-// // return () => chart.destroy();
-// // }, [chartData, type, title]);
-
-// return 0 ? chartData : defaultData} options={options} />;
-// };
-
-// export default LineGraphComponent;
-
-
-
-import React, { useEffect, useMemo, useState } from "react";
-import { Bar } from "react-chartjs-2";
-import io from "socket.io-client";
-import { useThemeStore } from "../../../../store/useThemeStore";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-interface ChartComponentProps {
- id: string;
- type: any;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
-}
-
-const BarGraphComponent = ({
- id,
- type,
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
-}: ChartComponentProps) => {
- const { themeColor } = useThemeStore();
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState("Widget")
- const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
- labels: [],
- datasets: [],
- });
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
- const defaultData = {
- labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
- datasets: [
- {
- label: "Dataset",
- data: [12, 19, 3, 5, 2, 3],
- backgroundColor: ["#6f42c1"],
- borderColor: "#b392f0",
- borderWidth: 1,
- },
- ],
- };
-
- useEffect(() => {
-
- },[])
-
- // Memoize Theme Colors
- const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
- const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
-
- // Memoize Font Styling
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
- const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- // Memoize Chart Options
- const options = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: name,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- },
- },
- scales: {
- x: {
- ticks: {
- display: true, // This hides the x-axis labels
- },
- },
- },
- }),
- [title, chartFontStyle, name]
- );
-
- // useEffect(() => {console.log(measurements);
- // },[measurements])
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1",
- borderColor: "#b392f0",
- borderWidth: 1,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return 0 ? chartData : defaultData} options={options} />;
-};
-
-export default BarGraphComponent;
+// import React, { useEffect, useRef, useMemo, useState } from "react";
+// import { Chart } from "chart.js/auto";
+// import { useThemeStore } from "../../../../store/useThemeStore";
+// import io from "socket.io-client";
+// import { Bar } from 'react-chartjs-2';
+// import useChartStore from "../../../../store/useChartStore";
+
+// // WebSocket Connection
+// // const socket = io("http://localhost:5000"); // Adjust to your backend URL
+
+// interface ChartComponentProps {
+// type: any;
+// title: string;
+// fontFamily?: string;
+// fontSize?: string;
+// fontWeight?: "Light" | "Regular" | "Bold";
+// data: any;
+// }
+
+// const LineGraphComponent = ({
+// type,
+// title,
+// fontFamily,
+// fontSize,
+// fontWeight = "Regular",
+// data,
+// }: ChartComponentProps) => {
+// const canvasRef = useRef(null);
+// const { themeColor } = useThemeStore();
+// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
+// labels: [],
+// datasets: [],
+// });
+
+// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+
+// const defaultData = {
+// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+// datasets: [
+// {
+// label: "Dataset",
+// data: [12, 19, 3, 5, 2, 3],
+// backgroundColor: ["#6f42c1"],
+// borderColor: "#ffffff",
+// borderWidth: 2,
+// },
+// ],
+// };
+
+// // Memoize Theme Colors to Prevent Unnecessary Recalculations
+// const buttonActionColor = useMemo(
+// () => themeColor[0] || "#5c87df",
+// [themeColor]
+// );
+// const buttonAbortColor = useMemo(
+// () => themeColor[1] || "#ffffff",
+// [themeColor]
+// );
+
+// // Memoize Font Weight Mapping
+// const chartFontWeightMap = useMemo(
+// () => ({
+// Light: "lighter" as const,
+// Regular: "normal" as const,
+// Bold: "bold" as const,
+// }),
+// []
+// );
+
+// // Parse and Memoize Font Size
+// const fontSizeValue = useMemo(
+// () => (fontSize ? parseInt(fontSize) : 12),
+// [fontSize]
+// );
+
+// // Determine and Memoize Font Weight
+// const fontWeightValue = useMemo(
+// () => chartFontWeightMap[fontWeight],
+// [fontWeight, chartFontWeightMap]
+// );
+
+// // Memoize Chart Font Style
+// const chartFontStyle = useMemo(
+// () => ({
+// family: fontFamily || "Arial",
+// size: fontSizeValue,
+// weight: fontWeightValue,
+// }),
+// [fontFamily, fontSizeValue, fontWeightValue]
+// );
+
+// // Memoize Chart Data
+// // const data = useMemo(() => propsData, [propsData]);
+
+// // Memoize Chart Options
+// const options = useMemo(
+// () => ({
+// responsive: true,
+// maintainAspectRatio: false,
+// plugins: {
+// title: {
+// display: true,
+// text: title,
+// font: chartFontStyle,
+// },
+// legend: {
+// display: false,
+// },
+// },
+// scales: {
+// x: {
+// ticks: {
+// display: true, // This hides the x-axis labels
+// },
+// },
+// },
+// }),
+// [title, chartFontStyle]
+// );
+
+// const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
+
+// useEffect(() => {
+
+// const socket = io(`http://${iotApiUrl}`);
+
+// if ( measurements.length > 0 ) {
+// var inputes = {
+// measurements: measurements,
+// duration: duration,
+// interval: 1000,
+// }
+
+// // Start stream
+// const startStream = () => {
+// socket.emit("lineInput", inputes);
+// }
+
+// socket.on('connect', startStream);
+
+// socket.on("lineOutput", (response) => {
+// const responceData = response.data;
+// console.log("Received data:", responceData);
+
+// // Extract timestamps and values
+// const labels = responceData.time;
+// const datasets = measurements.map((measurement: any) => {
+// const key = `${measurement.name}.${measurement.fields}`;
+// return {
+// label: key,
+// data: responceData[key]?.values ?? [], // Ensure it exists
+// backgroundColor: "#6f42c1",
+// borderColor: "#ffffff",
+// };
+// });
+
+// setChartData({ labels, datasets });
+// });
+// }
+
+// return () => {
+// socket.off("lineOutput");
+// socket.emit("stop_stream"); // Stop streaming when component unmounts
+// };
+// }, [measurements, duration]);
+
+// // useEffect(() => {
+// // if (!canvasRef.current) return;
+// // const ctx = canvasRef.current.getContext("2d");
+// // if (!ctx) return;
+
+// // const chart = new Chart(ctx, {
+// // type,
+// // data: chartData,
+// // options: options,
+// // });
+
+// // return () => chart.destroy();
+// // }, [chartData, type, title]);
+
+// return 0 ? chartData : defaultData} options={options} />;
+// };
+
+// export default LineGraphComponent;
+
+
+
+import React, { useEffect, useMemo, useState } from "react";
+import { Bar } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import { useThemeStore } from "../../../../../store/useThemeStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+
+interface ChartComponentProps {
+ id: string;
+ type: any;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+}
+
+const BarGraphComponent = ({
+ id,
+ type,
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+}: ChartComponentProps) => {
+ const { themeColor } = useThemeStore();
+ const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h")
+ const [name, setName] = useState("Widget")
+ const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
+ labels: [],
+ datasets: [],
+ });
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0]
+ const defaultData = {
+ labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+ datasets: [
+ {
+ label: "Dataset",
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: ["#6f42c1"],
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ useEffect(() => {
+
+ },[])
+
+ // Memoize Theme Colors
+ const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
+ const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
+
+ // Memoize Font Styling
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
+ const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ // Memoize Chart Options
+ const options = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: name,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ x: {
+ ticks: {
+ display: true, // This hides the x-axis labels
+ },
+ },
+ },
+ }),
+ [title, chartFontStyle, name]
+ );
+
+ // useEffect(() => {console.log(measurements);
+ // },[measurements])
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1",
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async() => {
+
+ if (id !== "") {
+ try {
+ const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements)
+ setDuration(response.data.Data.duration)
+ setName(response.data.widgetName)
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ }
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }
+ ,[chartMeasurements, chartDuration, widgetName])
+
+ return 0 ? chartData : defaultData} options={options} />;
+};
+
+export default BarGraphComponent;
diff --git a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx
similarity index 86%
rename from app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx
index 93c2960..4947b94 100644
--- a/app/src/components/ui/realTimeVis/charts/DoughnutGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/DoughnutGraphComponent.tsx
@@ -1,189 +1,192 @@
-import React, { useEffect, useMemo, useState } from "react";
-import { Doughnut } from "react-chartjs-2";
-import io from "socket.io-client";
-import { useThemeStore } from "../../../../store/useThemeStore";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-interface ChartComponentProps {
- id: string;
- type: any;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
-}
-
-const DoughnutGraphComponent = ({
- id,
- type,
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
-}: ChartComponentProps) => {
- const { themeColor } = useThemeStore();
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState("Widget")
- const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
- labels: [],
- datasets: [],
- });
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
- const defaultData = {
- labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
- datasets: [
- {
- label: "Dataset",
- data: [12, 19, 3, 5, 2, 3],
- backgroundColor: ["#6f42c1"],
- borderColor: "#b392f0",
- borderWidth: 1,
- },
- ],
- };
-
- useEffect(() => {
-
- },[])
-
- // Memoize Theme Colors
- const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
- const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
-
- // Memoize Font Styling
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
- const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- // Memoize Chart Options
- const options = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: name,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- },
- },
- scales: {
- // x: {
- // ticks: {
- // display: true, // This hides the x-axis labels
- // },
- // },
- },
- }),
- [title, chartFontStyle, name]
- );
-
- // useEffect(() => {console.log(measurements);
- // },[measurements])
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1",
- borderColor: "#b392f0",
- borderWidth: 1,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return 0 ? chartData : defaultData} options={options} />;
-};
-
-export default DoughnutGraphComponent;
\ No newline at end of file
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import { useThemeStore } from "../../../../../store/useThemeStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+
+interface ChartComponentProps {
+ id: string;
+ type: any;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+}
+
+const LineGraphComponent = ({
+ id,
+ type,
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+}: ChartComponentProps) => {
+ const { themeColor } = useThemeStore();
+ const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h")
+ const [name, setName] = useState("Widget")
+ const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
+ labels: [],
+ datasets: [],
+ });
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0]
+ const defaultData = {
+ labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+ datasets: [
+ {
+ label: "Dataset",
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: ["#6f42c1"],
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ useEffect(() => {
+
+ },[])
+
+ // Memoize Theme Colors
+ const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
+ const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
+
+ // Memoize Font Styling
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
+ const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ // Memoize Chart Options
+ const options = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: name,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ x: {
+ ticks: {
+ display: true, // This hides the x-axis labels
+ },
+ },
+ },
+ }),
+ [title, chartFontStyle, name]
+ );
+
+ // useEffect(() => {console.log(measurements);
+ // },[measurements])
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1",
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async() => {
+
+ if (id !== "") {
+ try {
+ const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
+ if (response.status === 200) {
+ console.log('line chart res',response);
+
+ setmeasurements(response.data.Data.measurements)
+ setDuration(response.data.Data.duration)
+ setName(response.data.widgetName)
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ }
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }
+ ,[chartMeasurements, chartDuration, widgetName])
+
+ return 0 ? chartData : defaultData} options={options} />;
+};
+
+export default LineGraphComponent;
diff --git a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/LineGraphComponent.tsx
similarity index 56%
rename from app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/LineGraphComponent.tsx
index c7f7252..4a6906a 100644
--- a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/LineGraphComponent.tsx
@@ -1,189 +1,212 @@
-import React, { useEffect, useMemo, useState } from "react";
-import { Line } from "react-chartjs-2";
-import io from "socket.io-client";
-import { useThemeStore } from "../../../../store/useThemeStore";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-interface ChartComponentProps {
- id: string;
- type: any;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
-}
-
-const LineGraphComponent = ({
- id,
- type,
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
-}: ChartComponentProps) => {
- const { themeColor } = useThemeStore();
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState("Widget")
- const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
- labels: [],
- datasets: [],
- });
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
- const defaultData = {
- labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
- datasets: [
- {
- label: "Dataset",
- data: [12, 19, 3, 5, 2, 3],
- backgroundColor: ["#6f42c1"],
- borderColor: "#b392f0",
- borderWidth: 1,
- },
- ],
- };
-
- useEffect(() => {
-
- },[])
-
- // Memoize Theme Colors
- const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
- const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
-
- // Memoize Font Styling
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
- const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- // Memoize Chart Options
- const options = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: name,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- },
- },
- scales: {
- x: {
- ticks: {
- display: true, // This hides the x-axis labels
- },
- },
- },
- }),
- [title, chartFontStyle, name]
- );
-
- // useEffect(() => {console.log(measurements);
- // },[measurements])
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1",
- borderColor: "#b392f0",
- borderWidth: 1,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return 0 ? chartData : defaultData} options={options} />;
-};
-
-export default LineGraphComponent;
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import { useThemeStore } from "../../../../../store/useThemeStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+
+interface ChartComponentProps {
+ id: string;
+ type: any;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+}
+
+const LineGraphComponent = ({
+ id,
+ type,
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+}: ChartComponentProps) => {
+ const { themeColor } = useThemeStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ const defaultData = {
+ labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+ datasets: [
+ {
+ label: "Dataset",
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: ["#6f42c1"],
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ useEffect(() => {}, []);
+
+ // Memoize Theme Colors
+ const buttonActionColor = useMemo(
+ () => themeColor[0] || "#5c87df",
+ [themeColor]
+ );
+ const buttonAbortColor = useMemo(
+ () => themeColor[1] || "#ffffff",
+ [themeColor]
+ );
+
+ // Memoize Font Styling
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(
+ () => (fontSize ? parseInt(fontSize) : 12),
+ [fontSize]
+ );
+ const fontWeightValue = useMemo(
+ () => chartFontWeightMap[fontWeight],
+ [fontWeight, chartFontWeightMap]
+ );
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ // Memoize Chart Options
+ const options = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: name,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ x: {
+ ticks: {
+ display: true, // This hides the x-axis labels
+ },
+ },
+ },
+ }),
+ [title, chartFontStyle, name]
+ );
+
+ // useEffect(() => {console.log(measurements);
+ // },[measurements])
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1",
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ return (
+ 0 ? chartData : defaultData}
+ options={options}
+ />
+ );
+};
+
+export default LineGraphComponent;
diff --git a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx
similarity index 75%
rename from app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx
index d7cc0da..b77e964 100644
--- a/app/src/components/ui/realTimeVis/charts/PieGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx
@@ -1,375 +1,397 @@
-// import React, { useEffect, useRef, useMemo, useState } from "react";
-// import { Chart } from "chart.js/auto";
-// import { useThemeStore } from "../../../../store/useThemeStore";
-// import io from "socket.io-client";
-// import { Pie } from 'react-chartjs-2';
-// import useChartStore from "../../../../store/useChartStore";
-
-// // WebSocket Connection
-// // const socket = io("http://localhost:5000"); // Adjust to your backend URL
-
-// interface ChartComponentProps {
-// type: any;
-// title: string;
-// fontFamily?: string;
-// fontSize?: string;
-// fontWeight?: "Light" | "Regular" | "Bold";
-// data: any;
-// }
-
-// const PieChartComponent = ({
-// type,
-// title,
-// fontFamily,
-// fontSize,
-// fontWeight = "Regular",
-// data,
-// }: ChartComponentProps) => {
-// const canvasRef = useRef(null);
-// const { themeColor } = useThemeStore();
-// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
-// labels: [],
-// datasets: [],
-// });
-
-// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
-
-// const defaultData = {
-// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
-// datasets: [
-// {
-// label: "Dataset",
-// data: [12, 19, 3, 5, 2, 3],
-// backgroundColor: ["#6f42c1"],
-// borderColor: "#ffffff",
-// borderWidth: 2,
-// },
-// ],
-// };
-
-// // Memoize Theme Colors to Prevent Unnecessary Recalculations
-// const buttonActionColor = useMemo(
-// () => themeColor[0] || "#6f42c1",
-// [themeColor]
-// );
-// const buttonAbortColor = useMemo(
-// () => themeColor[1] || "#ffffff",
-// [themeColor]
-// );
-
-// // Memoize Font Weight Mapping
-// const chartFontWeightMap = useMemo(
-// () => ({
-// Light: "lighter" as const,
-// Regular: "normal" as const,
-// Bold: "bold" as const,
-// }),
-// []
-// );
-
-// // Parse and Memoize Font Size
-// const fontSizeValue = useMemo(
-// () => (fontSize ? parseInt(fontSize) : 12),
-// [fontSize]
-// );
-
-// // Determine and Memoize Font Weight
-// const fontWeightValue = useMemo(
-// () => chartFontWeightMap[fontWeight],
-// [fontWeight, chartFontWeightMap]
-// );
-
-// // Memoize Chart Font Style
-// const chartFontStyle = useMemo(
-// () => ({
-// family: fontFamily || "Arial",
-// size: fontSizeValue,
-// weight: fontWeightValue,
-// }),
-// [fontFamily, fontSizeValue, fontWeightValue]
-// );
-
-// // Memoize Chart Data
-// // const data = useMemo(() => propsData, [propsData]);
-
-// // Memoize Chart Options
-// const options = useMemo(
-// () => ({
-// responsive: true,
-// maintainAspectRatio: false,
-// plugins: {
-// title: {
-// display: true,
-// text: title,
-// font: chartFontStyle,
-// },
-// legend: {
-// display: false,
-// },
-// },
-// scales: {
-// // x: {
-// // ticks: {
-// // display: true, // This hides the x-axis labels
-// // },
-// // },
-// },
-// }),
-// [title, chartFontStyle]
-// );
-
-// const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
-
-// useEffect(() => {
-
-// const socket = io(`http://${iotApiUrl}`);
-
-// if ( measurements.length > 0 ) {
-// var inputes = {
-// measurements: measurements,
-// duration: duration,
-// interval: 1000,
-// }
-
-// // Start stream
-// const startStream = () => {
-// socket.emit("lineInput", inputes);
-// }
-
-// socket.on('connect', startStream);
-
-// socket.on("lineOutput", (response) => {
-// const responceData = response.data;
-// console.log("Received data:", responceData);
-
-// // Extract timestamps and values
-// const labels = responceData.time;
-// const datasets = measurements.map((measurement: any) => {
-// const key = `${measurement.name}.${measurement.fields}`;
-// return {
-// label: key,
-// data: responceData[key]?.values ?? [], // Ensure it exists
-// backgroundColor: "#6f42c1",
-// borderColor: "#ffffff",
-// };
-// });
-
-// setChartData({ labels, datasets });
-// });
-// }
-
-// return () => {
-// socket.off("lineOutput");
-// socket.emit("stop_stream"); // Stop streaming when component unmounts
-// };
-// }, [measurements, duration]);
-
-// // useEffect(() => {
-// // if (!canvasRef.current) return;
-// // const ctx = canvasRef.current.getContext("2d");
-// // if (!ctx) return;
-
-// // const chart = new Chart(ctx, {
-// // type,
-// // data: chartData,
-// // options: options,
-// // });
-
-// // return () => chart.destroy();
-// // }, [chartData, type, title]);
-
-// return 0 ? chartData : defaultData} options={options} />;
-// };
-
-// export default PieChartComponent;
-
-
-import React, { useEffect, useMemo, useState } from "react";
-import { Pie } from "react-chartjs-2";
-import io from "socket.io-client";
-import { useThemeStore } from "../../../../store/useThemeStore";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-interface ChartComponentProps {
- id: string;
- type: any;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
-}
-
-const PieChartComponent = ({
- id,
- type,
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
-}: ChartComponentProps) => {
- const { themeColor } = useThemeStore();
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState("Widget")
- const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
- labels: [],
- datasets: [],
- });
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
- const defaultData = {
- labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
- datasets: [
- {
- label: "Dataset",
- data: [12, 19, 3, 5, 2, 3],
- backgroundColor: ["#6f42c1"],
- borderColor: "#b392f0",
- borderWidth: 1,
- },
- ],
- };
-
- useEffect(() => {
-
- },[])
-
- // Memoize Theme Colors
- const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
- const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
-
- // Memoize Font Styling
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
- const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- // Memoize Chart Options
- const options = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: name,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- },
- },
- scales: {
- // x: {
- // ticks: {
- // display: true, // This hides the x-axis labels
- // },
- // },
- },
- }),
- [title, chartFontStyle, name]
- );
-
- // useEffect(() => {console.log(measurements);
- // },[measurements])
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1",
- borderColor: "#b392f0",
- borderWidth: 1,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return 0 ? chartData : defaultData} options={options} />;
-};
-
-export default PieChartComponent;
\ No newline at end of file
+// import React, { useEffect, useRef, useMemo, useState } from "react";
+// import { Chart } from "chart.js/auto";
+// import { useThemeStore } from "../../../../store/useThemeStore";
+// import io from "socket.io-client";
+// import { Pie } from 'react-chartjs-2';
+// import useChartStore from "../../../../store/useChartStore";
+
+// // WebSocket Connection
+// // const socket = io("http://localhost:5000"); // Adjust to your backend URL
+
+// interface ChartComponentProps {
+// type: any;
+// title: string;
+// fontFamily?: string;
+// fontSize?: string;
+// fontWeight?: "Light" | "Regular" | "Bold";
+// data: any;
+// }
+
+// const PieChartComponent = ({
+// type,
+// title,
+// fontFamily,
+// fontSize,
+// fontWeight = "Regular",
+// data,
+// }: ChartComponentProps) => {
+// const canvasRef = useRef(null);
+// const { themeColor } = useThemeStore();
+// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
+// labels: [],
+// datasets: [],
+// });
+
+// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+
+// const defaultData = {
+// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+// datasets: [
+// {
+// label: "Dataset",
+// data: [12, 19, 3, 5, 2, 3],
+// backgroundColor: ["#6f42c1"],
+// borderColor: "#ffffff",
+// borderWidth: 2,
+// },
+// ],
+// };
+
+// // Memoize Theme Colors to Prevent Unnecessary Recalculations
+// const buttonActionColor = useMemo(
+// () => themeColor[0] || "#6f42c1",
+// [themeColor]
+// );
+// const buttonAbortColor = useMemo(
+// () => themeColor[1] || "#ffffff",
+// [themeColor]
+// );
+
+// // Memoize Font Weight Mapping
+// const chartFontWeightMap = useMemo(
+// () => ({
+// Light: "lighter" as const,
+// Regular: "normal" as const,
+// Bold: "bold" as const,
+// }),
+// []
+// );
+
+// // Parse and Memoize Font Size
+// const fontSizeValue = useMemo(
+// () => (fontSize ? parseInt(fontSize) : 12),
+// [fontSize]
+// );
+
+// // Determine and Memoize Font Weight
+// const fontWeightValue = useMemo(
+// () => chartFontWeightMap[fontWeight],
+// [fontWeight, chartFontWeightMap]
+// );
+
+// // Memoize Chart Font Style
+// const chartFontStyle = useMemo(
+// () => ({
+// family: fontFamily || "Arial",
+// size: fontSizeValue,
+// weight: fontWeightValue,
+// }),
+// [fontFamily, fontSizeValue, fontWeightValue]
+// );
+
+// // Memoize Chart Data
+// // const data = useMemo(() => propsData, [propsData]);
+
+// // Memoize Chart Options
+// const options = useMemo(
+// () => ({
+// responsive: true,
+// maintainAspectRatio: false,
+// plugins: {
+// title: {
+// display: true,
+// text: title,
+// font: chartFontStyle,
+// },
+// legend: {
+// display: false,
+// },
+// },
+// scales: {
+// // x: {
+// // ticks: {
+// // display: true, // This hides the x-axis labels
+// // },
+// // },
+// },
+// }),
+// [title, chartFontStyle]
+// );
+
+// const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
+
+// useEffect(() => {
+
+// const socket = io(`http://${iotApiUrl}`);
+
+// if ( measurements.length > 0 ) {
+// var inputes = {
+// measurements: measurements,
+// duration: duration,
+// interval: 1000,
+// }
+
+// // Start stream
+// const startStream = () => {
+// socket.emit("lineInput", inputes);
+// }
+
+// socket.on('connect', startStream);
+
+// socket.on("lineOutput", (response) => {
+// const responceData = response.data;
+// console.log("Received data:", responceData);
+
+// // Extract timestamps and values
+// const labels = responceData.time;
+// const datasets = measurements.map((measurement: any) => {
+// const key = `${measurement.name}.${measurement.fields}`;
+// return {
+// label: key,
+// data: responceData[key]?.values ?? [], // Ensure it exists
+// backgroundColor: "#6f42c1",
+// borderColor: "#ffffff",
+// };
+// });
+
+// setChartData({ labels, datasets });
+// });
+// }
+
+// return () => {
+// socket.off("lineOutput");
+// socket.emit("stop_stream"); // Stop streaming when component unmounts
+// };
+// }, [measurements, duration]);
+
+// // useEffect(() => {
+// // if (!canvasRef.current) return;
+// // const ctx = canvasRef.current.getContext("2d");
+// // if (!ctx) return;
+
+// // const chart = new Chart(ctx, {
+// // type,
+// // data: chartData,
+// // options: options,
+// // });
+
+// // return () => chart.destroy();
+// // }, [chartData, type, title]);
+
+// return 0 ? chartData : defaultData} options={options} />;
+// };
+
+// export default PieChartComponent;
+
+import React, { useEffect, useMemo, useState } from "react";
+import { Pie } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import { useThemeStore } from "../../../../../store/useThemeStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+
+interface ChartComponentProps {
+ id: string;
+ type: any;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+}
+
+const PieChartComponent = ({
+ id,
+ type,
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+}: ChartComponentProps) => {
+ const { themeColor } = useThemeStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ const defaultData = {
+ labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+ datasets: [
+ {
+ label: "Dataset",
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: ["#6f42c1"],
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ useEffect(() => {}, []);
+
+ // Memoize Theme Colors
+ const buttonActionColor = useMemo(
+ () => themeColor[0] || "#5c87df",
+ [themeColor]
+ );
+ const buttonAbortColor = useMemo(
+ () => themeColor[1] || "#ffffff",
+ [themeColor]
+ );
+
+ // Memoize Font Styling
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(
+ () => (fontSize ? parseInt(fontSize) : 12),
+ [fontSize]
+ );
+ const fontWeightValue = useMemo(
+ () => chartFontWeightMap[fontWeight],
+ [fontWeight, chartFontWeightMap]
+ );
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ // Memoize Chart Options
+ const options = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: name,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ // x: {
+ // ticks: {
+ // display: true, // This hides the x-axis labels
+ // },
+ // },
+ },
+ }),
+ [title, chartFontStyle, name]
+ );
+
+ // useEffect(() => {console.log(measurements);
+ // },[measurements])
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1",
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ return (
+ 0 ? chartData : defaultData}
+ options={options}
+ />
+ );
+};
+
+export default PieChartComponent;
diff --git a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/PolarAreaGraphComponent.tsx
similarity index 55%
rename from app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/PolarAreaGraphComponent.tsx
index fb87080..e4056dc 100644
--- a/app/src/components/ui/realTimeVis/charts/PolarAreaGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/PolarAreaGraphComponent.tsx
@@ -1,189 +1,212 @@
-import React, { useEffect, useMemo, useState } from "react";
-import { PolarArea } from "react-chartjs-2";
-import io from "socket.io-client";
-import { useThemeStore } from "../../../../store/useThemeStore";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import axios from "axios";
-
-interface ChartComponentProps {
- id: string;
- type: any;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
-}
-
-const PolarAreaGraphComponent = ({
- id,
- type,
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
-}: ChartComponentProps) => {
- const { themeColor } = useThemeStore();
- const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h")
- const [name, setName] = useState("Widget")
- const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({
- labels: [],
- datasets: [],
- });
- const { selectedChartId } = useWidgetStore();
-
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0]
- const defaultData = {
- labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
- datasets: [
- {
- label: "Dataset",
- data: [12, 19, 3, 5, 2, 3],
- backgroundColor: ["#6f42c1"],
- borderColor: "#b392f0",
- borderWidth: 1,
- },
- ],
- };
-
- useEffect(() => {
-
- },[])
-
- // Memoize Theme Colors
- const buttonActionColor = useMemo(() => themeColor[0] || "#5c87df", [themeColor]);
- const buttonAbortColor = useMemo(() => themeColor[1] || "#ffffff", [themeColor]);
-
- // Memoize Font Styling
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
- const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- // Memoize Chart Options
- const options = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: name,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- },
- },
- scales: {
- // x: {
- // ticks: {
- // display: true, // This hides the x-axis labels
- // },
- // },
- },
- }),
- [title, chartFontStyle, name]
- );
-
- // useEffect(() => {console.log(measurements);
- // },[measurements])
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1",
- borderColor: "#b392f0",
- borderWidth: 1,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async() => {
-
- if (id !== "") {
- try {
- const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements)
- setDuration(response.data.Data.duration)
- setName(response.data.widgetName)
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- }
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }
- ,[chartMeasurements, chartDuration, widgetName])
-
- return 0 ? chartData : defaultData} options={options} />;
-};
-
-export default PolarAreaGraphComponent;
\ No newline at end of file
+import React, { useEffect, useMemo, useState } from "react";
+import { PolarArea } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import { useThemeStore } from "../../../../../store/useThemeStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+
+interface ChartComponentProps {
+ id: string;
+ type: any;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+}
+
+const PolarAreaGraphComponent = ({
+ id,
+ type,
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+}: ChartComponentProps) => {
+ const { themeColor } = useThemeStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ const defaultData = {
+ labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+ datasets: [
+ {
+ label: "Dataset",
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: ["#6f42c1"],
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ useEffect(() => {}, []);
+
+ // Memoize Theme Colors
+ const buttonActionColor = useMemo(
+ () => themeColor[0] || "#5c87df",
+ [themeColor]
+ );
+ const buttonAbortColor = useMemo(
+ () => themeColor[1] || "#ffffff",
+ [themeColor]
+ );
+
+ // Memoize Font Styling
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(
+ () => (fontSize ? parseInt(fontSize) : 12),
+ [fontSize]
+ );
+ const fontWeightValue = useMemo(
+ () => chartFontWeightMap[fontWeight],
+ [fontWeight, chartFontWeightMap]
+ );
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ // Memoize Chart Options
+ const options = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: name,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ // x: {
+ // ticks: {
+ // display: true, // This hides the x-axis labels
+ // },
+ // },
+ },
+ }),
+ [title, chartFontStyle, name]
+ );
+
+ // useEffect(() => {console.log(measurements);
+ // },[measurements])
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1",
+ borderColor: "#b392f0",
+ borderWidth: 1,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ return (
+ 0 ? chartData : defaultData}
+ options={options}
+ />
+ );
+};
+
+export default PolarAreaGraphComponent;
diff --git a/app/src/components/ui/realTimeVis/charts/ProgressCard.tsx b/app/src/modules/visualization/widgets/2d/charts/ProgressCard.tsx
similarity index 87%
rename from app/src/components/ui/realTimeVis/charts/ProgressCard.tsx
rename to app/src/modules/visualization/widgets/2d/charts/ProgressCard.tsx
index 52daf42..e9ac1c3 100644
--- a/app/src/components/ui/realTimeVis/charts/ProgressCard.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/ProgressCard.tsx
@@ -1,29 +1,29 @@
-import { StockIncreseIcon } from "../../../icons/RealTimeVisulationIcons";
-
-const ProgressCard = ({
- title,
- data,
-}: {
- title: string;
- data: { stocks: Array<{ key: string; value: number; description: string }> };
-}) => (
-
-
{title}
- {data?.stocks?.map((stock, index) => (
-
-
-
- {stock.key}
- {stock.value}
-
- {stock.description}
-
-
-
-
-
- ))}
-
-);
-
-export default ProgressCard;
+import { StockIncreseIcon } from "../../../../../components/icons/RealTimeVisulationIcons";
+
+const ProgressCard = ({
+ title,
+ data,
+}: {
+ title: string;
+ data: { stocks: Array<{ key: string; value: number; description: string }> };
+}) => (
+
+
{title}
+ {data?.stocks?.map((stock, index) => (
+
+
+
+ {stock.key}
+ {stock.value}
+
+ {stock.description}
+
+
+
+
+
+ ))}
+
+);
+
+export default ProgressCard;
diff --git a/app/src/modules/visualization/widgets/2d/charts/ProgressCard1.tsx b/app/src/modules/visualization/widgets/2d/charts/ProgressCard1.tsx
new file mode 100644
index 0000000..70f09a2
--- /dev/null
+++ b/app/src/modules/visualization/widgets/2d/charts/ProgressCard1.tsx
@@ -0,0 +1,105 @@
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import { StockIncreseIcon } from "../../../../../components/icons/RealTimeVisulationIcons";
+
+const ProgressCard1 = ({ id, title }: { id: string; title: string }) => {
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h");
+ const [name, setName] = useState(title);
+ const [value, setValue] = useState("");
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+
+ useEffect(() => {
+ const socket = io(`http://${iotApiUrl}`);
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lastInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lastOutput", (response) => {
+ const responseData = response.input1;
+ setValue(responseData);
+ });
+
+ return () => {
+ socket.off("lastOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ return (
+
+
{name}
+
+
+
+ {value}
+ Units
+
+
+ {measurements ? `${measurements?.input1?.fields}` : "description"}
+
+
+
+
+
+
+
+ );
+};
+
+export default ProgressCard1;
diff --git a/app/src/modules/visualization/widgets/2d/charts/ProgressCard2.tsx b/app/src/modules/visualization/widgets/2d/charts/ProgressCard2.tsx
new file mode 100644
index 0000000..67ae415
--- /dev/null
+++ b/app/src/modules/visualization/widgets/2d/charts/ProgressCard2.tsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import io from "socket.io-client";
+
+import axios from "axios";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import { StockIncreseIcon } from "../../../../../components/icons/RealTimeVisulationIcons";
+
+const ProgressCard2 = ({ id, title }: { id: string; title: string }) => {
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState({});
+ const [duration, setDuration] = useState("1h");
+ const [name, setName] = useState(title);
+ const [value1, setValue1] = useState("");
+ const [value2, setValue2] = useState("");
+ const { selectedChartId } = useWidgetStore();
+
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lastInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lastOutput", (response) => {
+ const responseData1 = response.input1;
+ const responseData2 = response.input2;
+ setValue1(responseData1);
+ setValue2(responseData2);
+ });
+
+ return () => {
+ socket.off("lastOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ return (
+
+
{name}
+
+
+
+
+ {value1}
+ Units
+
+
+ {measurements ? `${measurements?.input1?.fields}` : "description"}
+
+
+
+
+
+
+
+
+
+
+ {value2}
+ Units
+
+
+ {measurements ? `${measurements?.input2?.fields}` : "description"}
+
+
+
+
+
+
+
+ );
+};
+
+export default ProgressCard2;
diff --git a/app/src/components/ui/realTimeVis/charts/RadarGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/RadarGraphComponent.tsx
similarity index 95%
rename from app/src/components/ui/realTimeVis/charts/RadarGraphComponent.tsx
rename to app/src/modules/visualization/widgets/2d/charts/RadarGraphComponent.tsx
index a9d3805..58be8c6 100644
--- a/app/src/components/ui/realTimeVis/charts/RadarGraphComponent.tsx
+++ b/app/src/modules/visualization/widgets/2d/charts/RadarGraphComponent.tsx
@@ -1,102 +1,102 @@
-import React, { useMemo } from "react";
-import { Radar } from "react-chartjs-2";
-import { ChartOptions, ChartData, RadialLinearScaleOptions } from "chart.js";
-
-interface ChartComponentProps {
- type: string;
- title: string;
- fontFamily?: string;
- fontSize?: string;
- fontWeight?: "Light" | "Regular" | "Bold";
- data: number[]; // Expecting an array of numbers for radar chart data
-}
-
-const RadarGraphComponent = ({
- title,
- fontFamily,
- fontSize,
- fontWeight = "Regular",
- data, // Now guaranteed to be number[]
-}: ChartComponentProps) => {
- // Memoize Font Weight Mapping
- const chartFontWeightMap = useMemo(
- () => ({
- Light: "lighter" as const,
- Regular: "normal" as const,
- Bold: "bold" as const,
- }),
- []
- );
-
- const fontSizeValue = useMemo(
- () => (fontSize ? parseInt(fontSize) : 12),
- [fontSize]
- );
-
- const fontWeightValue = useMemo(
- () => chartFontWeightMap[fontWeight],
- [fontWeight, chartFontWeightMap]
- );
-
- const chartFontStyle = useMemo(
- () => ({
- family: fontFamily || "Arial",
- size: fontSizeValue,
- weight: fontWeightValue,
- }),
- [fontFamily, fontSizeValue, fontWeightValue]
- );
-
- const options: ChartOptions<"radar"> = useMemo(
- () => ({
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- title: {
- display: true,
- text: title,
- font: chartFontStyle,
- },
- legend: {
- display: false,
- position: "top",
- },
- },
- scales: {
- r: {
- min: 0,
- max: 100,
- angleLines: {
- display: true,
- },
- ticks: {
- display: true,
- stepSize: 20,
- },
- } as RadialLinearScaleOptions,
- },
- }),
- [title, chartFontStyle]
- );
-
- const chartData: ChartData<"radar"> = useMemo(
- () => ({
- labels: ["January", "February", "March", "April", "May", "June", "July"],
- datasets: [
- {
- label: "Dataset 1",
- data, // Use the data passed as a prop
- backgroundColor: "rgba(111, 66, 193, 0.2)",
- borderColor: "#6f42c1",
- borderWidth: 2,
- fill: true,
- },
- ],
- }),
- [data]
- );
-
- return ;
-};
-
-export default RadarGraphComponent;
+import React, { useMemo } from "react";
+import { Radar } from "react-chartjs-2";
+import { ChartOptions, ChartData, RadialLinearScaleOptions } from "chart.js";
+
+interface ChartComponentProps {
+ type: string;
+ title: string;
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: "Light" | "Regular" | "Bold";
+ data: number[]; // Expecting an array of numbers for radar chart data
+}
+
+const RadarGraphComponent = ({
+ title,
+ fontFamily,
+ fontSize,
+ fontWeight = "Regular",
+ data, // Now guaranteed to be number[]
+}: ChartComponentProps) => {
+ // Memoize Font Weight Mapping
+ const chartFontWeightMap = useMemo(
+ () => ({
+ Light: "lighter" as const,
+ Regular: "normal" as const,
+ Bold: "bold" as const,
+ }),
+ []
+ );
+
+ const fontSizeValue = useMemo(
+ () => (fontSize ? parseInt(fontSize) : 12),
+ [fontSize]
+ );
+
+ const fontWeightValue = useMemo(
+ () => chartFontWeightMap[fontWeight],
+ [fontWeight, chartFontWeightMap]
+ );
+
+ const chartFontStyle = useMemo(
+ () => ({
+ family: fontFamily || "Arial",
+ size: fontSizeValue,
+ weight: fontWeightValue,
+ }),
+ [fontFamily, fontSizeValue, fontWeightValue]
+ );
+
+ const options: ChartOptions<"radar"> = useMemo(
+ () => ({
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ title: {
+ display: true,
+ text: title,
+ font: chartFontStyle,
+ },
+ legend: {
+ display: false,
+ position: "top",
+ },
+ },
+ scales: {
+ r: {
+ min: 0,
+ max: 100,
+ angleLines: {
+ display: true,
+ },
+ ticks: {
+ display: true,
+ stepSize: 20,
+ },
+ } as RadialLinearScaleOptions,
+ },
+ }),
+ [title, chartFontStyle]
+ );
+
+ const chartData: ChartData<"radar"> = useMemo(
+ () => ({
+ labels: ["January", "February", "March", "April", "May", "June", "July"],
+ datasets: [
+ {
+ label: "Dataset 1",
+ data, // Use the data passed as a prop
+ backgroundColor: "rgba(111, 66, 193, 0.2)",
+ borderColor: "#6f42c1",
+ borderWidth: 2,
+ fill: true,
+ },
+ ],
+ }),
+ [data]
+ );
+
+ return ;
+};
+
+export default RadarGraphComponent;
diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx
similarity index 67%
rename from app/src/components/ui/componets/Dropped3dWidget.tsx
rename to app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx
index 51ad60e..6acc994 100644
--- a/app/src/components/ui/componets/Dropped3dWidget.tsx
+++ b/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx
@@ -1,35 +1,25 @@
+import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import React, { useEffect, useRef, useState } from "react";
-import {
- useAsset3dWidget,
- useSocketStore,
- useWidgetSubOption,
-} from "../../../store/store";
-import useModuleStore from "../../../store/useModuleStore";
-import { ThreeState } from "../../../types/world/worldTypes";
-import * as THREE from "three";
-import Throughput from "../../layout/3D-cards/cards/Throughput";
-import ProductionCapacity from "../../layout/3D-cards/cards/ProductionCapacity";
-import ReturnOfInvestment from "../../layout/3D-cards/cards/ReturnOfInvestment";
-import StateWorking from "../../layout/3D-cards/cards/StateWorking";
-import { useSelectedZoneStore } from "../../../store/useZoneStore";
-import { generateUniqueId } from "../../../functions/generateUniqueId";
-import { adding3dWidgets } from "../../../services/realTimeVisulization/zoneData/add3dWidget";
-import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData";
-import { use3DWidget } from "../../../store/useDroppedObjectsStore";
-import {
- useEditWidgetOptionsStore,
- useLeftData,
- useRightClickSelected,
- useRightSelected,
- useTopData,
- useZoneWidgetStore,
-} from "../../../store/useZone3DWidgetStore";
-import { delete3dWidgetApi } from "../../../services/realTimeVisulization/zoneData/delete3dWidget";
-import {
- update3dWidget,
- update3dWidgetRotation,
-} from "../../../services/realTimeVisulization/zoneData/update3dWidget";
+import { useAsset3dWidget, useSocketStore, useWidgetSubOption } from "../../../../store/store";
+import useModuleStore from "../../../../store/useModuleStore";
+import { ThreeState } from "../../../../types/world/worldTypes";
+import { useSelectedZoneStore } from "../../../../store/useZoneStore";
+import { useEditWidgetOptionsStore, useLeftData, useRightClickSelected, useRightSelected, useTopData, useZoneWidgetStore } from "../../../../store/useZone3DWidgetStore";
+import { use3DWidget } from "../../../../store/useDroppedObjectsStore";
+import { get3dWidgetZoneData } from "../../../../services/realTimeVisulization/zoneData/get3dWidgetData";
+import { generateUniqueId } from "../../../../functions/generateUniqueId";
+import ProductionCapacity from "./cards/ProductionCapacity";
+import ReturnOfInvestment from "./cards/ReturnOfInvestment";
+import StateWorking from "./cards/StateWorking";
+import Throughput from "./cards/Throughput";
+import { useWidgetStore } from "../../../../store/useWidgetStore";
+import useChartStore from "../../../../store/useChartStore";
+
+
+
+
+
type WidgetData = {
id: string;
type: string;
@@ -57,10 +47,14 @@ export default function Dropped3dWidgets() {
const planeIntersect = useRef(new THREE.Vector3());
const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]);
const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
+ const { setSelectedChartId } = useWidgetStore();
+ const { measurements, duration} = useChartStore();
let [floorPlanesVertical, setFloorPlanesVertical] = useState(
new THREE.Plane(new THREE.Vector3(0, 1, 0))
);
-
+ const [intersectcontextmenu, setintersectcontextmenu] = useState();
+ const [horizontalX, setHorizontalX] = useState();
+ const [horizontalZ, setHorizontalZ] = useState();
const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || [];
useEffect(() => {
@@ -177,26 +171,30 @@ export default function Dropped3dWidgets() {
};
const onDrop = (event: any) => {
-
event.preventDefault();
event.stopPropagation();
-
+
hasEntered.current = false;
-
+
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
-
+
const newWidget = createdWidgetRef.current;
if (!newWidget || !widgetSelect.startsWith("ui")) return;
-
+
// β
Extract 2D drop position
- const [x, , z] = newWidget.position;
-
+ let [x, y, z] = newWidget.position;
+
+ // β
Clamp Y to at least 0
+ y = Math.max(y, 0);
+ newWidget.position = [x, y, z];
+
// β
Prepare polygon from selectedZone.points
const points3D = selectedZone.points as Array<[number, number, number]>;
const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]);
-
+
const isInside = isPointInPolygon([x, z], zonePolygonXZ);
+
// β
Remove temp widget
const prevWidgets = useZoneWidgetStore.getState().zoneWidgetData[selectedZone.zoneId] || [];
const cleanedWidgets = prevWidgets.filter(w => w.id !== newWidget.id);
@@ -206,26 +204,29 @@ export default function Dropped3dWidgets() {
[selectedZone.zoneId]: cleanedWidgets,
},
}));
+
+ // (Optional) Prevent adding if dropped outside zone
// if (!isInside) {
-
// createdWidgetRef.current = null;
- // return; // Stop here
+ // return;
// }
- // β
Add widget if inside polygon
+
+ // β
Add widget
addWidget(selectedZone.zoneId, newWidget);
-
+
const add3dWidget = {
organization,
widget: newWidget,
zoneId: selectedZone.zoneId,
};
-
+
if (visualizationSocket) {
visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget);
}
-
+
createdWidgetRef.current = null;
};
+
canvasElement.addEventListener("dragenter", handleDragEnter);
@@ -249,8 +250,10 @@ export default function Dropped3dWidgets() {
const widgetToDuplicate = activeZoneWidgets.find(
(w: WidgetData) => w.id === rightClickSelected
);
+ console.log("3d widget to duplecate", widgetToDuplicate);
+
if (!widgetToDuplicate) return;
- const newWidget: WidgetData = {
+ const newWidget: any = {
id: generateUniqueId(),
type: widgetToDuplicate.type,
position: [
@@ -259,6 +262,10 @@ export default function Dropped3dWidgets() {
widgetToDuplicate.position[2] + 0.5,
],
rotation: widgetToDuplicate.rotation || [0, 0, 0],
+ Data:{
+ measurements: measurements,
+ duration: duration
+ },
};
const adding3dWidget = {
organization: organization,
@@ -331,28 +338,40 @@ export default function Dropped3dWidgets() {
return inside;
}
const [prevX, setPrevX] = useState(0);
-
+
useEffect(() => {
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
const handleMouseDown = (event: MouseEvent) => {
if (!rightClickSelected || !rightSelect) return;
+ const selectedZoneId = Object.keys(zoneWidgetData).find(
+ (zoneId: string) =>
+ zoneWidgetData[zoneId].some(
+ (widget: WidgetData) => widget.id === rightClickSelected
+ )
+ );
+ if (!selectedZoneId) return;
+ const selectedWidget = zoneWidgetData[selectedZoneId].find(
+ (widget: WidgetData) => widget.id === rightClickSelected
+ );
+ if (!selectedWidget) return
+ // let points = [];
+ // points.push(new THREE.Vector3(0, 0, 0));
+ // points.push(new THREE.Vector3(0, selectedWidget.position[1], 0));
+ // const newgeometry = new THREE.BufferGeometry().setFromPoints(points);
+ // let vector = new THREE.Vector3();
+ // camera.getWorldDirection(vector);
+ // let cameraDirection = vector;
+ // let newPlane = new THREE.Plane(cameraDirection);
+ // floorPlanesVertical = newPlane;
+ // setFloorPlanesVertical(newPlane);
+ // const intersect1 = raycaster?.ray?.intersectPlane(
+ // floorPlanesVertical,
+ // planeIntersect.current
+ // );
+
+ // setintersectcontextmenu(intersect1.y);
- const cameraDirection = new THREE.Vector3();
- camera.getWorldDirection(cameraDirection);
-
- // Plane normal should be perpendicular to screen (XZ move), so use screen right direction
- const right = new THREE.Vector3();
- camera.getWorldDirection(cameraDirection);
- cameraDirection.y = 0;
- cameraDirection.normalize();
-
- right.crossVectors(new THREE.Vector3(0, 1, 0), cameraDirection).normalize();
-
- // Create a plane that allows vertical movement
- const verticalPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(right, new THREE.Vector3(0, 0, 0));
-
- setFloorPlanesVertical(verticalPlane);
if (rightSelect === "RotateX" || rightSelect === "RotateY") {
mouseStartRef.current = { x: event.clientX, y: event.clientY };
@@ -370,6 +389,7 @@ export default function Dropped3dWidgets() {
rotationStartRef.current = selectedWidget.rotation || [0, 0, 0];
}
}
+
};
const handleMouseMove = (event: MouseEvent) => {
@@ -392,90 +412,110 @@ export default function Dropped3dWidgets() {
raycaster.setFromCamera(mouse, camera);
- if (rightSelect === "Horizontal Move" &&raycaster.ray.intersectPlane(plane.current, planeIntersect.current)) {
- const points3D = selectedZone.points as Array<[number, number, number]>;
- const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]);
- const newPosition: [number, number, number] = [
- planeIntersect.current.x,
- selectedWidget.position[1],
- planeIntersect.current.z,
- ];
- const isInside = isPointInPolygon(
- [newPosition[0], newPosition[2]],
- zonePolygonXZ
- );
- // if (isInside) {
+ if (rightSelect === "Horizontal Move") {
+ const intersect = raycaster.ray.intersectPlane(plane.current, planeIntersect.current);
+ if (
+ intersect &&
+ typeof horizontalX === "number" &&
+ typeof horizontalZ === "number"
+ ) {
+ const selectedZoneId = Object.keys(zoneWidgetData).find(zoneId =>
+ zoneWidgetData[zoneId].some(widget => widget.id === rightClickSelected)
+ );
+ if (!selectedZoneId) return;
+
+ const selectedWidget = zoneWidgetData[selectedZoneId].find(widget => widget.id === rightClickSelected);
+ if (!selectedWidget) return;
+
+ const newPosition: [number, number, number] = [
+ intersect.x + horizontalX,
+ selectedWidget.position[1],
+ intersect.z + horizontalZ,
+ ];
+
+
updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
- // }
+ }
}
+
if (rightSelect === "Vertical Move") {
- if (raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current)) {
- const currentY = selectedWidget.position[1];
- const newY = planeIntersect.current.y;
- console.log('planeIntersect.current: ', planeIntersect.current);
-
- const deltaY = newY - currentY;
-
- // Reject if jump is too large (safety check)
- if (Math.abs(deltaY) > 200) return;
-
- // Clamp jump or apply smoothing
- const clampedY = currentY + THREE.MathUtils.clamp(deltaY, -10, 10);
-
- if (clampedY > 0) {
- updateWidgetPosition(selectedZoneId, rightClickSelected, [
- selectedWidget.position[0],
- clampedY,
- selectedWidget.position[2],
- ]);
- }
+ const intersect = raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current);
+
+ if (intersect && typeof intersectcontextmenu === "number") {
+ const diff = intersect.y - intersectcontextmenu;
+ const unclampedY = selectedWidget.position[1] + diff;
+ const newY = Math.max(0, unclampedY); // Prevent going below floor (y=0)
+
+ setintersectcontextmenu(intersect.y);
+
+ const newPosition: [number, number, number] = [
+ selectedWidget.position[0],
+ newY,
+ selectedWidget.position[2],
+ ];
+
+ updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
}
}
+
+ if (rightSelect?.startsWith("Rotate")) {
+ const axis = rightSelect.slice(-1).toLowerCase(); // "x", "y", or "z"
+ const currentX = event.pageX;
+ const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
+ setPrevX(currentX);
+ if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
+ const index = axis === "x" ? 0 : axis === "y" ? 1 : 2;
+ const currentRotation = selectedWidget.rotation as [number, number, number]; // assert type
+ const newRotation: [number, number, number] = [...currentRotation];
+ newRotation[index] += 0.05 * sign;
+ updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
+ }
+ }
+ // if (rightSelect === "RotateX") {
+ //
+ // const currentX = event.pageX;
+ // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
+ //
+ // setPrevX(currentX);
+ // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
+ //
+ // const newRotation: [number, number, number] = [
+ // selectedWidget.rotation[0] + 0.05 * sign,
+ // selectedWidget.rotation[1],
+ // selectedWidget.rotation[2],
+ // ];
+ // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
+ // }
+ // }
+ // if (rightSelect === "RotateY") {
+ // const currentX = event.pageX;
+ // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
+ // setPrevX(currentX);
+ // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
+ // const newRotation: [number, number, number] = [
+ // selectedWidget.rotation[0],
+ // selectedWidget.rotation[1] + 0.05 * sign,
+ // selectedWidget.rotation[2],
+ // ];
- if (rightSelect === "RotateX") {
- const currentX = event.pageX;
- const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
- setPrevX(currentX);
- if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
- const newRotation: [number, number, number] = [
- selectedWidget.rotation[0] + 0.05 * sign,
- selectedWidget.rotation[1],
- selectedWidget.rotation[2],
- ];
-
- updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
- }
- }
+ // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
+ // }
+ // }
+ // if (rightSelect === "RotateZ") {
+ // const currentX = event.pageX;
+ // const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
+ // setPrevX(currentX);
+ // if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
+ // const newRotation: [number, number, number] = [
+ // selectedWidget.rotation[0],
+ // selectedWidget.rotation[1],
+ // selectedWidget.rotation[2] + 0.05 * sign,
+ // ];
- if (rightSelect === "RotateY") {
- const currentX = event.pageX;
- const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
- setPrevX(currentX);
- if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
- const newRotation: [number, number, number] = [
- selectedWidget.rotation[0] ,
- selectedWidget.rotation[1]+ 0.05 * sign,
- selectedWidget.rotation[2],
- ];
-
- updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
- }
- }
- if (rightSelect === "RotateZ") {
- const currentX = event.pageX;
- const sign = currentX > prevX ? 1 : currentX < prevX ? -1 : 0;
- setPrevX(currentX);
- if (selectedWidget?.rotation && selectedWidget.rotation.length >= 3) {
- const newRotation: [number, number, number] = [
- selectedWidget.rotation[0] ,
- selectedWidget.rotation[1],
- selectedWidget.rotation[2]+ 0.05 * sign,
- ];
-
- updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
- }
- }
+ // updateWidgetRotation(selectedZoneId, rightClickSelected, newRotation);
+ // }
+ // }
};
const handleMouseUp = () => {
if (!rightClickSelected || !rightSelect) return;
@@ -525,7 +565,7 @@ export default function Dropped3dWidgets() {
const rotation = selectedWidget.rotation || [0, 0, 0];
let lastRotation = formatValues(rotation) as [number, number, number];
-
+
// (async () => {
// let response = await update3dWidgetRotation(selectedZoneId, organization, rightClickSelected, lastRotation);
//
@@ -564,11 +604,60 @@ export default function Dropped3dWidgets() {
};
}, [rightClickSelected, rightSelect, zoneWidgetData, gl]);
+ const handleRightClick3d = (event: React.MouseEvent, id: string) => {
+ event.preventDefault();
+
+ const canvasElement = document.getElementById("real-time-vis-canvas");
+ if (!canvasElement) throw new Error("Canvas element not found");
+
+ const canvasRect = canvasElement.getBoundingClientRect();
+ const relativeX = event.clientX - canvasRect.left;
+ const relativeY = event.clientY - canvasRect.top;
+
+ setEditWidgetOptions(true);
+ setRightClickSelected(id);
+ setTop(relativeY);
+ setLeft(relativeX);
+
+ const selectedZoneId = Object.keys(zoneWidgetData).find(zoneId =>
+ zoneWidgetData[zoneId].some(widget => widget.id === id)
+ );
+ if (!selectedZoneId) return;
+
+ const selectedWidget = zoneWidgetData[selectedZoneId].find(widget => widget.id === id);
+ if (!selectedWidget) return;
+
+ const { top, left, width, height } = canvasElement.getBoundingClientRect();
+ mouse.x = ((event.clientX - left) / width) * 2 - 1;
+ mouse.y = -((event.clientY - top) / height) * 2 + 1;
+
+ raycaster.setFromCamera(mouse, camera);
+
+ const cameraDirection = new THREE.Vector3();
+ camera.getWorldDirection(cameraDirection);
+ const verticalPlane = new THREE.Plane(cameraDirection);
+ setFloorPlanesVertical(verticalPlane);
+
+ const intersectPoint = raycaster.ray.intersectPlane(verticalPlane, planeIntersect.current);
+ if (intersectPoint) {
+ setintersectcontextmenu(intersectPoint.y);
+ }
+ const intersect2 = raycaster.ray.intersectPlane(plane.current, planeIntersect.current);
+ if (intersect2) {
+ const xDiff = -intersect2.x + selectedWidget.position[0];
+ const zDiff = -intersect2.z + selectedWidget.position[2];
+ setHorizontalX(xDiff);
+ setHorizontalZ(zDiff);
+ }
+ };
+
+
return (
<>
{activeZoneWidgets.map(
- ({ id, type, position, rotation = [0, 0, 0] }: WidgetData) => {
+ ({ id, type, position, Data, rotation = [0, 0, 0] }: any) => {
const handleRightClick = (event: React.MouseEvent, id: string) => {
+ setSelectedChartId({ id: id, type: type })
event.preventDefault();
const canvasElement = document.getElementById(
"real-time-vis-canvas"
@@ -581,6 +670,7 @@ export default function Dropped3dWidgets() {
setRightClickSelected(id);
setTop(relativeY);
setLeft(relativeX);
+ handleRightClick3d(event, id)
};
switch (type) {
@@ -592,6 +682,7 @@ export default function Dropped3dWidgets() {
type={type}
position={position}
rotation={rotation}
+ Data={Data}
onContextMenu={(e) => handleRightClick(e, id)}
/>
);
@@ -603,6 +694,7 @@ export default function Dropped3dWidgets() {
type={type}
position={position}
rotation={rotation}
+ Data={Data}
onContextMenu={(e) => handleRightClick(e, id)}
/>
);
@@ -614,6 +706,7 @@ export default function Dropped3dWidgets() {
type={type}
position={position}
rotation={rotation}
+ Data={Data}
onContextMenu={(e) => handleRightClick(e, id)}
/>
);
@@ -625,6 +718,7 @@ export default function Dropped3dWidgets() {
type={type}
position={position}
rotation={rotation}
+ Data={Data}
onContextMenu={(e) => handleRightClick(e, id)}
/>
);
diff --git a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx
similarity index 92%
rename from app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx
rename to app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx
index aff7867..274d6dd 100644
--- a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx
+++ b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx
@@ -1,267 +1,270 @@
-import { Html } from "@react-three/drei";
-import React, { useEffect, useMemo, useState } from "react";
-import { Bar } from "react-chartjs-2";
-import {
- Chart as ChartJS,
- CategoryScale,
- LinearScale,
- BarElement,
- Title,
- Tooltip,
- Legend,
- TooltipItem, // Import TooltipItem for typing
-} from "chart.js";
-import { ThroughputIcon } from "../../../icons/3dChartIcons";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import useChartStore from "../../../../store/useChartStore";
-import axios from "axios";
-import io from "socket.io-client";
-
-// Register ChartJS components
-ChartJS.register(
- CategoryScale,
- LinearScale,
- BarElement,
- Title,
- Tooltip,
- Legend
-);
-interface ProductionCapacityProps {
- id: string;
- type: string;
- position: [number, number, number];
- rotation: [number, number, number];
- onContextMenu?: (event: React.MouseEvent) => void;
- // onPointerDown:any
-}
-
-const ProductionCapacity: React.FC = ({
- id,
- type,
- position,
- rotation,
- onContextMenu,
-}) => {
- const { selectedChartId, setSelectedChartId } = useWidgetStore();
- const {
- measurements: chartMeasurements,
- duration: chartDuration,
- name: widgetName,
- } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h");
- const [name, setName] = useState("Widget");
- const [chartData, setChartData] = useState<{
- labels: string[];
- datasets: any[];
- }>({
- labels: [],
- datasets: [],
- });
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- // Chart data for a week
- const defaultChartData = {
- labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], // Days of the week
- datasets: [
- {
- label: "Production Capacity (units/day)",
- data: [1500, 1600, 1400, 1700, 1800, 1900, 2000], // Example daily production data
- backgroundColor: "#6f42c1", // Theme color
- borderColor: "#6f42c1",
- borderWidth: 1,
- borderRadius: 8, // Rounded corners for the bars
- borderSkipped: false, // Ensure all corners are rounded
- },
- ],
- };
-
- // Chart options
- const chartOptions = {
- responsive: true,
- plugins: {
- legend: {
- display: false, // Hide legend
- },
- title: {
- display: true,
- text: "Weekly Production Capacity",
- font: {
- size: 16,
- },
- },
- tooltip: {
- callbacks: {
- // Explicitly type the context parameter
- label: (context: TooltipItem<"bar">) => {
- const value = context.parsed.y; // Extract the y-axis value
- return `${value} units`; // Customize tooltip to display "units"
- },
- },
- },
- },
- scales: {
- x: {
- grid: {
- display: false, // Hide x-axis grid lines
- },
- },
- y: {
- display: false, // Remove the y-axis completely
- },
- },
- };
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
- return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- backgroundColor: "#6f42c1", // Theme color
- borderColor: "#6f42c1",
- borderWidth: 1,
- borderRadius: 8, // Rounded corners for the bars
- borderSkipped: false, // Ensure all corners are rounded
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async () => {
- if (id !== "") {
- try {
- const response = await axios.get(
- `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
- );
-
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements);
- setDuration(response.data.Data.duration);
- setName(response.data.widgetName);
- } else {
- }
- } catch (error) { }
- }
- };
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }, [chartMeasurements, chartDuration, widgetName]);
-
- useEffect(() => { }, [rotation]);
-
-
-
- return (
-
- {
- e.preventDefault();
- e.stopPropagation();
- }}
- onDrop={(e) => {
- e.preventDefault();
- // e.stopPropagation();
- }}
- wrapperClass="pointer-none"
-
- >
- setSelectedChartId({ id: id, type: type })}
- onContextMenu={onContextMenu}
-
- style={{
- width: "300px", // Original width
- height: "300px", // Original height
- // transform: transformStyle.transform,
- transformStyle: "preserve-3d",
- position: "absolute",
- }}
- >
-
-
Production Capacity
-
-
1,200
{" "}
-
units/hour
-
-
-
-
- {/*
units/hour
*/}
-
-
{" "}
-
- {/* Bar Chart */}
- 0
- ? chartData
- : defaultChartData
- }
- options={chartOptions}
- />
-
-
-
-
- );
-};
-
-export default ProductionCapacity;
+import { Html } from "@react-three/drei";
+import React, { useEffect, useMemo, useState } from "react";
+import { Bar } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+ TooltipItem, // Import TooltipItem for typing
+} from "chart.js";
+
+import axios from "axios";
+import io from "socket.io-client";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+
+// Register ChartJS components
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend
+);
+interface ProductionCapacityProps {
+ id: string;
+ type: string;
+ position: [number, number, number];
+ rotation: [number, number, number];
+ Data?: any,
+ onContextMenu?: (event: React.MouseEvent) => void;
+ // onPointerDown:any
+}
+
+const ProductionCapacity: React.FC = ({
+ id,
+ type,
+ Data,
+ position,
+ rotation,
+ onContextMenu,
+}) => {
+ const { selectedChartId, setSelectedChartId } = useWidgetStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {});
+ const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ // Chart data for a week
+ const defaultChartData = {
+ labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], // Days of the week
+ datasets: [
+ {
+ label: "Production Capacity (units/day)",
+ data: [1500, 1600, 1400, 1700, 1800, 1900, 2000], // Example daily production data
+ backgroundColor: "#6f42c1", // Theme color
+ borderColor: "#6f42c1",
+ borderWidth: 1,
+ borderRadius: 8, // Rounded corners for the bars
+ borderSkipped: false, // Ensure all corners are rounded
+ },
+ ],
+ };
+
+ // Chart options
+ const chartOptions = {
+ responsive: true,
+ plugins: {
+ legend: {
+ display: false, // Hide legend
+ },
+ title: {
+ display: true,
+ text: "Weekly Production Capacity",
+ font: {
+ size: 16,
+ },
+ },
+ tooltip: {
+ callbacks: {
+ // Explicitly type the context parameter
+ label: (context: TooltipItem<"bar">) => {
+ const value = context.parsed.y; // Extract the y-axis value
+ return `${value} units`; // Customize tooltip to display "units"
+ },
+ },
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false, // Hide x-axis grid lines
+ },
+ },
+ y: {
+ display: false, // Remove the y-axis completely
+ },
+ },
+ };
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ backgroundColor: "#6f42c1", // Theme color
+ borderColor: "#6f42c1",
+ borderWidth: 1,
+ borderRadius: 8, // Rounded corners for the bars
+ borderSkipped: false, // Ensure all corners are rounded
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
+ );
+
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ }
+ } catch (error) { }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ useEffect(() => { }, [rotation]);
+
+
+
+ return (
+
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onDrop={(e) => {
+ e.preventDefault();
+ // e.stopPropagation();
+ }}
+ wrapperClass="pointer-none"
+
+ >
+ setSelectedChartId({ id: id, type: type })}
+ onContextMenu={onContextMenu}
+
+ style={{
+ width: "300px", // Original width
+ height: "300px", // Original height
+ // transform: transformStyle.transform,
+ transformStyle: "preserve-3d",
+ position: "absolute",
+ transform:'translate(-50%, -50%)',
+ }}
+ >
+
+
Production Capacity
+
+
1,200
{" "}
+
units/hour
+
+
+
+
+ {/*
units/hour
*/}
+
+
{" "}
+
+ {/* Bar Chart */}
+ 0
+ ? chartData
+ : defaultChartData
+ }
+ options={chartOptions}
+ />
+
+
+
+
+ );
+};
+
+export default ProductionCapacity;
diff --git a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx
similarity index 92%
rename from app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx
rename to app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx
index 0dfe967..a4c04ca 100644
--- a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx
+++ b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx
@@ -1,275 +1,279 @@
-import { Html } from "@react-three/drei";
-import React, { useEffect, useMemo, useState } from "react";
-import { Line } from "react-chartjs-2";
-import {
- Chart as ChartJS,
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Tooltip,
- ChartData,
- ChartOptions,
-} from "chart.js";
-import { WavyIcon } from "../../../icons/3dChartIcons";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import useChartStore from "../../../../store/useChartStore";
-import axios from "axios";
-import io from "socket.io-client";
-
-// Register Chart.js components
-ChartJS.register(
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Tooltip
-);
-
-// Define Props for SmoothLineGraphComponent
-interface SmoothLineGraphProps {
- data: ChartData<"line">; // Type for chart data
- options?: ChartOptions<"line">; // Type for chart options (optional)
-}
-
-// SmoothLineGraphComponent using react-chartjs-2
-const SmoothLineGraphComponent: React.FC = ({
- data,
- options,
-}) => {
- return ;
-};
-interface ReturnOfInvestmentProps {
- id: string;
- type: string;
- position: [number, number, number];
- rotation: [number, number, number];
- onContextMenu?: (event: React.MouseEvent) => void;
-}
-const ReturnOfInvestment: React.FC = ({
- id,
- type,
- position,
- rotation,
- onContextMenu,
-}) => {
- const { selectedChartId, setSelectedChartId } = useWidgetStore();
- const {
- measurements: chartMeasurements,
- duration: chartDuration,
- name: widgetName,
- } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h");
- const [name, setName] = useState("Widget");
- const [chartData, setChartData] = useState<{
- labels: string[];
- datasets: any[];
- }>({
- labels: [],
- datasets: [],
- });
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- // Improved sample data for the smooth curve graph (single day)
- const graphData: ChartData<"line"> = {
- labels: [
- "12 AM",
- "3 AM",
- "6 AM",
- "9 AM",
- "12 PM",
- "3 PM",
- "6 PM",
- "9 PM",
- "12 AM",
- ],
- datasets: [
- {
- label: "Investment",
- data: [100, 250, 400, 400, 500, 600, 700, 800, 900], // Example investment growth
- borderColor: "rgba(75, 192, 192, 1)", // Light blue color
- backgroundColor: "rgba(75, 192, 192, 0.2)",
- fill: true,
- tension: 0.4, // Smooth curve effect
- pointRadius: 0, // Hide dots
- pointHoverRadius: 0, // Hide hover dots
- },
- {
- label: "Return",
- data: [100, 200, 500, 250, 300, 350, 400, 450, 500], // Example return values
- borderColor: "rgba(255, 99, 132, 1)", // Pink color
- backgroundColor: "rgba(255, 99, 132, 0.2)",
- fill: true,
- tension: 0.4, // Smooth curve effect
- pointRadius: 0, // Hide dots
- pointHoverRadius: 0, // Hide hover dots
- },
- ],
- };
-
- // Options for the smooth curve graph
- const graphOptions: ChartOptions<"line"> = {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- tooltip: {
- enabled: true, // Enable tooltips on hover
- mode: "index", // Show both datasets' values at the same index
- intersect: false, // Allow hovering anywhere on the graph
- },
- },
- scales: {
- x: {
- grid: {
- display: false, // Hide x-axis grid lines
- },
- ticks: {
- display: false, // Hide x-axis labels
- },
- },
- y: {
- grid: {
- display: false, // Hide y-axis grid lines
- },
- ticks: {
- display: false, // Hide y-axis labels
- },
- },
- },
- };
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
- return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key, index) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- borderColor:
- index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color
- backgroundColor:
- index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)",
- fill: true,
- tension: 0.4, // Smooth curve effect
- pointRadius: 0, // Hide dots
- pointHoverRadius: 0, // Hide hover dots
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async () => {
- if (id !== "") {
- try {
- const response = await axios.get(
- `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
- );
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements);
- setDuration(response.data.Data.duration);
- setName(response.data.widgetName);
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- };
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }, [chartMeasurements, chartDuration, widgetName]);
- const rotationDegrees = {
- x: (rotation[0] * 180) / Math.PI,
- y: (rotation[1] * 180) / Math.PI,
- z: (rotation[2] * 180) / Math.PI,
- };
-
- const transformStyle = {
- transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
- };
-
- return (
-
- setSelectedChartId({ id: id, type: type })}
- onContextMenu={onContextMenu}
- >
-
Return of Investment
-
- {/* Smooth curve graph with two datasets */}
- 0 ? chartData : graphData}
- options={graphOptions}
- />
-
-
-
- in 5y with avg 7% yearly return
-
-
-
- );
-};
-
-export default ReturnOfInvestment;
+import { Html } from "@react-three/drei";
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Tooltip,
+ ChartData,
+ ChartOptions,
+} from "chart.js";
+
+
+import axios from "axios";
+import io from "socket.io-client";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { WavyIcon } from "../../../../../components/icons/3dChartIcons";
+
+// Register Chart.js components
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Tooltip
+);
+
+// Define Props for SmoothLineGraphComponent
+interface SmoothLineGraphProps {
+ data: ChartData<"line">; // Type for chart data
+ options?: ChartOptions<"line">; // Type for chart options (optional)
+}
+
+// SmoothLineGraphComponent using react-chartjs-2
+const SmoothLineGraphComponent: React.FC = ({
+ data,
+ options,
+}) => {
+ return ;
+};
+interface ReturnOfInvestmentProps {
+ id: string;
+ type: string;
+ position: [number, number, number];
+ rotation: [number, number, number];
+ Data?: any;
+ onContextMenu?: (event: React.MouseEvent) => void;
+}
+const ReturnOfInvestment: React.FC = ({
+ id,
+ type,
+ Data,
+ position,
+ rotation,
+ onContextMenu,
+}) => {
+ const { selectedChartId, setSelectedChartId } = useWidgetStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {});
+ const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ // Improved sample data for the smooth curve graph (single day)
+ const graphData: ChartData<"line"> = {
+ labels: [
+ "12 AM",
+ "3 AM",
+ "6 AM",
+ "9 AM",
+ "12 PM",
+ "3 PM",
+ "6 PM",
+ "9 PM",
+ "12 AM",
+ ],
+ datasets: [
+ {
+ label: "Investment",
+ data: [100, 250, 400, 400, 500, 600, 700, 800, 900], // Example investment growth
+ borderColor: "rgba(75, 192, 192, 1)", // Light blue color
+ backgroundColor: "rgba(75, 192, 192, 0.2)",
+ fill: true,
+ tension: 0.4, // Smooth curve effect
+ pointRadius: 0, // Hide dots
+ pointHoverRadius: 0, // Hide hover dots
+ },
+ {
+ label: "Return",
+ data: [100, 200, 500, 250, 300, 350, 400, 450, 500], // Example return values
+ borderColor: "rgba(255, 99, 132, 1)", // Pink color
+ backgroundColor: "rgba(255, 99, 132, 0.2)",
+ fill: true,
+ tension: 0.4, // Smooth curve effect
+ pointRadius: 0, // Hide dots
+ pointHoverRadius: 0, // Hide hover dots
+ },
+ ],
+ };
+
+ // Options for the smooth curve graph
+ const graphOptions: ChartOptions<"line"> = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ tooltip: {
+ enabled: true, // Enable tooltips on hover
+ mode: "index", // Show both datasets' values at the same index
+ intersect: false, // Allow hovering anywhere on the graph
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false, // Hide x-axis grid lines
+ },
+ ticks: {
+ display: false, // Hide x-axis labels
+ },
+ },
+ y: {
+ grid: {
+ display: false, // Hide y-axis grid lines
+ },
+ ticks: {
+ display: false, // Hide y-axis labels
+ },
+ },
+ },
+ };
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key, index) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ borderColor:
+ index === 0 ? "rgba(75, 192, 192, 1)" : "rgba(255, 99, 132, 1)", // Light blue color
+ backgroundColor:
+ index === 0 ? "rgba(75, 192, 192, 0.2)" : "rgba(255, 99, 132, 0.2)",
+ fill: true,
+ tension: 0.4, // Smooth curve effect
+ pointRadius: 0, // Hide dots
+ pointHoverRadius: 0, // Hide hover dots
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+ const rotationDegrees = {
+ x: (rotation[0] * 180) / Math.PI,
+ y: (rotation[1] * 180) / Math.PI,
+ z: (rotation[2] * 180) / Math.PI,
+ };
+
+ const transformStyle = {
+ transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
+ };
+
+ return (
+
+ setSelectedChartId({ id: id, type: type })}
+ onContextMenu={onContextMenu}
+ >
+
Return of Investment
+
+ {/* Smooth curve graph with two datasets */}
+ 0 ? chartData : graphData}
+ options={graphOptions}
+ />
+
+
+
+ in 5y with avg 7% yearly return
+
+
+
+ );
+};
+
+export default ReturnOfInvestment;
diff --git a/app/src/components/layout/3D-cards/cards/StateWorking.tsx b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx
similarity index 93%
rename from app/src/components/layout/3D-cards/cards/StateWorking.tsx
rename to app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx
index 2e5623d..4d10955 100644
--- a/app/src/components/layout/3D-cards/cards/StateWorking.tsx
+++ b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx
@@ -1,201 +1,204 @@
-import { Html } from "@react-three/drei";
-import React, { useEffect, useMemo, useState } from "react";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import useChartStore from "../../../../store/useChartStore";
-import axios from "axios";
-import io from "socket.io-client";
-
-// import image from "../../../../assets/image/temp/image.png";
-interface StateWorkingProps {
- id: string;
- type: string;
- position: [number, number, number];
- rotation: [number, number, number];
- onContextMenu?: (event: React.MouseEvent) => void;
-}
-const StateWorking: React.FC = ({
- id,
- type,
- position,
- rotation,
- onContextMenu,
-}) => {
- const { selectedChartId, setSelectedChartId } = useWidgetStore();
- const {
- measurements: chartMeasurements,
- duration: chartDuration,
- name: widgetName,
- } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h");
- const [name, setName] = useState("Widget");
- const [datas, setDatas] = useState({});
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
- // const datas = [
- // { key: "Oil Tank:", value: "24/341" },
- // { key: "Oil Refin:", value: 36.023 },
- // { key: "Transmission:", value: 36.023 },
- // { key: "Fuel:", value: 36732 },
- // { key: "Power:", value: 1300 },
- // { key: "Time:", value: 13 - 9 - 2023 },
- // ];
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
- return;
- const socket = io(`http://${iotApiUrl}`);
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
- const startStream = () => {
- socket.emit("lastInput", inputData);
- };
- socket.on("connect", startStream);
- socket.on("lastOutput", (response) => {
- const responseData = response;
-
- setDatas(responseData);
- });
-
- return () => {
- socket.off("lastOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async () => {
- if (id !== "") {
- try {
- const response = await axios.get(
- `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
- );
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements);
- setDuration(response.data.Data.duration);
- setName(response.data.widgetName);
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- };
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }, [chartMeasurements, chartDuration, widgetName]);
-
- const rotationDegrees = {
- x: (rotation[0] * 180) / Math.PI,
- y: (rotation[1] * 180) / Math.PI,
- z: (rotation[2] * 180) / Math.PI,
- };
-
- const transformStyle = {
- transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
- };
- return (
-
- setSelectedChartId({ id: id, type: type })}
- onContextMenu={onContextMenu}
- >
-
-
- State
-
- {datas?.input1 ? datas.input1 : "input1"} .
-
-
-
{/*

*/}
-
- {/* Data */}
-
- {/* {datas.map((data, index) => (
-
-
{data.key}
-
{data.value}
-
- ))} */}
-
-
- {measurements?.input2?.fields
- ? measurements.input2.fields
- : "input2"}
-
-
{datas?.input2 ? datas.input2 : "data"}
-
-
-
- {measurements?.input3?.fields
- ? measurements.input3.fields
- : "input3"}
-
-
{datas?.input3 ? datas.input3 : "data"}
-
-
-
- {measurements?.input4?.fields
- ? measurements.input4.fields
- : "input4"}
-
-
{datas?.input4 ? datas.input4 : "data"}
-
-
-
- {measurements?.input5?.fields
- ? measurements.input5.fields
- : "input5"}
-
-
{datas?.input5 ? datas.input5 : "data"}
-
-
-
- {measurements?.input6?.fields
- ? measurements.input6.fields
- : "input6"}
-
-
{datas?.input6 ? datas.input6 : "data"}
-
-
-
- {measurements?.input7?.fields
- ? measurements.input7.fields
- : "input7"}
-
-
{datas?.input7 ? datas.input7 : "data"}
-
-
-
-
- );
-};
-
-export default StateWorking;
+import { Html } from "@react-three/drei";
+import React, { useEffect, useMemo, useState } from "react";
+
+import axios from "axios";
+import io from "socket.io-client";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+
+// import image from "../../../../assets/image/temp/image.png";
+interface StateWorkingProps {
+ id: string;
+ type: string;
+ position: [number, number, number];
+ rotation: [number, number, number];
+ Data?:any;
+ onContextMenu?: (event: React.MouseEvent) => void;
+}
+const StateWorking: React.FC = ({
+ id,
+ type,
+ Data,
+ position,
+ rotation,
+ onContextMenu,
+}) => {
+ const { selectedChartId, setSelectedChartId } = useWidgetStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {});
+ const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h");
+ const [name, setName] = useState("Widget");
+ const [datas, setDatas] = useState({});
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+ // const datas = [
+ // { key: "Oil Tank:", value: "24/341" },
+ // { key: "Oil Refin:", value: 36.023 },
+ // { key: "Transmission:", value: 36.023 },
+ // { key: "Fuel:", value: 36732 },
+ // { key: "Power:", value: 1300 },
+ // { key: "Time:", value: 13 - 9 - 2023 },
+ // ];
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+ const socket = io(`http://${iotApiUrl}`);
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+ const startStream = () => {
+ socket.emit("lastInput", inputData);
+ };
+ socket.on("connect", startStream);
+ socket.on("lastOutput", (response) => {
+ const responseData = response;
+
+ setDatas(responseData);
+ });
+
+ return () => {
+ socket.off("lastOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+
+ const rotationDegrees = {
+ x: (rotation[0] * 180) / Math.PI,
+ y: (rotation[1] * 180) / Math.PI,
+ z: (rotation[2] * 180) / Math.PI,
+ };
+
+ const transformStyle = {
+ transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
+ };
+ return (
+
+ setSelectedChartId({ id: id, type: type })}
+ onContextMenu={onContextMenu}
+ >
+
+
+ State
+
+ {datas?.input1 ? datas.input1 : "input1"} .
+
+
+
{/*

*/}
+
+ {/* Data */}
+
+ {/* {datas.map((data, index) => (
+
+
{data.key}
+
{data.value}
+
+ ))} */}
+
+
+ {measurements?.input2?.fields
+ ? measurements.input2.fields
+ : "input2"}
+
+
{datas?.input2 ? datas.input2 : "data"}
+
+
+
+ {measurements?.input3?.fields
+ ? measurements.input3.fields
+ : "input3"}
+
+
{datas?.input3 ? datas.input3 : "data"}
+
+
+
+ {measurements?.input4?.fields
+ ? measurements.input4.fields
+ : "input4"}
+
+
{datas?.input4 ? datas.input4 : "data"}
+
+
+
+ {measurements?.input5?.fields
+ ? measurements.input5.fields
+ : "input5"}
+
+
{datas?.input5 ? datas.input5 : "data"}
+
+
+
+ {measurements?.input6?.fields
+ ? measurements.input6.fields
+ : "input6"}
+
+
{datas?.input6 ? datas.input6 : "data"}
+
+
+
+ {measurements?.input7?.fields
+ ? measurements.input7.fields
+ : "input7"}
+
+
{datas?.input7 ? datas.input7 : "data"}
+
+
+
+
+ );
+};
+
+export default StateWorking;
diff --git a/app/src/components/layout/3D-cards/cards/Throughput.tsx b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx
similarity index 92%
rename from app/src/components/layout/3D-cards/cards/Throughput.tsx
rename to app/src/modules/visualization/widgets/3d/cards/Throughput.tsx
index 65e1bee..67397bd 100644
--- a/app/src/components/layout/3D-cards/cards/Throughput.tsx
+++ b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx
@@ -1,266 +1,270 @@
-import { Html } from "@react-three/drei";
-import React, { useEffect, useMemo, useState } from "react";
-import { Line } from "react-chartjs-2";
-import {
- Chart as ChartJS,
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend,
- ChartData,
- ChartOptions,
-} from "chart.js";
-import { ThroughputIcon } from "../../../icons/3dChartIcons";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
-import useChartStore from "../../../../store/useChartStore";
-import axios from "axios";
-import io from "socket.io-client";
-
-// Register Chart.js components
-ChartJS.register(
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend
-);
-
-// Define Props for LineGraphComponent
-interface LineGraphProps {
- data: ChartData<"line">; // Type for chart data
- options?: ChartOptions<"line">; // Type for chart options (optional)
-}
-
-// LineGraphComponent using react-chartjs-2
-const LineGraphComponent: React.FC = ({ data, options }) => {
- return ;
-};
-
-interface ThroughputProps {
- id: string;
- type: string;
- position: [number, number, number];
- rotation: [number, number, number];
- onContextMenu?: (event: React.MouseEvent) => void;
-}
-
-const Throughput: React.FC = ({
- id,
- type,
- position,
- rotation,
- onContextMenu,
-}) => {
- const { selectedChartId, setSelectedChartId } = useWidgetStore();
- const {
- measurements: chartMeasurements,
- duration: chartDuration,
- name: widgetName,
- } = useChartStore();
- const [measurements, setmeasurements] = useState({});
- const [duration, setDuration] = useState("1h");
- const [name, setName] = useState("Widget");
- const [chartData, setChartData] = useState<{
- labels: string[];
- datasets: any[];
- }>({
- labels: [],
- datasets: [],
- });
- const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
- const email = localStorage.getItem("email") || "";
- const organization = email?.split("@")[1]?.split(".")[0];
-
- // Sample data for the line graph
- const graphData: ChartData<"line"> = {
- labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
- datasets: [
- {
- label: "Throughput",
- data: [1000, 1200, 1100, 1300, 1250, 1400], // Example throughput values
- borderColor: "rgba(75, 192, 192, 1)",
- backgroundColor: "rgba(75, 192, 192, 0.2)",
- fill: true,
- },
- ],
- };
-
- // Options for the line graph
- const graphOptions: ChartOptions<"line"> = {
- responsive: true,
- plugins: {
- legend: {
- position: "top",
- display: false,
- },
- title: {
- display: true,
- text: "Throughput Over Time",
- },
- },
- scales: {
- x: {
- grid: {
- display: true, // Show vertical grid lines
- },
- ticks: {
- display: false, // Hide x-axis labels
- },
- },
- y: {
- grid: {
- display: false, // Hide horizontal grid lines
- },
- ticks: {
- display: false, // Hide y-axis labels
- },
- },
- },
- };
-
- useEffect(() => {
- if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
- return;
-
- const socket = io(`http://${iotApiUrl}`);
-
- const inputData = {
- measurements,
- duration,
- interval: 1000,
- };
-
- const startStream = () => {
- socket.emit("lineInput", inputData);
- };
-
- socket.on("connect", startStream);
-
- socket.on("lineOutput", (response) => {
- const responseData = response.data;
-
- // Extract timestamps and values
- const labels = responseData.time;
- const datasets = Object.keys(measurements).map((key) => {
- const measurement = measurements[key];
- const datasetKey = `${measurement.name}.${measurement.fields}`;
- return {
- label: datasetKey,
- data: responseData[datasetKey]?.values ?? [],
- borderColor: "rgba(75, 192, 192, 1)",
- backgroundColor: "rgba(75, 192, 192, 0.2)",
- fill: true,
- };
- });
-
- setChartData({ labels, datasets });
- });
-
- return () => {
- socket.off("lineOutput");
- socket.emit("stop_stream"); // Stop streaming when component unmounts
- socket.disconnect();
- };
- }, [measurements, duration, iotApiUrl]);
-
- const fetchSavedInputes = async () => {
- if (id !== "") {
- try {
- const response = await axios.get(
- `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
- );
- if (response.status === 200) {
- setmeasurements(response.data.Data.measurements);
- setDuration(response.data.Data.duration);
- setName(response.data.widgetName);
- } else {
- console.log("Unexpected response:", response);
- }
- } catch (error) {
- console.error("There was an error!", error);
- }
- }
- };
-
- useEffect(() => {
- fetchSavedInputes();
- }, []);
-
- useEffect(() => {
- if (selectedChartId?.id === id) {
- fetchSavedInputes();
- }
- }, [chartMeasurements, chartDuration, widgetName]);
- const rotationDegrees = {
- x: (rotation[0] * 180) / Math.PI,
- y: (rotation[1] * 180) / Math.PI,
- z: (rotation[2] * 180) / Math.PI,
- };
-
- const transformStyle = {
- transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
- };
-
- return (
-
- setSelectedChartId({ id: id, type: type })}
- onContextMenu={onContextMenu}
- >
-
{name}
-
-
- {/* Line graph using react-chartjs-2 */}
- 0 ? chartData : graphData}
- options={graphOptions}
- />
-
-
- You made an extra $1256.13 this month
-
-
-
- );
-};
-
-export default Throughput;
+import { Html } from "@react-three/drei";
+import React, { useEffect, useMemo, useState } from "react";
+import { Line } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+ ChartData,
+ ChartOptions,
+} from "chart.js";
+
+
+import axios from "axios";
+import io from "socket.io-client";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { ThroughputIcon } from "../../../../../components/icons/3dChartIcons";
+
+// Register Chart.js components
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend
+);
+
+// Define Props for LineGraphComponent
+interface LineGraphProps {
+ data: ChartData<"line">; // Type for chart data
+ options?: ChartOptions<"line">; // Type for chart options (optional)
+}
+
+// LineGraphComponent using react-chartjs-2
+const LineGraphComponent: React.FC = ({ data, options }) => {
+ return ;
+};
+
+interface ThroughputProps {
+ id: string;
+ type: string;
+ position: [number, number, number];
+ rotation: [number, number, number];
+ Data?:any;
+ onContextMenu?: (event: React.MouseEvent) => void;
+}
+
+const Throughput: React.FC = ({
+ id,
+ type,
+ Data,
+ position,
+ rotation,
+ onContextMenu,
+}) => {
+ const { selectedChartId, setSelectedChartId } = useWidgetStore();
+ const {
+ measurements: chartMeasurements,
+ duration: chartDuration,
+ name: widgetName,
+ } = useChartStore();
+ const [measurements, setmeasurements] = useState(Data?.measurements ? Data.measurements : {});
+ const [duration, setDuration] = useState(Data?.duration ? Data.duration : "1h");
+ const [name, setName] = useState("Widget");
+ const [chartData, setChartData] = useState<{
+ labels: string[];
+ datasets: any[];
+ }>({
+ labels: [],
+ datasets: [],
+ });
+ const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
+ const email = localStorage.getItem("email") || "";
+ const organization = email?.split("@")[1]?.split(".")[0];
+
+ // Sample data for the line graph
+ const graphData: ChartData<"line"> = {
+ labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
+ datasets: [
+ {
+ label: "Throughput",
+ data: [1000, 1200, 1100, 1300, 1250, 1400], // Example throughput values
+ borderColor: "rgba(75, 192, 192, 1)",
+ backgroundColor: "rgba(75, 192, 192, 0.2)",
+ fill: true,
+ },
+ ],
+ };
+
+ // Options for the line graph
+ const graphOptions: ChartOptions<"line"> = {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: "top",
+ display: false,
+ },
+ title: {
+ display: true,
+ text: "Throughput Over Time",
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: true, // Show vertical grid lines
+ },
+ ticks: {
+ display: false, // Hide x-axis labels
+ },
+ },
+ y: {
+ grid: {
+ display: false, // Hide horizontal grid lines
+ },
+ ticks: {
+ display: false, // Hide y-axis labels
+ },
+ },
+ },
+ };
+
+ useEffect(() => {
+ if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
+ return;
+
+ const socket = io(`http://${iotApiUrl}`);
+
+ const inputData = {
+ measurements,
+ duration,
+ interval: 1000,
+ };
+
+ const startStream = () => {
+ socket.emit("lineInput", inputData);
+ };
+
+ socket.on("connect", startStream);
+
+ socket.on("lineOutput", (response) => {
+ const responseData = response.data;
+
+ // Extract timestamps and values
+ const labels = responseData.time;
+ const datasets = Object.keys(measurements).map((key) => {
+ const measurement = measurements[key];
+ const datasetKey = `${measurement.name}.${measurement.fields}`;
+ return {
+ label: datasetKey,
+ data: responseData[datasetKey]?.values ?? [],
+ borderColor: "rgba(75, 192, 192, 1)",
+ backgroundColor: "rgba(75, 192, 192, 0.2)",
+ fill: true,
+ };
+ });
+
+ setChartData({ labels, datasets });
+ });
+
+ return () => {
+ socket.off("lineOutput");
+ socket.emit("stop_stream"); // Stop streaming when component unmounts
+ socket.disconnect();
+ };
+ }, [measurements, duration, iotApiUrl]);
+
+ const fetchSavedInputes = async () => {
+ if (id !== "") {
+ try {
+ const response = await axios.get(
+ `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${id}/${organization}`
+ );
+ if (response.status === 200) {
+ setmeasurements(response.data.Data.measurements);
+ setDuration(response.data.Data.duration);
+ setName(response.data.widgetName);
+ } else {
+ console.log("Unexpected response:", response);
+ }
+ } catch (error) {
+ console.error("There was an error!", error);
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchSavedInputes();
+ }, []);
+
+ useEffect(() => {
+ if (selectedChartId?.id === id) {
+ fetchSavedInputes();
+ }
+ }, [chartMeasurements, chartDuration, widgetName]);
+ const rotationDegrees = {
+ x: (rotation[0] * 180) / Math.PI,
+ y: (rotation[1] * 180) / Math.PI,
+ z: (rotation[2] * 180) / Math.PI,
+ };
+
+ const transformStyle = {
+ transform: `rotateX(${rotationDegrees.x}deg) rotateY(${rotationDegrees.y}deg) rotateZ(${rotationDegrees.z}deg)`,
+ };
+
+ return (
+
+ setSelectedChartId({ id: id, type: type })}
+ onContextMenu={onContextMenu}
+ >
+
{name}
+
+
+ {/* Line graph using react-chartjs-2 */}
+ 0 ? chartData : graphData}
+ options={graphOptions}
+ />
+
+
+ You made an extra $1256.13 this month
+
+
+
+ );
+};
+
+export default Throughput;
diff --git a/app/src/components/ui/componets/DistanceLines.tsx b/app/src/modules/visualization/widgets/floating/DistanceLines.tsx
similarity index 100%
rename from app/src/components/ui/componets/DistanceLines.tsx
rename to app/src/modules/visualization/widgets/floating/DistanceLines.tsx
diff --git a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx b/app/src/modules/visualization/widgets/floating/DroppedFloatingWidgets.tsx
similarity index 94%
rename from app/src/components/ui/componets/DroppedFloatingWidgets.tsx
rename to app/src/modules/visualization/widgets/floating/DroppedFloatingWidgets.tsx
index eed76e5..de48ef6 100644
--- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx
+++ b/app/src/modules/visualization/widgets/floating/DroppedFloatingWidgets.tsx
@@ -1,29 +1,29 @@
-import { WalletIcon } from "../../icons/3dChartIcons";
+import { WalletIcon } from "../../../../components/icons/3dChartIcons";
import { useEffect, useRef, useState } from "react";
import {
useDroppedObjectsStore,
Zones,
-} from "../../../store/useDroppedObjectsStore";
-import useModuleStore from "../../../store/useModuleStore";
-import { determinePosition } from "./functions/determinePosition";
-import { getActiveProperties } from "./functions/getActiveProperties";
-import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
+} from "../../../../store/useDroppedObjectsStore";
+import useModuleStore from "../../../../store/useModuleStore";
+import { determinePosition } from "../../functions/determinePosition";
+import { getActiveProperties } from "../../functions/getActiveProperties";
+import { addingFloatingWidgets } from "../../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
import {
DublicateIcon,
KebabIcon,
DeleteIcon,
-} from "../../icons/ExportCommonIcons";
+} from "../../../../components/icons/ExportCommonIcons";
import DistanceLines from "./DistanceLines"; // Import the DistanceLines component
-import { deleteFloatingWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteFloatingWidget";
+import { deleteFloatingWidgetApi } from "../../../../services/realTimeVisulization/zoneData/deleteFloatingWidget";
-import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent";
-import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent";
-import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent";
-import { useWidgetStore } from "../../../store/useWidgetStore";
-import { useSocketStore } from "../../../store/store";
-import { useClickOutside } from "./functions/handleWidgetsOuterClick";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
-import { useSelectedZoneStore } from "../../../store/useZoneStore";
+import TotalCardComponent from "./cards/TotalCardComponent";
+import WarehouseThroughputComponent from "./cards/WarehouseThroughputComponent";
+import FleetEfficiencyComponent from "./cards/FleetEfficiencyComponent";
+import { useWidgetStore } from "../../../../store/useWidgetStore";
+import { useSocketStore } from "../../../../store/store";
+import { useClickOutside } from "../../functions/handleWidgetsOuterClick";
+import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
+import { useSelectedZoneStore } from "../../../../store/useZoneStore";
interface DraggingState {
zone: string;
index: number;
@@ -560,7 +560,7 @@ const DroppedObjects: React.FC = () => {
key={`${zoneName}-${index}`}
className={`${obj.className} ${
selectedChartId?.id === obj.id && "activeChart"
- }`}
+ } `}
ref={chartWidget}
style={{
position: "absolute",
@@ -569,7 +569,9 @@ const DroppedObjects: React.FC = () => {
right: rightPosition,
bottom: bottomPosition,
pointerEvents: isPlaying ? "none" : "auto",
- minHeight: `${obj.className === "warehouseThroughput" && "150px !important"} `
+ minHeight: `${
+ obj.className === "warehouseThroughput" && "150px !important"
+ } `,
}}
onPointerDown={(event) => {
setSelectedChartId(obj);
@@ -661,3 +663,4 @@ const DroppedObjects: React.FC = () => {
};
export default DroppedObjects;
+
diff --git a/app/src/components/ui/realTimeVis/floating/FleetEfficiency.tsx b/app/src/modules/visualization/widgets/floating/cards/FleetEfficiency.tsx
similarity index 96%
rename from app/src/components/ui/realTimeVis/floating/FleetEfficiency.tsx
rename to app/src/modules/visualization/widgets/floating/cards/FleetEfficiency.tsx
index cabc3e7..2e8f7ef 100644
--- a/app/src/components/ui/realTimeVis/floating/FleetEfficiency.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/FleetEfficiency.tsx
@@ -1,49 +1,49 @@
-const FleetEfficiency = () => {
- const progress = 50; // Example progress value (0-100)
-
- // Calculate the rotation angle for the progress bar
- const rotationAngle = 45 + progress * 1.8;
-
- const handleDragStart = (event: React.DragEvent) => {
- const rect = event.currentTarget.getBoundingClientRect(); // Get position
-
- const cardData = JSON.stringify({
- className: event.currentTarget.className,
- position: [rect.top, rect.left], // Store position
- value: rotationAngle, // Example value (you can change if dynamic)
- per: progress,
-
- });
- event.dataTransfer.setData("text/plain", cardData);
- };
-
-
- return (
-
-
Fleet Efficiency
-
-
-
-
- {/* Apply dynamic rotation to the bar */}
-
-
-
-
-
-
-
0%
-
-
{progress}%
-
Optimal
-
-
100%
-
-
- );
-};
-
-export default FleetEfficiency;
+const FleetEfficiency = () => {
+ const progress = 50; // Example progress value (0-100)
+
+ // Calculate the rotation angle for the progress bar
+ const rotationAngle = 45 + progress * 1.8;
+
+ const handleDragStart = (event: React.DragEvent) => {
+ const rect = event.currentTarget.getBoundingClientRect(); // Get position
+
+ const cardData = JSON.stringify({
+ className: event.currentTarget.className,
+ position: [rect.top, rect.left], // Store position
+ value: rotationAngle, // Example value (you can change if dynamic)
+ per: progress,
+
+ });
+ event.dataTransfer.setData("text/plain", cardData);
+ };
+
+
+ return (
+
+
Fleet Efficiency
+
+
+
+
+ {/* Apply dynamic rotation to the bar */}
+
+
+
+
+
+
+
0%
+
+
{progress}%
+
Optimal
+
+
100%
+
+
+ );
+};
+
+export default FleetEfficiency;
diff --git a/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx b/app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx
similarity index 94%
rename from app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx
rename to app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx
index 2cf12e0..6799dcb 100644
--- a/app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { Line } from 'react-chartjs-2'
-import useChartStore from '../../../../store/useChartStore';
-import { useWidgetStore } from '../../../../store/useWidgetStore';
+import useChartStore from '../../../../../store/useChartStore';
+import { useWidgetStore } from '../../../../../store/useWidgetStore';
import axios from 'axios';
import io from "socket.io-client";
@@ -41,7 +41,7 @@ const FleetEfficiencyComponent = ({object}: any) => {
socket.on("lastOutput", (response) => {
const responseData = response.input1;
- console.log(responseData);
+ // console.log(responseData);
if (typeof responseData === "number") {
console.log("It's a number!");
diff --git a/app/src/components/ui/realTimeVis/floating/ProductivityDashboard.tsx b/app/src/modules/visualization/widgets/floating/cards/ProductivityDashboard.tsx
similarity index 95%
rename from app/src/components/ui/realTimeVis/floating/ProductivityDashboard.tsx
rename to app/src/modules/visualization/widgets/floating/cards/ProductivityDashboard.tsx
index b4d43b7..e0cd382 100644
--- a/app/src/components/ui/realTimeVis/floating/ProductivityDashboard.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/ProductivityDashboard.tsx
@@ -1,86 +1,86 @@
-import React from "react";
-
-
-interface ProductivityData {
- distancePerTask: number;
- spaceUtilization: number;
- taskCompletionTime: string;
-}
-
-const ProductivityDashboard: React.FC = () => {
- const data: ProductivityData = {
- distancePerTask: 45,
- spaceUtilization: 72,
- taskCompletionTime: "7:44",
- };
-
- // Function to calculate the stroke dash offset for the circular progress
- const calculateDashOffset = (percentage: number, circumference: number) => {
- return circumference - (percentage / 100) * circumference;
- };
-
- // Constants for the circular progress chart
- const radius = 60; // Radius of the circle
- const strokeWidth = 10; // Thickness of the stroke
- const diameter = radius * 2; // Diameter of the circle
- const circumference = Math.PI * (radius * 2); // Circumference of the circle
-
- return (
-
-
-
-
-
-
Distance per Task
-
{data.distancePerTask} m
-
-
-
Space Utilization
-
{data.spaceUtilization}%
-
-
-
-
-
-
Task Completion Time
-
{data.taskCompletionTime}
-
Total Score
-
-
-
-
- );
-};
-
+import React from "react";
+
+
+interface ProductivityData {
+ distancePerTask: number;
+ spaceUtilization: number;
+ taskCompletionTime: string;
+}
+
+const ProductivityDashboard: React.FC = () => {
+ const data: ProductivityData = {
+ distancePerTask: 45,
+ spaceUtilization: 72,
+ taskCompletionTime: "7:44",
+ };
+
+ // Function to calculate the stroke dash offset for the circular progress
+ const calculateDashOffset = (percentage: number, circumference: number) => {
+ return circumference - (percentage / 100) * circumference;
+ };
+
+ // Constants for the circular progress chart
+ const radius = 60; // Radius of the circle
+ const strokeWidth = 10; // Thickness of the stroke
+ const diameter = radius * 2; // Diameter of the circle
+ const circumference = Math.PI * (radius * 2); // Circumference of the circle
+
+ return (
+
+
+
+
+
+
Distance per Task
+
{data.distancePerTask} m
+
+
+
Space Utilization
+
{data.spaceUtilization}%
+
+
+
+
+
+
Task Completion Time
+
{data.taskCompletionTime}
+
Total Score
+
+
+
+
+ );
+};
+
export default ProductivityDashboard;
\ No newline at end of file
diff --git a/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx b/app/src/modules/visualization/widgets/floating/cards/SimpleCard.tsx
similarity index 86%
rename from app/src/components/ui/realTimeVis/floating/SimpleCard.tsx
rename to app/src/modules/visualization/widgets/floating/cards/SimpleCard.tsx
index 7fafb79..926ddbc 100644
--- a/app/src/components/ui/realTimeVis/floating/SimpleCard.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/SimpleCard.tsx
@@ -1,55 +1,58 @@
-import React from "react";
-
-interface SimpleCardProps {
- header: string;
- icon: React.ComponentType>; // React component for SVG icon
- value: string;
- per: string; // Percentage change
- position?: [number, number];
-}
-
-const SimpleCard: React.FC = ({
- header,
- icon: Icon,
- value,
- per,
- position = [0, 0],
-}) => {
- const handleDragStart = (event: React.DragEvent) => {
- const rect = event.currentTarget.getBoundingClientRect(); // Get position
- const cardData = JSON.stringify({
- header,
- value,
- per,
- icon: Icon,
-
- className: event.currentTarget.className,
- position: [rect.top, rect.left], // β
Store position
- });
-
- event.dataTransfer.setData("text/plain", cardData);
- };
-
- return (
-
- );
-};
-
-export default SimpleCard;
+import React from "react";
+
+interface SimpleCardProps {
+ header: string;
+ icon: React.ElementType; // React component for SVG icon
+ iconName?: string;
+ value: string;
+ per: string; // Percentage change
+ position?: [number, number];
+}
+
+const SimpleCard: React.FC = ({
+ header,
+ icon: Icon,
+ iconName,
+ value,
+ per,
+ position = [0, 0],
+}) => {
+ const handleDragStart = (event: React.DragEvent) => {
+ const rect = event.currentTarget.getBoundingClientRect(); // Get position
+
+ const cardData = JSON.stringify({
+ header,
+ value,
+ per,
+ iconName: iconName || "UnknownIcon", // Use the custom iconName
+ className: event.currentTarget.className,
+ position: [rect.top, rect.left], // β
Store position
+ });
+
+ event.dataTransfer.setData("text/plain", cardData);
+ console.log("cardData: ", cardData);
+ };
+
+ return (
+
+ );
+};
+
+export default SimpleCard;
diff --git a/app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx b/app/src/modules/visualization/widgets/floating/cards/TotalCardComponent.tsx
similarity index 79%
rename from app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx
rename to app/src/modules/visualization/widgets/floating/cards/TotalCardComponent.tsx
index 1dfab66..6e8d692 100644
--- a/app/src/components/ui/realTimeVis/floating/TotalCardComponent.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/TotalCardComponent.tsx
@@ -1,10 +1,15 @@
import React, { useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
-import useChartStore from "../../../../store/useChartStore";
-import { useWidgetStore } from "../../../../store/useWidgetStore";
+import useChartStore from "../../../../../store/useChartStore";
+import { useWidgetStore } from "../../../../../store/useWidgetStore";
import axios from "axios";
import io from "socket.io-client";
-import { WalletIcon } from "../../../icons/3dChartIcons";
+import {
+ CartIcon,
+ DocumentIcon,
+ GlobeIcon,
+ WalletIcon,
+} from "../../../../../components/icons/3dChartIcons";
const TotalCardComponent = ({ object }: any) => {
const [progress, setProgress] = useState(0);
@@ -77,6 +82,20 @@ const TotalCardComponent = ({ object }: any) => {
}
}, [header, flotingDuration, flotingMeasurements]);
+ const mapIcon = (iconName: string) => {
+ switch (iconName) {
+ case "WalletIcon":
+ return ;
+ case "GlobeIcon":
+ return ;
+ case "DocumentIcon":
+ return ;
+ case "CartIcon":
+ return ;
+ default:
+ return ;
+ }
+ };
return (
<>
@@ -86,9 +105,7 @@ const TotalCardComponent = ({ object }: any) => {
{object.per}
-
-
-
+
{mapIcon(object.iconName)}
>
);
};
diff --git a/app/src/components/ui/realTimeVis/floating/WarehouseThroughput.tsx b/app/src/modules/visualization/widgets/floating/cards/WarehouseThroughput.tsx
similarity index 96%
rename from app/src/components/ui/realTimeVis/floating/WarehouseThroughput.tsx
rename to app/src/modules/visualization/widgets/floating/cards/WarehouseThroughput.tsx
index 2a23e45..221c5d1 100644
--- a/app/src/components/ui/realTimeVis/floating/WarehouseThroughput.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/WarehouseThroughput.tsx
@@ -1,149 +1,149 @@
-import React from "react";
-import { Line } from "react-chartjs-2";
-import {
- Chart as ChartJS,
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend,
- Filler, // Import Filler for area fill
-} from "chart.js";
-
-// Register ChartJS components
-ChartJS.register(
- CategoryScale,
- LinearScale,
- PointElement,
- LineElement,
- Title,
- Tooltip,
- Legend,
- Filler
-);
-
-const WarehouseThroughput = () => {
- // Line graph data for a year (monthly throughput)
- const lineGraphData = {
- labels: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ], // Months of the year
- datasets: [
- {
- label: "Throughput (units/month)",
- data: [500, 400, 300, 450, 350, 250, 200, 300, 250, 150, 100, 150], // Example monthly data
- borderColor: "#6f42c1", // Use the desired color for the line (purple)
- backgroundColor: "rgba(111, 66, 193, 0.2)", // Use a semi-transparent purple for the fill
- borderWidth: 2, // Line thickness
- fill: true, // Enable fill for this dataset
- pointRadius: 0, // Remove dots at each data point
- tension: 0.5, // Smooth interpolation for the line
- },
- ],
- };
-
- // Line graph options
- const lineGraphOptions = {
- responsive: true,
- maintainAspectRatio: false, // Allow custom height/width adjustments
- plugins: {
- legend: {
- display: false, // Hide legend
- },
- title: {
- display: false, // No chart title needed
- },
- tooltip: {
- callbacks: {
- label: (context: any) => {
- const value = context.parsed.y;
- return `${value} units`; // Customize tooltip to display "units"
- },
- },
- },
- },
- scales: {
- x: {
- grid: {
- display: false, // Hide x-axis grid lines
- },
- ticks: {
- maxRotation: 0, // Prevent label rotation
- autoSkip: false, // Display all months
- font: {
- size: 8, // Adjust font size for readability
- color: "#ffffff", // Light text color for labels
- },
- },
- },
- y: {
- display: true, // Show y-axis
- grid: {
- drawBorder: false, // Remove border line
- color: "rgba(255, 255, 255, 0.2)", // Light gray color for grid lines
- borderDash: [5, 5], // Dotted line style (array defines dash and gap lengths)
- },
- ticks: {
- font: {
- size: 8, // Adjust font size for readability
- color: "#ffffff", // Light text color for ticks
- },
- },
- },
- },
- elements: {
- line: {
- tension: 0.5, // Smooth interpolation for the line
- },
- },
- };
-
- const handleDragStart = (event: React.DragEvent
) => {
- const rect = event.currentTarget.getBoundingClientRect(); // Get element position
-
- const cardData = JSON.stringify({
- header: "Warehouse Throughput", // Static header
- value: "+5", // Example value (you can change if dynamic)
- per: "2025", // Example percentage or date
- icon: "π", // Placeholder for an icon (if needed)
- className: event.currentTarget.className,
- position: [rect.top, rect.left], // β
Store initial position
- lineGraphData, // β
Include chart data
- lineGraphOptions, // β
Include chart options
- });
-
-
- event.dataTransfer.setData("text/plain", cardData);
- // event.dataTransfer.effectAllowed = "move"; // Improve drag effect
- };
-
- return (
-
-
-
Warehouse Throughput
-
- (+5) more in 2025
-
-
-
- {/* Line Graph */}
-
-
-
- );
-};
-
-export default WarehouseThroughput;
+import React from "react";
+import { Line } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+ Filler, // Import Filler for area fill
+} from "chart.js";
+
+// Register ChartJS components
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+ Filler
+);
+
+const WarehouseThroughput = () => {
+ // Line graph data for a year (monthly throughput)
+ const lineGraphData = {
+ labels: [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ], // Months of the year
+ datasets: [
+ {
+ label: "Throughput (units/month)",
+ data: [500, 400, 300, 450, 350, 250, 200, 300, 250, 150, 100, 150], // Example monthly data
+ borderColor: "#6f42c1", // Use the desired color for the line (purple)
+ backgroundColor: "rgba(111, 66, 193, 0.2)", // Use a semi-transparent purple for the fill
+ borderWidth: 2, // Line thickness
+ fill: true, // Enable fill for this dataset
+ pointRadius: 0, // Remove dots at each data point
+ tension: 0.5, // Smooth interpolation for the line
+ },
+ ],
+ };
+
+ // Line graph options
+ const lineGraphOptions = {
+ responsive: true,
+ maintainAspectRatio: false, // Allow custom height/width adjustments
+ plugins: {
+ legend: {
+ display: false, // Hide legend
+ },
+ title: {
+ display: false, // No chart title needed
+ },
+ tooltip: {
+ callbacks: {
+ label: (context: any) => {
+ const value = context.parsed.y;
+ return `${value} units`; // Customize tooltip to display "units"
+ },
+ },
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false, // Hide x-axis grid lines
+ },
+ ticks: {
+ maxRotation: 0, // Prevent label rotation
+ autoSkip: false, // Display all months
+ font: {
+ size: 8, // Adjust font size for readability
+ color: "#ffffff", // Light text color for labels
+ },
+ },
+ },
+ y: {
+ display: true, // Show y-axis
+ grid: {
+ drawBorder: false, // Remove border line
+ color: "rgba(255, 255, 255, 0.2)", // Light gray color for grid lines
+ borderDash: [5, 5], // Dotted line style (array defines dash and gap lengths)
+ },
+ ticks: {
+ font: {
+ size: 8, // Adjust font size for readability
+ color: "#ffffff", // Light text color for ticks
+ },
+ },
+ },
+ },
+ elements: {
+ line: {
+ tension: 0.5, // Smooth interpolation for the line
+ },
+ },
+ };
+
+ const handleDragStart = (event: React.DragEvent) => {
+ const rect = event.currentTarget.getBoundingClientRect(); // Get element position
+
+ const cardData = JSON.stringify({
+ header: "Warehouse Throughput", // Static header
+ value: "+5", // Example value (you can change if dynamic)
+ per: "2025", // Example percentage or date
+ icon: "π", // Placeholder for an icon (if needed)
+ className: event.currentTarget.className,
+ position: [rect.top, rect.left], // β
Store initial position
+ lineGraphData, // β
Include chart data
+ lineGraphOptions, // β
Include chart options
+ });
+
+
+ event.dataTransfer.setData("text/plain", cardData);
+ // event.dataTransfer.effectAllowed = "move"; // Improve drag effect
+ };
+
+ return (
+
+
+
Warehouse Throughput
+
+ (+5) more in 2025
+
+
+
+ {/* Line Graph */}
+
+
+
+ );
+};
+
+export default WarehouseThroughput;
diff --git a/app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx b/app/src/modules/visualization/widgets/floating/cards/WarehouseThroughputComponent.tsx
similarity index 97%
rename from app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx
rename to app/src/modules/visualization/widgets/floating/cards/WarehouseThroughputComponent.tsx
index 22f6529..012c2e7 100644
--- a/app/src/components/ui/realTimeVis/floating/WarehouseThroughputComponent.tsx
+++ b/app/src/modules/visualization/widgets/floating/cards/WarehouseThroughputComponent.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import { Line } from 'react-chartjs-2'
-import useChartStore from '../../../../store/useChartStore';
-import { useWidgetStore } from '../../../../store/useWidgetStore';
+import useChartStore from '../../../../../store/useChartStore';
+import { useWidgetStore } from '../../../../../store/useWidgetStore';
import axios from 'axios';
import io from "socket.io-client";
diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/modules/visualization/widgets/panel/AddButtons.tsx
similarity index 96%
rename from app/src/components/ui/componets/AddButtons.tsx
rename to app/src/modules/visualization/widgets/panel/AddButtons.tsx
index a113b3d..5469ffa 100644
--- a/app/src/components/ui/componets/AddButtons.tsx
+++ b/app/src/modules/visualization/widgets/panel/AddButtons.tsx
@@ -3,11 +3,11 @@ import {
CleanPannel,
EyeIcon,
LockIcon,
-} from "../../icons/RealTimeVisulationIcons";
-import { AddIcon } from "../../icons/ExportCommonIcons";
-import { useSocketStore } from "../../../store/store";
-import { clearPanel } from "../../../services/realTimeVisulization/zoneData/clearPanel";
-import { lockPanel } from "../../../services/realTimeVisulization/zoneData/lockPanel";
+} from "../../../../components/icons/RealTimeVisulationIcons";
+import { AddIcon } from "../../../../components/icons/ExportCommonIcons";
+import { useSocketStore } from "../../../../store/store";
+import { clearPanel } from "../../../../services/realTimeVisulization/zoneData/clearPanel";
+import { lockPanel } from "../../../../services/realTimeVisulization/zoneData/lockPanel";
// Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right";
diff --git a/app/src/components/ui/componets/Panel.tsx b/app/src/modules/visualization/widgets/panel/Panel.tsx
similarity index 91%
rename from app/src/components/ui/componets/Panel.tsx
rename to app/src/modules/visualization/widgets/panel/Panel.tsx
index 0f72dfc..f53b0ea 100644
--- a/app/src/components/ui/componets/Panel.tsx
+++ b/app/src/modules/visualization/widgets/panel/Panel.tsx
@@ -1,10 +1,10 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
-import { useWidgetStore } from "../../../store/useWidgetStore";
-import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
-import { DraggableWidget } from "./DraggableWidget";
+
import { arrayMove } from "@dnd-kit/sortable";
-import { addingWidgets } from "../../../services/realTimeVisulization/zoneData/addWidgets";
-import { useAsset3dWidget, useSocketStore } from "../../../store/store";
+import { useAsset3dWidget, useSocketStore } from "../../../../store/store";
+import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
+import { useWidgetStore } from "../../../../store/useWidgetStore";
+import { DraggableWidget } from "../2d/DraggableWidget";
type Side = "top" | "bottom" | "left" | "right";
@@ -22,7 +22,7 @@ interface PanelProps {
activeSides: Side[];
panelOrder: Side[];
lockedPanels: Side[];
- points:[];
+ points: [];
zoneId: string;
zoneViewPortTarget: number[];
zoneViewPortPosition: number[];
@@ -34,7 +34,7 @@ interface PanelProps {
activeSides: Side[];
panelOrder: Side[];
lockedPanels: Side[];
- points:[];
+ points: [];
zoneId: string;
zoneViewPortTarget: number[];
zoneViewPortPosition: number[];
@@ -108,9 +108,8 @@ const Panel: React.FC = ({
case "bottom":
return {
minWidth: "170px",
- width: `calc(100% - ${
- (leftActive ? panelSize : 0) + (rightActive ? panelSize : 0)
- }px)`,
+ width: `calc(100% - ${(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0)
+ }px)`,
minHeight: "170px",
height: `${panelSize}px`,
left: leftActive ? `${panelSize}px` : "0",
@@ -123,9 +122,8 @@ const Panel: React.FC = ({
minWidth: "170px",
width: `${panelSize}px`,
minHeight: "170px",
- height: `calc(100% - ${
- (topActive ? panelSize : 0) + (bottomActive ? panelSize : 0)
- }px)`,
+ height: `calc(100% - ${(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0)
+ }px)`,
top: topActive ? `${panelSize}px` : "0",
bottom: bottomActive ? `${panelSize}px` : "0",
[side]: "0",
@@ -151,6 +149,7 @@ const Panel: React.FC = ({
const currentWidgetsCount = getCurrentWidgetCount(panel);
const maxCapacity = calculatePanelCapacity(panel);
+
if (currentWidgetsCount < maxCapacity) {
addWidgetToPanel(draggedAsset, panel);
}
@@ -284,9 +283,8 @@ const Panel: React.FC = ({
handleDrop(e, side)}
onDragOver={(e) => e.preventDefault()}
@@ -303,7 +301,7 @@ const Panel: React.FC
= ({
style={{
pointerEvents:
selectedZone.lockedPanels.includes(side) ||
- hiddenPanels[selectedZone.zoneId]?.includes(side)
+ hiddenPanels[selectedZone.zoneId]?.includes(side)
? "none"
: "auto",
opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1",
diff --git a/app/src/components/ui/componets/zoneAssets.tsx b/app/src/modules/visualization/zoneAssets.tsx
similarity index 96%
rename from app/src/components/ui/componets/zoneAssets.tsx
rename to app/src/modules/visualization/zoneAssets.tsx
index e2ec994..7a48196 100644
--- a/app/src/components/ui/componets/zoneAssets.tsx
+++ b/app/src/modules/visualization/zoneAssets.tsx
@@ -1,8 +1,8 @@
import React, { useEffect, useRef } from 'react'
-import { useSelectedFloorItem, useZoneAssetId } from '../../../store/store';
+import { useSelectedFloorItem, useZoneAssetId } from '../../store/store';
import * as THREE from "three";
import { useThree } from '@react-three/fiber';
-import * as Types from "../../../types/world/worldTypes";
+import * as Types from "../../types/world/worldTypes";
export default function ZoneAssets() {
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { setSelectedFloorItem } = useSelectedFloorItem();
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index ac05db8..a772634 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -3,7 +3,7 @@ import ModuleToggle from "../components/ui/ModuleToggle";
import SideBarLeft from "../components/layout/sidebarLeft/SideBarLeft";
import SideBarRight from "../components/layout/sidebarRight/SideBarRight";
import useModuleStore, { useThreeDStore } from "../store/useModuleStore";
-import RealTimeVisulization from "../components/ui/componets/RealTimeVisulization";
+import RealTimeVisulization from "../modules/visualization/RealTimeVisulization";
import Tools from "../components/ui/Tools";
// import Scene from "../modules/scene/scene";
import {
diff --git a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts
index 9b03dbe..beaa1b3 100644
--- a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts
+++ b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts
@@ -8,7 +8,7 @@ export const addingFloatingWidgets = async (
) => {
-
+
try {
const response = await fetch(
`${url_Backend_dwinzo}/api/v2/floatwidget/save`,
diff --git a/app/src/store/useDroppedObjectsStore.ts b/app/src/store/useDroppedObjectsStore.ts
index 8f0067e..ca39d9f 100644
--- a/app/src/store/useDroppedObjectsStore.ts
+++ b/app/src/store/useDroppedObjectsStore.ts
@@ -1,6 +1,7 @@
import { create } from "zustand";
import { addingFloatingWidgets } from "../services/realTimeVisulization/zoneData/addFloatingWidgets";
import { useSocketStore } from "./store";
+import useChartStore from "./useChartStore";
type DroppedObject = {
className: string;
@@ -96,7 +97,10 @@ export const useDroppedObjectsStore = create((set) => ({
const state = useDroppedObjectsStore.getState(); // Get the current state
const zone = state.zones[zoneName];
let socketState = useSocketStore.getState();
+ let iotData = useChartStore.getState();
let visualizationSocket = socketState.visualizationSocket;
+ let iotMeasurements = iotData.flotingMeasurements;
+ let iotDuration = iotData.flotingDuration;
if (!zone) return;
@@ -109,6 +113,10 @@ export const useDroppedObjectsStore = create((set) => ({
// Create a shallow copy of the object with a unique ID and slightly adjusted position
const duplicatedObject: DroppedObject = {
...originalObject,
+ Data:{
+ measurements: iotMeasurements,
+ duration: iotDuration,
+ },
id: `${originalObject.id}-copy-${Date.now()}`, // Unique ID
position: {
...originalObject.position,
@@ -122,7 +130,8 @@ export const useDroppedObjectsStore = create((set) => ({
: originalObject.position.left,
},
};
-
+ console.log("duplicated object",duplicatedObject);
+
let duplicateFloatingWidget = {
organization: organization,
widget: duplicatedObject,
diff --git a/app/src/store/useWidgetStore.ts b/app/src/store/useWidgetStore.ts
index 047b57b..2d73c77 100644
--- a/app/src/store/useWidgetStore.ts
+++ b/app/src/store/useWidgetStore.ts
@@ -8,22 +8,8 @@ export interface Widget {
fontFamily?: string;
fontSize?: string;
fontWeight?: string;
- data: {
- // Chart data
- labels?: string[];
- datasets?: Array<{
- data: number[];
- backgroundColor: string;
- borderColor: string;
- borderWidth: number;
- }>;
- // Progress card data
- stocks?: Array<{
- key: string;
- value: number;
- description: string;
- }>;
- };
+ data?: any;
+ Data?:any;
}
interface WidgetStore {
diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss
index aca8474..305206a 100644
--- a/app/src/styles/layout/sidebar.scss
+++ b/app/src/styles/layout/sidebar.scss
@@ -130,13 +130,15 @@
grid-column: 1 / -1;
}
- .widget-left-sideBar {
- .widgets-wrapper {
+ .widgets-wrapper {
+
+ min-height: 50vh;
+ max-height: 60vh;
+ overflow: auto;
+ }
+
+ .widget-left-sideBar {
- min-height: 50vh;
- max-height: 60vh;
- overflow: auto;
- }
.widget2D {
overflow: auto;
@@ -1088,43 +1090,51 @@
right: -10px;
transform: translate(0, -50%);
}
- &:nth-child(1), &:nth-child(9) {
+
+ &:nth-child(1),
+ &:nth-child(9) {
&::after {
@include gradient-by-child(1); // First child uses the first color
}
}
- &:nth-child(2), &:nth-child(10) {
+ &:nth-child(2),
+ &:nth-child(10) {
&::after {
@include gradient-by-child(2); // Second child uses the second color
}
}
- &:nth-child(3), &:nth-child(11) {
+ &:nth-child(3),
+ &:nth-child(11) {
&::after {
@include gradient-by-child(3); // Third child uses the third color
}
}
- &:nth-child(4), &:nth-child(12) {
+ &:nth-child(4),
+ &:nth-child(12) {
&::after {
@include gradient-by-child(4); // Fourth child uses the fourth color
}
}
- &:nth-child(5), &:nth-child(13) {
+ &:nth-child(5),
+ &:nth-child(13) {
&::after {
@include gradient-by-child(5); // Fifth child uses the fifth color
}
}
- &:nth-child(6), &:nth-child(14) {
+ &:nth-child(6),
+ &:nth-child(14) {
&::after {
@include gradient-by-child(6); // Fifth child uses the fifth color
}
}
- &:nth-child(7), &:nth-child(15) {
+ &:nth-child(7),
+ &:nth-child(15) {
&::after {
@include gradient-by-child(7); // Fifth child uses the fifth color
}
@@ -1160,6 +1170,7 @@
position: relative;
overflow: hidden;
padding: 0;
+
&:hover {
.asset-name {
opacity: 1;
@@ -1173,21 +1184,24 @@
padding: 8px;
width: 100%;
font-size: var(--font-size-regular);
- background: color-mix(
- in srgb,
- var(--background-color) 40%,
- transparent
- );
+ background: color-mix(in srgb,
+ var(--background-color) 40%,
+ transparent);
backdrop-filter: blur(5px);
opacity: 0;
transition: opacity 0.3s ease;
/* Added properties for ellipsis */
- display: -webkit-box; /* Necessary for multiline truncation */
- -webkit-line-clamp: 2; /* Number of lines to show */
- -webkit-box-orient: vertical; /* Box orientation for the ellipsis */
- overflow: hidden; /* Hide overflowing content */
- text-overflow: ellipsis; /* Add ellipsis for truncated content */
+ display: -webkit-box;
+ /* Necessary for multiline truncation */
+ -webkit-line-clamp: 2;
+ /* Number of lines to show */
+ -webkit-box-orient: vertical;
+ /* Box orientation for the ellipsis */
+ overflow: hidden;
+ /* Hide overflowing content */
+ text-overflow: ellipsis;
+ /* Add ellipsis for truncated content */
}
.asset-image {
@@ -1257,4 +1271,4 @@
.assets-wrapper {
margin: 0;
}
-}
+}
\ No newline at end of file
diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss
index c6f980a..0d4f98d 100644
--- a/app/src/styles/pages/realTimeViz.scss
+++ b/app/src/styles/pages/realTimeViz.scss
@@ -285,9 +285,6 @@
}
}
- .chart-container.notLinked {
- border-color: red;
- }
.close-btn {
position: absolute;
@@ -755,6 +752,13 @@
z-index: 2 !important;
}
+.chart-container.notLinked {
+
+ outline: 1px solid red;
+
+
+}
+
.connectionSuccess {
outline-color: #43c06d;
}