first commit
This commit is contained in:
78
app/src/components/ui/compareVersion/Compare.tsx
Normal file
78
app/src/components/ui/compareVersion/Compare.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from "react";
|
||||
import { InfoIcon } from "../../icons/ShortcutIcons";
|
||||
import { SaveDiskIcon } from "../../icons/ExportCommonIcons";
|
||||
import { useCompareStore } from "../../../store/builder/store";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
|
||||
interface ComparePopUpProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ComparePopUp: React.FC<ComparePopUpProps> = ({ onClose }) => {
|
||||
const { setComparePopUp } = useCompareStore();
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["compare-wrapper", "input"],
|
||||
setMenuVisible: () => setComparePopUp(false),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="compare-container">
|
||||
<div className="compare-wrapper">
|
||||
<div className="grid-wrapper">
|
||||
<div className="header">
|
||||
Do you want to save this version before comparing?
|
||||
</div>
|
||||
|
||||
<div className="cards-container">
|
||||
<div className="card"></div>
|
||||
<div className="card"></div>
|
||||
|
||||
<div className="card-layout-wrapper">
|
||||
<div className="card-layout-container">
|
||||
<div className="tab-header">
|
||||
<div className="label-tab">Layout !</div>
|
||||
<div className="status"></div>
|
||||
</div>
|
||||
<div className="icon">
|
||||
<SaveDiskIcon />
|
||||
</div>
|
||||
<div className="skeleton-wrapper">
|
||||
<div className="skeleton"></div>
|
||||
<div className="skeleton"></div>
|
||||
<div className="skeleton"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card"></div>
|
||||
<div className="card"></div>
|
||||
</div>
|
||||
|
||||
<div className="button-wrapper">
|
||||
<div className="button-group">
|
||||
<button className="save btn" onClick={onClose}>
|
||||
Save & Continue
|
||||
</button>
|
||||
<div className="replace btn">Replace Existing Version</div>
|
||||
</div>
|
||||
<button
|
||||
className="cancel btn"
|
||||
id="compare-cancel-btn"
|
||||
onClick={() => setComparePopUp(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer">
|
||||
<InfoIcon /> Save this version and proceed.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComparePopUp;
|
||||
195
app/src/components/ui/compareVersion/CompareLayOut.tsx
Normal file
195
app/src/components/ui/compareVersion/CompareLayOut.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import React, { useState, useRef, useEffect, Suspense } from "react";
|
||||
import {
|
||||
CompareLayoutIcon,
|
||||
LayoutIcon,
|
||||
ResizerIcon,
|
||||
} from "../../icons/SimulationIcons";
|
||||
import {
|
||||
useLoadingProgress,
|
||||
useSaveVersion,
|
||||
} from "../../../store/builder/store";
|
||||
import Search from "../inputs/Search";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
import { useProductStore } from "../../../store/simulation/useProductStore";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePauseButtonStore, usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } =
|
||||
useComparisonProduct();
|
||||
const { products } = useProductStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const [width, setWidth] = useState("50vw");
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [showLayoutDropdown, setShowLayoutDropdown] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const startWidthRef = useRef<number>(0);
|
||||
const startXRef = useRef<number>(0);
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { setIsPaused } = usePauseButtonStore();
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["displayLayouts-container", "selectLayout"],
|
||||
setMenuVisible: () => setShowLayoutDropdown(false),
|
||||
});
|
||||
|
||||
const handleStartResizing = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
startXRef.current = e.clientX;
|
||||
if (wrapperRef.current) {
|
||||
startWidthRef.current = wrapperRef.current.getBoundingClientRect().width;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isResizing || !wrapperRef.current) return;
|
||||
|
||||
const dx = startXRef.current - e.clientX;
|
||||
const newWidthPx = startWidthRef.current + dx;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const newWidthVw = (newWidthPx / viewportWidth) * 100;
|
||||
|
||||
if (newWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
} else if (newWidthVw <= 90) {
|
||||
setWidth(`${newWidthPx}px`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!isResizing) return;
|
||||
|
||||
if (wrapperRef.current) {
|
||||
const finalWidthPx = wrapperRef.current.getBoundingClientRect().width;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const finalWidthVw = (finalWidthPx / viewportWidth) * 100;
|
||||
|
||||
if (finalWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
setIsVersionSaved(false);
|
||||
clearComparisonProduct();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
setWidth(`${finalWidthVw}vw`);
|
||||
}
|
||||
}
|
||||
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isResizing) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.add("resizing-active");
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.remove("resizing-active");
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isResizing]);
|
||||
|
||||
// Maintain proportional width on window resize
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (!wrapperRef.current || isResizing) return;
|
||||
|
||||
const currentWidth = wrapperRef.current.style.width;
|
||||
if (currentWidth === "0px" || currentWidth.endsWith("vw")) return;
|
||||
|
||||
const pxWidth = parseFloat(currentWidth);
|
||||
const vwWidth = (pxWidth / window.innerWidth) * 100;
|
||||
setWidth(`${vwWidth}vw`);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [isResizing]);
|
||||
|
||||
const handleSelectLayout = (option: string) => {
|
||||
const product = products.find((product) => product.productName === option);
|
||||
if (product) {
|
||||
setComparisonProduct(product.productUuid, product.productName);
|
||||
setLoadingProgress(1);
|
||||
setIsPlaying(true);
|
||||
setIsPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`compareLayOut-wrapper ${width === "0px" ? "closed" : ""}`}
|
||||
ref={wrapperRef}
|
||||
style={{ width }}
|
||||
>
|
||||
{loadingProgress == 0 && (
|
||||
<button
|
||||
title="resize-canvas"
|
||||
id="compare-resize-slider-btn"
|
||||
className="resizer"
|
||||
onMouseDown={handleStartResizing}
|
||||
>
|
||||
<ResizerIcon />
|
||||
</button>
|
||||
)}
|
||||
<div className="chooseLayout-container">
|
||||
{comparisonProduct && (
|
||||
<div className="compare-layout-canvas-container">
|
||||
<Suspense fallback={null}>
|
||||
<Scene layout="Comparison Layout" />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{width !== "0px" &&
|
||||
!comparisonProduct && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
<div className="icon">
|
||||
<CompareLayoutIcon />
|
||||
</div>
|
||||
<div className="value">Choose Layout to compare</div>
|
||||
<button
|
||||
className="selectLayout"
|
||||
onClick={() => setShowLayoutDropdown(!showLayoutDropdown)}
|
||||
>
|
||||
Select Layout
|
||||
</button>
|
||||
|
||||
{showLayoutDropdown && (
|
||||
<div className="displayLayouts-container">
|
||||
<div className="header">Layouts</div>
|
||||
<Search onChange={() => {}} />
|
||||
<div className="layouts-container">
|
||||
{products.map((layout) => (
|
||||
<button
|
||||
key={layout.productUuid}
|
||||
className="layout-wrapper"
|
||||
onClick={() => {
|
||||
handleSelectLayout(layout.productName);
|
||||
setShowLayoutDropdown(false);
|
||||
}}
|
||||
>
|
||||
<LayoutIcon />
|
||||
<div className="layout">{layout.productName}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Always show after layout is selected */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompareLayOut;
|
||||
168
app/src/components/ui/compareVersion/ComparisonResult.tsx
Normal file
168
app/src/components/ui/compareVersion/ComparisonResult.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { useMemo } from "react";
|
||||
import PerformanceResult from "./result-card/PerformanceResult";
|
||||
import EnergyUsage from "./result-card/EnergyUsage";
|
||||
import { Bar, Line } from "react-chartjs-2";
|
||||
|
||||
const ComparisonResult = () => {
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: { display: false },
|
||||
legend: { display: false },
|
||||
},
|
||||
scales: {
|
||||
x: { display: false, grid: { display: false } },
|
||||
y: { display: false, grid: { display: false } },
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Color palette
|
||||
const purpleDark = "#6a0dad";
|
||||
const purpleLight = "#b19cd9";
|
||||
|
||||
const throughputData = {
|
||||
labels: ["Layout 1", "Layout 2"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Throughput (units/hr)",
|
||||
data: [500, 550],
|
||||
backgroundColor: [purpleDark, purpleLight],
|
||||
borderColor: [purpleDark, purpleLight],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const cycleTimeData = {
|
||||
labels: ["Layout 1", "Layout 2"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Cycle Time (sec)",
|
||||
data: [110, 110],
|
||||
backgroundColor: [purpleLight],
|
||||
borderColor: purpleDark,
|
||||
borderWidth: 2,
|
||||
tension: 0.4,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const downtimeData = {
|
||||
labels: ["Layout 1", "Layout 2"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Downtime (mins)",
|
||||
data: [17, 12],
|
||||
backgroundColor: [purpleDark, purpleLight],
|
||||
borderColor: [purpleDark, purpleLight],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const scrapRateData = {
|
||||
labels: ["Layout 1", "Layout 2"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Scrap Rate (tons)",
|
||||
data: [2.7, 1.9],
|
||||
backgroundColor: [purpleDark, purpleLight],
|
||||
borderColor: [purpleDark, purpleLight],
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="compare-result-container">
|
||||
<div className="header">Performance Comparison</div>
|
||||
<div className="compare-result-wrapper">
|
||||
<EnergyUsage />
|
||||
|
||||
<div className="throughPutCard-container comparisionCard">
|
||||
<h4>Throughput (units/hr)</h4>
|
||||
<div className="layers-wrapper">
|
||||
<div className="layer-wrapper">
|
||||
<div className="key">Layout 1</div>
|
||||
<div className="value">500/ hr</div>
|
||||
</div>
|
||||
<div className="layer-wrapper">
|
||||
<div className="key">Layout 2</div>
|
||||
<div className="value">550/ hr</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart">
|
||||
<Bar data={throughputData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="cycle-time-container comparisionCard">
|
||||
<div className="cycle-main">
|
||||
<div className="cycle-header">Cycle Time</div>
|
||||
<div className="layers-wrapper">
|
||||
<div className="layers">
|
||||
<div className="layer-name">Layout 1</div>
|
||||
<div className="layer-time">120 Sec</div>
|
||||
<div className="layer-profit">
|
||||
<span>↑</span>19.6%
|
||||
</div>
|
||||
</div>
|
||||
<div className="layers">
|
||||
<div className="layer-name">Layout 2</div>
|
||||
<div className="layer-time">110 Sec</div>
|
||||
<div className="layer-profit">
|
||||
<span>↑</span>1.6%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart">
|
||||
<Line data={cycleTimeData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overallDowntime-container comparisionCard">
|
||||
<div className="overallDowntime-header">Overall Downtime</div>
|
||||
<div className="totalDownTime-wrapper">
|
||||
<div className="totalDownTime">
|
||||
<div className="totalDownTime-right">
|
||||
<div className="totalDownTime-label">Total down time</div>
|
||||
<div className="totalDownTime-subLabel">(Simulation 1)</div>
|
||||
</div>
|
||||
<div className="totalDownTime-left">
|
||||
<div className="value">17</div>
|
||||
<div className="key">mins</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="chart">
|
||||
<Bar data={downtimeData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overallScrapRate comparisionCard">
|
||||
<div className="overallScrapRate-header">Overall Scrap Rate</div>
|
||||
<div className="overallScrapRate-wrapper">
|
||||
<div className="overallScrapRate-value">
|
||||
<div className="overallScrapRate-label">Layout 1</div>
|
||||
<div className="overallScrapRate-key">Total scrap produced by</div>
|
||||
<div className="overallScrapRateKey-value">2.7 ton</div>
|
||||
</div>
|
||||
<div className="chart">
|
||||
<Bar data={scrapRateData} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PerformanceResult />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComparisonResult;
|
||||
108
app/src/components/ui/compareVersion/result-card/EnergyUsage.tsx
Normal file
108
app/src/components/ui/compareVersion/result-card/EnergyUsage.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LineElement,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
|
||||
const EnergyUsage = () => {
|
||||
const data = {
|
||||
labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Simulation 1",
|
||||
data: [400, 600, 450, 1000, 1000],
|
||||
borderColor: "#6a0dad",
|
||||
fill: false,
|
||||
tension: 0.5, // More curved line
|
||||
pointRadius: 0, // Remove point indicators
|
||||
},
|
||||
{
|
||||
label: "Simulation 2",
|
||||
data: [300, 500, 700, 950, 1100],
|
||||
borderColor: "#b19cd9",
|
||||
fill: false,
|
||||
tension: 0.5,
|
||||
pointRadius: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false, // Hide x-axis
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false, // Hide y-axis
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="comparisionCard energy-usage">
|
||||
<div className="energy-usage-wrapper">
|
||||
<h4>Energy Usage</h4>
|
||||
<p className="value">
|
||||
2500 <span>kWh</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="simulation-details">
|
||||
<div className="simulation-wrapper sim-1">
|
||||
<div className="icon"></div>
|
||||
<div className="simulation-details-wrapper">
|
||||
<div className="label">Simulation 1</div>
|
||||
<div className="value">98%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="simulation-wrapper sim-2">
|
||||
<div className="icon"></div>
|
||||
<div className="simulation-details-wrapper">
|
||||
<div className="label">Simulation 2</div>
|
||||
<div className="value">97%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="chart">
|
||||
<Line data={data} options={options} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnergyUsage;
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
import {
|
||||
GreenTickIcon,
|
||||
PerformanceIcon,
|
||||
TickIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
|
||||
const PerformanceResult = () => {
|
||||
return (
|
||||
<div className="performanceResult-wrapper comparisionCard">
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<PerformanceIcon />
|
||||
</div>
|
||||
<div className="head">Performance result</div>
|
||||
</div>
|
||||
|
||||
<div className="metrics-container">
|
||||
<div className="metrics-left">
|
||||
<div className="metric">
|
||||
<div className="metric-label">
|
||||
Success rate{" "}
|
||||
<span>
|
||||
<GreenTickIcon />
|
||||
</span>
|
||||
</div>
|
||||
<div className="metric-value">98%</div>
|
||||
</div>
|
||||
<div className="label">Environmental impact</div>
|
||||
</div>
|
||||
|
||||
<div className="metrics-right">
|
||||
<div className="metric-wrapper">
|
||||
<div className="metric-label">Waste generation</div>
|
||||
<div className="metric">
|
||||
<div className="metric-icon">I</div>
|
||||
<div className="metric-value">0.5%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="metric-wrapper">
|
||||
<div className="metric-label">Risk
management</div>
|
||||
<div className="metric">
|
||||
<div className="metric-icon">I</div>
|
||||
<div className="metric-value">0.1%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="metric-wrapper">
|
||||
<div className="metric">
|
||||
<div className="metric-icon">I</div>
|
||||
<div className="metric-value">0.5%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-tag">Simulation 1</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PerformanceResult;
|
||||
Reference in New Issue
Block a user