From 6b8ccc02c740f9b1c857b5b867a81c5096fd8dfa Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Mon, 31 Mar 2025 19:20:03 +0530 Subject: [PATCH] 3d widget api added and template frontend and backend completed --- .../sidebarLeft/visualization/Templates.tsx | 78 ++++++-- .../properties/ZoneProperties.tsx | 7 +- app/src/components/ui/Tools.tsx | 9 +- .../components/ui/componets/AddButtons.tsx | 1 - .../components/ui/componets/DisplayZone.tsx | 17 +- .../components/ui/componets/DistanceLine.tsx | 93 ++++++++++ .../ui/componets/Dropped3dWidget.tsx | 170 ++++++++++++------ .../ui/componets/DroppedFloatingWidgets.tsx | 52 +++++- .../ui/componets/RealTimeVisulization.tsx | 11 +- .../componets/functions/determinePosition.ts | 67 +++++++ .../ui/componets/handleDropTemplate .tsx | 29 +++ app/src/modules/simulation/simulation.tsx | 1 - .../visualization/handleSaveTemplate.ts | 61 ++++--- .../zoneData/add3dWidget.ts | 36 ++++ .../zoneData/addFloatingWidgets.ts | 1 + .../zoneData/deleteFloatingWidget.ts | 35 ++++ .../zoneData/deleteTemplate.ts | 32 ++++ .../zoneData/get3dWidgetData.ts | 25 +++ .../zoneData/getSelect2dZoneData.ts | 1 + .../zoneData/getTemplate.ts | 23 +++ .../realTimeVisulization/zoneData/getZones.ts | 2 +- .../zoneData/loadTemplate.ts | 33 ++++ .../zoneData/saveTempleteApi.ts | 28 +++ app/src/store/store.ts | 1 + app/src/store/useDroppedObjectsStore.ts | 77 ++++++++ app/src/store/useTemplateStore.ts | 18 +- app/src/styles/pages/realTimeViz.scss | 11 -- 27 files changed, 790 insertions(+), 129 deletions(-) create mode 100644 app/src/components/ui/componets/DistanceLine.tsx create mode 100644 app/src/components/ui/componets/handleDropTemplate .tsx create mode 100644 app/src/services/realTimeVisulization/zoneData/add3dWidget.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/getTemplate.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/loadTemplate.ts create mode 100644 app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts diff --git a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx index ef710f7..4b13429 100644 --- a/app/src/components/layout/sidebarLeft/visualization/Templates.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/Templates.tsx @@ -1,25 +1,76 @@ +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 { deleteTemplateApi } from "../../../../services/realTimeVisulization/zoneData/deleteTemplate"; +import { loadTempleteApi } from "../../../../services/realTimeVisulization/zoneData/loadTemplate"; const Templates = () => { const { templates, removeTemplate } = useTemplateStore(); - const { setSelectedZone } = useSelectedZoneStore(); + const { setTemplates } = useTemplateStore(); + const { setSelectedZone, selectedZone } = useSelectedZoneStore(); - const handleDeleteTemplate = (id: string) => { - removeTemplate(id); + useEffect(() => { + async function templateData() { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + let response = await getTemplateData(organization); + setTemplates(response); + } catch (error) { + console.error("Error fetching template data:", error); + } + } + + templateData(); + }, []); + + const handleDeleteTemplate = async (id: string) => { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let response = await deleteTemplateApi(id, organization); + + if (response.message === "Template deleted successfully") { + removeTemplate(id); + } + } catch (error) { + console.error("Error deleting template:", error); + } }; - const handleLoadTemplate = (template: any) => { - setSelectedZone((prev) => ({ - ...prev, - panelOrder: template.panelOrder, - activeSides: Array.from( - new Set([...prev.activeSides, ...template.panelOrder]) - ), - widgets: template.widgets, - })); + const handleLoadTemplate = async (template: any) => { + try { + if (selectedZone.zoneName === "") return; + + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let response = await loadTempleteApi(template.id, selectedZone.zoneId, organization); + + if (response.message === "Template placed in Zone") { + 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 (
{ transition: "box-shadow 0.3s ease", }} > - {template.snapshot && ( + {template?.snapshot && (
{" "} {/* 16:9 aspect ratio */} @@ -122,3 +173,4 @@ const Templates = () => { }; export default Templates; + diff --git a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 9234cc5..9e8b37e 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -20,17 +20,16 @@ const ZoneProperties: React.FC = () => { try { const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; - + let zonesdata = { zoneId: selectedZone.zoneId, viewPortposition: zonePosition, viewPortCenter: zoneTarget }; - + let response = await zoneCameraUpdate(zonesdata, organization); console.log('response: ', response); - - + setEdit(false); } catch (error) { console.error("Error in handleSetView:", error); diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index c86164c..ba1f6e8 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -32,6 +32,7 @@ import { useTransformMode, } from "../../store/store"; import useToggleStore from "../../store/useUIToggleStore"; +import { use3DWidget, useFloatingWidget } from "../../store/useDroppedObjectsStore"; const Tools: React.FC = () => { const { templates } = useTemplateStore(); @@ -46,6 +47,8 @@ const Tools: React.FC = () => { const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { addTemplate } = useTemplateStore(); const { selectedZone } = useSelectedZoneStore(); + const { floatingWidget } = useFloatingWidget() + const { widgets3D } = use3DWidget() // wall options const { toggleView, setToggleView } = useToggleView(); @@ -378,13 +381,17 @@ const Tools: React.FC = () => {
+ onClick={() => { + handleSaveTemplate({ addTemplate, + floatingWidget, + widgets3D, selectedZone, templates, }) } + } >
diff --git a/app/src/components/ui/componets/AddButtons.tsx b/app/src/components/ui/componets/AddButtons.tsx index 4f3f9b7..e0ca342 100644 --- a/app/src/components/ui/componets/AddButtons.tsx +++ b/app/src/components/ui/componets/AddButtons.tsx @@ -119,7 +119,6 @@ const AddButtons: React.FC = ({ }; // Delete the selectedZone state - setSelectedZone(updatedZone); } else { const updatePanelData = async () => { diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index 7689e51..3889139 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -2,9 +2,10 @@ import React, { useEffect, useRef, useState, useCallback } from "react"; import { Widget } from "../../../store/useWidgetStore"; import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons"; import { InfoIcon } from "../../icons/ExportCommonIcons"; -import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore"; +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"; @@ -72,6 +73,7 @@ const DisplayZone: React.FC = ({ // State to track overflow visibility const [showLeftArrow, setShowLeftArrow] = useState(false); const [showRightArrow, setShowRightArrow] = useState(false); + const { floatingWidget, setFloatingWidget } = useFloatingWidget() // Function to calculate overflow state const updateOverflowState = useCallback(() => { @@ -150,14 +152,16 @@ const DisplayZone: React.FC = ({ async function handleSelect2dZoneData(zoneId: string, zoneName: string) { 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); + console.log('response: ', response); let res = await getFloatingZoneData(zoneId, organization); + setFloatingWidget(res) // Set the selected zone in the store useDroppedObjectsStore.getState().setZone(zoneName, zoneId); if (Array.isArray(res)) { @@ -177,8 +181,8 @@ const DisplayZone: React.FC = ({ zoneViewPortPosition: response.viewPortposition || {}, }); } catch (error) { - console.log('error: ', error); - + + } } @@ -186,9 +190,8 @@ const DisplayZone: React.FC = ({ return (
{/* Left Arrow */} {showLeftArrow && ( diff --git a/app/src/components/ui/componets/DistanceLine.tsx b/app/src/components/ui/componets/DistanceLine.tsx new file mode 100644 index 0000000..8dec7b1 --- /dev/null +++ b/app/src/components/ui/componets/DistanceLine.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +interface DistanceLinesProps { + obj: { + position: { + top?: number | "auto"; + left?: number | "auto"; + right?: number | "auto"; + bottom?: number | "auto"; + }; + }; + activeEdges: { + vertical: "top" | "bottom"; + horizontal: "left" | "right"; + } | null; +} + +const DistanceLines: React.FC = ({ obj, activeEdges }) => { + if (!activeEdges) return null; + + return ( + <> + {activeEdges.vertical === "top" && typeof obj.position.top === "number" && ( +
+ {obj.position.top.toFixed()}px +
+ )} + + {activeEdges.vertical === "bottom" && + typeof obj.position.bottom === "number" && ( +
+ {obj.position.bottom.toFixed()}px +
+ )} + + {activeEdges.horizontal === "left" && + typeof obj.position.left === "number" && ( +
+ {obj.position.left.toFixed()}px +
+ )} + + {activeEdges.horizontal === "right" && + typeof obj.position.right === "number" && ( +
+ {obj.position.right.toFixed()}px +
+ )} + + ); +}; + +export default DistanceLines; \ No newline at end of file diff --git a/app/src/components/ui/componets/Dropped3dWidget.tsx b/app/src/components/ui/componets/Dropped3dWidget.tsx index 10dd343..c627af4 100644 --- a/app/src/components/ui/componets/Dropped3dWidget.tsx +++ b/app/src/components/ui/componets/Dropped3dWidget.tsx @@ -10,79 +10,139 @@ 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"; export default function Dropped3dWidgets() { - const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); + const { widgetSelect } = 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 // Widget type -> Positions array - >>({}); - + const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); + const { selectedZone } = useSelectedZoneStore(); // Get the currently active zone + // 🔥 Store widget data (id, type, position) based on the selected zone + const [zoneWidgetData, setZoneWidgetData] = useState< + Record + >({}); + const { setWidgets3D } = use3DWidget() useEffect(() => { - 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 (activeModule !== "visualization") return + if (selectedZone.zoneName === "") return; - setZoneWidgets((prev) => ({ - ...prev, - [selectedZone.zoneId]: { - ...(prev[selectedZone.zoneId] || {}), - [widgetSelect]: [...(prev[selectedZone.zoneId]?.[widgetSelect] || []), [x, y, z]], - }, - })); - } + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + async function get3dWidgetData() { + let result = await get3dWidgetZoneData(selectedZone.zoneId, organization); + setWidgets3D(result) + // Ensure the extracted data has id, type, and position correctly mapped + const formattedWidgets = result.map((widget: any) => ({ + id: widget.id, + type: widget.type, + position: widget.position, + })); + + setZoneWidgetData((prev) => ({ + ...prev, + [selectedZone.zoneId]: formattedWidgets, + })); + } + + get3dWidgetData(); + + }, [selectedZone.zoneId,activeModule]); + // useEffect(() => { + // // ✅ Set data only for the selected zone, keeping existing state structure + // setZoneWidgetData((prev) => ({ + // ...prev, + // [selectedZone.zoneId]: [ + // { + // "id": "1743322674626-50mucpb1c", + // "type": "ui-Widget 1", + // "position": [120.94655021768133, 4.142360029666558, 124.39283546121099] + // }, + // { + // "id": "1743322682086-je2h9x33v", + // "type": "ui-Widget 2", + // "position": [131.28751045879255, 0.009999999999970264, 133.92059801984362] + // } + // ] + // })); + // }, [selectedZone.zoneId]); // ✅ Only update when the zone changes + + useEffect(() => { + if (activeModule !== "visualization") return; + if (widgetSubOption === "Floating") return; + if (selectedZone.zoneName === "") return + const canvasElement = gl.domElement; + const onDrop = async (event: DragEvent) => { + event.preventDefault(); // Prevent default browser behavior + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + if (!widgetSelect.startsWith("ui")) return; + const group1 = scene.getObjectByName("itemsGroup"); + if (!group1) return; + 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; + + // ✅ Explicitly define position as a tuple + const newWidget: { id: string; type: string; position: [number, number, number] } = { + id: generateUniqueId(), + type: widgetSelect, + position: [x, y, z], // Ensures TypeScript recognizes it as a tuple + }; + + + let response = await adding3dWidgets(selectedZone.zoneId, organization, newWidget) + + + // ✅ Store widgets uniquely for each zone + setZoneWidgetData((prev) => ({ + ...prev, + [selectedZone.zoneId]: [...(prev[selectedZone.zoneId] || []), newWidget], + })); } }; + canvasElement.addEventListener("drop", onDrop); return () => { - canvasElement.removeEventListener("drop", onDrop) - // setWidgetSelect() + canvasElement.removeEventListener("drop", onDrop); }; - }, [widgetSelect, activeModule, widgetSubOption]); + }, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption]); + + // Get widgets for the currently active zone + const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || []; return ( <> - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 1"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 2"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 3"]?.map((pos, index) => ( - - ))} - {zoneWidgets[selectedZone.zoneId]?.["ui-Widget 4"]?.map((pos, index) => ( - - ))} + {activeZoneWidgets.map(({ id, type, position }) => { + switch (type) { + case "ui-Widget 1": + return ; + case "ui-Widget 2": + return ; + case "ui-Widget 3": + return ; + case "ui-Widget 4": + return ; + default: + return null; + } + })} ); } + diff --git a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx index 272051c..5526902 100644 --- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx +++ b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx @@ -1,3 +1,4 @@ + import { WalletIcon } from "../../icons/3dChartIcons"; import { useEffect, useRef, useState } from "react"; import { @@ -14,6 +15,7 @@ import { DeleteIcon, } from "../../icons/ExportCommonIcons"; import DistanceLines from "./DistanceLines"; // Import the DistanceLines component +import { deleteFloatingWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteFloatingWidget"; interface DraggingState { zone: string; @@ -26,12 +28,25 @@ interface DraggingState { }; } +interface DraggingState { + zone: string; + index: number; + initialPosition: { + top: number | "auto"; + left: number | "auto"; + right: number | "auto"; + bottom: number | "auto"; + }; +} const DroppedObjects: React.FC = () => { const zones = useDroppedObjectsStore((state) => state.zones); const [openKebabId, setOpenKebabId] = useState(null); const updateObjectPosition = useDroppedObjectsStore( (state) => state.updateObjectPosition ); + const deleteObject = useDroppedObjectsStore((state) => state.deleteObject); + + const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject); const [draggingIndex, setDraggingIndex] = useState( null ); @@ -62,6 +77,28 @@ const DroppedObjects: React.FC = () => { if (zoneEntries.length === 0) return null; const [zoneName, zone] = zoneEntries[0]; + function handleDuplicate(zoneName: string, index: number) { + setOpenKebabId(null) + duplicateObject(zoneName, index); // Call the duplicateObject method from the store + } + + async function handleDelete(zoneName: string, id: string, index: number) { + try { + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + let res = await deleteFloatingWidgetApi(id, organization); + console.log('res: ', res); + + if (res.message === "FloatingWidget deleted successfully") { + deleteObject(zoneName, index); // Call the deleteObject method from the store + } + } catch (error) { + console.error("Error deleting floating widget:", error); + } + } + + const handlePointerDown = (event: React.PointerEvent, index: number) => { const obj = zone.objects[index]; const container = document.getElementById("real-time-vis-canvas"); @@ -228,11 +265,12 @@ const DroppedObjects: React.FC = () => { animationRef.current = null; } } catch (error) { - console.error("Error in handlePointerUp:", error); + } }; const handleKebabClick = (id: string, event: React.MouseEvent) => { + event.stopPropagation(); setOpenKebabId((prevId) => (prevId === id ? null : id)); }; @@ -337,13 +375,19 @@ const DroppedObjects: React.FC = () => {
{openKebabId === obj.id && (
-
+
{ + event.stopPropagation(); + handleDuplicate(zoneName, index); // Call the duplicate handler + }}>
Duplicate
-
+
{ + event.stopPropagation(); + handleDelete(zoneName, obj.id, index); // Call the delete handler + }}>
@@ -388,4 +432,4 @@ const DroppedObjects: React.FC = () => { export default DroppedObjects; -// in pointer move even when i goes to top right the value not changes to top right same problem in bottom left + diff --git a/app/src/components/ui/componets/RealTimeVisulization.tsx b/app/src/components/ui/componets/RealTimeVisulization.tsx index 45db987..3f81518 100644 --- a/app/src/components/ui/componets/RealTimeVisulization.tsx +++ b/app/src/components/ui/componets/RealTimeVisulization.tsx @@ -63,6 +63,7 @@ const RealTimeVisulization: React.FC = () => { const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); + if (!Array.isArray(response)) { return; } @@ -83,7 +84,7 @@ const RealTimeVisulization: React.FC = () => { ); setZonesData(formattedData); } catch (error) { - console.log("error: ", error); + } } @@ -109,7 +110,7 @@ const RealTimeVisulization: React.FC = () => { }); }, [selectedZone]); - useEffect(() => {}, [floatingWidgets]); + // useEffect(() => {}, [floatingWidgets]); const handleDrop = async (event: React.DragEvent) => { try { @@ -135,6 +136,7 @@ const RealTimeVisulization: React.FC = () => { id: generateUniqueId(), position: determinePosition(canvasRect, relativeX, relativeY), }; + console.log('newObject: ', newObject); let response = await addingFloatingWidgets( selectedZone.zoneId, @@ -150,7 +152,6 @@ const RealTimeVisulization: React.FC = () => { .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 @@ -171,7 +172,7 @@ const RealTimeVisulization: React.FC = () => { ], }, })); - } catch (error) {} + } catch (error) { } }; return ( @@ -198,7 +199,7 @@ const RealTimeVisulization: React.FC = () => { >
- + {activeModule === "visualization" && selectedZone.zoneName !== "" && } {activeModule === "visualization" && ( <> { +// const { getTemplate } = useTemplateStore.getState(); +// const { setSelectedZone } = useSelectedZoneStore.getState(); + +// // Find the template by ID +// const template: Template | undefined = getTemplate(templateId); + +// if (!template) { +// console.error("Template not found!"); +// return; +// } + +// // Update the selected zone with the template data +// setSelectedZone((prev) => ({ +// ...prev, +// panelOrder: template.panelOrder, +// activeSides: Array.from(new Set([...prev.activeSides, ...template.panelOrder])), +// widgets: template.widgets, // Keep widget structure the same +// })); + +// console.log("Dropped template applied:", template); +// }; diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 10934fb..0682f62 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -14,7 +14,6 @@ function Simulation() { const [processes, setProcesses] = useState([]); useEffect(() => { - console.log('simulationPaths: ', simulationPaths); }, [simulationPaths]); // useEffect(() => { diff --git a/app/src/modules/visualization/handleSaveTemplate.ts b/app/src/modules/visualization/handleSaveTemplate.ts index c489688..a71e654 100644 --- a/app/src/modules/visualization/handleSaveTemplate.ts +++ b/app/src/modules/visualization/handleSaveTemplate.ts @@ -1,74 +1,89 @@ +import { saveTemplateApi } from "../../services/realTimeVisulization/zoneData/saveTempleteApi"; 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[]; // Adjust the type based on actual data structure - widgets: any[]; // Replace `any` with the actual widget type + panelOrder: string[]; + widgets: any[]; }; templates?: Template[]; }; -// Generate a unique ID (placeholder function) +// Generate a unique ID const generateUniqueId = (): string => { return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; }; -// Refactored function export const handleSaveTemplate = async ({ addTemplate, + floatingWidget, + widgets3D, selectedZone, templates = [], }: HandleSaveTemplateProps): Promise => { try { // Check if the selected zone has any widgets if (!selectedZone.widgets || selectedZone.widgets.length === 0) { - console.warn("Cannot save an empty template."); + console.warn("No widgets found in the selected zone."); return; } // Check if the template already exists - const isDuplicate = templates.some((template) => { - const isSamePanelOrder = + const isDuplicate = templates.some( + (template) => JSON.stringify(template.panelOrder) === - JSON.stringify(selectedZone.panelOrder); - const isSameWidgets = + JSON.stringify(selectedZone.panelOrder) && JSON.stringify(template.widgets) === - JSON.stringify(selectedZone.widgets); - return isSamePanelOrder && isSameWidgets; - }); + JSON.stringify(selectedZone.widgets) + ); if (isDuplicate) { - console.warn("This template already exists."); return; } // Capture visualization snapshot const snapshot = await captureVisualization(); - if (!snapshot) { - console.error("Failed to capture visualization snapshot."); - return; - } - + console.log("snapshot: ", snapshot); + // if (!snapshot) { + // return; + // } // Create a new template const newTemplate: Template = { id: generateUniqueId(), - name: `Template ${Date.now()}`, + name: `Template ${new Date().toISOString()}`, // Better name formatting panelOrder: selectedZone.panelOrder, widgets: selectedZone.widgets, snapshot, + floatingWidget, + widgets3D, }; - console.log("Saving template:", newTemplate); + // Extract organization from email + const email = localStorage.getItem("email") || ""; + const organization = email.includes("@") + ? email.split("@")[1]?.split(".")[0] + : ""; + + if (!organization) { + console.error("Organization could not be determined from email."); + return; + } // Save the template try { + const response = await saveTemplateApi(organization, newTemplate); + console.log("Save API Response:", response); + + // Add template only if API call succeeds addTemplate(newTemplate); - } catch (error) { - console.error("Failed to add template:", error); + } catch (apiError) { + console.error("Error saving template to API:", apiError); } } catch (error) { - console.error("Failed to save template:", error); + console.error("Error in handleSaveTemplate:", error); } }; diff --git a/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts new file mode 100644 index 0000000..82562b7 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/add3dWidget.ts @@ -0,0 +1,36 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const adding3dWidgets = async ( + zoneId: string, + organization: string, + widget: {} +) => { + console.log('widget: ', widget); + console.log('organization: ', organization); + console.log('zoneId: ', zoneId); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/3dwidget/save`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, widget }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts index 338f26b..d73ec0d 100644 --- a/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts +++ b/app/src/services/realTimeVisulization/zoneData/addFloatingWidgets.ts @@ -1,5 +1,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; // let url_Backend_dwinzo = `http://192.168.0.102:5000`; + export const addingFloatingWidgets = async ( zoneId: string, organization: string, diff --git a/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts b/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts new file mode 100644 index 0000000..85c96b8 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/deleteFloatingWidget.ts @@ -0,0 +1,35 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const deleteFloatingWidgetApi = async ( + floatWidgetID: string, + organization: string +) => { + console.log('organization: ', organization); + console.log('floatWidgetID: ', floatWidgetID); + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/floatwidget/delete`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, floatWidgetID }), + } + ); + + if (!response.ok) { + throw new Error("Failed to delete floating widget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts b/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts new file mode 100644 index 0000000..e452f6d --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/deleteTemplate.ts @@ -0,0 +1,32 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const deleteTemplateApi = async ( + templateID: string, + organization?: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/TemplateDelete/${templateID}/${organization}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error("Failed to delete template "); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts b/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts new file mode 100644 index 0000000..b5b6200 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/get3dWidgetData.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const get3dWidgetZoneData = async ( + ZoneId?: string, + organization?: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/3dwidgetData/${ZoneId}/${organization}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { + throw new Error("Failed to fetch Zone3dWidgetData"); + } + + return await response.json(); + } catch (error: any) { + throw new Error(error.message); + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts b/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts index 26a88c5..00d4dfe 100644 --- a/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts +++ b/app/src/services/realTimeVisulization/zoneData/getSelect2dZoneData.ts @@ -1,4 +1,5 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getSelect2dZoneData = async ( ZoneId?: string, diff --git a/app/src/services/realTimeVisulization/zoneData/getTemplate.ts b/app/src/services/realTimeVisulization/zoneData/getTemplate.ts new file mode 100644 index 0000000..a3aa3a3 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/getTemplate.ts @@ -0,0 +1,23 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const getTemplateData = async (organization?: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/templateData/${organization}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error("Failed to fetch ZoneFloatingData"); + } + + return await response.json(); + } catch (error: any) { + throw new Error(error.message); + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/getZones.ts b/app/src/services/realTimeVisulization/zoneData/getZones.ts index a760959..8dbf79a 100644 --- a/app/src/services/realTimeVisulization/zoneData/getZones.ts +++ b/app/src/services/realTimeVisulization/zoneData/getZones.ts @@ -1,4 +1,5 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; export const getZoneData = async (zoneId: string, organization: string) => { console.log("organization: ", organization); @@ -14,7 +15,6 @@ export const getZoneData = async (zoneId: string, organization: string) => { } ); - if (!response.ok) { throw new Error("Failed to fetch zoneData"); } diff --git a/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts b/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts new file mode 100644 index 0000000..915160d --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/loadTemplate.ts @@ -0,0 +1,33 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const loadTempleteApi = async ( + templateID: string, + zoneId: string, + organization: string +) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/v2/TemplatetoZone`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, zoneId, templateID }), + } + ); + + if (!response.ok) { + throw new Error("Failed to add 3dwidget in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts new file mode 100644 index 0000000..5c18031 --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/saveTempleteApi.ts @@ -0,0 +1,28 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; +export const saveTemplateApi = async (organization: string, template: {}) => { + console.log('template: ', template); + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/template/save`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ organization, template }), + }); + + if (!response.ok) { + throw new Error("Failed to save template zone"); + } + + const result = await response.json(); + console.log('result: ', result); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/store/store.ts b/app/src/store/store.ts index a64f417..f24da0c 100644 --- a/app/src/store/store.ts +++ b/app/src/store/store.ts @@ -375,3 +375,4 @@ export const useWidgetSubOption = create((set: any) => ({ widgetSubOption: "2D", setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), })); + diff --git a/app/src/store/useDroppedObjectsStore.ts b/app/src/store/useDroppedObjectsStore.ts index 5ded701..83f3a8a 100644 --- a/app/src/store/useDroppedObjectsStore.ts +++ b/app/src/store/useDroppedObjectsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { addingFloatingWidgets } from "../services/realTimeVisulization/zoneData/addFloatingWidgets"; type DroppedObject = { className: string; @@ -35,6 +36,8 @@ type DroppedObjectsState = { bottom: number | "auto"; } ) => void; + deleteObject: (zoneName: string, index: number) => void; // Add this line + duplicateObject: (zoneName: string, index: number) => void; // Add this line }; export const useDroppedObjectsStore = create((set) => ({ @@ -73,6 +76,72 @@ export const useDroppedObjectsStore = create((set) => ({ }, }; }), + + deleteObject: (zoneName: string, index: number) => + set((state) => { + const zone = state.zones[zoneName]; + if (!zone) return state; + return { + zones: { + [zoneName]: { + ...zone, + objects: zone.objects.filter((_, i) => i !== index), // Remove object at the given index + }, + }, + }; + }), + duplicateObject: async (zoneName: string, index: number) => { + const state = useDroppedObjectsStore.getState(); // Get the current state + const zone = state.zones[zoneName]; + + if (!zone) return; + + const originalObject = zone.objects[index]; + if (!originalObject) return; + + const email = localStorage.getItem("email") || ""; + const organization = email?.split("@")[1]?.split(".")[0]; + + // Create a shallow copy of the object with a unique ID and slightly adjusted position + const duplicatedObject: DroppedObject = { + ...originalObject, + id: `${originalObject.id}-copy-${Date.now()}`, // Unique ID + position: { + ...originalObject.position, + top: + typeof originalObject.position.top === "number" + ? originalObject.position.top + 20 // Offset vertically + : originalObject.position.top, + left: + typeof originalObject.position.left === "number" + ? originalObject.position.left + 20 // Offset horizontally + : originalObject.position.left, + }, + }; + + console.log("zone: ", zone.zoneId); + console.log("duplicatedObject: ", duplicatedObject); + + // Make async API call outside of Zustand set function + // let response = await addingFloatingWidgets( + // zone.zoneId, + // organization, + // duplicatedObject + // ); + + // if (response.message === "FloatWidget created successfully") { + // Update the state inside `set` + useDroppedObjectsStore.setState((state) => ({ + zones: { + ...state.zones, + [zoneName]: { + ...state.zones[zoneName], + objects: [...state.zones[zoneName].objects, duplicatedObject], // Append duplicated object + }, + }, + })); + // } + }, })); export interface DroppedObjects { @@ -91,4 +160,12 @@ export interface Zones { objects: DroppedObject[]; } +export const use3DWidget = create((set: any) => ({ + widgets3D: [], + setWidgets3D: (x: any) => set({ widgets3D: x }), +})); +export const useFloatingWidget = create((set: any) => ({ + floatingWidget: [], + setFloatingWidget: (x: any) => set({ floatingWidget: x }), +})); diff --git a/app/src/store/useTemplateStore.ts b/app/src/store/useTemplateStore.ts index 2adcd2f..d416154 100644 --- a/app/src/store/useTemplateStore.ts +++ b/app/src/store/useTemplateStore.ts @@ -1,7 +1,5 @@ import { create } from "zustand"; -// type Side = "top" | "bottom" | "left" | "right"; - export interface Widget { id: string; type: string; @@ -15,21 +13,34 @@ export interface Template { name: string; panelOrder: string[]; widgets: Widget[]; - snapshot?: string | null; // Add an optional image property (base64) + floatingWidget: any[]; // Fixed empty array type + widgets3D: any[]; // Fixed empty array type + snapshot?: string | null; } interface TemplateStore { templates: Template[]; addTemplate: (template: Template) => void; + setTemplates: (templates: Template[]) => void; // Changed from `setTemplate` removeTemplate: (id: string) => void; } export const useTemplateStore = create((set) => ({ templates: [], + + // Add a new template to the list addTemplate: (template) => set((state) => ({ templates: [...state.templates, template], })), + + // Set (replace) the templates list with a new array + setTemplates: (templates) => + set(() => ({ + templates, // Ensures no duplication + })), + + // Remove a template by ID removeTemplate: (id) => set((state) => ({ templates: state.templates.filter((t) => t.id !== id), @@ -37,3 +48,4 @@ export const useTemplateStore = create((set) => ({ })); export default useTemplateStore; + diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index b080840..5df1bf7 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -543,8 +543,6 @@ color: white; } - - .floating-wrapper { .icon { width: 25px !important; @@ -612,9 +610,6 @@ .dublicate { cursor: not-allowed; } - - - } } @@ -622,12 +617,6 @@ - - - - - -/* General styles for all distance lines */ .distance-line { position: absolute; border-style: dashed;