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 ( -
- {/* */} - {/* 3d-cards */} - - {/* */} - {/* */} - {/* */} - {/* */} - - {/* */} - {/* */} -
- ); -}; - -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/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx index abcf44d..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; @@ -48,24 +48,28 @@ const WidgetsFloating = () => { {" "} 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/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/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 68% rename from app/src/components/ui/componets/RealTimeVisulization.tsx rename to app/src/modules/visualization/RealTimeVisulization.tsx index b0cdd14..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() { @@ -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)} /> 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 86% rename from app/src/components/layout/sidebarLeft/visualization/Templates.tsx rename to app/src/modules/visualization/template/Templates.tsx index e796101..8370112 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/modules/visualization/template/Templates.tsx @@ -1,133 +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 ( - 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.name} -
- )} -
-
- {/* {`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.name} +
+ )} +
+
+ {/* {`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 86% rename from app/src/components/ui/componets/DraggableWidget.tsx rename to app/src/modules/visualization/widgets/2d/DraggableWidget.tsx index 3c726c3..3b9e8d7 100644 --- a/app/src/components/ui/componets/DraggableWidget.tsx +++ b/app/src/modules/visualization/widgets/2d/DraggableWidget.tsx @@ -1,25 +1,25 @@ -import { useWidgetStore } from "../../../store/useWidgetStore"; -import ProgressCard from "../realTimeVis/charts/ProgressCard"; -import useChartStore from "../../../store/useChartStore"; -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"; @@ -45,7 +45,7 @@ export const DraggableWidget = ({ zoneName: string; zoneId: string; activeSides: Side[]; - points:[]; + points: []; panelOrder: Side[]; lockedPanels: Side[]; widgets: Widget[]; @@ -55,7 +55,7 @@ export const DraggableWidget = ({ zoneName: string; activeSides: Side[]; panelOrder: Side[]; - points:[]; + points: []; lockedPanels: Side[]; zoneId: string; zoneViewPortTarget: number[]; @@ -100,19 +100,6 @@ export const DraggableWidget = ({ const chartWidget = useRef(null); - OuterClick({ - contextClassName: [ - "chart-container", - "floating", - "sidebar-right-wrapper", - "card", - "dropdown-menu", - "sidebar-right-content-container", - "dropdown-options" - ], - setMenuVisible: () => setSelectedChartId(null), - }); - const deleteSelectedChart = async () => { try { const email = localStorage.getItem("email") || ""; @@ -198,7 +185,7 @@ export const DraggableWidget = ({ }, id: `${widget.id}-copy-${Date.now()}`, }; - console.log('duplicatedWidget: ', duplicatedWidget); + console.log("duplicatedWidget: ", duplicatedWidget); let duplicateWidget = { organization: organization, @@ -420,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 8d4d4e9..4a6906a 100644 --- a/app/src/components/ui/realTimeVis/charts/LineGraphComponent.tsx +++ b/app/src/modules/visualization/widgets/2d/charts/LineGraphComponent.tsx @@ -1,191 +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) { - 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; +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 95% rename from app/src/components/ui/componets/Dropped3dWidget.tsx rename to app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx index 18f7602..6acc994 100644 --- a/app/src/components/ui/componets/Dropped3dWidget.tsx +++ b/app/src/modules/visualization/widgets/3d/Dropped3dWidget.tsx @@ -1,37 +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 useChartStore from "../../../store/useChartStore"; -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 { useWidgetStore } from "../../../store/useWidgetStore"; +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; diff --git a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx similarity index 94% rename from app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx rename to app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx index d6980b3..274d6dd 100644 --- a/app/src/components/layout/3D-cards/cards/ProductionCapacity.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx @@ -1,269 +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]; - 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", - }} - > -
-
Production Capacity
-
-
1,200
{" "} -
units/hour
-
-
-
-
Current
-
1500
-
-
-
Target
-
2.345
-
- {/*
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
+
+
+
+
Current
+
1500
+
+
+
Target
+
2.345
+
+ {/*
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 94% rename from app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx rename to app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx index 16bb8dd..a4c04ca 100644 --- a/app/src/components/layout/3D-cards/cards/ReturnOfInvestment.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx @@ -1,277 +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]; - 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} - /> -
-
-
- -
-
5.78
-
Years
-
-
- 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} + /> +
+
+
+ +
+
5.78
+
Years
+
+
+ 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 95% rename from app/src/components/layout/3D-cards/cards/StateWorking.tsx rename to app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx index 9df9642..4d10955 100644 --- a/app/src/components/layout/3D-cards/cards/StateWorking.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx @@ -1,203 +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]; - 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; +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 93% rename from app/src/components/layout/3D-cards/cards/Throughput.tsx rename to app/src/modules/visualization/widgets/3d/cards/Throughput.tsx index a433692..67397bd 100644 --- a/app/src/components/layout/3D-cards/cards/Throughput.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx @@ -1,268 +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]; - 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}
-
-
-
- -
-
-
-
1,200
-
Units/hr
-
-
-
316
-
sales
-
-
-
-
-
5.77%
-
-
-
- {/* 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}
+
+
+
+ +
+
+
+
1,200
+
Units/hr
+
+
+
316
+
sales
+
+
+
+
+
5.77%
+
+
+
+ {/* 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 95% rename from app/src/components/ui/realTimeVis/floating/FleetEfficiencyComponent.tsx rename to app/src/modules/visualization/widgets/floating/cards/FleetEfficiencyComponent.tsx index 6751028..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"; 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 ( -
-
-

Productivity

-
...
-
-
-
-
-

Distance per Task

-

{data.distancePerTask} m

-
-
-

Space Utilization

-

{data.spaceUtilization}%

-
-
-
- - {/* Background Circle */} - - {/* Progress Circle */} - - -
-

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 ( +
+
+

Productivity

+
...
+
+
+
+
+

Distance per Task

+

{data.distancePerTask} m

+
+
+

Space Utilization

+

{data.spaceUtilization}%

+
+
+
+ + {/* Background Circle */} + + {/* Progress Circle */} + + +
+

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 ( -
-
-
{header}
-
-
{value}
-
{per}
-
-
- -
- -
-
- ); -}; - -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 ( +
+
+
{header}
+
+
{value}
+
{per}
+
+
+ +
+ +
+
+ ); +}; + +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 96% rename from app/src/components/ui/componets/Panel.tsx rename to app/src/modules/visualization/widgets/panel/Panel.tsx index 82764d1..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[]; 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/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