diff --git a/app/src/components/ui/componets/DistanceLines.tsx b/app/src/components/ui/componets/DistanceLines.tsx new file mode 100644 index 0000000..07cf202 --- /dev/null +++ b/app/src/components/ui/componets/DistanceLines.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +interface DistanceLinesProps { + obj: { + position: { + top?: number | "auto"; + left?: number | "auto"; + right?: number | "auto"; + bottom?: number | "auto"; + }; + }; + activeEdges: { + vertical: "top" | "bottom"; + horizontal: "left" | "right"; + } | null; +} + +const DistanceLines: React.FC = ({ obj, activeEdges }) => { + if (!activeEdges) return null; + + return ( + <> + {activeEdges.vertical === "top" && typeof obj.position.top === "number" && ( +
+ {obj.position.top}px +
+ )} + + {activeEdges.vertical === "bottom" && + typeof obj.position.bottom === "number" && ( +
+ {obj.position.bottom}px +
+ )} + + {activeEdges.horizontal === "left" && + typeof obj.position.left === "number" && ( +
+ {obj.position.left}px +
+ )} + + {activeEdges.horizontal === "right" && + typeof obj.position.right === "number" && ( +
+ {obj.position.right}px +
+ )} + + ); +}; + +export default DistanceLines; \ No newline at end of file diff --git a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx index bf19bb9..272051c 100644 --- a/app/src/components/ui/componets/DroppedFloatingWidgets.tsx +++ b/app/src/components/ui/componets/DroppedFloatingWidgets.tsx @@ -1,6 +1,5 @@ import { WalletIcon } from "../../icons/3dChartIcons"; import { useEffect, useRef, useState } from "react"; -import { Line } from "react-chartjs-2"; import { useDroppedObjectsStore, Zones, @@ -9,29 +8,61 @@ import useModuleStore from "../../../store/useModuleStore"; import { determinePosition } from "./functions/determinePosition"; import { getActiveProperties } from "./functions/getActiveProperties"; import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets"; +import { + DublicateIcon, + KebabIcon, + DeleteIcon, +} from "../../icons/ExportCommonIcons"; +import DistanceLines from "./DistanceLines"; // Import the DistanceLines component + +interface DraggingState { + zone: string; + index: number; + initialPosition: { + top: number | "auto"; + left: number | "auto"; + right: number | "auto"; + bottom: number | "auto"; + }; +} const DroppedObjects: React.FC = () => { const zones = useDroppedObjectsStore((state) => state.zones); - + const [openKebabId, setOpenKebabId] = useState(null); const updateObjectPosition = useDroppedObjectsStore( (state) => state.updateObjectPosition ); - const [draggingIndex, setDraggingIndex] = useState<{ - zone: string; - index: number; - } | null>(null); + const [draggingIndex, setDraggingIndex] = useState( + null + ); const [offset, setOffset] = useState<[number, number] | null>(null); - const positionRef = useRef<[number, number] | null>(null); + const [activeEdges, setActiveEdges] = useState<{ + vertical: "top" | "bottom"; + 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(null); const { activeModule } = useModuleStore(); - // Get the first zone and its objects - const zoneEntries = Object.entries(zones); - if (zoneEntries.length === 0) return null; // No zone, nothing to render - const [zoneName, zone] = zoneEntries[0]; // Only render the first zone + // Clean up animation frame on unmount + useEffect(() => { + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + }; + }, []); - // Handle pointer down event - function handlePointerDown(event: React.PointerEvent, index: number) { + const zoneEntries = Object.entries(zones); + if (zoneEntries.length === 0) return null; + const [zoneName, zone] = zoneEntries[0]; + + const handlePointerDown = (event: React.PointerEvent, index: number) => { const obj = zone.objects[index]; const container = document.getElementById("real-time-vis-canvas"); if (!container) return; @@ -40,41 +71,41 @@ const DroppedObjects: React.FC = () => { const relativeX = event.clientX - rect.left; const relativeY = event.clientY - rect.top; - // Determine which properties are active for this object + // Determine active properties for the initial position const [activeProp1, activeProp2] = getActiveProperties(obj.position); - // Calculate the offset based on the active properties + // Set active edges for distance lines + const vertical = activeProp1 === "top" ? "top" : "bottom"; + const horizontal = activeProp2 === "left" ? "left" : "right"; + setActiveEdges({ vertical, horizontal }); + + // Store the initial position strategy and active edges + setDraggingIndex({ + zone: zoneName, + index, + initialPosition: { ...obj.position }, + }); + + // Calculate offset from mouse to object edges 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); + offsetY = relativeY - (obj.position.top as number); + } else { + offsetY = rect.height - relativeY - (obj.position.bottom as number); } 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); + offsetX = relativeX - (obj.position.left as number); + } else { + offsetX = rect.width - relativeX - (obj.position.right as number); } - setDraggingIndex({ zone: zoneName, index }); setOffset([offsetY, offsetX]); - } + }; - // Handle pointer move event - function handlePointerMove(event: React.PointerEvent) { + const handlePointerMove = (event: React.PointerEvent) => { if (!draggingIndex || !offset) return; const container = document.getElementById("real-time-vis-canvas"); @@ -84,91 +115,194 @@ const DroppedObjects: React.FC = () => { 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); + // Dynamically determine the current position strategy + const newPositionStrategy = determinePosition(rect, relativeX, relativeY); + const [activeProp1, activeProp2] = getActiveProperties(newPositionStrategy); - // Calculate the new position based on the active properties - let newX = 0; + // Update active edges for distance lines + const vertical = activeProp1 === "top" ? "top" : "bottom"; + const horizontal = activeProp2 === "left" ? "left" : "right"; + setActiveEdges({ vertical, horizontal }); + + // Calculate new position based on the active properties let newY = 0; - - if (activeProp2 === "left") { - newX = relativeX - offset[1]; - } else if (activeProp2 === "right") { - newX = rect.width - (relativeX + offset[1]); - } + let newX = 0; if (activeProp1 === "top") { newY = relativeY - offset[0]; - } else if (activeProp1 === "bottom") { + } else { newY = rect.height - (relativeY + offset[0]); } - // Ensure the object stays within the canvas boundaries + if (activeProp2 === "left") { + newX = relativeX - offset[1]; + } else { + newX = rect.width - (relativeX + offset[1]); + } + + // Apply 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]; + // 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); - // 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], - }); - } + updateObjectPosition(zoneName, draggingIndex.index, newPosition); animationRef.current = null; }); } - } + }; - // Handle pointer up event - async function handlePointerUp(event: React.MouseEvent) { + const handlePointerUp = async (event: React.PointerEvent) => { try { if (!draggingIndex || !offset) return; - const email = localStorage.getItem("email") || ""; - const organization = email?.split("@")[1]?.split(".")[0]; const container = document.getElementById("real-time-vis-canvas"); - if (!container) throw new Error("Canvas container not found"); + if (!container) return; const rect = container.getBoundingClientRect(); const relativeX = event.clientX - rect.left; const relativeY = event.clientY - rect.top; - // Recalculate the position using determinePosition - const newPosition = determinePosition(rect, relativeX, relativeY); + // Only now determine the final position strategy + const finalPosition = determinePosition(rect, relativeX, relativeY); + const [activeProp1, activeProp2] = getActiveProperties(finalPosition); - // Validate the dragging index and get the object - if (!zone.objects[draggingIndex.index]) { - throw new Error("Dragged object not found in the zone"); + // 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]); } - const obj = { ...zone.objects[draggingIndex.index], position: newPosition }; - let response = await addingFloatingWidgets(zone.zoneId, organization, obj); + + 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, newPosition); + updateObjectPosition(zoneName, draggingIndex.index, boundedPosition); } - // Reset states + // 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 ( + <> +
+
{obj.header}
+
+
{obj.value}
+
{obj.per}
+
+
+
+ +
+ + ); + case "warehouseThroughput floating": + return ( + <> +
+

Warehouse Throughput

+

+ (+5) more in 2025 +

+
+
+ {/* */} +
+ + ); + case "fleetEfficiency floating": + return ( + <> +

Fleet Efficiency

+
+
+
+
+
+
+
+
+ 0% +
+
{obj.per}%
+
Optimal
+
+ 100% +
+ + ); + default: + return null; + } + }; return ( -
+
{zone.objects.map((obj, index) => (
{ style={{ position: "absolute", top: - typeof obj.position.top !== "string" + typeof obj.position.top === "number" ? `${obj.position.top}px` : "auto", left: - typeof obj.position.left !== "string" + typeof obj.position.left === "number" ? `${obj.position.left}px` : "auto", right: - typeof obj.position.right !== "string" + typeof obj.position.right === "number" ? `${obj.position.right}px` : "auto", bottom: - typeof obj.position.bottom !== "string" + typeof obj.position.bottom === "number" ? `${obj.position.bottom}px` : "auto", - // transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out", }} onPointerDown={(event) => handlePointerDown(event, index)} > - {obj.className === "floating total-card" ? ( - <> -
-
{obj.header}
-
-
{obj.value}
-
{obj.per}
+ {renderObjectContent(obj)} +
handleKebabClick(obj.id, event)} + > + +
+ {openKebabId === obj.id && ( +
+
+
+
+
Duplicate
-
- -
- - ) : obj.className === "warehouseThroughput floating" ? ( - <> -
-

Warehouse Throughput

-

- (+5) more in 2025 -

-
-
- {/* */} -
- - ) : obj.className === "fleetEfficiency floating" ? ( - <> -

Fleet Efficiency

-
-
-
-
-
+
+
+
+
Delete
-
- 0% -
-
{obj.per}%
-
Optimal
-
- 100% -
- - ) : null} +
+ )}
))} + + {/* Render DistanceLines component during drag */} + {draggingIndex !== null && + activeEdges !== null && + currentPosition !== null && ( + + )}
); }; export default DroppedObjects; + +// in pointer move even when i goes to top right the value not changes to top right same problem in bottom left diff --git a/app/src/components/ui/componets/functions/determinePosition.ts b/app/src/components/ui/componets/functions/determinePosition.ts index 0fcd727..82f2303 100644 --- a/app/src/components/ui/componets/functions/determinePosition.ts +++ b/app/src/components/ui/componets/functions/determinePosition.ts @@ -8,11 +8,9 @@ export function determinePosition( 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"; @@ -21,9 +19,8 @@ export function determinePosition( }; if (relativeY < centerY) { - // Top half if (relativeX < centerX) { - // Left side + console.log("Top-left"); position = { top: relativeY, left: relativeX, @@ -31,7 +28,7 @@ export function determinePosition( bottom: "auto", }; } else { - // Right side + console.log("Top-right"); position = { top: relativeY, right: canvasRect.width - relativeX, @@ -40,9 +37,8 @@ export function determinePosition( }; } } else { - // Bottom half if (relativeX < centerX) { - // Left side + console.log("Bottom-left"); position = { bottom: canvasRect.height - relativeY, left: relativeX, @@ -50,7 +46,7 @@ export function determinePosition( top: "auto", }; } else { - // Right side + console.log("Bottom-right"); position = { bottom: canvasRect.height - relativeY, right: canvasRect.width - relativeX, @@ -61,4 +57,4 @@ export function determinePosition( } return position; -} +} \ No newline at end of file diff --git a/app/src/components/ui/componets/functions/getActiveProperties.ts b/app/src/components/ui/componets/functions/getActiveProperties.ts index 2cb0b1b..1a05d34 100644 --- a/app/src/components/ui/componets/functions/getActiveProperties.ts +++ b/app/src/components/ui/componets/functions/getActiveProperties.ts @@ -1,17 +1,11 @@ -export const getActiveProperties = (position: { - top: number | "auto"; - left: number | "auto"; - right: number | "auto"; - bottom: number | "auto"; -}) => { - let activeProps: ["top", "left"] | ["bottom", "right"] = ["top", "left"]; // Default to top-left - - if ( - typeof position.bottom !== "string" && - typeof position.right !== "string" - ) { - activeProps = ["bottom", "right"]; +export function getActiveProperties(position: any): [string, string] { + if (position.top !== "auto" && position.left !== "auto") { + return ["top", "left"]; // Top-left + } else if (position.top !== "auto" && position.right !== "auto") { + return ["top", "right"]; // Top-right + } else if (position.bottom !== "auto" && position.left !== "auto") { + return ["bottom", "left"]; // Bottom-left + } else { + return ["bottom", "right"]; // Bottom-right } - - return activeProps; -}; +} \ No newline at end of file diff --git a/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts b/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts new file mode 100644 index 0000000..39a542a --- /dev/null +++ b/app/src/services/realTimeVisulization/zoneData/useFloatingDataStore.ts @@ -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; diff --git a/app/src/store/useDroppedObjectsStore.ts b/app/src/store/useDroppedObjectsStore.ts index dc648a9..5ded701 100644 --- a/app/src/store/useDroppedObjectsStore.ts +++ b/app/src/store/useDroppedObjectsStore.ts @@ -90,3 +90,5 @@ export interface Zones { zoneId: string; objects: DroppedObject[]; } + + diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 69df460..b080840 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -541,4 +541,156 @@ .zone.active { background-color: #007bff; 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; + } + + + + } +} + + + + + + + + + + +/* General styles for all distance lines */ +.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 */ } \ No newline at end of file