Merge pull request 'ui' (#57) from ui into main

Reviewed-on: http://185.100.212.76:7776/Dwinzo-Beta/Dwinzo_dev/pulls/57
This commit is contained in:
Vishnu 2025-04-11 03:39:44 +00:00
commit 8ed035b969
15 changed files with 200 additions and 164 deletions

View File

@ -88,22 +88,22 @@ const Templates = () => {
return (
<div className="template-list">
{templates.map((template, index) => (
<div key={template.id} className="template-item">
<div
key={template.id}
className="template-item"
onClick={() => handleLoadTemplate(template)}
>
{template?.snapshot && (
<div className="template-image-container">
<img
src={template.snapshot}
alt={`${template.name} preview`}
className="template-image"
onClick={() => handleLoadTemplate(template)}
/>
</div>
)}
<div className="template-details">
<div
onClick={() => handleLoadTemplate(template)}
className="template-name"
>
<div className="template-name">
{/* {`Template ${index + 1}`} */}
<RenameInput value={`Template ${index + 1}`} />
</div>

View File

@ -104,7 +104,7 @@ const ProgressBarWidget = ({
const Widgets2D = () => {
return (
<div className="widget2D">
<div className="widget2D widgets-wrapper">
<div className="chart-container">
{chartTypes.map((type, index) => {
const widgetTitle = `Widget ${index + 1}`;

View File

@ -12,22 +12,21 @@ const Widgets3D = () => {
];
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
return (
<div className="widgets-container widget3D">
<div className="widgets-container widgets-wrapper widget3D">
{widgets?.map((widget, index) => (
<div
key={index}
className="widget-item"
draggable
onDragStart={(e) => {
let name = widget.name
let crt = e.target
let name = widget.name;
let crt = e.target;
if (crt instanceof HTMLElement) {
const widget = crt.cloneNode(true) as HTMLElement;
e.dataTransfer.setDragImage(widget, 0, 0)
e.dataTransfer.effectAllowed = "move"
e.dataTransfer.setData("text/plain", "ui-" + name)
e.dataTransfer.setDragImage(widget, 0, 0);
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("text/plain", "ui-" + name);
}
}}
onPointerDown={() => {
@ -42,7 +41,7 @@ const Widgets3D = () => {
className="widget-image"
src={widget.img}
alt={widget.name}
draggable={false}
draggable={false}
/>
</div>
))}

View File

@ -122,7 +122,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
// // Update the selectedZone state
// setSelectedZone(updatedZone);
// }
};
// Function to clean all widgets from a panel
@ -170,6 +169,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
// Function to handle "+" button click
const handlePlusButtonClick = async (side: Side) => {
if (selectedZone.activeSides.includes(side)) {
console.log("open");
// Panel already exists: Remove widgets from that side and update activeSides
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
@ -254,7 +254,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
// } else {
//
// }
} catch (error) { }
} catch (error) {}
}
};
return (
@ -264,8 +264,9 @@ const AddButtons: React.FC<ButtonsProps> = ({
<div key={side} className={`side-button-container ${side}`}>
{/* "+" Button */}
<button
className={`side-button ${side}${selectedZone.activeSides.includes(side) ? " active" : ""
}`}
className={`side-button ${side}${
selectedZone.activeSides.includes(side) ? " active" : ""
}`}
onClick={() => handlePlusButtonClick(side)}
title={
selectedZone.activeSides.includes(side)
@ -314,8 +315,9 @@ const AddButtons: React.FC<ButtonsProps> = ({
{/* Lock/Unlock Panel */}
<div
className={`icon ${selectedZone.lockedPanels.includes(side) ? "active" : ""
}`}
className={`icon ${
selectedZone.lockedPanels.includes(side) ? "active" : ""
}`}
title={
selectedZone.lockedPanels.includes(side)
? "Unlock Panel"

View File

@ -13,6 +13,10 @@ import { get3dWidgetZoneData } from "../../../services/realTimeVisulization/zone
// Define the type for `Side`
type Side = "top" | "bottom" | "left" | "right";
interface HiddenPanels {
[zoneId: string]: Side[];
}
interface DisplayZoneProps {
zonesData: {
[key: string]: {
@ -62,12 +66,15 @@ interface DisplayZoneProps {
}[];
}>
>;
hiddenPanels: HiddenPanels; // Updated prop type
setHiddenPanels: React.Dispatch<React.SetStateAction<HiddenPanels>>; // Updated prop type
}
const DisplayZone: React.FC<DisplayZoneProps> = ({
zonesData,
selectedZone,
setSelectedZone,
hiddenPanels,
}) => {
// Ref for the container element
const containerRef = useRef<HTMLDivElement | null>(null);
@ -155,7 +162,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);
@ -191,7 +198,12 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
return (
<div
ref={containerRef}
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") ? "bottom" : ""}`}
className={`zone-wrapper ${
selectedZone?.activeSides?.includes("bottom") &&
!hiddenPanels[selectedZone.zoneId]?.includes("bottom")
? "bottom"
: ""
}`}
>
{/* Left Arrow */}
{showLeftArrow && (
@ -211,8 +223,12 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
{Object.keys(zonesData).map((zoneName, index) => (
<div
key={index}
className={`zone ${selectedZone.zoneName === zoneName ? "active" : ""}`}
onClick={() => handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)}
className={`zone ${
selectedZone.zoneName === zoneName ? "active" : ""
}`}
onClick={() =>
handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)
}
>
{zoneName}
</div>

View File

@ -95,6 +95,7 @@ export const DraggableWidget = ({
"floating",
"sidebar-right-wrapper",
"card",
"dropdown-menu",
],
setMenuVisible: () => setSelectedChartId(null),
});

View File

@ -49,6 +49,7 @@ const DroppedObjects: React.FC = () => {
const { visualizationSocket } = useSocketStore();
const { isPlaying } = usePlayButtonStore();
const zones = useDroppedObjectsStore((state) => state.zones);
const [openKebabId, setOpenKebabId] = useState<string | null>(null);
const updateObjectPosition = useDroppedObjectsStore(
(state) => state.updateObjectPosition
@ -70,6 +71,7 @@ const DroppedObjects: React.FC = () => {
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";
@ -143,7 +145,7 @@ const DroppedObjects: React.FC = () => {
// if (res.message === "FloatingWidget deleted successfully") {
// deleteObject(zoneName, id, index); // Call the deleteObject method from the store
// }
} catch (error) { }
} catch (error) {}
}
const handlePointerDown = (event: React.PointerEvent, index: number) => {
@ -519,41 +521,46 @@ const DroppedObjects: React.FC = () => {
{zone.objects.map((obj, index) => {
const topPosition =
typeof obj.position.top === "number"
? `calc(${obj.position.top}px + ${isPlaying && selectedZone.activeSides.includes("top")
? `${heightMultiplier - 55}px`
: "0px"
})`
? `calc(${obj.position.top}px + ${
isPlaying && selectedZone.activeSides.includes("top")
? `${heightMultiplier - 55}px`
: "0px"
})`
: "auto";
const leftPosition =
typeof obj.position.left === "number"
? `calc(${obj.position.left}px + ${isPlaying && selectedZone.activeSides.includes("left")
? `${widthMultiplier - 100}px`
: "0px"
})`
? `calc(${obj.position.left}px + ${
isPlaying && selectedZone.activeSides.includes("left")
? `${widthMultiplier - 100}px`
: "0px"
})`
: "auto";
const rightPosition =
typeof obj.position.right === "number"
? `calc(${obj.position.right}px + ${isPlaying && selectedZone.activeSides.includes("right")
? `${widthMultiplier - 100}px`
: "0px"
})`
? `calc(${obj.position.right}px + ${
isPlaying && selectedZone.activeSides.includes("right")
? `${widthMultiplier - 100}px`
: "0px"
})`
: "auto";
const bottomPosition =
typeof obj.position.bottom === "number"
? `calc(${obj.position.bottom}px + ${isPlaying && selectedZone.activeSides.includes("bottom")
? `${heightMultiplier - 55}px`
: "0px"
})`
? `calc(${obj.position.bottom}px + ${
isPlaying && selectedZone.activeSides.includes("bottom")
? `${heightMultiplier - 55}px`
: "0px"
})`
: "auto";
return (
<div
key={`${zoneName}-${index}`}
className={`${obj.className} ${selectedChartId?.id === obj.id && "activeChart"
}`}
className={`${obj.className} ${
selectedChartId?.id === obj.id && "activeChart"
}`}
ref={chartWidget}
style={{
position: "absolute",
@ -561,6 +568,7 @@ const DroppedObjects: React.FC = () => {
left: leftPosition,
right: rightPosition,
bottom: bottomPosition,
pointerEvents: isPlaying ? "none" : "auto",
minHeight: `${obj.className === "warehouseThroughput" && "150px !important"} `
}}
onPointerDown={(event) => {

View File

@ -166,8 +166,8 @@ const Panel: React.FC<PanelProps> = ({
// Calculate panel capacity
const calculatePanelCapacity = (panel: Side) => {
const CHART_WIDTH = panelSize;
const CHART_HEIGHT = panelSize;
const CHART_WIDTH = panelSize - 10;
const CHART_HEIGHT = panelSize - 10;
const dimensions = panelDimensions[panel];
if (!dimensions) {

View File

@ -298,6 +298,8 @@ const RealTimeVisulization: React.FC = () => {
zonesData={zonesData}
selectedZone={selectedZone}
setSelectedZone={setSelectedZone}
hiddenPanels={hiddenPanels}
setHiddenPanels={setHiddenPanels}
/>
{!isPlaying && selectedZone?.zoneName !== "" && (

View File

@ -1,103 +1,96 @@
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 { WalletIcon } from '../../../icons/3dChartIcons';
import { WalletIcon } from "../../../icons/3dChartIcons";
const TotalCardComponent = ({ 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 TotalCardComponent = ({
object
}: any) => {
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
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();
useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0)
return;
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
const socket = io(`http://${iotApiUrl}`);
useEffect(() => {
if (!iotApiUrl || !measurements || Object.keys(measurements).length === 0) return;
const socket = io(`http://${iotApiUrl}`);
const inputData = {
measurements,
duration,
interval: 1000,
};
const startStream = () => {
socket.emit("lastInput", inputData);
};
socket.on("connect", startStream);
socket.on("lastOutput", (response) => {
const responseData = response.input1;
if (typeof responseData === "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);
}
}
const inputData = {
measurements,
duration,
interval: 1000,
};
const startStream = () => {
socket.emit("lastInput", inputData);
};
socket.on("connect", startStream);
socket.on("lastOutput", (response) => {
const responseData = response.input1;
if (typeof responseData === "number") {
setProgress(responseData);
}
useEffect(() => {
fetchSavedInputes();
}, []);
useEffect(() => {
if (selectedChartId?.id === object?.id) {
fetchSavedInputes();
});
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 {
}
}
,[header, flotingDuration, flotingMeasurements])
} catch (error) {}
}
};
return (
<>
<div className="header-wrapper" >
<div className="header">{name}</div>
<div className="data-values">
<div className="value">{progress}</div>
<div className="per">{object.per}</div>
</div>
</div>
<div className="icon">
<WalletIcon />
</div>
</>
)
}
useEffect(() => {
fetchSavedInputes();
}, []);
export default TotalCardComponent
useEffect(() => {
if (selectedChartId?.id === object?.id) {
fetchSavedInputes();
}
}, [header, flotingDuration, flotingMeasurements]);
return (
<>
<div className="header-wrapper">
<div className="header">{name}</div>
<div className="data-values">
<div className="value">{progress}</div>
<div className="per">{object.per}</div>
</div>
</div>
<div className="icon">
<WalletIcon />
</div>
</>
);
};
export default TotalCardComponent;

View File

@ -1,22 +1,33 @@
// import html2canvas from "html2canvas";
import html2canvas from "html2canvas";
export const captureVisualization = async (): Promise<string | null> => {
const container = document.getElementById("real-time-vis-canvas");
if (!container) return null;
if (!container) {
console.error("Container element not found");
return null;
}
try {
// Use html2canvas to capture the container
// const canvas = await html2canvas(container, {
// scale: 1, // Adjust scale for higher/lower resolution
// });
// Hide any elements you don't want in the screenshot
const originalVisibility = container.style.visibility;
container.style.visibility = 'visible';
const canvas = await html2canvas(container, {
scale: 2, // Higher scale for better quality
logging: false, // Disable console logging
useCORS: true, // Handle cross-origin images
allowTaint: true, // Allow tainted canvas
backgroundColor: '#ffffff', // Set white background
removeContainer: true // Clean up temporary containers
});
// // Convert the canvas to a data URL (PNG format)
// const dataUrl = canvas.toDataURL("image/png");
// return dataUrl;
// Restore original visibility
container.style.visibility = originalVisibility;
return null;
// Convert to PNG with highest quality
return canvas.toDataURL('image/png', 1.0);
} catch (error) {
console.error("Error capturing visualization:", error);
return null;
}
};
};

View File

@ -1,5 +1,3 @@
import { saveTemplateApi } from "../../services/realTimeVisulization/zoneData/saveTempleteApi";
import { useSocketStore } from "../../store/store";
import { Template } from "../../store/useTemplateStore";
import { captureVisualization } from "./captureVisualization";
@ -28,7 +26,7 @@ export const handleSaveTemplate = async ({
templates = [],
visualizationSocket,
}: HandleSaveTemplateProps): Promise<void> => {
console.log('floatingWidget: ', floatingWidget);
console.log("floatingWidget: ", floatingWidget);
try {
// Check if the selected zone has any widgets
if (!selectedZone.panelOrder || selectedZone.panelOrder.length === 0) {
@ -49,12 +47,12 @@ export const handleSaveTemplate = async ({
}
// Capture visualization snapshot
// const snapshot = await captureVisualization();
const snapshot = null;
const snapshot = await captureVisualization();
if (!snapshot) {
return;
}
// if (!snapshot) {
// return;
// }
// Create a new template
const newTemplate: Template = {
id: generateUniqueId(),

View File

@ -59,6 +59,7 @@ input {
.toggle-header-container {
@include flex-center;
padding: 6px 12px;
margin: 6px 0;
.toggle-header-item {
width: 100%;
@ -567,6 +568,7 @@ input {
.input-value {
width: 42px;
text-align: center;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
@ -669,4 +671,4 @@ input {
.multi-email-invite-input.active {
border: 1px solid var(--accent-color);
}
}
}

View File

@ -66,9 +66,9 @@
.sidebar-left-content-container {
border-bottom: 1px solid var(--border-color);
// flex: 1;
height: calc(100% - 36px);
// height: calc(100% - 36px);
position: relative;
overflow: auto;
// overflow: auto;
.template-list {
display: flex;
@ -131,8 +131,12 @@
}
.widget-left-sideBar {
min-height: 50vh;
max-height: 60vh;
.widgets-wrapper {
min-height: 50vh;
max-height: 60vh;
overflow: auto;
}
.widget2D {
overflow: auto;

View File

@ -365,7 +365,7 @@
.panel.hidePanel {
pointer-events: none;
opacity: 0.1;
opacity: 0;
}
}