Merge remote-tracking branch 'origin/rtViz' into simulation
This commit is contained in:
commit
29ea88a58a
|
@ -1,25 +1,76 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useDroppedObjectsStore } from "../../../../store/useDroppedObjectsStore";
|
||||||
import useTemplateStore from "../../../../store/useTemplateStore";
|
import useTemplateStore from "../../../../store/useTemplateStore";
|
||||||
import { useSelectedZoneStore } from "../../../../store/useZoneStore";
|
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 = () => {
|
||||||
const { templates, removeTemplate } = useTemplateStore();
|
const { templates, removeTemplate } = useTemplateStore();
|
||||||
const { setSelectedZone } = useSelectedZoneStore();
|
const { setTemplates } = useTemplateStore();
|
||||||
|
const { setSelectedZone, selectedZone } = useSelectedZoneStore();
|
||||||
|
|
||||||
const handleDeleteTemplate = (id: string) => {
|
useEffect(() => {
|
||||||
removeTemplate(id);
|
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) => {
|
const handleLoadTemplate = async (template: any) => {
|
||||||
setSelectedZone((prev) => ({
|
try {
|
||||||
...prev,
|
if (selectedZone.zoneName === "") return;
|
||||||
panelOrder: template.panelOrder,
|
|
||||||
activeSides: Array.from(
|
const email = localStorage.getItem("email") || "";
|
||||||
new Set([...prev.activeSides, ...template.panelOrder])
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
),
|
|
||||||
widgets: template.widgets,
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className="template-list"
|
className="template-list"
|
||||||
|
@ -41,7 +92,7 @@ const Templates = () => {
|
||||||
transition: "box-shadow 0.3s ease",
|
transition: "box-shadow 0.3s ease",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{template.snapshot && (
|
{template?.snapshot && (
|
||||||
<div style={{ position: "relative", paddingBottom: "56.25%" }}>
|
<div style={{ position: "relative", paddingBottom: "56.25%" }}>
|
||||||
{" "}
|
{" "}
|
||||||
{/* 16:9 aspect ratio */}
|
{/* 16:9 aspect ratio */}
|
||||||
|
@ -122,3 +173,4 @@ const Templates = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Templates;
|
export default Templates;
|
||||||
|
|
||||||
|
|
|
@ -20,17 +20,16 @@ const ZoneProperties: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
const email = localStorage.getItem("email") || "";
|
const email = localStorage.getItem("email") || "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
let zonesdata = {
|
let zonesdata = {
|
||||||
zoneId: selectedZone.zoneId,
|
zoneId: selectedZone.zoneId,
|
||||||
viewPortposition: zonePosition,
|
viewPortposition: zonePosition,
|
||||||
viewPortCenter: zoneTarget
|
viewPortCenter: zoneTarget
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await zoneCameraUpdate(zonesdata, organization);
|
let response = await zoneCameraUpdate(zonesdata, organization);
|
||||||
console.log('response: ', response);
|
console.log('response: ', response);
|
||||||
|
|
||||||
|
|
||||||
setEdit(false);
|
setEdit(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in handleSetView:", error);
|
console.error("Error in handleSetView:", error);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
useTransformMode,
|
useTransformMode,
|
||||||
} from "../../store/store";
|
} from "../../store/store";
|
||||||
import useToggleStore from "../../store/useUIToggleStore";
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
|
import { use3DWidget, useFloatingWidget } from "../../store/useDroppedObjectsStore";
|
||||||
|
|
||||||
const Tools: React.FC = () => {
|
const Tools: React.FC = () => {
|
||||||
const { templates } = useTemplateStore();
|
const { templates } = useTemplateStore();
|
||||||
|
@ -46,6 +47,8 @@ const Tools: React.FC = () => {
|
||||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||||
const { addTemplate } = useTemplateStore();
|
const { addTemplate } = useTemplateStore();
|
||||||
const { selectedZone } = useSelectedZoneStore();
|
const { selectedZone } = useSelectedZoneStore();
|
||||||
|
const { floatingWidget } = useFloatingWidget()
|
||||||
|
const { widgets3D } = use3DWidget()
|
||||||
|
|
||||||
// wall options
|
// wall options
|
||||||
const { toggleView, setToggleView } = useToggleView();
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
@ -386,13 +389,17 @@ const Tools: React.FC = () => {
|
||||||
<div className="draw-tools">
|
<div className="draw-tools">
|
||||||
<div
|
<div
|
||||||
className={`tool-button`}
|
className={`tool-button`}
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
|
|
||||||
handleSaveTemplate({
|
handleSaveTemplate({
|
||||||
addTemplate,
|
addTemplate,
|
||||||
|
floatingWidget,
|
||||||
|
widgets3D,
|
||||||
selectedZone,
|
selectedZone,
|
||||||
templates,
|
templates,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SaveTemplateIcon isActive={false} />
|
<SaveTemplateIcon isActive={false} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -119,7 +119,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete the selectedZone state
|
// Delete the selectedZone state
|
||||||
|
|
||||||
setSelectedZone(updatedZone);
|
setSelectedZone(updatedZone);
|
||||||
} else {
|
} else {
|
||||||
const updatePanelData = async () => {
|
const updatePanelData = async () => {
|
||||||
|
|
|
@ -2,9 +2,10 @@ import React, { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { Widget } from "../../../store/useWidgetStore";
|
import { Widget } from "../../../store/useWidgetStore";
|
||||||
import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons";
|
import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons";
|
||||||
import { InfoIcon } from "../../icons/ExportCommonIcons";
|
import { InfoIcon } from "../../icons/ExportCommonIcons";
|
||||||
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
|
import { useDroppedObjectsStore, useFloatingWidget } from "../../../store/useDroppedObjectsStore";
|
||||||
import { getSelect2dZoneData } from "../../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
|
import { getSelect2dZoneData } from "../../../services/realTimeVisulization/zoneData/getSelect2dZoneData";
|
||||||
import { getFloatingZoneData } from "../../../services/realTimeVisulization/zoneData/getFloatingData";
|
import { getFloatingZoneData } from "../../../services/realTimeVisulization/zoneData/getFloatingData";
|
||||||
|
import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zoneData/get3dWidgetData";
|
||||||
|
|
||||||
// Define the type for `Side`
|
// Define the type for `Side`
|
||||||
type Side = "top" | "bottom" | "left" | "right";
|
type Side = "top" | "bottom" | "left" | "right";
|
||||||
|
@ -72,6 +73,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
// State to track overflow visibility
|
// State to track overflow visibility
|
||||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||||
const [showRightArrow, setShowRightArrow] = useState(false);
|
const [showRightArrow, setShowRightArrow] = useState(false);
|
||||||
|
const { floatingWidget, setFloatingWidget } = useFloatingWidget()
|
||||||
|
|
||||||
// Function to calculate overflow state
|
// Function to calculate overflow state
|
||||||
const updateOverflowState = useCallback(() => {
|
const updateOverflowState = useCallback(() => {
|
||||||
|
@ -150,14 +152,16 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
|
async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
|
||||||
try {
|
try {
|
||||||
if (selectedZone?.zoneId === zoneId) {
|
if (selectedZone?.zoneId === zoneId) {
|
||||||
console.log("Zone is already selected:", zoneName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const email = localStorage.getItem("email") || "";
|
const email = localStorage.getItem("email") || "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
// Fetch data from backend
|
// Fetch data from backend
|
||||||
let response = await getSelect2dZoneData(zoneId, organization);
|
let response = await getSelect2dZoneData(zoneId, organization);
|
||||||
|
console.log('response: ', response);
|
||||||
let res = await getFloatingZoneData(zoneId, organization);
|
let res = await getFloatingZoneData(zoneId, organization);
|
||||||
|
setFloatingWidget(res)
|
||||||
// Set the selected zone in the store
|
// Set the selected zone in the store
|
||||||
useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
|
useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
|
||||||
if (Array.isArray(res)) {
|
if (Array.isArray(res)) {
|
||||||
|
@ -177,8 +181,8 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
zoneViewPortPosition: response.viewPortposition || {},
|
zoneViewPortPosition: response.viewPortposition || {},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error: ', error);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +190,8 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={`zone-wrapper ${
|
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") && "bottom"
|
||||||
selectedZone?.activeSides?.includes("bottom") && "bottom"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{/* Left Arrow */}
|
{/* Left Arrow */}
|
||||||
{showLeftArrow && (
|
{showLeftArrow && (
|
||||||
|
|
|
@ -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;
|
|
@ -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}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}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}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}px</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DistanceLines;
|
|
@ -10,79 +10,138 @@ import ProductionCapacity from "../../layout/3D-cards/cards/ProductionCapacity";
|
||||||
import ReturnOfInvestment from "../../layout/3D-cards/cards/ReturnOfInvestment";
|
import ReturnOfInvestment from "../../layout/3D-cards/cards/ReturnOfInvestment";
|
||||||
import StateWorking from "../../layout/3D-cards/cards/StateWorking";
|
import StateWorking from "../../layout/3D-cards/cards/StateWorking";
|
||||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
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() {
|
export default function Dropped3dWidgets() {
|
||||||
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
|
const { widgetSelect } = useAsset3dWidget();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { raycaster, gl, scene }: ThreeState = useThree();
|
const { raycaster, gl, scene }: ThreeState = useThree();
|
||||||
const { selectedZone } = useSelectedZoneStore(); // Get currently selected zone
|
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption();
|
||||||
const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption()
|
const { selectedZone } = useSelectedZoneStore(); // Get the currently active zone
|
||||||
// 🔥 Store widget positions per zone
|
// 🔥 Store widget data (id, type, position) based on the selected zone
|
||||||
const [zoneWidgets, setZoneWidgets] = useState<Record<
|
const [zoneWidgetData, setZoneWidgetData] = useState<
|
||||||
string, // Zone ID
|
Record<string, { id: string; type: string; position: [number, number, number] }[]>
|
||||||
Record<string, [number, number, number][]> // Widget type -> Positions array
|
>({});
|
||||||
>>({});
|
const { setWidgets3D } = use3DWidget()
|
||||||
|
useEffect(() => {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (widgetSubOption === "Floating") return
|
if (activeModule !== "visualization") return;
|
||||||
// if (activeModule !== "visualization") return;
|
if (widgetSubOption === "Floating") return;
|
||||||
|
if (selectedZone.zoneName === "") return
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
const onDrop = (event: DragEvent) => {
|
const onDrop = async (event: DragEvent) => {
|
||||||
event.preventDefault(); // Prevent default browser behavior
|
event.preventDefault(); // Prevent default browser behavior
|
||||||
if (widgetSubOption === "3D") {
|
const email = localStorage.getItem("email") || "";
|
||||||
if (selectedZone.zoneName === "") return
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
if (!widgetSelect?.startsWith("ui")) return;
|
if (!widgetSelect.startsWith("ui")) return;
|
||||||
const group1 = scene.getObjectByName("itemsGroup");
|
const group1 = scene.getObjectByName("itemsGroup");
|
||||||
if (!group1) return;
|
if (!group1) return;
|
||||||
const Assets = group1.children
|
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
||||||
.map((val) => scene.getObjectByProperty("uuid", val.uuid))
|
(intersect) =>
|
||||||
.filter(Boolean) as THREE.Object3D[];
|
!intersect.object.name.includes("Roof") &&
|
||||||
const intersects = raycaster.intersectObjects(scene.children, true).filter(
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
(intersect) =>
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
!intersect.object.name.includes("Roof") &&
|
!intersect.object.userData.isPathObject &&
|
||||||
!intersect.object.name.includes("agv-collider") &&
|
!(intersect.object.type === "GridHelper")
|
||||||
!intersect.object.name.includes("MeasurementReference") &&
|
);
|
||||||
!intersect.object.userData.isPathObject &&
|
if (intersects.length > 0) {
|
||||||
!(intersect.object.type === "GridHelper")
|
const { x, y, z } = intersects[0].point;
|
||||||
);
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
const { x, y, z } = intersects[0].point;
|
|
||||||
|
|
||||||
setZoneWidgets((prev) => ({
|
// ✅ Explicitly define position as a tuple
|
||||||
...prev,
|
const newWidget: { id: string; type: string; position: [number, number, number] } = {
|
||||||
[selectedZone.zoneId]: {
|
id: generateUniqueId(),
|
||||||
...(prev[selectedZone.zoneId] || {}),
|
type: widgetSelect,
|
||||||
[widgetSelect]: [...(prev[selectedZone.zoneId]?.[widgetSelect] || []), [x, y, z]],
|
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);
|
canvasElement.addEventListener("drop", onDrop);
|
||||||
return () => {
|
return () => {
|
||||||
canvasElement.removeEventListener("drop", onDrop)
|
canvasElement.removeEventListener("drop", onDrop);
|
||||||
// setWidgetSelect()
|
|
||||||
};
|
};
|
||||||
}, [widgetSelect, activeModule, widgetSubOption]);
|
}, [widgetSelect, activeModule, selectedZone.zoneId, widgetSubOption]);
|
||||||
|
|
||||||
|
// Get widgets for the currently active zone
|
||||||
|
const activeZoneWidgets = zoneWidgetData[selectedZone.zoneId] || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 1"]?.map((pos, index) => (
|
{activeZoneWidgets.map(({ id, type, position }) => {
|
||||||
<ProductionCapacity key={`Widget1-${index}`} position={pos} />
|
switch (type) {
|
||||||
))}
|
case "ui-Widget 1":
|
||||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 2"]?.map((pos, index) => (
|
return <ProductionCapacity key={id} position={position} />;
|
||||||
<ReturnOfInvestment key={`Widget2-${index}`} position={pos} />
|
case "ui-Widget 2":
|
||||||
))}
|
return <ReturnOfInvestment key={id} position={position} />;
|
||||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 3"]?.map((pos, index) => (
|
case "ui-Widget 3":
|
||||||
<StateWorking key={`Widget3-${index}`} position={pos} />
|
return <StateWorking key={id} position={position} />;
|
||||||
))}
|
case "ui-Widget 4":
|
||||||
{zoneWidgets[selectedZone.zoneId]?.["ui-Widget 4"]?.map((pos, index) => (
|
return <Throughput key={id} position={position} />;
|
||||||
<Throughput key={`Widget4-${index}`} position={pos} />
|
default:
|
||||||
))}
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
import { WalletIcon } from "../../icons/3dChartIcons";
|
import { WalletIcon } from "../../icons/3dChartIcons";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Line } from "react-chartjs-2";
|
|
||||||
import {
|
import {
|
||||||
useDroppedObjectsStore,
|
useDroppedObjectsStore,
|
||||||
Zones,
|
Zones,
|
||||||
|
@ -9,21 +9,51 @@ import useModuleStore from "../../../store/useModuleStore";
|
||||||
import { determinePosition } from "./functions/determinePosition";
|
import { determinePosition } from "./functions/determinePosition";
|
||||||
import { getActiveProperties } from "./functions/getActiveProperties";
|
import { getActiveProperties } from "./functions/getActiveProperties";
|
||||||
import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
|
import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
|
||||||
|
import {
|
||||||
|
DublicateIcon,
|
||||||
|
KebabIcon,
|
||||||
|
DeleteIcon,
|
||||||
|
} from "../../icons/ExportCommonIcons";
|
||||||
|
import DistanceLines from "./DistanceLines"; // Import the DistanceLines component
|
||||||
|
import { deleteFloatingWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteFloatingWidget";
|
||||||
|
|
||||||
import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent";
|
import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent";
|
||||||
import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent";
|
import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent";
|
||||||
import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent";
|
import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent";
|
||||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||||
|
interface DraggingState {
|
||||||
|
zone: string;
|
||||||
|
index: number;
|
||||||
|
initialPosition: {
|
||||||
|
top: number | "auto";
|
||||||
|
left: number | "auto";
|
||||||
|
right: number | "auto";
|
||||||
|
bottom: number | "auto";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DraggingState {
|
||||||
|
zone: string;
|
||||||
|
index: number;
|
||||||
|
initialPosition: {
|
||||||
|
top: number | "auto";
|
||||||
|
left: number | "auto";
|
||||||
|
right: number | "auto";
|
||||||
|
bottom: number | "auto";
|
||||||
|
};
|
||||||
|
}
|
||||||
const DroppedObjects: React.FC = () => {
|
const DroppedObjects: React.FC = () => {
|
||||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
const zones = useDroppedObjectsStore((state) => state.zones);
|
||||||
|
const [openKebabId, setOpenKebabId] = useState<string | null>(null);
|
||||||
const updateObjectPosition = useDroppedObjectsStore(
|
const updateObjectPosition = useDroppedObjectsStore(
|
||||||
(state) => state.updateObjectPosition
|
(state) => state.updateObjectPosition
|
||||||
);
|
);
|
||||||
const [draggingIndex, setDraggingIndex] = useState<{
|
const deleteObject = useDroppedObjectsStore((state) => state.deleteObject);
|
||||||
zone: string;
|
|
||||||
index: number;
|
const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject);
|
||||||
} | null>(null);
|
const [draggingIndex, setDraggingIndex] = useState<DraggingState | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [offset, setOffset] = useState<[number, number] | null>(null);
|
const [offset, setOffset] = useState<[number, number] | null>(null);
|
||||||
const { setSelectedChartId } = useWidgetStore();
|
const { setSelectedChartId } = useWidgetStore();
|
||||||
const positionRef = useRef<[number, number] | null>(null);
|
const positionRef = useRef<[number, number] | null>(null);
|
||||||
|
@ -41,189 +71,401 @@ const DroppedObjects: React.FC = () => {
|
||||||
// Handle pointer down event
|
// Handle pointer down event
|
||||||
function handlePointerDown(event: React.PointerEvent, index: number) {
|
function handlePointerDown(event: React.PointerEvent, index: number) {
|
||||||
|
|
||||||
const obj = zone.objects[index];
|
const [activeEdges, setActiveEdges] = useState<{
|
||||||
const container = document.getElementById("real-time-vis-canvas");
|
vertical: "top" | "bottom";
|
||||||
if (!container) return;
|
horizontal: "left" | "right";
|
||||||
|
} | null>(null); // State to track active edges for distance lines
|
||||||
|
const [currentPosition, setCurrentPosition] = useState<{
|
||||||
|
top: number | "auto";
|
||||||
|
left: number | "auto";
|
||||||
|
right: number | "auto";
|
||||||
|
bottom: number | "auto";
|
||||||
|
} | null>(null); // State to track the current position during drag
|
||||||
|
const animationRef = useRef<number | null>(null);
|
||||||
|
const { activeModule } = useModuleStore();
|
||||||
|
|
||||||
const rect = container.getBoundingClientRect();
|
// Clean up animation frame on unmount
|
||||||
const relativeX = event.clientX - rect.left;
|
useEffect(() => {
|
||||||
const relativeY = event.clientY - rect.top;
|
return () => {
|
||||||
|
if (animationRef.current) {
|
||||||
// Determine which properties are active for this object
|
cancelAnimationFrame(animationRef.current);
|
||||||
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;
|
|
||||||
|
|
||||||
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 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, {
|
|
||||||
...obj.position,
|
|
||||||
[activeProp1]: positionRef.current[0],
|
|
||||||
[activeProp2]: positionRef.current[1],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
animationRef.current = null;
|
};
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
|
const zoneEntries = Object.entries(zones);
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointer up event
|
async function handleDelete(zoneName: string, id: string, index: number) {
|
||||||
async function handlePointerUp(event: React.MouseEvent<HTMLDivElement>) {
|
try {
|
||||||
try {
|
const email = localStorage.getItem("email") || "";
|
||||||
if (!draggingIndex || !offset) return;
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
const email = localStorage.getItem("email") || "";
|
let res = await deleteFloatingWidgetApi(id, organization);
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
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");
|
const container = document.getElementById("real-time-vis-canvas");
|
||||||
if (!container) throw new Error("Canvas container not found");
|
if (!container) return;
|
||||||
|
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
const relativeX = event.clientX - rect.left;
|
const relativeX = event.clientX - rect.left;
|
||||||
const relativeY = event.clientY - rect.top;
|
const relativeY = event.clientY - rect.top;
|
||||||
|
|
||||||
// Recalculate the position using determinePosition
|
// Determine active properties for the initial position
|
||||||
const newPosition = determinePosition(rect, relativeX, relativeY);
|
const [activeProp1, activeProp2] = getActiveProperties(obj.position);
|
||||||
|
|
||||||
// Validate the dragging index and get the object
|
// Set active edges for distance lines
|
||||||
if (!zone.objects[draggingIndex.index]) {
|
const vertical = activeProp1 === "top" ? "top" : "bottom";
|
||||||
throw new Error("Dragged object not found in the zone");
|
const horizontal = activeProp2 === "left" ? "left" : "right";
|
||||||
}
|
setActiveEdges({ vertical, horizontal });
|
||||||
const obj = { ...zone.objects[draggingIndex.index], position: newPosition };
|
|
||||||
let response = await addingFloatingWidgets(zone.zoneId, organization, obj);
|
// Store the initial position strategy and active edges
|
||||||
if (response.message === "Widget updated successfully") {
|
setDraggingIndex({
|
||||||
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
|
zone: zoneName,
|
||||||
|
index,
|
||||||
|
initialPosition: { ...obj.position },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate offset from mouse to object edges
|
||||||
|
let offsetX = 0;
|
||||||
|
let offsetY = 0;
|
||||||
|
|
||||||
|
if (activeProp1 === "top") {
|
||||||
|
offsetY = relativeY - (obj.position.top as number);
|
||||||
|
} else {
|
||||||
|
offsetY = rect.height - relativeY - (obj.position.bottom as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset states
|
if (activeProp2 === "left") {
|
||||||
setDraggingIndex(null);
|
offsetX = relativeX - (obj.position.left as number);
|
||||||
setOffset(null);
|
} else {
|
||||||
|
offsetX = rect.width - relativeX - (obj.position.right as number);
|
||||||
if (animationRef.current) {
|
|
||||||
cancelAnimationFrame(animationRef.current);
|
|
||||||
animationRef.current = null;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
setOffset([offsetY, offsetX]);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (event: React.PointerEvent) => {
|
||||||
|
if (!draggingIndex || !offset) return;
|
||||||
|
|
||||||
return (
|
const container = document.getElementById("real-time-vis-canvas");
|
||||||
<div onPointerMove={handlePointerMove} onPointerUp={handlePointerUp}>
|
if (!container) return;
|
||||||
{zone.objects.map((obj, index) => (
|
|
||||||
<div
|
const rect = container.getBoundingClientRect();
|
||||||
key={`${zoneName}-${index}`}
|
const relativeX = event.clientX - rect.left;
|
||||||
className={obj.className}
|
const relativeY = event.clientY - rect.top;
|
||||||
style={{
|
|
||||||
position: "absolute",
|
// Dynamically determine the current position strategy
|
||||||
top:
|
const newPositionStrategy = determinePosition(rect, relativeX, relativeY);
|
||||||
typeof obj.position.top !== "string"
|
const [activeProp1, activeProp2] = getActiveProperties(newPositionStrategy);
|
||||||
? `${obj.position.top}px`
|
|
||||||
: "auto",
|
// Update active edges for distance lines
|
||||||
left:
|
const vertical = activeProp1 === "top" ? "top" : "bottom";
|
||||||
typeof obj.position.left !== "string"
|
const horizontal = activeProp2 === "left" ? "left" : "right";
|
||||||
? `${obj.position.left}px`
|
setActiveEdges({ vertical, horizontal });
|
||||||
: "auto",
|
|
||||||
right:
|
// Calculate new position based on the active properties
|
||||||
typeof obj.position.right !== "string"
|
let newY = 0;
|
||||||
? `${obj.position.right}px`
|
let newX = 0;
|
||||||
: "auto",
|
|
||||||
bottom:
|
if (activeProp1 === "top") {
|
||||||
typeof obj.position.bottom !== "string"
|
newY = relativeY - offset[0];
|
||||||
? `${obj.position.bottom}px`
|
} else {
|
||||||
: "auto",
|
newY = rect.height - (relativeY + offset[0]);
|
||||||
// transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out",
|
}
|
||||||
}}
|
|
||||||
onPointerDown={(event) => handlePointerDown(event, index)}
|
if (activeProp2 === "left") {
|
||||||
onClick={() => {
|
newX = relativeX - offset[1];
|
||||||
setSelectedChartId(obj)
|
} else {
|
||||||
}}
|
newX = rect.width - (relativeX + offset[1]);
|
||||||
>
|
}
|
||||||
{obj.className === "floating total-card" ? (
|
|
||||||
|
// Apply boundaries
|
||||||
|
newX = Math.max(0, Math.min(rect.width - 50, newX));
|
||||||
|
newY = Math.max(0, Math.min(rect.height - 50, newY));
|
||||||
|
|
||||||
|
// Create new position object
|
||||||
|
const newPosition = {
|
||||||
|
...newPositionStrategy,
|
||||||
|
[activeProp1]: newY,
|
||||||
|
[activeProp2]: newX,
|
||||||
|
// Clear opposite properties
|
||||||
|
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
|
||||||
|
[activeProp2 === "left" ? "right" : "left"]: "auto",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the current position state for DistanceLines
|
||||||
|
setCurrentPosition(newPosition);
|
||||||
|
|
||||||
|
if (!animationRef.current) {
|
||||||
|
animationRef.current = requestAnimationFrame(() => {
|
||||||
|
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
|
||||||
|
animationRef.current = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerUp = async (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
|
try {
|
||||||
|
if (!draggingIndex || !offset) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Only now determine the final position strategy
|
||||||
|
const finalPosition = determinePosition(rect, relativeX, relativeY);
|
||||||
|
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
|
||||||
|
|
||||||
|
// Calculate final position using the new strategy
|
||||||
|
let finalY = 0;
|
||||||
|
let finalX = 0;
|
||||||
|
|
||||||
|
if (activeProp1 === "top") {
|
||||||
|
finalY = relativeY - offset[0];
|
||||||
|
} else {
|
||||||
|
finalY = rect.height - (relativeY + offset[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeProp2 === "left") {
|
||||||
|
finalX = relativeX - offset[1];
|
||||||
|
} else {
|
||||||
|
finalX = rect.width - (relativeX + offset[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply boundaries
|
||||||
|
finalX = Math.max(0, Math.min(rect.width - 50, finalX));
|
||||||
|
finalY = Math.max(0, Math.min(rect.height - 50, finalY));
|
||||||
|
|
||||||
|
const boundedPosition = {
|
||||||
|
...finalPosition,
|
||||||
|
[activeProp1]: finalY,
|
||||||
|
[activeProp2]: finalX,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save to backend
|
||||||
|
const email = localStorage.getItem("email") || "";
|
||||||
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
const response = await addingFloatingWidgets(zone.zoneId, organization, {
|
||||||
|
...zone.objects[draggingIndex.index],
|
||||||
|
position: boundedPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.message === "Widget updated successfully") {
|
||||||
|
updateObjectPosition(zoneName, draggingIndex.index, boundedPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
setDraggingIndex(null);
|
||||||
|
setOffset(null);
|
||||||
|
setActiveEdges(null); // Clear active edges
|
||||||
|
setCurrentPosition(null); // Reset current position
|
||||||
|
if (animationRef.current) {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderObjectContent = (obj: any) => {
|
||||||
|
switch (obj.className) {
|
||||||
|
case "floating total-card":
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<TotalCardComponent object={obj} />
|
<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>
|
||||||
</>
|
</>
|
||||||
) : obj.className === "warehouseThroughput floating" ? (
|
);
|
||||||
|
case "warehouseThroughput floating":
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<WarehouseThroughputComponent />
|
<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>
|
||||||
</>
|
</>
|
||||||
) : obj.className === "fleetEfficiency floating" ? (
|
);
|
||||||
|
case "fleetEfficiency floating":
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<FleetEfficiencyComponent object={obj} />
|
<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>
|
||||||
</>
|
</>
|
||||||
) : null}
|
);
|
||||||
</div>
|
default:
|
||||||
))}
|
return null;
|
||||||
</div>
|
}
|
||||||
);
|
};
|
||||||
};
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
onPointerUp={handlePointerUp}
|
||||||
|
className="floating-wrapper"
|
||||||
|
>
|
||||||
|
{zone.objects.map((obj, index) => (
|
||||||
|
<div
|
||||||
|
key={`${zoneName}-${index}`}
|
||||||
|
className={obj.className}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top:
|
||||||
|
typeof obj.position.top === "number"
|
||||||
|
? `${obj.position.top}px`
|
||||||
|
: "auto",
|
||||||
|
left:
|
||||||
|
typeof obj.position.left === "number"
|
||||||
|
? `${obj.position.left}px`
|
||||||
|
: "auto",
|
||||||
|
right:
|
||||||
|
typeof obj.position.right === "number"
|
||||||
|
? `${obj.position.right}px`
|
||||||
|
: "auto",
|
||||||
|
bottom:
|
||||||
|
typeof obj.position.bottom === "number"
|
||||||
|
? `${obj.position.bottom}px`
|
||||||
|
: "auto",
|
||||||
|
}}
|
||||||
|
onPointerDown={(event) => handlePointerDown(event, index)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedChartId(obj)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obj.className === "floating total-card" ? (
|
||||||
|
<>
|
||||||
|
<TotalCardComponent object={obj} />
|
||||||
|
</>
|
||||||
|
) : obj.className === "warehouseThroughput floating" ? (
|
||||||
|
<>
|
||||||
|
<WarehouseThroughputComponent />
|
||||||
|
</>
|
||||||
|
) : obj.className === "fleetEfficiency floating" ? (
|
||||||
|
<>
|
||||||
|
<FleetEfficiencyComponent object={obj} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
{renderObjectContent(obj)}
|
||||||
|
<div
|
||||||
|
className="icon kebab"
|
||||||
|
onClick={(event) => handleKebabClick(obj.id, event)}
|
||||||
|
>
|
||||||
|
<KebabIcon />
|
||||||
|
</div>
|
||||||
|
{openKebabId === obj.id && (
|
||||||
|
<div className="kebab-options">
|
||||||
|
<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" onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
handleDelete(zoneName, obj.id, index); // Call the delete handler
|
||||||
|
}}>
|
||||||
|
<div className="icon">
|
||||||
|
<DeleteIcon />
|
||||||
|
</div>
|
||||||
|
<div className="label">Delete</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Render DistanceLines component during drag */}
|
||||||
|
{draggingIndex !== null &&
|
||||||
|
activeEdges !== null &&
|
||||||
|
currentPosition !== null && (
|
||||||
|
<DistanceLines
|
||||||
|
obj={{
|
||||||
|
position: {
|
||||||
|
top:
|
||||||
|
currentPosition.top !== "auto"
|
||||||
|
? currentPosition.top
|
||||||
|
: undefined,
|
||||||
|
bottom:
|
||||||
|
currentPosition.bottom !== "auto"
|
||||||
|
? currentPosition.bottom
|
||||||
|
: undefined,
|
||||||
|
left:
|
||||||
|
currentPosition.left !== "auto"
|
||||||
|
? currentPosition.left
|
||||||
|
: undefined,
|
||||||
|
right:
|
||||||
|
currentPosition.right !== "auto"
|
||||||
|
? currentPosition.right
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
activeEdges={activeEdges}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default DroppedObjects;
|
export default DroppedObjects;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
try {
|
try {
|
||||||
const response = await getZone2dData(organization);
|
const response = await getZone2dData(organization);
|
||||||
|
console.log('response: ', response);
|
||||||
|
|
||||||
if (!Array.isArray(response)) {
|
if (!Array.isArray(response)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
);
|
);
|
||||||
setZonesData(formattedData);
|
setZonesData(formattedData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error: ", error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +111,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
});
|
});
|
||||||
}, [selectedZone]);
|
}, [selectedZone]);
|
||||||
|
|
||||||
useEffect(() => {}, [floatingWidgets]);
|
// useEffect(() => {}, [floatingWidgets]);
|
||||||
|
|
||||||
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
const handleDrop = async (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
try {
|
try {
|
||||||
|
@ -135,6 +137,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
id: generateUniqueId(),
|
id: generateUniqueId(),
|
||||||
position: determinePosition(canvasRect, relativeX, relativeY),
|
position: determinePosition(canvasRect, relativeX, relativeY),
|
||||||
};
|
};
|
||||||
|
console.log('newObject: ', newObject);
|
||||||
|
|
||||||
let response = await addingFloatingWidgets(
|
let response = await addingFloatingWidgets(
|
||||||
selectedZone.zoneId,
|
selectedZone.zoneId,
|
||||||
|
@ -171,7 +174,7 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -198,7 +201,8 @@ const RealTimeVisulization: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Scene />
|
<Scene />
|
||||||
</div>
|
</div>
|
||||||
<DroppedObjects />
|
{activeModule === "visualization" && selectedZone.zoneName !== "" && <DroppedObjects />}
|
||||||
|
{/* <DroppedObjects /> */}
|
||||||
{activeModule === "visualization" && (
|
{activeModule === "visualization" && (
|
||||||
<>
|
<>
|
||||||
<DisplayZone
|
<DisplayZone
|
||||||
|
|
|
@ -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(
|
export function determinePosition(
|
||||||
canvasRect: DOMRect,
|
canvasRect: DOMRect,
|
||||||
relativeX: number,
|
relativeX: number,
|
||||||
|
@ -8,11 +75,9 @@ export function determinePosition(
|
||||||
right: number | "auto";
|
right: number | "auto";
|
||||||
bottom: number | "auto";
|
bottom: number | "auto";
|
||||||
} {
|
} {
|
||||||
// Calculate the midpoints of the canvas
|
|
||||||
const centerX = canvasRect.width / 2;
|
const centerX = canvasRect.width / 2;
|
||||||
const centerY = canvasRect.height / 2;
|
const centerY = canvasRect.height / 2;
|
||||||
|
|
||||||
// Initialize position with default values
|
|
||||||
let position: {
|
let position: {
|
||||||
top: number | "auto";
|
top: number | "auto";
|
||||||
left: number | "auto";
|
left: number | "auto";
|
||||||
|
@ -21,9 +86,8 @@ export function determinePosition(
|
||||||
};
|
};
|
||||||
|
|
||||||
if (relativeY < centerY) {
|
if (relativeY < centerY) {
|
||||||
// Top half
|
|
||||||
if (relativeX < centerX) {
|
if (relativeX < centerX) {
|
||||||
// Left side
|
console.log("Top-left");
|
||||||
position = {
|
position = {
|
||||||
top: relativeY,
|
top: relativeY,
|
||||||
left: relativeX,
|
left: relativeX,
|
||||||
|
@ -31,7 +95,7 @@ export function determinePosition(
|
||||||
bottom: "auto",
|
bottom: "auto",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Right side
|
console.log("Top-right");
|
||||||
position = {
|
position = {
|
||||||
top: relativeY,
|
top: relativeY,
|
||||||
right: canvasRect.width - relativeX,
|
right: canvasRect.width - relativeX,
|
||||||
|
@ -40,9 +104,8 @@ export function determinePosition(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Bottom half
|
|
||||||
if (relativeX < centerX) {
|
if (relativeX < centerX) {
|
||||||
// Left side
|
console.log("Bottom-left");
|
||||||
position = {
|
position = {
|
||||||
bottom: canvasRect.height - relativeY,
|
bottom: canvasRect.height - relativeY,
|
||||||
left: relativeX,
|
left: relativeX,
|
||||||
|
@ -50,7 +113,7 @@ export function determinePosition(
|
||||||
top: "auto",
|
top: "auto",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Right side
|
console.log("Bottom-right");
|
||||||
position = {
|
position = {
|
||||||
bottom: canvasRect.height - relativeY,
|
bottom: canvasRect.height - relativeY,
|
||||||
right: canvasRect.width - relativeX,
|
right: canvasRect.width - relativeX,
|
||||||
|
@ -61,4 +124,4 @@ export function determinePosition(
|
||||||
}
|
}
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
|
@ -1,17 +1,11 @@
|
||||||
export const getActiveProperties = (position: {
|
export function getActiveProperties(position: any): [string, string] {
|
||||||
top: number | "auto";
|
if (position.top !== "auto" && position.left !== "auto") {
|
||||||
left: number | "auto";
|
return ["top", "left"]; // Top-left
|
||||||
right: number | "auto";
|
} else if (position.top !== "auto" && position.right !== "auto") {
|
||||||
bottom: number | "auto";
|
return ["top", "right"]; // Top-right
|
||||||
}) => {
|
} else if (position.bottom !== "auto" && position.left !== "auto") {
|
||||||
let activeProps: ["top", "left"] | ["bottom", "right"] = ["top", "left"]; // Default to top-left
|
return ["bottom", "left"]; // Bottom-left
|
||||||
|
} else {
|
||||||
if (
|
return ["bottom", "right"]; // Bottom-right
|
||||||
typeof position.bottom !== "string" &&
|
|
||||||
typeof position.right !== "string"
|
|
||||||
) {
|
|
||||||
activeProps = ["bottom", "right"];
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return activeProps;
|
|
||||||
};
|
|
|
@ -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);
|
||||||
|
// };
|
|
@ -13,7 +13,6 @@ function Simulation() {
|
||||||
const [processes, setProcesses] = useState([]);
|
const [processes, setProcesses] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('simulationPaths: ', simulationPaths);
|
|
||||||
}, [simulationPaths]);
|
}, [simulationPaths]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
|
|
|
@ -1,74 +1,89 @@
|
||||||
|
import { saveTemplateApi } from "../../services/realTimeVisulization/zoneData/saveTempleteApi";
|
||||||
import { Template } from "../../store/useTemplateStore";
|
import { Template } from "../../store/useTemplateStore";
|
||||||
import { captureVisualization } from "./captureVisualization";
|
import { captureVisualization } from "./captureVisualization";
|
||||||
|
|
||||||
type HandleSaveTemplateProps = {
|
type HandleSaveTemplateProps = {
|
||||||
addTemplate: (template: Template) => void;
|
addTemplate: (template: Template) => void;
|
||||||
|
floatingWidget: []; // Updated type from `[]` to `any[]` for clarity
|
||||||
|
widgets3D: []; // Updated type from `[]` to `any[]` for clarity
|
||||||
selectedZone: {
|
selectedZone: {
|
||||||
panelOrder: string[]; // Adjust the type based on actual data structure
|
panelOrder: string[];
|
||||||
widgets: any[]; // Replace `any` with the actual widget type
|
widgets: any[];
|
||||||
};
|
};
|
||||||
templates?: Template[];
|
templates?: Template[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a unique ID (placeholder function)
|
// Generate a unique ID
|
||||||
const generateUniqueId = (): string => {
|
const generateUniqueId = (): string => {
|
||||||
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Refactored function
|
|
||||||
export const handleSaveTemplate = async ({
|
export const handleSaveTemplate = async ({
|
||||||
addTemplate,
|
addTemplate,
|
||||||
|
floatingWidget,
|
||||||
|
widgets3D,
|
||||||
selectedZone,
|
selectedZone,
|
||||||
templates = [],
|
templates = [],
|
||||||
}: HandleSaveTemplateProps): Promise<void> => {
|
}: HandleSaveTemplateProps): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// Check if the selected zone has any widgets
|
// Check if the selected zone has any widgets
|
||||||
if (!selectedZone.widgets || selectedZone.widgets.length === 0) {
|
if (!selectedZone.widgets || selectedZone.widgets.length === 0) {
|
||||||
console.warn("Cannot save an empty template.");
|
console.warn("No widgets found in the selected zone.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the template already exists
|
// Check if the template already exists
|
||||||
const isDuplicate = templates.some((template) => {
|
const isDuplicate = templates.some(
|
||||||
const isSamePanelOrder =
|
(template) =>
|
||||||
JSON.stringify(template.panelOrder) ===
|
JSON.stringify(template.panelOrder) ===
|
||||||
JSON.stringify(selectedZone.panelOrder);
|
JSON.stringify(selectedZone.panelOrder) &&
|
||||||
const isSameWidgets =
|
|
||||||
JSON.stringify(template.widgets) ===
|
JSON.stringify(template.widgets) ===
|
||||||
JSON.stringify(selectedZone.widgets);
|
JSON.stringify(selectedZone.widgets)
|
||||||
return isSamePanelOrder && isSameWidgets;
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
console.warn("This template already exists.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture visualization snapshot
|
// Capture visualization snapshot
|
||||||
const snapshot = await captureVisualization();
|
const snapshot = await captureVisualization();
|
||||||
if (!snapshot) {
|
console.log("snapshot: ", snapshot);
|
||||||
console.error("Failed to capture visualization snapshot.");
|
// if (!snapshot) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Create a new template
|
// Create a new template
|
||||||
const newTemplate: Template = {
|
const newTemplate: Template = {
|
||||||
id: generateUniqueId(),
|
id: generateUniqueId(),
|
||||||
name: `Template ${Date.now()}`,
|
name: `Template ${new Date().toISOString()}`, // Better name formatting
|
||||||
panelOrder: selectedZone.panelOrder,
|
panelOrder: selectedZone.panelOrder,
|
||||||
widgets: selectedZone.widgets,
|
widgets: selectedZone.widgets,
|
||||||
snapshot,
|
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
|
// Save the template
|
||||||
try {
|
try {
|
||||||
|
const response = await saveTemplateApi(organization, newTemplate);
|
||||||
|
console.log("Save API Response:", response);
|
||||||
|
|
||||||
|
// Add template only if API call succeeds
|
||||||
addTemplate(newTemplate);
|
addTemplate(newTemplate);
|
||||||
} catch (error) {
|
} catch (apiError) {
|
||||||
console.error("Failed to add template:", error);
|
console.error("Error saving template to API:", apiError);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save template:", error);
|
console.error("Error in handleSaveTemplate:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,10 +1,14 @@
|
||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
// let url_Backend_dwinzo = `http://192.168.0.102:5000`;
|
// let url_Backend_dwinzo = `http://192.168.0.102:5000`;
|
||||||
|
|
||||||
export const addingFloatingWidgets = async (
|
export const addingFloatingWidgets = async (
|
||||||
zoneId: string,
|
zoneId: string,
|
||||||
organization: string,
|
organization: string,
|
||||||
widget: {}
|
widget: {}
|
||||||
) => {
|
) => {
|
||||||
|
console.log('organization: ', organization);
|
||||||
|
console.log('widget: ', widget);
|
||||||
|
console.log('zoneId: ', zoneId);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${url_Backend_dwinzo}/api/v2/floatwidget/save`,
|
`${url_Backend_dwinzo}/api/v2/floatwidget/save`,
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
console.log("url_Backend_dwinzo: ", url_Backend_dwinzo);
|
// let url_Backend_dwinzo = `http://192.168.0.102:5000`;
|
||||||
|
|
||||||
export const getSelect2dZoneData = async (
|
export const getSelect2dZoneData = async (
|
||||||
ZoneId?: string,
|
ZoneId?: string,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
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) => {
|
export const getZoneData = async (zoneId: string, organization: string) => {
|
||||||
console.log("organization: ", organization);
|
console.log("organization: ", organization);
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
const useFloatingDataStore = create((set) => ({
|
||||||
|
floatingdata: [], // Initial state
|
||||||
|
setfloatingadata: (newData: []) => set({ floatingdata: newData }), // Setter function
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useFloatingDataStore;
|
|
@ -393,3 +393,4 @@ export const useWidgetSubOption = create<any>((set: any) => ({
|
||||||
widgetSubOption: "2D",
|
widgetSubOption: "2D",
|
||||||
setWidgetSubOption: (x: any) => set({ widgetSubOption: x }),
|
setWidgetSubOption: (x: any) => set({ widgetSubOption: x }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import { addingFloatingWidgets } from "../services/realTimeVisulization/zoneData/addFloatingWidgets";
|
||||||
|
|
||||||
type DroppedObject = {
|
type DroppedObject = {
|
||||||
className: string;
|
className: string;
|
||||||
|
@ -35,6 +36,8 @@ type DroppedObjectsState = {
|
||||||
bottom: number | "auto";
|
bottom: number | "auto";
|
||||||
}
|
}
|
||||||
) => void;
|
) => 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) => ({
|
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 {
|
export interface DroppedObjects {
|
||||||
|
@ -90,3 +159,13 @@ export interface Zones {
|
||||||
zoneId: string;
|
zoneId: string;
|
||||||
objects: DroppedObject[];
|
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 }),
|
||||||
|
}));
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
// type Side = "top" | "bottom" | "left" | "right";
|
|
||||||
|
|
||||||
export interface Widget {
|
export interface Widget {
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -15,21 +13,34 @@ export interface Template {
|
||||||
name: string;
|
name: string;
|
||||||
panelOrder: string[];
|
panelOrder: string[];
|
||||||
widgets: Widget[];
|
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 {
|
interface TemplateStore {
|
||||||
templates: Template[];
|
templates: Template[];
|
||||||
addTemplate: (template: Template) => void;
|
addTemplate: (template: Template) => void;
|
||||||
|
setTemplates: (templates: Template[]) => void; // Changed from `setTemplate`
|
||||||
removeTemplate: (id: string) => void;
|
removeTemplate: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTemplateStore = create<TemplateStore>((set) => ({
|
export const useTemplateStore = create<TemplateStore>((set) => ({
|
||||||
templates: [],
|
templates: [],
|
||||||
|
|
||||||
|
// Add a new template to the list
|
||||||
addTemplate: (template) =>
|
addTemplate: (template) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
templates: [...state.templates, template],
|
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) =>
|
removeTemplate: (id) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
templates: state.templates.filter((t) => t.id !== id),
|
templates: state.templates.filter((t) => t.id !== id),
|
||||||
|
@ -37,3 +48,4 @@ export const useTemplateStore = create<TemplateStore>((set) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useTemplateStore;
|
export default useTemplateStore;
|
||||||
|
|
||||||
|
|
|
@ -542,4 +542,145 @@
|
||||||
.zone.active {
|
.zone.active {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-wrapper {
|
||||||
|
.icon {
|
||||||
|
width: 25px !important;
|
||||||
|
height: 25px !important;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kebab {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 10;
|
||||||
|
cursor: pointer;
|
||||||
|
@include flex-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kebab-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 5px;
|
||||||
|
transform: translate(0px, 0);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
box-shadow: var(--box-shadow-medium);
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.label {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--highlight-accent-color);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
&:first-child {
|
||||||
|
fill: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
fill: auto;
|
||||||
|
stroke: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dublicate {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.distance-line {
|
||||||
|
position: absolute;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: var(--accent-color); /* Green color for visibility */
|
||||||
|
border-width: 1px;
|
||||||
|
pointer-events: none; /* Ensure lins don't interfere with dragging */
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label styles for displaying distance values */
|
||||||
|
.distance-label {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transform: translate(-50%, -50%); /* Center the label */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific styles for each type of line */
|
||||||
|
|
||||||
|
/* Top distance line */
|
||||||
|
.distance-line.top {
|
||||||
|
border-bottom: none; /* Remove bottom border for a single line */
|
||||||
|
width: 2px; /* Thin vertical line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-line.top .distance-label {
|
||||||
|
top: -10px; /* Position label above the line */
|
||||||
|
left: 50%; /* Center horizontally */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom distance line */
|
||||||
|
.distance-line.bottom {
|
||||||
|
border-top: none; /* Remove top border for a single line */
|
||||||
|
width: 2px; /* Thin vertical line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-line.bottom .distance-label {
|
||||||
|
bottom: -10px; /* Position label below the line */
|
||||||
|
left: 50%; /* Center horizontally */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left distance line */
|
||||||
|
.distance-line.left {
|
||||||
|
border-right: none; /* Remove right border for a single line */
|
||||||
|
height: 2px; /* Thin horizontal line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-line.left .distance-label {
|
||||||
|
left: -10px; /* Position label to the left of the line */
|
||||||
|
top: 50%; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right distance line */
|
||||||
|
.distance-line.right {
|
||||||
|
border-left: none; /* Remove left border for a single line */
|
||||||
|
height: 2px; /* Thin horizontal line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.distance-line.right .distance-label {
|
||||||
|
right: -10px; /* Position label to the right of the line */
|
||||||
|
top: 50%; /* Center vertically */
|
||||||
}
|
}
|
Loading…
Reference in New Issue