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

This commit is contained in:
2025-03-28 14:19:43 +05:30
49 changed files with 3088 additions and 1862 deletions

View File

@@ -1,6 +1,8 @@
import React, { useEffect, useRef, useState, useCallback } from "react";
import { Widget } from "../../../store/useWidgetStore";
import { MoveArrowLeft, MoveArrowRight } from "../../icons/SimulationIcons";
import { InfoIcon } from "../../icons/ExportCommonIcons";
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
// Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right";
@@ -154,33 +156,40 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
)}
{/* Zones Wrapper */}
<div ref={containerRef} className="zones-wrapper">
{Object.keys(zonesData).map((zoneName, index) => (
<div
key={index}
className={`zone ${
selectedZone.zoneName === zoneName ? "active" : ""
}`}
onClick={() => {
console.log("zoneName: ", zoneName);
setSelectedZone({
zoneName,
activeSides: zonesData[zoneName].activeSides || [],
panelOrder: zonesData[zoneName].panelOrder || [],
lockedPanels: zonesData[zoneName].lockedPanels || [],
widgets: zonesData[zoneName].widgets || [],
zoneId: zonesData[zoneName]?.zoneId || "",
zoneViewPortTarget:
zonesData[zoneName].zoneViewPortTarget || [],
zoneViewPortPosition:
zonesData[zoneName].zoneViewPortPosition || [],
});
}}
>
{zoneName}
</div>
))}
</div>
{Object.keys(zonesData).length !== 0 ? (
<div ref={containerRef} className="zones-wrapper">
{Object.keys(zonesData).map((zoneName, index) => (
<div
key={index}
className={`zone ${
selectedZone.zoneName === zoneName ? "active" : ""
}`}
onClick={() => {
useDroppedObjectsStore.getState().setZone(zoneName, zonesData[zoneName]?.zoneId);
setSelectedZone({
zoneName,
activeSides: zonesData[zoneName].activeSides || [],
panelOrder: zonesData[zoneName].panelOrder || [],
lockedPanels: zonesData[zoneName].lockedPanels || [],
widgets: zonesData[zoneName].widgets || [],
zoneId: zonesData[zoneName]?.zoneId || "",
zoneViewPortTarget:
zonesData[zoneName].zoneViewPortTarget || [],
zoneViewPortPosition:
zonesData[zoneName].zoneViewPortPosition || [],
});
}}
>
{zoneName}
</div>
))}
</div>
) : (
<div className="no-zone">
<InfoIcon />
No zones? Create one!
</div>
)}
{/* Right Arrow */}
{showRightArrow && (

View File

@@ -1,89 +1,215 @@
// import { useState } from "react";
// import { useThree } from "@react-three/fiber";
// import * as THREE from "three";
// const DroppedObjects = () => {
// const { camera } = useThree(); // Now inside Canvas ✅
// const [objects, setObjects] = useState<{ id: number; position: [number, number, number] }[]>([]);
// // Function to convert drop event into 3D position
// const handleDrop = (event: DragEvent) => {
// event.preventDefault();
// const data = event.dataTransfer?.getData("text/plain");
// if (!data) return;
// try {
// const cardData = JSON.parse(data);
// if (!cardData.className.includes("floating total-card")) {
// console.log("Drop rejected: Incorrect element.");
// return;
// }
// // Convert 2D drop position to 3D world coordinates
// const x = (event.clientX / window.innerWidth) * 2 - 1;
// const y = -(event.clientY / window.innerHeight) * 2 + 1;
// // Raycasting to determine the drop position in 3D
// const raycaster = new THREE.Raycaster();
// const mouseVector = new THREE.Vector2(x, y);
// raycaster.setFromCamera(mouseVector, camera);
// // Intersect with a ground plane (assume y = 0)
// const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
// const intersection = new THREE.Vector3();
// raycaster.ray.intersectPlane(groundPlane, intersection);
// console.log("Spawn Object at:", intersection);
// // Add the dropped object to the scene state
// setObjects((prev) => [...prev, { id: Date.now(), position: [intersection.x, intersection.y, intersection.z] }]);
// } catch (error) {
// console.error("Invalid data:", error);
// }
// };
// return (
// <group>
// {/* Render dropped objects as green boxes */}
// {objects.map((obj) => (
// <mesh key={obj.id} position={obj.position}>
// <boxGeometry args={[1, 1, 1]} />
// <meshStandardMaterial color="green" />
// </mesh>
// ))}
// </group>
// );
// };
import { Html } from "@react-three/drei";
import { useDroppedObjectsStore } from "../../../store/store";
import { WalletIcon } from "../../icons/3dChartIcons";
import { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import { useDroppedObjectsStore, Zones } from "../../../store/useDroppedObjectsStore";
const DroppedObjects: React.FC = () => {
const objects = useDroppedObjectsStore((state) => state.objects); // Get objects from Zustand store
console.log('objects: ', objects);
const zones = useDroppedObjectsStore((state) => state.zones);
const updateObjectPosition = useDroppedObjectsStore((state) => state.updateObjectPosition);
const [draggingIndex, setDraggingIndex] = useState<{ zone: string; index: number } | null>(null);
const [offset, setOffset] = useState<[number, number] | null>(null);
const positionRef = useRef<[number, number] | null>(null);
const animationRef = useRef<number | null>(null);
// useEffect(() => {
// const initialZones: Record<string, Zones> = {
// "Zone 1": {
// zoneName: "Zone 1",
// zoneId: "2e996073-546c-470c-8323-55bd3700c6aa",
// objects: [
// {
// header: "Todays Money",
// value: 53000, // ✅ Converted to number
// per: "+55%",
// className: "floating total-card",
// position: [146, 214], // ✅ No need for 'as' here
// },
// {
// header: "New Clients",
// value: 250, // ✅ Converted to number
// per: "+12%",
// className: "floating total-card",
// position: [344, 295],
// },
// ],
// },
// };
// useDroppedObjectsStore.setState({ zones: initialZones });
// }, []);
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
function handlePointerDown(event: React.PointerEvent, index: number) {
const obj = zone.objects[index];
const offsetX = event.clientX - obj.position[1];
const offsetY = event.clientY - obj.position[0];
setDraggingIndex({ zone: zoneName, index });
setOffset([offsetY, offsetX]);
}
function handlePointerMove(event: React.PointerEvent) {
if (!draggingIndex || !offset) return;
const container = document.getElementById("real-time-vis-canvas");
if (!container) return;
const rect = container.getBoundingClientRect();
let newX = event.clientX - offset[1];
let newY = event.clientY - offset[0];
newX = Math.max(0, Math.min(rect.width - 50, newX));
newY = Math.max(0, Math.min(rect.height - 50, newY));
positionRef.current = [newY, newX];
if (!animationRef.current) {
animationRef.current = requestAnimationFrame(() => {
if (positionRef.current) {
updateObjectPosition(zoneName, draggingIndex.index, positionRef.current);
}
animationRef.current = null;
});
}
}
function handlePointerUp() {
setDraggingIndex(null);
setOffset(null);
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
animationRef.current = null;
}
}
return (
<>
{objects.map((obj, index) => (
<group key={index} position={[Math.random() * 5, Math.random() * 5, 0]}>
<Html wrapperClass={obj.className}>
<div style={{ padding: "10px", background: "#fff", borderRadius: "6px" }}>
<div className="header">{obj.header}</div>
<div className="data-values">
<div className="value">{obj.value}</div>
<div className="per">{obj.per}</div>
<div onPointerMove={handlePointerMove} onPointerUp={handlePointerUp}>
{zone.objects.map((obj, index) => (
<div
key={`${zoneName}-${index}`}
className={obj.className}
style={{
top: obj.position[0] + "px",
left: obj.position[1] + "px",
transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out",
}}
onPointerDown={(event) => handlePointerDown(event, index)}
>
{obj.className === "floating total-card" ? (
<div>
<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>
</div>
</Html>
</group>
) : obj.className === "warehouseThroughput floating" ? (
<div>
<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>
</div>
) : obj.className === "fleetEfficiency floating" ? (
<div>
<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>
</div>
) : null}
</div>
))}
</>
</div>
);
};
export default DroppedObjects;
// import { Html } from "@react-three/drei";
// import { useDroppedObjectsStore } from "../../../store/store";
// import { CartIcon, DocumentIcon, GlobeIcon, WalletIcon } from "../../icons/3dChartIcons";
// import SimpleCard from "../realTimeVis/floating/SimpleCard";
// const ICON_MAP: Record<string, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
// WalletIcon,
// GlobeIcon,
// DocumentIcon,
// CartIcon
// };
// const DroppedObjects: React.FC = () => {
// const objects = useDroppedObjectsStore((state) => state.objects); // Get objects from Zustand store
//
// return (
// <>
// {objects.map((obj, index) => {
// const IconComponent = obj.Icon || WalletIcon; // Use obj.Icon directly if it exists
// return (
// <SimpleCard
// key={index}
// position={obj.position}
// header={obj.header}
// icon={IconComponent} // ✅ No need to look it up in ICON_MAP
// value={obj.value}
// per={obj.per}
// />
// );
// })}
// </>
// );
// };
// export default DroppedObjects;

View File

@@ -6,8 +6,10 @@ import { useSelectedZoneStore } from "../../../store/useZoneStore";
import DisplayZone from "./DisplayZone";
import Scene from "../../../modules/scene/scene";
import useModuleStore from "../../../store/useModuleStore";
import { useDroppedObjectsStore, useZones } from "../../../store/store";
import DroppedObjects from "./DroppedFloatingWidgets";
import { useDroppedObjectsStore } from "../../../store/useDroppedObjectsStore";
import { useZones } from "../../../store/store";
type Side = "top" | "bottom" | "left" | "right";
@@ -80,36 +82,36 @@ const RealTimeVisulization: React.FC = () => {
});
}, [selectedZone]);
// const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
// console.log("Drop event fired! ✅");
// event.preventDefault();
// const data = event.dataTransfer.getData("text/plain");
// if (!data) {
// console.log("❌ No data received on drop!");
// return;
// }
// try {
// const droppedData = JSON.parse(data);
// console.log("✅ Dropped Data:", droppedData);
// console.log('droppedData: ', droppedData);
// setDroppedObjects((prev) => [...prev, droppedData]); // ✅ Add to state
// console.log(droppedObjects);
// } catch (error) {
// console.error("❌ Error parsing dropped data:", error);
// }
// };
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
const data = event.dataTransfer.getData("text/plain"); // Use "text/plain" to match the drag event
if (data) {
const droppedData = JSON.parse(data);
useDroppedObjectsStore.getState().addObject(droppedData); // Add to Zustand store
const data = event.dataTransfer.getData("text/plain");
if (!data || !selectedZone.zoneName) return;
const droppedData = JSON.parse(data);
const canvasElement = document.getElementById("real-time-vis-canvas");
if (!canvasElement) return;
const canvasRect = canvasElement.getBoundingClientRect();
const relativeX = event.clientX - canvasRect.left;
const relativeY = event.clientY - canvasRect.top;
const newObject = {
...droppedData,
position: [relativeY, relativeX], // Y first because of top/left style
};
console.log("newObject: ", newObject);
// Only set zone if its not already in the store (prevents overwriting objects)
const existingZone = useDroppedObjectsStore.getState().zones[selectedZone.zoneName];
if (!existingZone) {
useDroppedObjectsStore.getState().setZone(selectedZone.zoneName, selectedZone.zoneId);
}
// Add the dropped object to the zone
useDroppedObjectsStore.getState().addObject(selectedZone.zoneName, newObject);
};
return (
<div
@@ -133,9 +135,10 @@ const RealTimeVisulization: React.FC = () => {
onDrop={(event) => handleDrop(event)}
onDragOver={(event) => event.preventDefault()}
>
<Scene />
</div>
<DroppedObjects />
{activeModule === "visualization" && (
<>
<DisplayZone

View File

@@ -47,12 +47,9 @@ export default function ZoneCentreTarget() {
const worldUp = new THREE.Vector3(0, 0, 1);
const right = new THREE.Vector3().crossVectors(worldUp, direction).normalize();
const up = new THREE.Vector3().crossVectors(direction, right).normalize();
const offsetPosition = up.clone().multiplyScalar(20);
camPosition.add(offsetPosition);
const setCam = async () => {
controls.setLookAt(centrePoint[0], 100, centrePoint[2], ...centrePoint, true);
setTimeout(() => {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React from "react";
interface InputToggleProps {
label: string; // Represents the toggle state (on/off)
@@ -7,23 +7,18 @@ interface InputToggleProps {
inputKey: string;
}
// Update InputToggle.tsx to be fully controlled
const InputToggle: React.FC<InputToggleProps> = ({
label,
onClick,
value = false,
inputKey,
}) => {
const [activeValue, setActiveValue] = useState<boolean>(value);
// Remove internal state and use the value prop directly
function handleOnClick() {
setActiveValue(!activeValue);
if (onClick) onClick();
}
useEffect(() => {
setActiveValue(value);
}, [value]);
return (
<div className="input-toggle-container">
<label htmlFor={`toogle-input-${inputKey}`} className="label">
@@ -33,15 +28,16 @@ const InputToggle: React.FC<InputToggleProps> = ({
<div
className="check-box-style"
style={{
left: activeValue ? "50%" : "2px",
background: activeValue ? "" : "var(--text-disabled)",
left: value ? "50%" : "2px",
background: value ? "" : "var(--text-disabled)",
}}
></div>
<input
type="checkbox"
name=""
id={`toogle-input-${inputKey}`}
defaultChecked={value}
checked={value}
readOnly
/>
</div>
</div>

View File

@@ -4,21 +4,27 @@ import RenameInput from "./RenameInput";
type InputWithDropDownProps = {
label: string;
value: string;
min?: number
defaultValue?: string;
options?: string[]; // Array of dropdown options
activeOption?: string; // The currently active dropdown option
onClick?: () => void;
onChange: (newValue: string) => void;
editableLabel?: boolean;
placeholder?: string; // New placeholder prop
};
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
label,
value,
min,
defaultValue,
options,
activeOption,
onClick,
onChange,
editableLabel = false,
placeholder = "Inherit", // Default empty placeholder
}) => {
const separatedWords = label
.split(/(?=[A-Z])/)
@@ -38,11 +44,13 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
)}
<div className="input default" id={separatedWords}>
<input
type="text"
min={min}
type="number"
defaultValue={value}
onChange={(e) => {
onChange(e.target.value);
}}
placeholder={placeholder} // Added placeholder prop
/>
{activeOption && (
@@ -73,4 +81,4 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
);
};
export default InputWithDropDown;
export default InputWithDropDown;

View File

@@ -1,29 +1,49 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import RegularDropDown from "./RegularDropDown";
type LabledDropdownProps = {
defaultOption: string; // Initial active option
options: string[]; // Array of dropdown options
label?: string; // Customizable label text
onSelect?: (option: string) => void; // Callback when option is selected
className?: string; // Additional className for styling
disabled?: boolean; // Disable dropdown
search?: boolean; // Enable/disable search functionality
};
const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options }) => {
const [activeOption, setActiveOption] = useState(defaultOption); // State for active option
const LabledDropdown: React.FC<LabledDropdownProps> = ({
defaultOption,
options,
label = "Type",
onSelect,
className = "",
search = false
}) => {
const [activeOption, setActiveOption] = useState(defaultOption);
// Update active option if defaultOption changes
useEffect(() => {
setActiveOption(defaultOption);
}, [defaultOption]);
const handleSelect = (option: string) => {
setActiveOption(option); // Update the active option state
setActiveOption(option);
if (onSelect) {
onSelect(option);
}
};
return (
<div className="value-field-container">
<div className="label">Type</div>
<div className={`value-field-container ${className}`}>
<div className="label">{label}</div>
<RegularDropDown
header={activeOption} // Display the current active option
options={options} // Use the options from props
onSelect={handleSelect} // Handle option selection
search = {false}
header={activeOption}
options={options}
onSelect={handleSelect}
search={search}
/>
</div>
);
};
export default LabledDropdown;
export default LabledDropdown;

View File

@@ -201,18 +201,16 @@ const LineGraphComponent = ({
const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
useEffect(() => {
if ( measurements.length > 0 ) {
const socket = io("http://192.168.0.192:5010");
const socket = io("http://192.168.0.192:5010");
if ( measurements.length > 0 ) {
var inputes = {
measurements: measurements,
duration: duration,
interval: 1000,
}
console.log('graphHHHHHHHHHHHHHHHHHHHHHHHHHHHHH',inputes);
// Start stream
const startStream = () => {
socket.emit("lineInput", inputes);
@@ -226,23 +224,24 @@ const LineGraphComponent = ({
// Extract timestamps and values
const labels = responceData.time;
const datasets = data.measurements.map((measurement: any) => ({
label: `${measurement.name}.${measurement.fields}`,
data: responceData[`${measurement.name}.${measurement.fields}`]?.values || [],
backgroundColor: themeColor[0] || "#5c87df",
borderColor: themeColor[1] || "#ffffff",
borderWidth: 2,
// fill: false,
}));
const datasets = measurements.map((measurement: any) => {
const key = `${measurement.name}.${measurement.fields}`;
return {
label: key,
data: responceData[key]?.values ?? [], // Ensure it exists
backgroundColor: themeColor[0] || "#5c87df",
borderColor: themeColor[1] || "#ffffff",
};
});
setChartData({ labels, datasets });
});
}
return () => {
socket.off("lineOutput");
socket.emit("stop_stream"); // Stop streaming when component unmounts
};
}
}, [measurements, duration]);
// useEffect(() => {

View File

@@ -4,8 +4,24 @@ const FleetEfficiency = () => {
// Calculate the rotation angle for the progress bar
const rotationAngle = -90 + progress * 3.6; // Progress starts from the left (-90°)
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
const rect = event.currentTarget.getBoundingClientRect(); // Get position
const cardData = JSON.stringify({
className: event.currentTarget.className,
position: [rect.top, rect.left], // Store position
value: rotationAngle, // Example value (you can change if dynamic)
per: progress,
});
console.log("Dragged Data:", cardData);
event.dataTransfer.setData("text/plain", cardData);
};
return (
<div className="fleetEfficiency floating">
<div className="fleetEfficiency floating" draggable onDragStart={handleDragStart}>
<h2 className="header">Fleet Efficiency</h2>
<div className="progressContainer">

View File

@@ -5,6 +5,7 @@ interface SimpleCardProps {
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; // React component for SVG icon
value: string;
per: string; // Percentage change
position?: [number, number]
}
const SimpleCard: React.FC<SimpleCardProps> = ({
@@ -12,23 +13,30 @@ const SimpleCard: React.FC<SimpleCardProps> = ({
icon: Icon,
value,
per,
position = [0, 0],
}) => {
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
const rect = event.currentTarget.getBoundingClientRect(); // Get position
const cardData = JSON.stringify({
header,
value,
per,
className: event.currentTarget.className,
icon: Icon,
className: event.currentTarget.className,
position: [rect.top, rect.left], // ✅ Store position
});
console.log("Dragging Data:", cardData); // ✅ Debugging log
event.dataTransfer.setData("text/plain", cardData);
};
return (
<div className="floating total-card" draggable onDragStart={handleDragStart}>
<div
className="floating total-card"
draggable
onDragStart={handleDragStart}
style={{ top: position[0], left: position[1] }} // No need for ?? 0 if position is guaranteed
>
<div className="header-wrapper">
<div className="header">{header}</div>
<div className="data-values">

View File

@@ -111,8 +111,27 @@ const WarehouseThroughput = () => {
},
};
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
const rect = event.currentTarget.getBoundingClientRect(); // Get element position
const cardData = JSON.stringify({
header: "Warehouse Throughput", // Static header
value: "+5", // Example value (you can change if dynamic)
per: "2025", // Example percentage or date
icon: "📊", // Placeholder for an icon (if needed)
className: event.currentTarget.className,
position: [rect.top, rect.left], // ✅ Store initial position
lineGraphData, // ✅ Include chart data
lineGraphOptions, // ✅ Include chart options
});
event.dataTransfer.setData("text/plain", cardData);
// event.dataTransfer.effectAllowed = "move"; // Improve drag effect
};
return (
<div className="warehouseThroughput floating" draggable>
<div className="warehouseThroughput floating" draggable onDragStart={handleDragStart}>
<div className="header">
<h2>Warehouse Throughput</h2>
<p>