added donut and pole area chart and added its iot data
This commit is contained in:
@@ -1,12 +1,24 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./inputs/RenameInput";
|
||||
import { ArrowIcon } from "../icons/ExportCommonIcons";
|
||||
import MenuBar from "./menu/menu";
|
||||
|
||||
const FileMenu: React.FC = () => {
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
return (
|
||||
<div className="project-dropdowm-container">
|
||||
<div className="project-name">
|
||||
<RenameInput value="untitled" />
|
||||
</div>
|
||||
<div
|
||||
className="more-options-button"
|
||||
onClick={() => {
|
||||
setOpenMenu(!openMenu);
|
||||
}}
|
||||
>
|
||||
<ArrowIcon />
|
||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import {
|
||||
BuilderIcon,
|
||||
@@ -7,18 +7,20 @@ import {
|
||||
VisualizationIcon,
|
||||
} from "../icons/ExportModuleIcons";
|
||||
import useToggleStore from "../../store/useUIToggleStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
|
||||
const ModuleToggle: React.FC = () => {
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setToggleUI } = useToggleStore();
|
||||
|
||||
|
||||
return (
|
||||
<div className="module-toggle-container">
|
||||
<div
|
||||
className={`module-list ${activeModule === "builder" && "active"}`}
|
||||
onClick={() => {
|
||||
setActiveModule("builder");
|
||||
setToggleUI(true);
|
||||
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true)
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
@@ -30,7 +32,7 @@ const ModuleToggle: React.FC = () => {
|
||||
className={`module-list ${activeModule === "simulation" && "active"}`}
|
||||
onClick={() => {
|
||||
setActiveModule("simulation");
|
||||
setToggleUI(true);
|
||||
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true)
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
@@ -39,12 +41,11 @@ const ModuleToggle: React.FC = () => {
|
||||
<div className="module">Simulation</div>
|
||||
</div>
|
||||
<div
|
||||
className={`module-list ${
|
||||
activeModule === "visualization" && "active"
|
||||
}`}
|
||||
className={`module-list ${activeModule === "visualization" && "active"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveModule("visualization");
|
||||
setToggleUI(true);
|
||||
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true)
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
AsileIcon,
|
||||
CommentIcon,
|
||||
CursorIcon,
|
||||
DeleteIcon,
|
||||
FloorIcon,
|
||||
FreeMoveIcon,
|
||||
MeasureToolIcon,
|
||||
PenIcon,
|
||||
PlayIcon,
|
||||
SaveTemplateIcon,
|
||||
@@ -12,23 +14,30 @@ import {
|
||||
ZoneIcon,
|
||||
} from "../icons/ExportToolsIcons";
|
||||
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
|
||||
import { handleSaveTemplate } from "../../modules/visualization/handleSaveTemplate";
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useTemplateStore from "../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
import {
|
||||
useActiveTool,
|
||||
useAddAction,
|
||||
useDeleteModels,
|
||||
useDeletePointOrLine,
|
||||
useMovePoint,
|
||||
useRefTextUpdate,
|
||||
useSelectedWallItem,
|
||||
useToggleView,
|
||||
useToolMode,
|
||||
useTransformMode,
|
||||
} from "../../store/store";
|
||||
import useToggleStore from "../../store/useUIToggleStore";
|
||||
|
||||
const Tools: React.FC = () => {
|
||||
const { templates } = useTemplateStore();
|
||||
const [activeTool, setActiveTool] = useState("cursor");
|
||||
const [activeSubTool, setActiveSubTool] = useState("cursor");
|
||||
const [toggleThreeD, setToggleThreeD] = useState(true);
|
||||
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
|
||||
const { setToggleUI } = useToggleStore();
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [openDrop, setOpenDrop] = useState(false);
|
||||
@@ -39,12 +48,27 @@ const Tools: React.FC = () => {
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
|
||||
// wall options
|
||||
const { setToggleView } = useToggleView();
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { setDeleteModels } = useDeleteModels();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
|
||||
const { transformMode, setTransformMode } = useTransformMode();
|
||||
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
|
||||
const { movePoint, setMovePoint } = useMovePoint();
|
||||
const { toolMode, setToolMode } = useToolMode();
|
||||
const { activeTool, setActiveTool } = useActiveTool();
|
||||
const { refTextupdate, setRefTextUpdate } = useRefTextUpdate();
|
||||
|
||||
// Reset activeTool whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setToggleUI(
|
||||
localStorage.getItem("navBarUi")
|
||||
? localStorage.getItem("navBarUi") === "true"
|
||||
: true
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTool(activeSubTool);
|
||||
setActiveSubTool(activeSubTool);
|
||||
@@ -56,10 +80,18 @@ const Tools: React.FC = () => {
|
||||
setDeleteModels(false);
|
||||
setAddAction(null);
|
||||
setToggleView(true);
|
||||
// localStorage.setItem("navBarUi", JSON.stringify(!toggleThreeD));
|
||||
} else {
|
||||
setToggleView(false);
|
||||
}
|
||||
setToggleUI(
|
||||
localStorage.getItem("navBarUi")
|
||||
? localStorage.getItem("navBarUi") === "true"
|
||||
: true
|
||||
);
|
||||
setToggleThreeD(!toggleThreeD);
|
||||
setActiveSubTool("cursor");
|
||||
setActiveTool("cursor");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,6 +116,90 @@ const Tools: React.FC = () => {
|
||||
document.removeEventListener("keydown", handleEscKeyPress); // Clean up the event listener
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!toggleThreeD) {
|
||||
setToggleUI(false);
|
||||
}
|
||||
}, [toggleThreeD]);
|
||||
|
||||
useEffect(() => {
|
||||
setToolMode(null);
|
||||
setDeleteModels(false);
|
||||
setAddAction(null);
|
||||
setTransformMode(null);
|
||||
setMovePoint(false);
|
||||
setDeletePointOrLine(false);
|
||||
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
|
||||
|
||||
switch (activeTool) {
|
||||
case "Move":
|
||||
if (toggleView) {
|
||||
setMovePoint(true);
|
||||
} else {
|
||||
setTransformMode("translate");
|
||||
}
|
||||
break;
|
||||
|
||||
case "Rotate":
|
||||
if (!toggleView) {
|
||||
setTransformMode("rotate");
|
||||
}
|
||||
break;
|
||||
|
||||
case "Scale":
|
||||
if (!toggleView) {
|
||||
setTransformMode("scale");
|
||||
}
|
||||
break;
|
||||
|
||||
case "draw-wall":
|
||||
if (toggleView) {
|
||||
setToolMode("Wall");
|
||||
}
|
||||
break;
|
||||
|
||||
case "draw-aisle":
|
||||
if (toggleView) {
|
||||
setToolMode("Aisle");
|
||||
}
|
||||
break;
|
||||
|
||||
case "draw-zone":
|
||||
if (toggleView) {
|
||||
setToolMode("Zone");
|
||||
}
|
||||
break;
|
||||
|
||||
case "draw-floor":
|
||||
if (toggleView) {
|
||||
setToolMode("Floor");
|
||||
}
|
||||
break;
|
||||
|
||||
case "measure":
|
||||
setToolMode("MeasurementScale");
|
||||
break;
|
||||
|
||||
case "Add pillar":
|
||||
if (!toggleView) {
|
||||
setAddAction("pillar");
|
||||
}
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
if (toggleView) {
|
||||
setDeletePointOrLine(true);
|
||||
} else {
|
||||
setDeleteModels(true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
setActiveTool(activeTool);
|
||||
}, [activeTool]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -116,13 +232,24 @@ const Tools: React.FC = () => {
|
||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||
</div>
|
||||
)}
|
||||
{activeSubTool == "delete" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "delete" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("delete");
|
||||
}}
|
||||
>
|
||||
<DeleteIcon isActive={activeTool === "delete"} />
|
||||
</div>
|
||||
)}
|
||||
{activeModule !== "visualization" && (
|
||||
<div
|
||||
className="drop-down-option-button"
|
||||
ref={dropdownRef}
|
||||
onClick={() => {
|
||||
setOpenDrop(!openDrop);
|
||||
console.log(openDrop);
|
||||
}}
|
||||
>
|
||||
<ArrowIcon />
|
||||
@@ -156,6 +283,20 @@ const Tools: React.FC = () => {
|
||||
<FreeMoveIcon isActive={false} />
|
||||
<div className="option">Free Hand</div>
|
||||
</div>
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("delete");
|
||||
setActiveSubTool("delete");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "delete" && <TickIcon />}
|
||||
</div>
|
||||
<DeleteIcon isActive={false} />
|
||||
<div className="option">Delete</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -173,6 +314,7 @@ const Tools: React.FC = () => {
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
}}
|
||||
title="Wall"
|
||||
>
|
||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||
</div>
|
||||
@@ -183,6 +325,7 @@ const Tools: React.FC = () => {
|
||||
onClick={() => {
|
||||
setActiveTool("draw-zone");
|
||||
}}
|
||||
title="Zone"
|
||||
>
|
||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||
</div>
|
||||
@@ -193,6 +336,7 @@ const Tools: React.FC = () => {
|
||||
onClick={() => {
|
||||
setActiveTool("draw-aisle");
|
||||
}}
|
||||
title="Aisle"
|
||||
>
|
||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||
</div>
|
||||
@@ -203,12 +347,31 @@ const Tools: React.FC = () => {
|
||||
onClick={() => {
|
||||
setActiveTool("draw-floor");
|
||||
}}
|
||||
title="Floor"
|
||||
>
|
||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "measure" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("measure");
|
||||
}}
|
||||
title="Measure"
|
||||
>
|
||||
<MeasureToolIcon isActive={activeTool === "measure"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
@@ -257,31 +420,41 @@ const Tools: React.FC = () => {
|
||||
>
|
||||
<CommentIcon isActive={activeTool === "comment"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "play" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div
|
||||
className={`toggle-threed-button${
|
||||
toggleThreeD ? " toggled" : ""
|
||||
}`}
|
||||
onClick={toggleSwitch}
|
||||
>
|
||||
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
||||
2d
|
||||
</div>
|
||||
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>
|
||||
3d
|
||||
</div>
|
||||
{toggleThreeD && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "play" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div
|
||||
className={`toggle-threed-button${
|
||||
toggleThreeD ? " toggled" : ""
|
||||
}`}
|
||||
onClick={toggleSwitch}
|
||||
>
|
||||
<div
|
||||
className={`toggle-option${!toggleThreeD ? " active" : ""}`}
|
||||
>
|
||||
2d
|
||||
</div>
|
||||
<div
|
||||
className={`toggle-option${toggleThreeD ? " active" : ""}`}
|
||||
>
|
||||
3d
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -119,7 +119,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
};
|
||||
|
||||
// Delete the selectedZone state
|
||||
console.log("updatedZone: ", updatedZone);
|
||||
|
||||
setSelectedZone(updatedZone);
|
||||
} else {
|
||||
const updatePanelData = async () => {
|
||||
@@ -141,13 +141,13 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
|
||||
// API call
|
||||
const response = await panelData(organization, selectedZone.zoneId, newActiveSides);
|
||||
console.log("response: ", response);
|
||||
|
||||
|
||||
// Update state
|
||||
console.log("updatedZone: ", updatedZone);
|
||||
|
||||
setSelectedZone(updatedZone);
|
||||
} catch (error) {
|
||||
console.error("Error updating panel data:", error);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons";
|
||||
import { InfoIcon } from "../../icons/ExportCommonIcons";
|
||||
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
|
||||
import { getSelect2dZoneData } from "../../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
|
||||
import { getFloatingZoneData } from "../../../services/realTimeVisulization/zoneData/getFloatingData";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
@@ -147,27 +148,47 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
};
|
||||
|
||||
async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0]
|
||||
let response = await getSelect2dZoneData(zoneId, organization)
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
activeSides: response.activeSides,
|
||||
panelOrder: response.panelOrder,
|
||||
lockedPanels: response.lockedPanels,
|
||||
widgets: response.widgets,
|
||||
zoneId: zoneId,
|
||||
zoneViewPortTarget:
|
||||
response.viewPortCenter,
|
||||
zoneViewPortPosition:
|
||||
response.viewPortposition,
|
||||
});
|
||||
try {
|
||||
if (selectedZone?.zoneId === zoneId) {
|
||||
console.log("Zone is already selected:", zoneName);
|
||||
return;
|
||||
}
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
// Fetch data from backend
|
||||
let response = await getSelect2dZoneData(zoneId, organization);
|
||||
let res = await getFloatingZoneData(zoneId, organization);
|
||||
// Set the selected zone in the store
|
||||
useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
|
||||
if (Array.isArray(res)) {
|
||||
res.forEach((val) => {
|
||||
useDroppedObjectsStore.getState().addObject(zoneName, val);
|
||||
});
|
||||
}
|
||||
// Update selected zone state
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
activeSides: response.activeSides || [],
|
||||
panelOrder: response.panelOrder || [],
|
||||
lockedPanels: response.lockedPanels || [],
|
||||
widgets: response.widgets || [],
|
||||
zoneId: zoneId,
|
||||
zoneViewPortTarget: response.viewPortCenter || {},
|
||||
zoneViewPortPosition: response.viewPortposition || {},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('error: ', error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") && "bottom"
|
||||
}`}
|
||||
ref={containerRef}
|
||||
className={`zone-wrapper ${
|
||||
selectedZone?.activeSides?.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{/* Left Arrow */}
|
||||
{showLeftArrow && (
|
||||
@@ -185,7 +206,6 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
className={`zone ${selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
useDroppedObjectsStore.getState().setZone(zoneName, zonesData[zoneName]?.zoneId);
|
||||
handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
KebabIcon,
|
||||
} from "../../icons/ExportCommonIcons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { duplicateWidgetApi } from "../../../services/realTimeVisulization/zoneData/duplicateWidget";
|
||||
import { deleteWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteWidgetApi";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
@@ -34,9 +36,9 @@ export const DraggableWidget = ({
|
||||
}: {
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
zoneId: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
@@ -79,21 +81,28 @@ export const DraggableWidget = ({
|
||||
|
||||
const isPanelHidden = hiddenPanels.includes(widget.panel);
|
||||
|
||||
const deleteSelectedChart = () => {
|
||||
console.log('widget.id: ', widget.id);
|
||||
const updatedWidgets = selectedZone.widgets.filter(
|
||||
(w: Widget) => w.id !== widget.id
|
||||
);
|
||||
console.log('updatedWidgets: ', updatedWidgets);
|
||||
const deleteSelectedChart = async () => {
|
||||
try {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const response = await deleteWidgetApi(widget.id, organization);
|
||||
if (response?.message === "Widget deleted successfully") {
|
||||
const updatedWidgets = selectedZone.widgets.filter(
|
||||
(w: Widget) => w.id !== widget.id
|
||||
);
|
||||
setSelectedZone((prevZone: any) => ({
|
||||
...prevZone,
|
||||
widgets: updatedWidgets,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
setSelectedZone((prevZone: any) => ({
|
||||
...prevZone,
|
||||
widgets: updatedWidgets,
|
||||
}));
|
||||
|
||||
setOpenKebabId(null);
|
||||
} finally {
|
||||
setOpenKebabId(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getCurrentWidgetCount = (panel: Side) =>
|
||||
selectedZone.widgets.filter((w) => w.panel === panel).length;
|
||||
|
||||
@@ -121,21 +130,32 @@ export const DraggableWidget = ({
|
||||
return currentWidgetCount >= panelCapacity;
|
||||
};
|
||||
|
||||
const duplicateWidget = () => {
|
||||
const duplicatedWidget: Widget = {
|
||||
...widget,
|
||||
id: `${widget.id}-copy-${Date.now()}`,
|
||||
};
|
||||
setSelectedZone((prevZone: any) => ({
|
||||
...prevZone,
|
||||
widgets: [...prevZone.widgets, duplicatedWidget],
|
||||
}));
|
||||
const duplicateWidget = async () => {
|
||||
try {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
setOpenKebabId(null);
|
||||
const duplicatedWidget: Widget = {
|
||||
...widget,
|
||||
id: `${widget.id}-copy-${Date.now()}`,
|
||||
};
|
||||
|
||||
const response = await duplicateWidgetApi(selectedZone.zoneId, organization, duplicatedWidget);
|
||||
|
||||
if (response?.message === "Widget created successfully") {
|
||||
setSelectedZone((prevZone: any) => ({
|
||||
...prevZone,
|
||||
widgets: [...prevZone.widgets, duplicatedWidget],
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
} finally {
|
||||
setOpenKebabId(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleKebabClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
if (openKebabId === widget.id) {
|
||||
@@ -176,7 +196,6 @@ export const DraggableWidget = ({
|
||||
};
|
||||
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
|
||||
event.preventDefault();
|
||||
const fromIndex = parseInt(event.dataTransfer.getData("text/plain"), 10); // Get the dragged widget's index
|
||||
const toIndex = index; // The index of the widget where the drop occurred
|
||||
@@ -296,18 +315,11 @@ export const DraggableWidget = ({
|
||||
)}
|
||||
{widget.type === "polarArea" && (
|
||||
<PolarAreaGraphComponent
|
||||
id={widget.id}
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useAsset3dWidget } from "../../../store/store";
|
||||
import { useAsset3dWidget, useWidgetSubOption } from "../../../store/store";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import { ThreeState } from "../../../types/world/worldTypes";
|
||||
import * as THREE from "three";
|
||||
@@ -9,71 +9,80 @@ 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";
|
||||
|
||||
export default function Dropped3dWidgets() {
|
||||
const { widgetSelect } = useAsset3dWidget();
|
||||
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { raycaster, gl, scene }: ThreeState = useThree();
|
||||
const { selectedZone } = useSelectedZoneStore(); // Get currently selected zone
|
||||
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption()
|
||||
// 🔥 Store widget positions per zone
|
||||
const [zoneWidgets, setZoneWidgets] = useState<Record<
|
||||
string, // Zone ID
|
||||
Record<string, [number, number, number][]> // Widget type -> Positions array
|
||||
>>({});
|
||||
|
||||
// 🔥 Store multiple instances per widget type
|
||||
const [widgetPositions, setWidgetPositions] = useState<Record<string, [number, number, number][]>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== "visualization") return;
|
||||
if (widgetSubOption === "Floating") return
|
||||
// if (activeModule !== "visualization") return;
|
||||
const canvasElement = gl.domElement;
|
||||
|
||||
const onDrop = (event: DragEvent) => {
|
||||
event.preventDefault(); // Prevent default browser behavior
|
||||
if (widgetSubOption === "3D") {
|
||||
if (selectedZone.zoneName === "") return
|
||||
if (!widgetSelect?.startsWith("ui")) return;
|
||||
const group1 = scene.getObjectByName("itemsGroup");
|
||||
if (!group1) return;
|
||||
const Assets = group1.children
|
||||
.map((val) => scene.getObjectByProperty("uuid", val.uuid))
|
||||
.filter(Boolean) as THREE.Object3D[];
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
||||
(intersect) =>
|
||||
!intersect.object.name.includes("Roof") &&
|
||||
!intersect.object.name.includes("agv-collider") &&
|
||||
!intersect.object.name.includes("MeasurementReference") &&
|
||||
!intersect.object.userData.isPathObject &&
|
||||
!(intersect.object.type === "GridHelper")
|
||||
);
|
||||
if (intersects.length > 0) {
|
||||
const { x, y, z } = intersects[0].point;
|
||||
|
||||
if (!widgetSelect.startsWith("ui")) return;
|
||||
|
||||
|
||||
const group1 = scene.getObjectByName("itemsGroup");
|
||||
if (!group1) return;
|
||||
|
||||
|
||||
|
||||
const Assets = group1.children
|
||||
.map((val) => scene.getObjectByProperty("uuid", val.uuid))
|
||||
.filter(Boolean) as THREE.Object3D[];
|
||||
|
||||
|
||||
const intersects = raycaster.intersectObjects(Assets);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const { x, y, z } = intersects[0].point;
|
||||
|
||||
|
||||
// ✅ Allow multiple instances by storing positions in an array
|
||||
setWidgetPositions((prev) => ({
|
||||
...prev,
|
||||
[widgetSelect]: [...(prev[widgetSelect] || []), [x, y, z]],
|
||||
}));
|
||||
setZoneWidgets((prev) => ({
|
||||
...prev,
|
||||
[selectedZone.zoneId]: {
|
||||
...(prev[selectedZone.zoneId] || {}),
|
||||
[widgetSelect]: [...(prev[selectedZone.zoneId]?.[widgetSelect] || []), [x, y, z]],
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
canvasElement.addEventListener("drop", onDrop);
|
||||
|
||||
return () => {
|
||||
canvasElement.removeEventListener("drop", onDrop);
|
||||
canvasElement.removeEventListener("drop", onDrop)
|
||||
// setWidgetSelect()
|
||||
};
|
||||
}, [widgetSelect, activeModule]);
|
||||
}, [widgetSelect, activeModule, widgetSubOption]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{widgetPositions["ui-Widget 1"]?.map((pos, index) => (
|
||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 1"]?.map((pos, index) => (
|
||||
<ProductionCapacity key={`Widget1-${index}`} position={pos} />
|
||||
))}
|
||||
{widgetPositions["ui-Widget 2"]?.map((pos, index) => (
|
||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 2"]?.map((pos, index) => (
|
||||
<ReturnOfInvestment key={`Widget2-${index}`} position={pos} />
|
||||
))}
|
||||
{widgetPositions["ui-Widget 3"]?.map((pos, index) => (
|
||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 3"]?.map((pos, index) => (
|
||||
<StateWorking key={`Widget3-${index}`} position={pos} />
|
||||
))}
|
||||
{widgetPositions["ui-Widget 4"]?.map((pos, index) => (
|
||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 4"]?.map((pos, index) => (
|
||||
<Throughput key={`Widget4-${index}`} position={pos} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,59 +1,88 @@
|
||||
|
||||
|
||||
|
||||
import { WalletIcon } from "../../icons/3dChartIcons";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import { useDroppedObjectsStore, Zones } from "../../../store/useDroppedObjectsStore";
|
||||
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";
|
||||
|
||||
import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent";
|
||||
import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent";
|
||||
import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent";
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
const DroppedObjects: React.FC = () => {
|
||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
||||
const updateObjectPosition = useDroppedObjectsStore((state) => state.updateObjectPosition);
|
||||
const [draggingIndex, setDraggingIndex] = useState<{ zone: string; index: number } | null>(null);
|
||||
|
||||
const updateObjectPosition = useDroppedObjectsStore(
|
||||
(state) => state.updateObjectPosition
|
||||
);
|
||||
const [draggingIndex, setDraggingIndex] = useState<{
|
||||
zone: string;
|
||||
index: number;
|
||||
} | null>(null);
|
||||
const [offset, setOffset] = useState<[number, number] | null>(null);
|
||||
const { setSelectedChartId } = useWidgetStore();
|
||||
const positionRef = useRef<[number, number] | null>(null);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
// useEffect(() => {
|
||||
// const initialZones: Record<string, Zones> = {
|
||||
// "Zone 1": {
|
||||
// zoneName: "Zone 1",
|
||||
// zoneId: "2e996073-546c-470c-8323-55bd3700c6aa",
|
||||
// objects: [
|
||||
// {
|
||||
// header: "Today’s Money",
|
||||
// value: 53000, // ✅ Converted to number
|
||||
// per: "+55%",
|
||||
// className: "floating total-card",
|
||||
// position: [146, 214], // ✅ No need for 'as' here
|
||||
// },
|
||||
// {
|
||||
// header: "New Clients",
|
||||
// value: 250, // ✅ Converted to number
|
||||
// per: "+12%",
|
||||
// className: "floating total-card",
|
||||
// position: [344, 295],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// };
|
||||
|
||||
// useDroppedObjectsStore.setState({ zones: initialZones });
|
||||
// }, []);
|
||||
|
||||
// Get the first zone and its objects
|
||||
const zoneEntries = Object.entries(zones);
|
||||
if (zoneEntries.length === 0) return null; // No zone, nothing to render
|
||||
const [zoneName, zone] = zoneEntries[0]; // Only render the first zone
|
||||
console.log("zone", zone);
|
||||
|
||||
|
||||
|
||||
// Handle pointer down event
|
||||
function handlePointerDown(event: React.PointerEvent, index: number) {
|
||||
|
||||
const obj = zone.objects[index];
|
||||
const offsetX = event.clientX - obj.position[1];
|
||||
const offsetY = event.clientY - obj.position[0];
|
||||
const container = document.getElementById("real-time-vis-canvas");
|
||||
if (!container) return;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const relativeX = event.clientX - rect.left;
|
||||
const relativeY = event.clientY - rect.top;
|
||||
|
||||
// Determine which properties are active for this object
|
||||
const [activeProp1, activeProp2] = getActiveProperties(obj.position);
|
||||
|
||||
// Calculate the offset based on the active properties
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
|
||||
if (activeProp1 === "top") {
|
||||
offsetY =
|
||||
relativeY -
|
||||
(typeof obj.position.top === "number" ? obj.position.top : 0);
|
||||
} else if (activeProp1 === "bottom") {
|
||||
offsetY =
|
||||
rect.height -
|
||||
relativeY -
|
||||
(typeof obj.position.bottom === "number" ? obj.position.bottom : 0);
|
||||
}
|
||||
|
||||
if (activeProp2 === "left") {
|
||||
offsetX =
|
||||
relativeX -
|
||||
(typeof obj.position.left === "number" ? obj.position.left : 0);
|
||||
} else if (activeProp2 === "right") {
|
||||
offsetX =
|
||||
rect.width -
|
||||
relativeX -
|
||||
(typeof obj.position.right === "number" ? obj.position.right : 0);
|
||||
}
|
||||
|
||||
setDraggingIndex({ zone: zoneName, index });
|
||||
setOffset([offsetY, offsetX]);
|
||||
}
|
||||
|
||||
// Handle pointer move event
|
||||
function handlePointerMove(event: React.PointerEvent) {
|
||||
if (!draggingIndex || !offset) return;
|
||||
|
||||
@@ -61,34 +90,92 @@ const DroppedObjects: React.FC = () => {
|
||||
if (!container) return;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const relativeX = event.clientX - rect.left;
|
||||
const relativeY = event.clientY - rect.top;
|
||||
|
||||
let newX = event.clientX - offset[1];
|
||||
let newY = event.clientY - offset[0];
|
||||
// Determine which properties are active for the dragged object
|
||||
const obj = zone.objects[draggingIndex.index];
|
||||
const [activeProp1, activeProp2] = getActiveProperties(obj.position);
|
||||
|
||||
// Calculate the new position based on the active properties
|
||||
let newX = 0;
|
||||
let newY = 0;
|
||||
|
||||
if (activeProp2 === "left") {
|
||||
newX = relativeX - offset[1];
|
||||
} else if (activeProp2 === "right") {
|
||||
newX = rect.width - (relativeX + offset[1]);
|
||||
}
|
||||
|
||||
if (activeProp1 === "top") {
|
||||
newY = relativeY - offset[0];
|
||||
} else if (activeProp1 === "bottom") {
|
||||
newY = rect.height - (relativeY + offset[0]);
|
||||
}
|
||||
|
||||
// Ensure the object stays within the canvas boundaries
|
||||
newX = Math.max(0, Math.min(rect.width - 50, newX));
|
||||
newY = Math.max(0, Math.min(rect.height - 50, newY));
|
||||
|
||||
// Update the position reference
|
||||
positionRef.current = [newY, newX];
|
||||
|
||||
// Update the object's position using requestAnimationFrame for smoother animations
|
||||
if (!animationRef.current) {
|
||||
animationRef.current = requestAnimationFrame(() => {
|
||||
if (positionRef.current) {
|
||||
updateObjectPosition(zoneName, draggingIndex.index, positionRef.current);
|
||||
updateObjectPosition(zoneName, draggingIndex.index, {
|
||||
...obj.position,
|
||||
[activeProp1]: positionRef.current[0],
|
||||
[activeProp2]: positionRef.current[1],
|
||||
});
|
||||
}
|
||||
animationRef.current = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handlePointerUp() {
|
||||
setDraggingIndex(null);
|
||||
setOffset(null);
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
animationRef.current = null;
|
||||
// Handle pointer up event
|
||||
async function handlePointerUp(event: React.MouseEvent<HTMLDivElement>) {
|
||||
try {
|
||||
if (!draggingIndex || !offset) return;
|
||||
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const container = document.getElementById("real-time-vis-canvas");
|
||||
if (!container) throw new Error("Canvas container not found");
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const relativeX = event.clientX - rect.left;
|
||||
const relativeY = event.clientY - rect.top;
|
||||
|
||||
// Recalculate the position using determinePosition
|
||||
const newPosition = determinePosition(rect, relativeX, relativeY);
|
||||
|
||||
// Validate the dragging index and get the object
|
||||
if (!zone.objects[draggingIndex.index]) {
|
||||
throw new Error("Dragged object not found in the zone");
|
||||
}
|
||||
const obj = { ...zone.objects[draggingIndex.index], position: newPosition };
|
||||
let response = await addingFloatingWidgets(zone.zoneId, organization, obj);
|
||||
if (response.message === "Widget updated successfully") {
|
||||
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
|
||||
}
|
||||
|
||||
// Reset states
|
||||
setDraggingIndex(null);
|
||||
setOffset(null);
|
||||
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
animationRef.current = null;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div onPointerMove={handlePointerMove} onPointerUp={handlePointerUp}>
|
||||
{zone.objects.map((obj, index) => (
|
||||
@@ -96,59 +183,42 @@ const DroppedObjects: React.FC = () => {
|
||||
key={`${zoneName}-${index}`}
|
||||
className={obj.className}
|
||||
style={{
|
||||
top: obj.position[0] + "px",
|
||||
left: obj.position[1] + "px",
|
||||
transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out",
|
||||
position: "absolute",
|
||||
top:
|
||||
typeof obj.position.top !== "string"
|
||||
? `${obj.position.top}px`
|
||||
: "auto",
|
||||
left:
|
||||
typeof obj.position.left !== "string"
|
||||
? `${obj.position.left}px`
|
||||
: "auto",
|
||||
right:
|
||||
typeof obj.position.right !== "string"
|
||||
? `${obj.position.right}px`
|
||||
: "auto",
|
||||
bottom:
|
||||
typeof obj.position.bottom !== "string"
|
||||
? `${obj.position.bottom}px`
|
||||
: "auto",
|
||||
// transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out",
|
||||
}}
|
||||
onPointerDown={(event) => handlePointerDown(event, index)}
|
||||
onClick={() => {
|
||||
setSelectedChartId(obj)
|
||||
}}
|
||||
>
|
||||
{obj.className === "floating total-card" ? (
|
||||
<div>
|
||||
<div className="header-wrapper">
|
||||
<div className="header">{obj.header}</div>
|
||||
<div className="data-values">
|
||||
<div className="value">{obj.value}</div>
|
||||
<div className="per">{obj.per}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="icon">
|
||||
<WalletIcon />
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<TotalCardComponent object={obj} />
|
||||
</>
|
||||
) : obj.className === "warehouseThroughput floating" ? (
|
||||
<div>
|
||||
<div className="header">
|
||||
<h2>Warehouse Throughput</h2>
|
||||
<p>
|
||||
<span>(+5) more</span> in 2025
|
||||
</p>
|
||||
</div>
|
||||
<div className="lineGraph" style={{ height: "100%" }}>
|
||||
{/* <Line data={lineGraphData} options={lineGraphOptions} /> */}
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<WarehouseThroughputComponent />
|
||||
</>
|
||||
) : obj.className === "fleetEfficiency floating" ? (
|
||||
<div>
|
||||
<h2 className="header">Fleet Efficiency</h2>
|
||||
<div className="progressContainer">
|
||||
<div className="progress">
|
||||
<div className="barOverflow">
|
||||
<div
|
||||
className="bar"
|
||||
style={{ transform: `rotate(${obj.value}deg)` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="scaleLabels">
|
||||
<span>0%</span>
|
||||
<div className="centerText">
|
||||
<div className="percentage">{obj.per}%</div>
|
||||
<div className="status">Optimal</div>
|
||||
</div>
|
||||
<span>100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<FleetEfficiencyComponent object={obj} />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
@@ -156,59 +226,4 @@ const DroppedObjects: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default DroppedObjects;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// import { Html } from "@react-three/drei";
|
||||
// import { useDroppedObjectsStore } from "../../../store/store";
|
||||
// import { CartIcon, DocumentIcon, GlobeIcon, WalletIcon } from "../../icons/3dChartIcons";
|
||||
// import SimpleCard from "../realTimeVis/floating/SimpleCard";
|
||||
|
||||
// const ICON_MAP: Record<string, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
|
||||
// WalletIcon,
|
||||
// GlobeIcon,
|
||||
// DocumentIcon,
|
||||
// CartIcon
|
||||
// };
|
||||
// const DroppedObjects: React.FC = () => {
|
||||
// const objects = useDroppedObjectsStore((state) => state.objects); // Get objects from Zustand store
|
||||
//
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// {objects.map((obj, index) => {
|
||||
// const IconComponent = obj.Icon || WalletIcon; // Use obj.Icon directly if it exists
|
||||
// return (
|
||||
// <SimpleCard
|
||||
// key={index}
|
||||
// position={obj.position}
|
||||
// header={obj.header}
|
||||
// icon={IconComponent} // ✅ No need to look it up in ICON_MAP
|
||||
// value={obj.value}
|
||||
// per={obj.per}
|
||||
// />
|
||||
// );
|
||||
// })}
|
||||
// </>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default DroppedObjects;
|
||||
|
||||
@@ -149,7 +149,7 @@ const Panel: React.FC<PanelProps> = ({
|
||||
};
|
||||
try {
|
||||
let response = await addingWidgets(selectedZone.zoneId, organization, newWidget);
|
||||
console.log("response: ", response);
|
||||
|
||||
if (response.message === "Widget created successfully") {
|
||||
setSelectedZone((prev) => ({
|
||||
...prev,
|
||||
|
||||
@@ -9,10 +9,15 @@ import useModuleStore from "../../../store/useModuleStore";
|
||||
|
||||
import DroppedObjects from "./DroppedFloatingWidgets";
|
||||
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
|
||||
import { useAsset3dWidget, useZones } from "../../../store/store";
|
||||
import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones";
|
||||
import {
|
||||
useAsset3dWidget,
|
||||
useWidgetSubOption,
|
||||
useZones,
|
||||
} 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";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
@@ -25,7 +30,7 @@ type FormattedZoneData = Record<
|
||||
lockedPanels: Side[];
|
||||
zoneId: string;
|
||||
zoneViewPortTarget: number[];
|
||||
zoneViewPortPosition: number[]
|
||||
zoneViewPortPosition: number[];
|
||||
widgets: Widget[];
|
||||
}
|
||||
>;
|
||||
@@ -45,9 +50,13 @@ const RealTimeVisulization: React.FC = () => {
|
||||
const [droppedObjects, setDroppedObjects] = useState<any[]>([]);
|
||||
const [zonesData, setZonesData] = useState<FormattedZoneData>({});
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
const { zones } = useZones()
|
||||
const [floatingWidgets, setFloatingWidgets] = useState<Record<string, { zoneName: string; zoneId: string; objects: any[] }>>({});
|
||||
const { zones } = useZones();
|
||||
const [floatingWidgets, setFloatingWidgets] = useState<
|
||||
Record<string, { zoneName: string; zoneId: string; objects: any[] }>
|
||||
>({});
|
||||
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
|
||||
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption();
|
||||
|
||||
useEffect(() => {
|
||||
async function GetZoneData() {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
@@ -57,27 +66,29 @@ const RealTimeVisulization: React.FC = () => {
|
||||
if (!Array.isArray(response)) {
|
||||
return;
|
||||
}
|
||||
const formattedData = response.reduce<FormattedZoneData>((acc, zone) => {
|
||||
acc[zone.zoneName] = {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
zoneId: zone.zoneId,
|
||||
zoneViewPortTarget: zone.viewPortCenter,
|
||||
zoneViewPortPosition: zone.viewPortposition,
|
||||
widgets: [],
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
const formattedData = response.reduce<FormattedZoneData>(
|
||||
(acc, zone) => {
|
||||
acc[zone.zoneName] = {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
zoneId: zone.zoneId,
|
||||
zoneViewPortTarget: zone.viewPortCenter,
|
||||
zoneViewPortPosition: zone.viewPortposition,
|
||||
widgets: [],
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
setZonesData(formattedData);
|
||||
} catch (error) {
|
||||
console.log('error: ', error);
|
||||
console.log("error: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
GetZoneData();
|
||||
}, []); // Removed `zones` from dependencies
|
||||
|
||||
}, [activeModule]); // Removed `zones` from dependencies
|
||||
|
||||
useEffect(() => {
|
||||
setZonesData((prev) => {
|
||||
@@ -97,48 +108,72 @@ const RealTimeVisulization: React.FC = () => {
|
||||
};
|
||||
});
|
||||
}, [selectedZone]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [floatingWidgets])
|
||||
useEffect(() => {}, [floatingWidgets]);
|
||||
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const data = event.dataTransfer.getData("text/plain");
|
||||
if (widgetSelect !== "") return;
|
||||
if (!data || selectedZone.zoneName === "") return;
|
||||
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||
try {
|
||||
event.preventDefault();
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
const droppedData = JSON.parse(data);
|
||||
const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||
if (!canvasElement) return;
|
||||
const data = event.dataTransfer.getData("text/plain");
|
||||
// if (widgetSelect !== "") return;
|
||||
if (widgetSubOption === "3D") return;
|
||||
if (!data || selectedZone.zoneName === "") return;
|
||||
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const relativeX = event.clientX - canvasRect.left;
|
||||
const relativeY = event.clientY - canvasRect.top;
|
||||
const droppedData = JSON.parse(data);
|
||||
const canvasElement = document.getElementById("real-time-vis-canvas");
|
||||
if (!canvasElement) throw new Error("Canvas element not found");
|
||||
|
||||
const newObject = {
|
||||
...droppedData,
|
||||
position: [relativeY, relativeX], // Y first because of top/left style
|
||||
};
|
||||
// Only set zone if it’s not already in the store (prevents overwriting objects)
|
||||
const existingZone = useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||
if (!existingZone) {
|
||||
useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||
}
|
||||
// Add the dropped object to the zone
|
||||
useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, newObject);
|
||||
setFloatingWidgets((prevWidgets) => ({
|
||||
...prevWidgets,
|
||||
[selectedZone.zoneName]: {
|
||||
...prevWidgets[selectedZone.zoneName],
|
||||
zoneName: selectedZone.zoneName,
|
||||
zoneId: selectedZone.zoneId,
|
||||
objects: [...(prevWidgets[selectedZone.zoneName]?.objects || []), newObject],
|
||||
},
|
||||
}));
|
||||
const canvasRect = canvasElement.getBoundingClientRect();
|
||||
const relativeX = event.clientX - canvasRect.left;
|
||||
const relativeY = event.clientY - canvasRect.top;
|
||||
|
||||
const newObject = {
|
||||
...droppedData,
|
||||
id: generateUniqueId(),
|
||||
position: determinePosition(canvasRect, relativeX, relativeY),
|
||||
};
|
||||
|
||||
let response = await addingFloatingWidgets(
|
||||
selectedZone.zoneId,
|
||||
organization,
|
||||
newObject
|
||||
);
|
||||
|
||||
// Only set zone if it’s not already in the store (prevents overwriting objects)
|
||||
const existingZone =
|
||||
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
|
||||
if (!existingZone) {
|
||||
useDroppedObjectsStore
|
||||
.getState()
|
||||
.setZone(selectedZone.zoneName, selectedZone.zoneId);
|
||||
}
|
||||
|
||||
// Add the dropped object to the zone if the API call is successful
|
||||
if (response.message === "FloatWidget created successfully") {
|
||||
useDroppedObjectsStore
|
||||
.getState()
|
||||
.addObject(selectedZone.zoneName, newObject);
|
||||
}
|
||||
|
||||
// Update floating widgets state
|
||||
setFloatingWidgets((prevWidgets) => ({
|
||||
...prevWidgets,
|
||||
[selectedZone.zoneName]: {
|
||||
...prevWidgets[selectedZone.zoneName],
|
||||
zoneName: selectedZone.zoneName,
|
||||
zoneId: selectedZone.zoneId,
|
||||
objects: [
|
||||
...(prevWidgets[selectedZone.zoneName]?.objects || []),
|
||||
newObject,
|
||||
],
|
||||
},
|
||||
}));
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
@@ -149,7 +184,6 @@ const RealTimeVisulization: React.FC = () => {
|
||||
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
||||
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
||||
}}
|
||||
|
||||
>
|
||||
<div
|
||||
className="scene-container"
|
||||
@@ -162,7 +196,6 @@ const RealTimeVisulization: React.FC = () => {
|
||||
onDrop={(event) => handleDrop(event)}
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
>
|
||||
|
||||
<Scene />
|
||||
</div>
|
||||
<DroppedObjects />
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
export function determinePosition(
|
||||
canvasRect: DOMRect,
|
||||
relativeX: number,
|
||||
relativeY: number
|
||||
): {
|
||||
top: number | "auto";
|
||||
left: number | "auto";
|
||||
right: number | "auto";
|
||||
bottom: number | "auto";
|
||||
} {
|
||||
// Calculate the midpoints of the canvas
|
||||
const centerX = canvasRect.width / 2;
|
||||
const centerY = canvasRect.height / 2;
|
||||
|
||||
// Initialize position with default values
|
||||
let position: {
|
||||
top: number | "auto";
|
||||
left: number | "auto";
|
||||
right: number | "auto";
|
||||
bottom: number | "auto";
|
||||
};
|
||||
|
||||
if (relativeY < centerY) {
|
||||
// Top half
|
||||
if (relativeX < centerX) {
|
||||
// Left side
|
||||
position = {
|
||||
top: relativeY,
|
||||
left: relativeX,
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
};
|
||||
} else {
|
||||
// Right side
|
||||
position = {
|
||||
top: relativeY,
|
||||
right: canvasRect.width - relativeX,
|
||||
left: "auto",
|
||||
bottom: "auto",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Bottom half
|
||||
if (relativeX < centerX) {
|
||||
// Left side
|
||||
position = {
|
||||
bottom: canvasRect.height - relativeY,
|
||||
left: relativeX,
|
||||
right: "auto",
|
||||
top: "auto",
|
||||
};
|
||||
} else {
|
||||
// Right side
|
||||
position = {
|
||||
bottom: canvasRect.height - relativeY,
|
||||
right: canvasRect.width - relativeX,
|
||||
left: "auto",
|
||||
top: "auto",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export const getActiveProperties = (position: {
|
||||
top: number | "auto";
|
||||
left: number | "auto";
|
||||
right: number | "auto";
|
||||
bottom: number | "auto";
|
||||
}) => {
|
||||
let activeProps: ["top", "left"] | ["bottom", "right"] = ["top", "left"]; // Default to top-left
|
||||
|
||||
if (
|
||||
typeof position.bottom !== "string" &&
|
||||
typeof position.right !== "string"
|
||||
) {
|
||||
activeProps = ["bottom", "right"];
|
||||
}
|
||||
|
||||
return activeProps;
|
||||
};
|
||||
@@ -14,6 +14,7 @@ export default function ZoneCentreTarget() {
|
||||
const { Edit, setEdit } = useEditPosition();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
selectedZone.zoneViewPortTarget &&
|
||||
@@ -39,22 +40,22 @@ export default function ZoneCentreTarget() {
|
||||
|
||||
if (centrePoint.length > 0) {
|
||||
|
||||
let camPosition = new THREE.Vector3(...selectedZone.zoneViewPortPosition);
|
||||
let CamTarget = new THREE.Vector3(...selectedZone.zoneViewPortTarget);
|
||||
// let camPosition = new THREE.Vector3(...selectedZone.zoneViewPortPosition);
|
||||
// let CamTarget = new THREE.Vector3(...selectedZone.zoneViewPortTarget);
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(CamTarget, camPosition).normalize();
|
||||
// const direction = new THREE.Vector3().subVectors(CamTarget, camPosition).normalize();
|
||||
|
||||
const worldUp = new THREE.Vector3(0, 0, 1);
|
||||
const right = new THREE.Vector3().crossVectors(worldUp, direction).normalize();
|
||||
const up = new THREE.Vector3().crossVectors(direction, right).normalize();
|
||||
const offsetPosition = up.clone().multiplyScalar(20);
|
||||
camPosition.add(offsetPosition);
|
||||
// const worldUp = new THREE.Vector3(0, 0, 1);
|
||||
// const right = new THREE.Vector3().crossVectors(worldUp, direction).normalize();
|
||||
// const up = new THREE.Vector3().crossVectors(direction, right).normalize();
|
||||
// const offsetPosition = up.clone().multiplyScalar(20);
|
||||
// camPosition.add(offsetPosition);
|
||||
|
||||
const setCam = async () => {
|
||||
controls.setLookAt(centrePoint[0], 100, centrePoint[2], ...centrePoint, true);
|
||||
setTimeout(() => {
|
||||
controls?.setLookAt(
|
||||
...camPosition.toArray(),
|
||||
...selectedZone.zoneViewPortPosition,
|
||||
selectedZone.zoneViewPortTarget[0],
|
||||
selectedZone.zoneViewPortTarget[1],
|
||||
selectedZone.zoneViewPortTarget[2],
|
||||
@@ -65,21 +66,9 @@ export default function ZoneCentreTarget() {
|
||||
setCam();
|
||||
} else {
|
||||
|
||||
let camPosition = new THREE.Vector3(...selectedZone.zoneViewPortPosition);
|
||||
let CamTarget = new THREE.Vector3(...selectedZone.zoneViewPortTarget);
|
||||
|
||||
const direction = new THREE.Vector3().subVectors(CamTarget, camPosition).normalize();
|
||||
|
||||
const worldUp = new THREE.Vector3(0, 0, 1);
|
||||
const right = new THREE.Vector3().crossVectors(worldUp, direction).normalize();
|
||||
const up = new THREE.Vector3().crossVectors(direction, right).normalize();
|
||||
|
||||
const offsetPosition = up.clone().multiplyScalar(20);
|
||||
|
||||
camPosition.add(offsetPosition);
|
||||
const setCam = async () => {
|
||||
controls?.setLookAt(
|
||||
...camPosition.toArray(),
|
||||
...selectedZone.zoneViewPortPosition,
|
||||
selectedZone.zoneViewPortTarget[0],
|
||||
selectedZone.zoneViewPortTarget[1],
|
||||
selectedZone.zoneViewPortTarget[2],
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
import React from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
import { EyeDroperIcon } from "../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
interface EyeDropInputProps {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
const EyeDropInput: React.FC<EyeDropInputProps> = ({
|
||||
label = "Object",
|
||||
onChange,
|
||||
}) => {
|
||||
const handleEyeDropClick = () => {
|
||||
// Here you would typically implement the eye dropper functionality
|
||||
// For now, we'll just simulate selecting a value
|
||||
const simulatedValue = "picked_value"; // Replace with actual eye dropper logic
|
||||
onChange(simulatedValue);
|
||||
};
|
||||
|
||||
const EyeDropInput: React.FC = () => {
|
||||
return (
|
||||
<div className="eye-dropper-input-container">
|
||||
<div className="label">Object</div>
|
||||
<div className="label">{label}</div>
|
||||
<div className="input-container">
|
||||
{/* <input disabled type="text" /> */}
|
||||
<RegularDropDown
|
||||
header="select object"
|
||||
options={[]}
|
||||
onSelect={() => {}}
|
||||
onSelect={() => { }}
|
||||
/>
|
||||
<div className="eye-picker-button">
|
||||
<div className="eye-picker-button" onClick={handleEyeDropClick}>
|
||||
<EyeDroperIcon isActive={false} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,4 +38,4 @@ const EyeDropInput: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EyeDropInput;
|
||||
export default EyeDropInput;
|
||||
@@ -58,12 +58,15 @@ const MultiEmailInvite: React.FC = () => {
|
||||
onFocus={() => setInputFocus(true)}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter email and press Enter or comma"
|
||||
placeholder="Enter email and press Enter or comma to seperate"
|
||||
/>
|
||||
</div>
|
||||
<div onClick={handleAddEmail} className="invite-button">
|
||||
Invite
|
||||
</div>
|
||||
<div className="users-list-container">
|
||||
{/* list available users */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -253,7 +253,8 @@ const MultiLevelDropdown = ({
|
||||
className={`dropdown-button ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{displayLabel} <span className="icon">▾</span>
|
||||
<div className="label">{displayLabel}</div>
|
||||
<span className="icon">▾</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="dropdown-menu">
|
||||
|
||||
@@ -32,7 +32,7 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
listType = "default",
|
||||
remove,
|
||||
}) => {
|
||||
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(defaultOpen);
|
||||
const { zones, setZones } = useZones()
|
||||
|
||||
@@ -43,6 +43,17 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(zones);
|
||||
// setZoneDataList([
|
||||
// { id: "2e996073-546c-470c-8323-55bd3700c6aa", name: "zone1" },
|
||||
// { id: "3f473bf0-197c-471c-a71f-943fc9ca2b47", name: "zone2" },
|
||||
// { id: "905e8fb6-9e18-469b-9474-e0478fb9601b", name: "zone3" },
|
||||
// { id: "9d9efcbe-8e96-47eb-bfad-128a9e4c532e", name: "zone4" },
|
||||
// { id: "884f3d29-eb5a-49a5-abe9-d11971c08e85", name: "zone5" },
|
||||
// { id: "70fa55cd-b5c9-4f80-a8c4-6319af3bfb4e", name: "zone6" },
|
||||
// ])
|
||||
|
||||
|
||||
const value = (zones || []).map((val: { zoneId: string; zoneName: string }) => ({
|
||||
id: val.zoneId,
|
||||
name: val.zoneName
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import RenameInput from "../inputs/RenameInput";
|
||||
import { EyeIcon, LockIcon, RmoveIcon } from "../../icons/ExportCommonIcons";
|
||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||
import { getZoneData } from "../../../services/realTimeVisulization/zoneData/getZones";
|
||||
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
|
||||
interface ListProps {
|
||||
items?: { id: string; name: string }[]; // Optional array of items to render
|
||||
@@ -12,27 +12,55 @@ interface ListProps {
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
const { setSelectedZone } = useSelectedZoneStore();
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
const { setSubModule } = useSubModuleStore();
|
||||
useEffect(() => {
|
||||
useSelectedZoneStore.getState().setSelectedZone({
|
||||
zoneName: "",
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
zoneId: "",
|
||||
zoneViewPortTarget: [],
|
||||
zoneViewPortPosition: [],
|
||||
widgets: [],
|
||||
});
|
||||
}, [activeModule]);
|
||||
|
||||
|
||||
async function handleSelectZone(id: string) {
|
||||
setSubModule("zoneProperties")
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
let response = await getZoneData(id, organization)
|
||||
setSelectedZone({
|
||||
zoneName: response?.zoneName,
|
||||
activeSides: response?.activeSides || [],
|
||||
panelOrder: response?.panelOrder || [],
|
||||
lockedPanels: response?.lockedPanels || [],
|
||||
widgets: response?.widgets || [],
|
||||
zoneId: response?.zoneId,
|
||||
zoneViewPortTarget: response?.viewPortCenter || [],
|
||||
zoneViewPortPosition:
|
||||
response?.viewPortposition || [],
|
||||
});
|
||||
try {
|
||||
// Avoid re-fetching if the same zone is already selected
|
||||
if (selectedZone?.zoneId === id) {
|
||||
console.log("Zone is already selected:", selectedZone.zoneName);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubModule("zoneProperties");
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "";
|
||||
|
||||
let response = await getZoneData(id, organization);
|
||||
|
||||
setSelectedZone({
|
||||
zoneName: response?.zoneName,
|
||||
activeSides: response?.activeSides || [],
|
||||
panelOrder: response?.panelOrder || [],
|
||||
lockedPanels: response?.lockedPanels || [],
|
||||
widgets: response?.widgets || [],
|
||||
zoneId: response?.zoneId,
|
||||
zoneViewPortTarget: response?.viewPortCenter || [],
|
||||
zoneViewPortPosition: response?.viewPortposition || [],
|
||||
});
|
||||
|
||||
console.log("Zone selected:", response?.zoneName);
|
||||
} catch (error) {
|
||||
console.error("Error selecting zone:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{items.length > 0 ? (
|
||||
|
||||
523
app/src/components/ui/menu/menu.tsx
Normal file
523
app/src/components/ui/menu/menu.tsx
Normal file
@@ -0,0 +1,523 @@
|
||||
import React, { useState } from "react";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
interface MenuBarProps {
|
||||
setOpenMenu: (isOpen: boolean) => void; // Function to update menu state
|
||||
}
|
||||
|
||||
const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
const [activeMenu, setActiveMenu] = useState<string | null>(null);
|
||||
const [activeSubMenu, setActiveSubMenu] = useState<string | null>(null);
|
||||
|
||||
// State to track selection for all menu items
|
||||
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
);
|
||||
|
||||
// Function to toggle selection for a specific item
|
||||
const toggleSelection = (itemName: string) => {
|
||||
setSelectedItems((prev) => ({
|
||||
...prev,
|
||||
[itemName]: !prev[itemName], // Toggle the selection state
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="menu-bar"
|
||||
onPointerLeave={() => {
|
||||
setOpenMenu(false);
|
||||
}}
|
||||
>
|
||||
{/* Top-level menu buttons */}
|
||||
<div className="menu-buttons">
|
||||
{/* File Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("File")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">
|
||||
File
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* File Dropdown */}
|
||||
{activeMenu === "File" && (
|
||||
<div className="dropdown-menu">
|
||||
{/* New File */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("New File")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>New File</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + N</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Open Local File */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Open Local File")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Open Local File</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + O</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Version */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Save Version")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Save Version</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
|
||||
{/* Make a Copy */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Make a Copy")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Make a Copy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Share */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Share")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Share</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rename */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Rename")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Rename</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
|
||||
{/* Import */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Import")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Import</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Close File */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Close File")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Close File</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Edit Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("Edit")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">
|
||||
Edit
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Edit Dropdown */}
|
||||
{activeMenu === "Edit" && (
|
||||
<div className="dropdown-menu">
|
||||
{/* Undo */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Undo")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Undo</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + Z</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Redo */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Redo")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Redo</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + Shift + Z</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
|
||||
{/* Undo History */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Undo History")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Undo History</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Redo History */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Redo History")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Redo History</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
|
||||
{/* Find */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Find")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Find</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + F</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Delete")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Select by... */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Select by...")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Select by...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keymap */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Keymap")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Keymap</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* View Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("View")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">
|
||||
View
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* View Dropdown */}
|
||||
{activeMenu === "View" && (
|
||||
<div className="dropdown-menu">
|
||||
{/* Grid */}
|
||||
<div
|
||||
className={"menu-item-container"}
|
||||
onClick={() => toggleSelection("Grid")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Grid</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Gizmo */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onMouseEnter={() => setActiveSubMenu("View-Gizmo")}
|
||||
onMouseLeave={() => setActiveSubMenu(null)}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Gizmo</span>
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
|
||||
{/* Gizmo Submenu */}
|
||||
{activeSubMenu === "View-Gizmo" && (
|
||||
<div className="submenu">
|
||||
{/* Visibility */}
|
||||
<div
|
||||
className="submenu-item"
|
||||
onClick={() => toggleSelection("Visibility")}
|
||||
>
|
||||
<span>
|
||||
{selectedItems["Visibility"] && "✓ "}
|
||||
Visibility
|
||||
</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
|
||||
{/* Cube view */}
|
||||
<div
|
||||
className="submenu-item"
|
||||
onClick={() => toggleSelection("Cube view")}
|
||||
>
|
||||
<span>Cube view</span>
|
||||
</div>
|
||||
|
||||
{/* Sphere view */}
|
||||
<div
|
||||
className="submenu-item"
|
||||
onClick={() => toggleSelection("Sphere view")}
|
||||
>
|
||||
<span>Sphere view</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Zoom */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Zoom")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Zoom</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Full Screen */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Full Screen")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Full Screen</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">F11</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Version History Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("Version history")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">Version history</div>
|
||||
</div>
|
||||
|
||||
{/* Export As Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("Export as...")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">Export as...</div>
|
||||
</div>
|
||||
|
||||
{/* Apps Menu */}
|
||||
{/* <div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("Apps")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">
|
||||
Apps
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{activeMenu === "Apps" && (
|
||||
<div className="dropdown-menu">
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("New App")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>
|
||||
|
||||
New App
|
||||
</span>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Work-flow Monitor")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>
|
||||
|
||||
Work-flow Monitor
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Temperature Visualizer")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>
|
||||
|
||||
Temperature Visualizer
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("View all")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>
|
||||
|
||||
View all
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div> */}
|
||||
|
||||
{/* Help Menu */}
|
||||
<div
|
||||
className="menu-button-container"
|
||||
onMouseEnter={() => setActiveMenu("Help")}
|
||||
onMouseLeave={() => {
|
||||
setActiveMenu(null);
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">
|
||||
Help
|
||||
<span className="dropdown-icon">
|
||||
<ArrowIcon />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Help Dropdown */}
|
||||
{activeMenu === "Help" && (
|
||||
<div className="dropdown-menu">
|
||||
{/* Shortcuts */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Shortcuts")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Shortcuts</span>
|
||||
<div className="menu-item-right">
|
||||
<span className="shortcut">Ctrl + Shift + ?</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Manual */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Manual")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Manual</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Video Tutorials */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Video Tutorials")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Video Tutorials</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Report a bug */}
|
||||
<div
|
||||
className="menu-item-container"
|
||||
onClick={() => toggleSelection("Report a bug")}
|
||||
>
|
||||
<div className="menu-item">
|
||||
<span>Report a bug</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuBar;
|
||||
@@ -345,7 +345,7 @@ const BarGraphComponent = ({
|
||||
|
||||
if (id !== "") {
|
||||
try {
|
||||
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
|
||||
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)
|
||||
|
||||
@@ -158,7 +158,7 @@ const DoughnutGraphComponent = ({
|
||||
|
||||
if (id !== "") {
|
||||
try {
|
||||
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
|
||||
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)
|
||||
|
||||
@@ -158,7 +158,7 @@ const LineGraphComponent = ({
|
||||
|
||||
if (id !== "") {
|
||||
try {
|
||||
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
|
||||
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)
|
||||
|
||||
@@ -344,7 +344,7 @@ const PieChartComponent = ({
|
||||
|
||||
if (id !== "") {
|
||||
try {
|
||||
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
|
||||
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)
|
||||
|
||||
@@ -1,22 +1,64 @@
|
||||
import { useMemo } from "react";
|
||||
import { Line, PolarArea } from "react-chartjs-2";
|
||||
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";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const PolarAreaGraphComponent = ({
|
||||
id,
|
||||
type,
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const { themeColor } = useThemeStore();
|
||||
const { measurements: chartMeasurements, duration: chartDuration, name: widgetName } = useChartStore();
|
||||
const [measurements, setmeasurements] = useState<any>({});
|
||||
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(() => {
|
||||
console.log("titleeeeeeeeeeeeeeeeeee",title);
|
||||
},[])
|
||||
|
||||
// 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,
|
||||
@@ -26,19 +68,9 @@ const PolarAreaGraphComponent = ({
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [fontSize]);
|
||||
const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [fontWeight, chartFontWeightMap]);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
@@ -48,46 +80,110 @@ const PolarAreaGraphComponent = ({
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
// Memoize Chart Options
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: name,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
scales: {
|
||||
// x: {
|
||||
// ticks: {
|
||||
// display: true, // This hides the x-axis labels
|
||||
// },
|
||||
// },
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle, name]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple)
|
||||
borderColor: "#ffffff", // Keeping border color white
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
// useEffect(() => {console.log(measurements);
|
||||
// },[measurements])
|
||||
|
||||
return <PolarArea data={chartData} options={options} />;
|
||||
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 <PolarArea data={Object.keys(measurements).length > 0 ? chartData : defaultData} options={options} />;
|
||||
};
|
||||
|
||||
export default PolarAreaGraphComponent;
|
||||
export default PolarAreaGraphComponent;
|
||||
@@ -14,8 +14,6 @@ const FleetEfficiency = () => {
|
||||
per: progress,
|
||||
|
||||
});
|
||||
|
||||
console.log("Dragged Data:", cardData);
|
||||
event.dataTransfer.setData("text/plain", cardData);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
|
||||
type Props = {}
|
||||
|
||||
const FleetEfficiencyComponent = ({
|
||||
object
|
||||
}: any) => {
|
||||
return (
|
||||
<>
|
||||
<h2 className="header">Fleet Efficiency</h2>
|
||||
<div className="progressContainer">
|
||||
<div className="progress">
|
||||
<div className="barOverflow">
|
||||
<div
|
||||
className="bar"
|
||||
style={{ transform: `rotate(${object.value}deg)` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="scaleLabels">
|
||||
<span>0%</span>
|
||||
<div className="centerText">
|
||||
<div className="percentage">{object.per}%</div>
|
||||
<div className="status">Optimal</div>
|
||||
</div>
|
||||
<span>100%</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FleetEfficiencyComponent
|
||||
@@ -23,6 +23,7 @@ const SimpleCard: React.FC<SimpleCardProps> = ({
|
||||
value,
|
||||
per,
|
||||
icon: Icon,
|
||||
|
||||
className: event.currentTarget.className,
|
||||
position: [rect.top, rect.left], // ✅ Store position
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { WalletIcon } from '../../../icons/3dChartIcons'
|
||||
import { useWidgetStore } from '../../../../store/useWidgetStore';
|
||||
|
||||
const TotalCardComponent = ({
|
||||
object
|
||||
}: any) => {
|
||||
|
||||
const { setSelectedChartId } =
|
||||
useWidgetStore();
|
||||
return (
|
||||
<>
|
||||
<div className="header-wrapper" onClick={() => {
|
||||
setSelectedChartId(object.id)
|
||||
}}>
|
||||
<div className="header">{object.header}</div>
|
||||
<div className="data-values">
|
||||
<div className="value">{object.value}</div>
|
||||
<div className="per">{object.per}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="icon">
|
||||
<WalletIcon />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TotalCardComponent
|
||||
@@ -0,0 +1,132 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Line } from 'react-chartjs-2'
|
||||
import axios from 'axios';
|
||||
|
||||
const WarehouseThroughputComponent = ({
|
||||
object
|
||||
}: any) => {
|
||||
|
||||
const [measurements, setmeasurements] = useState<any>({});
|
||||
const [duration, setDuration] = useState("1h")
|
||||
|
||||
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 fetchSavedInputes = async() => {
|
||||
|
||||
// if (object.id !== "") {
|
||||
// try {
|
||||
// const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_LOCAL_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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="header">
|
||||
<h2>Warehouse Throughput</h2>
|
||||
<p>
|
||||
<span>(+5) more</span> in 2025
|
||||
</p>
|
||||
</div>
|
||||
<div className="lineGraph" style={{ height: "100%" }}>
|
||||
<Line data={lineGraphData} options={lineGraphOptions} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarehouseThroughputComponent
|
||||
Reference in New Issue
Block a user