3d widget api added and template frontend and backend completed

This commit is contained in:
Gomathi 2025-03-31 19:20:03 +05:30
parent 9611ad69cf
commit 6b8ccc02c7
27 changed files with 790 additions and 129 deletions

View File

@ -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) => {
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,
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([...prev.activeSides, ...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 (
<div
className="template-list"
@ -41,7 +92,7 @@ const Templates = () => {
transition: "box-shadow 0.3s ease",
}}
>
{template.snapshot && (
{template?.snapshot && (
<div style={{ position: "relative", paddingBottom: "56.25%" }}>
{" "}
{/* 16:9 aspect ratio */}
@ -122,3 +173,4 @@ const Templates = () => {
};
export default Templates;

View File

@ -30,7 +30,6 @@ const ZoneProperties: React.FC = () => {
let response = await zoneCameraUpdate(zonesdata, organization);
console.log('response: ', response);
setEdit(false);
} catch (error) {
console.error("Error in handleSetView:", error);

View File

@ -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 = () => {
<div className="draw-tools">
<div
className={`tool-button`}
onClick={() =>
onClick={() => {
handleSaveTemplate({
addTemplate,
floatingWidget,
widgets3D,
selectedZone,
templates,
})
}
}
>
<SaveTemplateIcon isActive={false} />
</div>

View File

@ -119,7 +119,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
};
// Delete the selectedZone state
setSelectedZone(updatedZone);
} else {
const updatePanelData = async () => {

View File

@ -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<DisplayZoneProps> = ({
// 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<DisplayZoneProps> = ({
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,7 +181,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
zoneViewPortPosition: response.viewPortposition || {},
});
} catch (error) {
console.log('error: ', error);
}
}
@ -186,8 +190,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
return (
<div
ref={containerRef}
className={`zone-wrapper ${
selectedZone?.activeSides?.includes("bottom") && "bottom"
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") && "bottom"
}`}
>
{/* Left Arrow */}

View File

@ -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<DistanceLinesProps> = ({ obj, activeEdges }) => {
if (!activeEdges) return null;
return (
<>
{activeEdges.vertical === "top" && typeof obj.position.top === "number" && (
<div
className="distance-line top"
style={{
top: 0,
left:
activeEdges.horizontal === "left"
? `${(obj.position.left as number) + 125}px`
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
height: `${obj.position.top}px`,
}}
>
<span className="distance-label">{obj.position.top.toFixed()}px</span>
</div>
)}
{activeEdges.vertical === "bottom" &&
typeof obj.position.bottom === "number" && (
<div
className="distance-line bottom"
style={{
bottom: 0,
left:
activeEdges.horizontal === "left"
? `${(obj.position.left as number) + 125}px`
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
height: `${obj.position.bottom}px`,
}}
>
<span className="distance-label">{obj.position.bottom.toFixed()}px</span>
</div>
)}
{activeEdges.horizontal === "left" &&
typeof obj.position.left === "number" && (
<div
className="distance-line left"
style={{
left: 0,
top:
activeEdges.vertical === "top"
? `${(obj.position.top as number) + 41.5}px`
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
width: `${obj.position.left}px`,
}}
>
<span className="distance-label">{obj.position.left.toFixed()}px</span>
</div>
)}
{activeEdges.horizontal === "right" &&
typeof obj.position.right === "number" && (
<div
className="distance-line right"
style={{
right: 0,
top:
activeEdges.vertical === "top"
? `${(obj.position.top as number) + 41.5}px`
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
width: `${obj.position.right}px`,
}}
>
<span className="distance-label">{obj.position.right.toFixed()}px</span>
</div>
)}
</>
);
};
export default DistanceLines;

View File

@ -10,34 +10,80 @@ 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<Record<
string, // Zone ID
Record<string, [number, number, number][]> // 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<string, { id: string; type: string; position: [number, number, number] }[]>
>({});
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 (activeModule !== "visualization") return
if (selectedZone.zoneName === "") return;
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
if (!widgetSelect?.startsWith("ui")) 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 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") &&
@ -49,40 +95,54 @@ export default function Dropped3dWidgets() {
if (intersects.length > 0) {
const { x, y, z } = intersects[0].point;
setZoneWidgets((prev) => ({
// ✅ 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] || {}),
[widgetSelect]: [...(prev[selectedZone.zoneId]?.[widgetSelect] || []), [x, y, z]],
},
[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) => (
<ProductionCapacity key={`Widget1-${index}`} position={pos} />
))}
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 2"]?.map((pos, index) => (
<ReturnOfInvestment key={`Widget2-${index}`} position={pos} />
))}
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 3"]?.map((pos, index) => (
<StateWorking key={`Widget3-${index}`} position={pos} />
))}
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 4"]?.map((pos, index) => (
<Throughput key={`Widget4-${index}`} position={pos} />
))}
{activeZoneWidgets.map(({ id, type, position }) => {
switch (type) {
case "ui-Widget 1":
return <ProductionCapacity key={id} position={position} />;
case "ui-Widget 2":
return <ReturnOfInvestment key={id} position={position} />;
case "ui-Widget 3":
return <StateWorking key={id} position={position} />;
case "ui-Widget 4":
return <Throughput key={id} position={position} />;
default:
return null;
}
})}
</>
);
}

View File

@ -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<string | null>(null);
const updateObjectPosition = useDroppedObjectsStore(
(state) => state.updateObjectPosition
);
const deleteObject = useDroppedObjectsStore((state) => state.deleteObject);
const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject);
const [draggingIndex, setDraggingIndex] = useState<DraggingState | null>(
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 = () => {
</div>
{openKebabId === obj.id && (
<div className="kebab-options">
<div className="dublicate btn">
<div className="dublicate btn" onClick={(event) => {
event.stopPropagation();
handleDuplicate(zoneName, index); // Call the duplicate handler
}}>
<div className="icon">
<DublicateIcon />
</div>
<div className="label">Duplicate</div>
</div>
<div className="edit btn">
<div className="edit btn" onClick={(event) => {
event.stopPropagation();
handleDelete(zoneName, obj.id, index); // Call the delete handler
}}>
<div className="icon">
<DeleteIcon />
</div>
@ -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

View File

@ -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<HTMLDivElement>) => {
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 = () => {
>
<Scene />
</div>
<DroppedObjects />
{activeModule === "visualization" && selectedZone.zoneName !== "" && <DroppedObjects />}
{activeModule === "visualization" && (
<>
<DisplayZone

View File

@ -1,3 +1,70 @@
// 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;
// }
export function determinePosition(
canvasRect: DOMRect,
relativeX: number,

View File

@ -0,0 +1,29 @@
// import { useSelectedZoneStore } from "../../../store/useZoneStore";
// type HandleDropTemplateProps = {
// templateId: string;
// };
// export const handleDropTemplate = ({ templateId }: HandleDropTemplateProps): void => {
// 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);
// };

View File

@ -14,7 +14,6 @@ function Simulation() {
const [processes, setProcesses] = useState([]);
useEffect(() => {
console.log('simulationPaths: ', simulationPaths);
}, [simulationPaths]);
// useEffect(() => {

View File

@ -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<void> => {
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);
}
};

View File

@ -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");
}
}
};

View File

@ -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,

View File

@ -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");
}
}
};

View File

@ -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");
}
}
};

View File

@ -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);
}
};

View File

@ -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,

View File

@ -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);
}
};

View File

@ -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");
}

View File

@ -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");
}
}
};

View File

@ -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");
}
}
};

View File

@ -375,3 +375,4 @@ export const useWidgetSubOption = create<any>((set: any) => ({
widgetSubOption: "2D",
setWidgetSubOption: (x: any) => set({ widgetSubOption: x }),
}));

View File

@ -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<DroppedObjectsState>((set) => ({
@ -73,6 +76,72 @@ export const useDroppedObjectsStore = create<DroppedObjectsState>((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<any>((set: any) => ({
widgets3D: [],
setWidgets3D: (x: any) => set({ widgets3D: x }),
}));
export const useFloatingWidget = create<any>((set: any) => ({
floatingWidget: [],
setFloatingWidget: (x: any) => set({ floatingWidget: x }),
}));

View File

@ -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<TemplateStore>((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<TemplateStore>((set) => ({
}));
export default useTemplateStore;

View File

@ -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;