Merge remote-tracking branch 'origin/ui' into simulation

This commit is contained in:
Jerald-Golden-B 2025-04-14 18:17:19 +05:30
commit 4ac8826399
16 changed files with 591 additions and 498 deletions

View File

@ -258,7 +258,11 @@ const MultiLevelDropdown = ({
</button>
{open && (
<div className="dropdown-menu">
<div className="dropdown-content">
<div className="dropdown-content ">
{/* loading list */}
{/* <div className="loading" /> */}
{/* Unselect Option */}
<DropdownItem label="Unselect" onClick={handleItemUnselect} />
{/* Nested Dropdown Items */}

View File

@ -17,7 +17,7 @@ import MeasurementTool from "./tools/measurementTool";
import Simulation from "../simulation/simulation";
// import Simulation from "./simulationtemp/simulation";
import ZoneCentreTarget from "../../components/ui/componets/zoneCameraTarget";
import ZoneCentreTarget from "../visualization/functions/zoneCameraTarget";
import Dropped3dWidgets from "../../modules/visualization/widgets/3d/Dropped3dWidget";
import ZoneAssets from "../visualization/zoneAssets";

View File

@ -97,11 +97,16 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
if (container) {
const isOverflowing = container.scrollWidth > container.clientWidth;
const canScrollLeft = container.scrollLeft > 0;
const canScrollRight =
container.scrollLeft + container.clientWidth < container.scrollWidth;
const canScrollRight =
container.scrollLeft + container.clientWidth + 1 <
container.scrollWidth;
setShowLeftArrow(isOverflowing && canScrollLeft);
setShowRightArrow(isOverflowing && canScrollRight);
console.log('canScrollRight: ', canScrollRight);
console.log('isOverflowing: ', isOverflowing);
}
}, []);
@ -166,7 +171,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
if (selectedZone?.zoneId === zoneId) {
return;
}
setSelectedChartId(null);
// setSelectedChartId(null);
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
let response = await getSelect2dZoneData(zoneId, organization);

View File

@ -34,6 +34,7 @@ import {
import Dropped3dWidgets from "./widgets/3d/Dropped3dWidget";
import OuterClick from "../../utils/outerClick";
import { useWidgetStore } from "../../store/useWidgetStore";
import { getActiveProperties } from "./functions/getActiveProperties";
type Side = "top" | "bottom" | "left" | "right";
@ -92,6 +93,7 @@ const RealTimeVisulization: React.FC = () => {
"sidebar-right-wrapper",
"card",
"dropdown-menu",
"dropdown-options",
],
setMenuVisible: () => setSelectedChartId(null),
});
@ -166,60 +168,65 @@ const RealTimeVisulization: React.FC = () => {
const canvasElement = document.getElementById("real-time-vis-canvas");
if (!canvasElement) throw new Error("Canvas element not found");
// Get canvas dimensions and mouse position
const rect = canvasElement.getBoundingClientRect();
let relativeX = (event.clientX - rect.left) ;
let relativeY = event.clientY - rect.top;
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Widget dimensions (with defaults)
const widgetWidth = droppedData.width || 125; // 250/2 as default
const widgetHeight = droppedData.height || 100; // 83/2 as default
// Widget dimensions
const widgetWidth = droppedData.width || 125;
const widgetHeight = droppedData.height || 100;
// Clamp to ensure widget stays fully inside canvas
const clampedX = Math.max(
0, // Prevent going beyond left edge
Math.min(
relativeX,
rect.width - widgetWidth // Prevent going beyond right edge
)
);
// Center the widget at cursor
const centerOffsetX = widgetWidth / 2;
const centerOffsetY = widgetHeight / 2;
console.log('clampedX: ', clampedX);
const clampedY = Math.max(
0, // Prevent going beyond top edge
Math.min(
relativeY,
rect.height - widgetHeight // Prevent going beyond bottom edge
)
);
const adjustedX = relativeX - centerOffsetX;
const adjustedY = relativeY - centerOffsetY;
// Debug logging (optional)
console.log("Drop coordinates:", {
rawX: relativeX,
rawY: relativeY,
clampedX,
clampedY,
canvasWidth: rect.width,
canvasHeight: rect.height,
widgetWidth,
widgetHeight
});
const finalPosition = determinePosition(rect, adjustedX, adjustedY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
const finalPosition = determinePosition(rect, clampedX, clampedY);
let finalY = 0;
let finalX = 0;
if (activeProp1 === "top") {
finalY = adjustedY;
} else {
finalY = rect.height - (adjustedY + widgetHeight);
}
if (activeProp2 === "left") {
finalX = adjustedX;
} else {
finalX = rect.width - (adjustedX + widgetWidth);
}
// Clamp to boundaries
finalX = Math.max(0, Math.min(rect.width - widgetWidth, finalX));
finalY = Math.max(0, Math.min(rect.height - widgetHeight, finalY));
const boundedPosition = {
...finalPosition,
[activeProp1]: finalY,
[activeProp2]: finalX,
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto",
};
const newObject = {
...droppedData,
id: generateUniqueId(),
position: finalPosition,
position: boundedPosition,
};
// Zone management
const existingZone = useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
const existingZone =
useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
if (!existingZone) {
useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId);
useDroppedObjectsStore
.getState()
.setZone(selectedZone.zoneName, selectedZone.zoneId);
}
// Socket emission
const addFloatingWidget = {
organization,
widget: newObject,
@ -230,23 +237,24 @@ const RealTimeVisulization: React.FC = () => {
visualizationSocket.emit("v2:viz-float:add", addFloatingWidget);
}
// Store update
useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, newObject);
useDroppedObjectsStore
.getState()
.addObject(selectedZone.zoneName, newObject);
// Post-drop verification
const droppedObjectsStore = useDroppedObjectsStore.getState();
const currentZone = droppedObjectsStore.zones[selectedZone.zoneName];
if (currentZone && currentZone.zoneId === selectedZone.zoneId) {
console.log(`Objects for Zone ${selectedZone.zoneId}:`, currentZone.objects);
console.log(
`Objects for Zone ${selectedZone.zoneId}:`,
currentZone.objects
);
setFloatingWidget(currentZone.objects);
} else {
console.warn("Zone not found or zoneId mismatch");
}
} catch (error) {
console.error("Error in handleDrop:", error);
// Consider adding user feedback here (e.g., toast notification)
}
};
@ -270,8 +278,41 @@ const RealTimeVisulization: React.FC = () => {
};
}, [setRightClickSelected]);
const [canvasDimensions, setCanvasDimensions] = useState({
width: 0,
height: 0,
});
useEffect(() => {
const canvas = document.getElementById("real-time-vis-canvas");
if (!canvas) return;
const updateCanvasDimensions = () => {
const rect = canvas.getBoundingClientRect();
setCanvasDimensions({
width: rect.width,
height: rect.height,
});
};
updateCanvasDimensions();
const resizeObserver = new ResizeObserver(updateCanvasDimensions);
resizeObserver.observe(canvas);
return () => resizeObserver.unobserve(canvas);
}, []);
return (
<>
<style>
{`
:root {
--realTimeViz-container-width: ${canvasDimensions.width}px;
--realTimeViz-container-height: ${canvasDimensions.height}px;
}
`}
</style>
<div
ref={containerRef}
id="real-time-vis-canvas"

View File

@ -89,9 +89,7 @@ export const DraggableWidget = ({
width: 0,
height: 0,
});
useEffect(() => {
console.log("changes loggggg", measurements, duration, name);
}, [measurements, duration, name])
useEffect(() => {}, [measurements, duration, name]);
const handlePointerDown = () => {
if (selectedChartId?.id !== widget.id) {
setSelectedChartId(widget);
@ -167,25 +165,22 @@ export const DraggableWidget = ({
const currentWidgetCount = getCurrentWidgetCount(panel);
const panelCapacity = calculatePanelCapacity(panel);
return currentWidgetCount > panelCapacity;
return currentWidgetCount > panelCapacity;
};
const duplicateWidget = async () => {
try {
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
console.log("widget data sent", widget);
const duplicatedWidget: Widget = {
...widget,
Data: {
duration: duration,
measurements: { ...measurements }
measurements: { ...measurements },
},
id: `${widget.id}-copy-${Date.now()}`,
};
console.log("duplicatedWidget: ", duplicatedWidget);
let duplicateWidget = {
organization: organization,
@ -193,8 +188,6 @@ export const DraggableWidget = ({
widget: duplicatedWidget,
};
if (visualizationSocket) {
console.log("duplecate widget", duplicateWidget);
visualizationSocket.emit("v2:viz-widget:add", duplicateWidget);
}
setSelectedZone((prevZone: any) => ({
@ -293,20 +286,12 @@ export const DraggableWidget = ({
return (
<>
<style>
{`
:root {
--realTimeViz-container-width: ${canvasDimensions.width}px;
--realTimeViz-container-height: ${canvasDimensions.height}px;
}
`}
</style>
<div
draggable
key={widget.id}
className={`chart-container ${selectedChartId?.id === widget.id && !isPlaying && "activeChart"
}`}
className={`chart-container ${
selectedChartId?.id === widget.id && !isPlaying && "activeChart"
}`}
onPointerDown={handlePointerDown}
onDragStart={handleDragStart}
onDragEnter={handleDragEnter}
@ -317,7 +302,7 @@ export const DraggableWidget = ({
? `calc(${canvasDimensions.width}px / 6)`
: undefined,
height: ["left", "right"].includes(widget.panel)
? `calc(${canvasDimensions.height - 10}px / 4)`
? `calc(${canvasDimensions.height - 15}px / 4)`
: undefined,
}}
ref={chartWidget}
@ -332,8 +317,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={duplicateWidget}
>
<div className="icon">

View File

@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from "react";
import { Line } from "react-chartjs-2";
import { Doughnut } from "react-chartjs-2";
import io from "socket.io-client";
import axios from "axios";
@ -16,7 +16,7 @@ interface ChartComponentProps {
fontWeight?: "Light" | "Regular" | "Bold";
}
const LineGraphComponent = ({
const DoughnutGraphComponent = ({
id,
type,
title,
@ -97,11 +97,11 @@ const LineGraphComponent = ({
},
},
scales: {
x: {
ticks: {
display: true, // This hides the x-axis labels
},
},
// x: {
// ticks: {
// display: true, // This hides the x-axis labels
// },
// },
},
}),
[title, chartFontStyle, name]
@ -161,8 +161,6 @@ const LineGraphComponent = ({
try {
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/WidgetData/${id}/${organization}`);
if (response.status === 200) {
console.log('line chart res',response);
setmeasurements(response.data.Data.measurements)
setDuration(response.data.Data.duration)
setName(response.data.widgetName)
@ -186,7 +184,7 @@ const LineGraphComponent = ({
}
,[chartMeasurements, chartDuration, widgetName])
return <Line data={Object.keys(measurements).length > 0 ? chartData : defaultData} options={options} />;
return <Doughnut data={Object.keys(measurements).length > 0 ? chartData : defaultData} options={options} />;
};
export default LineGraphComponent;
export default DoughnutGraphComponent;

View File

@ -238,7 +238,7 @@ const ReturnOfInvestment: React.FC<ReturnOfInvestmentProps> = ({
rotation={rotation}
scale={[0.5, 0.5, 0.5]}
transform
zIndexRange={[1, 0]}
sprite={false}
// style={{
// transform: transformStyle.transform,

View File

@ -121,10 +121,12 @@ const DroppedObjects: React.FC = () => {
function handleDuplicate(zoneName: string, index: number) {
setOpenKebabId(null);
duplicateObject(zoneName, index); // Call the duplicateObject method from the store
setSelectedChartId(null);
}
async function handleDelete(zoneName: string, id: string) {
try {
setSelectedChartId(null);
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
@ -532,7 +534,7 @@ const DroppedObjects: React.FC = () => {
typeof obj.position.left === "number"
? `calc(${obj.position.left}px + ${
isPlaying && selectedZone.activeSides.includes("left")
? `${widthMultiplier - 100}px`
? `${widthMultiplier - 150}px`
: "0px"
})`
: "auto";
@ -541,11 +543,10 @@ const DroppedObjects: React.FC = () => {
typeof obj.position.right === "number"
? `calc(${obj.position.right}px + ${
isPlaying && selectedZone.activeSides.includes("right")
? `${widthMultiplier - 100}px`
? `${widthMultiplier - 150}px`
: "0px"
})`
: "auto";
const bottomPosition =
typeof obj.position.bottom === "number"
? `calc(${obj.position.bottom}px + ${
@ -663,4 +664,3 @@ const DroppedObjects: React.FC = () => {
};
export default DroppedObjects;

View File

@ -1,113 +1,117 @@
import React, { useState, useEffect } from 'react'
import { Line } from 'react-chartjs-2'
import useChartStore from '../../../../../store/useChartStore';
import { useWidgetStore } from '../../../../../store/useWidgetStore';
import axios from 'axios';
import React, { useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
import useChartStore from "../../../../../store/useChartStore";
import { useWidgetStore } from "../../../../../store/useWidgetStore";
import axios from "axios";
import io from "socket.io-client";
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
const FleetEfficiencyComponent = ({object}: any) => {
const [ progress, setProgress ] = useState<any>(0)
const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h")
const [name, setName] = useState(object.header ? object.header : '')
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]
const { header, flotingDuration, flotingMeasurements } = useChartStore();
const { selectedChartId } = useWidgetStore();
const FleetEfficiencyComponent = ({ object }: any) => {
const [progress, setProgress] = useState<any>(0);
const [measurements, setmeasurements] = useState<any>({});
const [duration, setDuration] = useState("1h");
const [name, setName] = useState(object.header ? object.header : "");
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
const { header, flotingDuration, flotingMeasurements } = useChartStore();
const { selectedChartId } = useWidgetStore();
const { isPlaying } = usePlayButtonStore();
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
// Calculate the rotation angle for the progress bar
const rotationAngle = 45 + progress * 1.8;
// Calculate the rotation angle for the progress bar
const rotationAngle = 45 + progress * 1.8;
useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
return;
useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
const socket = io(`http://${iotApiUrl}`);
const socket = io(`http://${iotApiUrl}`);
const inputData = {
measurements,
duration,
interval: 1000,
};
const inputData = {
measurements,
duration,
interval: 1000,
};
const startStream = () => {
socket.emit("lastInput", inputData);
};
socket.on("connect", startStream);
const startStream = () => {
socket.emit("lastInput", inputData);
};
socket.on("lastOutput", (response) => {
const responseData = response.input1;
// console.log(responseData);
socket.on("connect", startStream);
socket.on("lastOutput", (response) => {
const responseData = response.input1;
// console.log(responseData);
if (typeof responseData === "number") {
console.log("It's a number!");
setProgress(responseData);
}
});
return () => {
socket.off("lastOutput");
socket.emit("stop_stream"); // Stop streaming when component unmounts
socket.disconnect();
};
}, [measurements, duration, iotApiUrl]);
const fetchSavedInputes = async() => {
if (object?.id !== "") {
try {
const response = await axios.get(`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${object?.id}/${organization}`);
if (response.status === 200) {
setmeasurements(response.data.Data.measurements)
setDuration(response.data.Data.duration)
setName(response.data.header)
} else {
console.log("Unexpected response:", response);
}
} catch (error) {
console.error("There was an error!", error);
}
}
if (typeof responseData === "number") {
console.log("It's a number!");
setProgress(responseData);
}
});
useEffect(() => {
fetchSavedInputes();
}, []);
return () => {
socket.off("lastOutput");
socket.emit("stop_stream"); // Stop streaming when component unmounts
socket.disconnect();
};
}, [measurements, duration, iotApiUrl]);
useEffect(() => {
if (selectedChartId?.id === object?.id) {
fetchSavedInputes();
const fetchSavedInputes = async () => {
if (object?.id !== "") {
try {
const response = await axios.get(
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${object?.id}/${organization}`
);
if (response.status === 200) {
setmeasurements(response.data.Data.measurements);
setDuration(response.data.Data.duration);
setName(response.data.header);
} else {
console.log("Unexpected response:", response);
}
} catch (error) {
console.error("There was an error!", error);
}
,[header, flotingDuration, flotingMeasurements])
}
};
return (
<>
<h2 className="header">{name}</h2>
<div className="progressContainer">
<div className="progress">
<div className="barOverflow">
<div
className="bar"
style={{ transform: `rotate(${rotationAngle}deg)` }}
></div>
</div>
</div>
</div>
<div className="scaleLabels">
<span>0%</span>
<div className="centerText">
<div className="percentage">{progress}%</div>
<div className="status">Optimal</div>
</div>
<span>100%</span>
</div>
</>
)
}
useEffect(() => {
fetchSavedInputes();
}, []);
export default FleetEfficiencyComponent
useEffect(() => {
if (selectedChartId?.id === object?.id) {
fetchSavedInputes();
}
}, [header, flotingDuration, flotingMeasurements]);
return (
<>
<h2 className="header">{name}</h2>
<div
className="progressContainer"
style={{ transform: isPlaying ? "skew(-14deg, 0deg)" : "none" }}
>
<div className="progress">
<div className="barOverflow">
<div
className="bar"
style={{ transform: `rotate(${rotationAngle}deg)` }}
></div>
</div>
</div>
</div>
<div className="scaleLabels">
<span>0%</span>
<div className="centerText">
<div className="percentage">{progress}%</div>
<div className="status">Optimal</div>
</div>
<span>100%</span>
</div>
</>
);
};
export default FleetEfficiencyComponent;

View File

@ -128,6 +128,12 @@ const AddButtons: React.FC<ButtonsProps> = ({
const cleanPanel = async (side: Side) => {
//add api
// console.log('side: ', side);
if (
hiddenPanels[selectedZone.zoneId]?.includes(side) ||
selectedZone.lockedPanels.includes(side)
)
return;
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
@ -197,13 +203,12 @@ const AddButtons: React.FC<ButtonsProps> = ({
}
setSelectedZone(updatedZone);
if (hiddenPanels[selectedZone.zoneId]?.includes(side)) {
setHiddenPanels(prev => ({
setHiddenPanels((prev) => ({
...prev,
[selectedZone.zoneId]: prev[selectedZone.zoneId].filter(s => s !== side)
[selectedZone.zoneId]: prev[selectedZone.zoneId].filter(
(s) => s !== side
),
}));
}
@ -284,10 +289,11 @@ const AddButtons: React.FC<ButtonsProps> = ({
<div className="extra-Bs">
{/* Hide Panel */}
<div
className={`icon ${hiddenPanels[selectedZone.zoneId]?.includes(side)
? "active"
: ""
}`}
className={`icon ${
hiddenPanels[selectedZone.zoneId]?.includes(side)
? "active"
: ""
}`}
title={
hiddenPanels[selectedZone.zoneId]?.includes(side)
? "Show Panel"
@ -309,6 +315,13 @@ const AddButtons: React.FC<ButtonsProps> = ({
className="icon"
title="Clean Panel"
onClick={() => cleanPanel(side)}
style={{
cursor:
hiddenPanels[selectedZone.zoneId]?.includes(side) ||
selectedZone.lockedPanels.includes(side)
? "not-allowed"
: "pointer",
}}
>
<CleanPannel />
</div>

View File

@ -344,6 +344,45 @@ input {
padding: 10px;
}
.loading {
position: absolute;
bottom: 0;
left: 0;
height: 2px;
/* slim progress bar */
width: 100%;
overflow: hidden;
background: rgba(0, 0, 0, 0.05);
/* optional track background */
}
.loading::before {
content: "";
position: absolute;
top: 0;
left: -50%;
height: 100%;
width: 50%;
background: linear-gradient(to right,
var(--accent-color),
transparent);
animation: loadingAnimation 1.2s linear infinite;
border-radius: 4px;
}
@keyframes loadingAnimation {
0% {
left: -50%;
}
100% {
left: 100%;
}
}
.dropdown-item {
display: block;
padding: 5px 10px;

View File

@ -480,3 +480,6 @@
}
// progress should be progress {progress}