added active chart select

This commit is contained in:
Nalvazhuthi
2025-04-02 12:29:07 +05:30
10 changed files with 700 additions and 435 deletions

View File

@@ -118,7 +118,7 @@ const Data = () => {
};
});
};
console.log("selectedChartId", selectedChartId);
return (
<div className="dataSideBar">

View File

@@ -13,6 +13,7 @@ import {
import { useEffect, useRef, useState } from "react";
import { duplicateWidgetApi } from "../../../services/realTimeVisulization/zoneData/duplicateWidget";
import { deleteWidgetApi } from "../../../services/realTimeVisulization/zoneData/deleteWidgetApi";
import { useClickOutside } from "./functions/handleWidgetsOuterClick";
type Side = "top" | "bottom" | "left" | "right";
@@ -79,6 +80,8 @@ export const DraggableWidget = ({
}
};
const chartWidget = useRef<HTMLDivElement>(null);
const isPanelHidden = hiddenPanels.includes(widget.panel);
const deleteSelectedChart = async () => {
@@ -96,13 +99,11 @@ export const DraggableWidget = ({
}));
}
} catch (error) {
} finally {
setOpenKebabId(null);
}
};
const getCurrentWidgetCount = (panel: Side) =>
selectedZone.widgets.filter((w) => w.panel === panel).length;
@@ -140,7 +141,11 @@ export const DraggableWidget = ({
id: `${widget.id}-copy-${Date.now()}`,
};
const response = await duplicateWidgetApi(selectedZone.zoneId, organization, duplicatedWidget);
const response = await duplicateWidgetApi(
selectedZone.zoneId,
organization,
duplicatedWidget
);
if (response?.message === "Widget created successfully") {
setSelectedZone((prevZone: any) => ({
@@ -149,13 +154,11 @@ export const DraggableWidget = ({
}));
}
} catch (error) {
} finally {
setOpenKebabId(null);
}
};
const handleKebabClick = (event: React.MouseEvent<HTMLDivElement>) => {
event.stopPropagation();
if (openKebabId === widget.id) {
@@ -204,13 +207,18 @@ export const DraggableWidget = ({
}
};
useClickOutside(chartWidget, () => {
setSelectedChartId(null);
});
return (
<>
<div
draggable
key={widget.id}
className={`chart-container ${selectedChartId?.id === widget.id && "activeChart"
}`}
className={`chart-container ${
selectedChartId?.id === widget.id && "activeChart"
}`}
onPointerDown={handlePointerDown}
onDragStart={handleDragStart}
onDragEnter={handleDragEnter}
@@ -220,6 +228,8 @@ export const DraggableWidget = ({
opacity: isPanelHidden ? 0 : 1,
pointerEvents: isPanelHidden ? "none" : "auto",
}}
ref={chartWidget}
onClick={() => setSelectedChartId(widget)}
>
{/* Kebab Icon */}
<div className="icon kebab" onClick={handleKebabClick}>
@@ -230,8 +240,9 @@ export const DraggableWidget = ({
{openKebabId === widget.id && (
<div className="kebab-options" ref={widgetRef}>
<div
className={`edit btn ${isPanelFull(widget.panel) ? "btn-blur" : ""
}`}
className={`edit btn ${
isPanelFull(widget.panel) ? "btn-blur" : ""
}`}
onClick={isPanelFull(widget.panel) ? undefined : duplicateWidget}
>
<div className="icon">
@@ -249,7 +260,7 @@ export const DraggableWidget = ({
)}
{/* Render charts based on widget type */}
{widget.type === "progress" && (
<ProgressCard title={widget.title} data={widget.data} />
)}

View File

@@ -1,4 +1,3 @@
import { WalletIcon } from "../../icons/3dChartIcons";
import { useEffect, useRef, useState } from "react";
import {
@@ -21,6 +20,7 @@ import TotalCardComponent from "../realTimeVis/floating/TotalCardComponent";
import WarehouseThroughputComponent from "../realTimeVis/floating/WarehouseThroughputComponent";
import FleetEfficiencyComponent from "../realTimeVis/floating/FleetEfficiencyComponent";
import { useWidgetStore } from "../../../store/useWidgetStore";
import { useClickOutside } from "./functions/handleWidgetsOuterClick";
interface DraggingState {
zone: string;
index: number;
@@ -50,12 +50,14 @@ const DroppedObjects: React.FC = () => {
);
const deleteObject = useDroppedObjectsStore((state) => state.deleteObject);
const duplicateObject = useDroppedObjectsStore((state) => state.duplicateObject);
const duplicateObject = useDroppedObjectsStore(
(state) => state.duplicateObject
);
const [draggingIndex, setDraggingIndex] = useState<DraggingState | null>(
null
);
const [offset, setOffset] = useState<[number, number] | null>(null);
const { setSelectedChartId } = useWidgetStore();
const { selectedChartId, setSelectedChartId } = useWidgetStore();
const [activeEdges, setActiveEdges] = useState<{
vertical: "top" | "bottom";
horizontal: "left" | "right";
@@ -68,6 +70,12 @@ const DroppedObjects: React.FC = () => {
} | null>(null); // State to track the current position during drag
const animationRef = useRef<number | null>(null);
const { activeModule } = useModuleStore();
const chartWidgetFloating = useRef<HTMLDivElement>(null);
// useClickOutside(chartWidgetFloating, () => {
// setSelectedChartId(null);
// });
// Clean up animation frame on unmount
useEffect(() => {
@@ -83,7 +91,7 @@ const DroppedObjects: React.FC = () => {
const [zoneName, zone] = zoneEntries[0];
function handleDuplicate(zoneName: string, index: number) {
setOpenKebabId(null)
setOpenKebabId(null);
duplicateObject(zoneName, index); // Call the duplicateObject method from the store
}
@@ -93,19 +101,27 @@ const DroppedObjects: React.FC = () => {
const organization = email?.split("@")[1]?.split(".")[0];
let res = await deleteFloatingWidgetApi(id, organization);
console.log('res: ', res);
if (res.message === "FloatingWidget deleted successfully") {
deleteObject(zoneName, index); // Call the deleteObject method from the store
}
} catch (error) {
console.error("Error deleting floating widget:", error);
}
} catch (error) {}
}
const handlePointerDown = (event: React.PointerEvent, index: number) => {
if (
(event.target as HTMLElement).closest(".kebab-options") ||
(event.target as HTMLElement).closest(".kebab")
) {
return;
}
const obj = zone.objects[index];
const element = event.currentTarget as HTMLElement;
// Set pointer capture to ensure we get all move events
element.setPointerCapture(event.pointerId);
const container = document.getElementById("real-time-vis-canvas");
if (!container) return;
@@ -145,6 +161,145 @@ const DroppedObjects: React.FC = () => {
}
setOffset([offsetY, offsetX]);
// Add native event listeners for smoother tracking
const handlePointerMoveNative = (e: PointerEvent) => {
if (!draggingIndex || !offset) return;
const rect = container.getBoundingClientRect();
const relativeX = e.clientX - rect.left;
const relativeY = e.clientY - rect.top;
// Use requestAnimationFrame for smooth updates
animationRef.current = requestAnimationFrame(() => {
// Dynamically determine the current position strategy
const newPositionStrategy = determinePosition(
rect,
relativeX,
relativeY
);
const [activeProp1, activeProp2] =
getActiveProperties(newPositionStrategy);
// 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;
let newX = 0;
if (activeProp1 === "top") {
newY = relativeY - offset[0];
} else {
newY = rect.height - (relativeY + offset[0]);
}
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));
// 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);
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
});
};
const handlePointerUpNative = async (e: PointerEvent) => {
// Clean up native event listeners
element.removeEventListener("pointermove", handlePointerMoveNative);
element.removeEventListener("pointerup", handlePointerUpNative);
element.releasePointerCapture(e.pointerId);
if (!draggingIndex || !offset) return;
const rect = container.getBoundingClientRect();
const relativeX = e.clientX - rect.left;
const relativeY = e.clientY - rect.top;
// Determine final position strategy
const finalPosition = determinePosition(rect, relativeX, relativeY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
// Calculate final position
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,
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto",
};
try {
// 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);
}
} catch (error) {
console.error("Error in handlePointerUp:", error);
} finally {
setDraggingIndex(null);
setOffset(null);
setActiveEdges(null);
setCurrentPosition(null);
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
animationRef.current = null;
}
}
};
// Add native event listeners
element.addEventListener("pointermove", handlePointerMoveNative);
element.addEventListener("pointerup", handlePointerUpNative);
};
const handlePointerMove = (event: React.PointerEvent) => {
@@ -199,12 +354,8 @@ const DroppedObjects: React.FC = () => {
// Update the current position state for DistanceLines
setCurrentPosition(newPosition);
if (!animationRef.current) {
animationRef.current = requestAnimationFrame(() => {
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
animationRef.current = null;
});
}
// Update position immediately without animation frame
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
};
const handlePointerUp = async (event: React.PointerEvent<HTMLDivElement>) => {
@@ -218,11 +369,11 @@ const DroppedObjects: React.FC = () => {
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Only now determine the final position strategy
// Determine final position strategy
const finalPosition = determinePosition(rect, relativeX, relativeY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
// Calculate final position using the new strategy
// Calculate final position
let finalY = 0;
let finalX = 0;
@@ -246,6 +397,9 @@ const DroppedObjects: React.FC = () => {
...finalPosition,
[activeProp1]: finalY,
[activeProp2]: finalX,
// Clear opposite properties
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto",
};
// Save to backend
@@ -259,18 +413,20 @@ const DroppedObjects: React.FC = () => {
if (response.message === "Widget updated successfully") {
updateObjectPosition(zoneName, draggingIndex.index, boundedPosition);
}
// Clean up
} catch (error) {
console.error("Error in handlePointerUp:", error);
} finally {
// Clean up regardless of success or failure
setDraggingIndex(null);
setOffset(null);
setActiveEdges(null); // Clear active edges
setCurrentPosition(null); // Reset current position
setActiveEdges(null);
setCurrentPosition(null);
// Cancel any pending animation frame
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
animationRef.current = null;
}
} catch (error) {
console.error("Error in handlePointerUp:", error);
}
};
@@ -279,66 +435,6 @@ const DroppedObjects: React.FC = () => {
setOpenKebabId((prevId) => (prevId === id ? null : id));
};
const renderObjectContent = (obj: any) => {
switch (obj.className) {
case "floating total-card":
return (
<>
<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>
</>
);
case "warehouseThroughput floating":
return (
<>
<div className="header">
<h2>Warehouse Throughput</h2>
<p>
<span>(+5) more</span> in 2025
</p>
</div>
<div className="lineGraph" style={{ height: "100%" }}>
{/* <Line data={lineGraphData} options={lineGraphOptions} /> */}
</div>
</>
);
case "fleetEfficiency floating":
return (
<>
<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>
</>
);
default:
return null;
}
};
return (
<div
onPointerMove={handlePointerMove}
@@ -348,7 +444,10 @@ const DroppedObjects: React.FC = () => {
{zone.objects.map((obj, index) => (
<div
key={`${zoneName}-${index}`}
className={obj.className}
className={`${obj.className} ${
selectedChartId?.id === obj.id && "activeChart"
}`}
ref={chartWidgetFloating}
style={{
position: "absolute",
top:
@@ -370,7 +469,7 @@ const DroppedObjects: React.FC = () => {
}}
onPointerDown={(event) => handlePointerDown(event, index)}
onClick={() => {
setSelectedChartId(obj)
setSelectedChartId(obj);
}}
>
{obj.className === "floating total-card" ? (
@@ -394,19 +493,25 @@ const DroppedObjects: React.FC = () => {
</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="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="edit btn"
onClick={(event) => {
event.stopPropagation();
handleDelete(zoneName, obj.id, index); // Call the delete handler
}}
>
<div className="icon">
<DeleteIcon />
</div>
@@ -450,5 +555,3 @@ const DroppedObjects: React.FC = () => {
};
export default DroppedObjects;

View File

@@ -264,3 +264,4 @@ const Panel: React.FC<PanelProps> = ({
};
export default Panel;

View File

@@ -63,7 +63,6 @@ const RealTimeVisulization: React.FC = () => {
const organization = email?.split("@")[1]?.split(".")[0];
try {
const response = await getZone2dData(organization);
console.log('response: ', response);
if (!Array.isArray(response)) {
return;
@@ -84,9 +83,7 @@ const RealTimeVisulization: React.FC = () => {
{}
);
setZonesData(formattedData);
} catch (error) {
}
} catch (error) {}
}
GetZoneData();
@@ -137,7 +134,6 @@ const RealTimeVisulization: React.FC = () => {
id: generateUniqueId(),
position: determinePosition(canvasRect, relativeX, relativeY),
};
console.log('newObject: ', newObject);
let response = await addingFloatingWidgets(
selectedZone.zoneId,
@@ -174,7 +170,7 @@ const RealTimeVisulization: React.FC = () => {
],
},
}));
} catch (error) { }
} catch (error) {}
};
return (
@@ -201,7 +197,9 @@ const RealTimeVisulization: React.FC = () => {
>
<Scene />
</div>
{activeModule === "visualization" && selectedZone.zoneName !== "" && <DroppedObjects />}
{activeModule === "visualization" && selectedZone.zoneName !== "" && (
<DroppedObjects />
)}
{/* <DroppedObjects /> */}
{activeModule === "visualization" && (
<>

View File

@@ -87,7 +87,7 @@ export function determinePosition(
if (relativeY < centerY) {
if (relativeX < centerX) {
console.log("Top-left");
position = {
top: relativeY,
left: relativeX,
@@ -95,7 +95,7 @@ export function determinePosition(
bottom: "auto",
};
} else {
console.log("Top-right");
position = {
top: relativeY,
right: canvasRect.width - relativeX,
@@ -105,7 +105,7 @@ export function determinePosition(
}
} else {
if (relativeX < centerX) {
console.log("Bottom-left");
position = {
bottom: canvasRect.height - relativeY,
left: relativeX,
@@ -113,7 +113,7 @@ export function determinePosition(
top: "auto",
};
} else {
console.log("Bottom-right");
position = {
bottom: canvasRect.height - relativeY,
right: canvasRect.width - relativeX,

View File

@@ -0,0 +1,22 @@
import { useEffect } from "react";
export const useClickOutside = (
ref: React.RefObject<HTMLElement>,
callback: () => void
) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback();
}
};
// Add event listener to document
document.addEventListener("mousedown", handleClickOutside);
// Cleanup event listener on component unmount
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref, callback]);
};