Merge branch 'main' into v2

This commit is contained in:
2025-04-30 12:01:39 +05:30
75 changed files with 3063 additions and 1495 deletions

View File

@@ -1,25 +1,53 @@
import React, { useState } from "react";
import React, { useState, useEffect, useRef } from "react";
import RenameInput from "./inputs/RenameInput";
import { ArrowIcon } from "../icons/ExportCommonIcons";
import MenuBar from "./menu/menu";
import { ProjectIcon } from "../icons/HeaderIcons";
const FileMenu: React.FC = () => {
const [openMenu, setOpenMenu] = useState(false);
const containerRef = useRef<HTMLButtonElement>(null);
let clickTimeout: NodeJS.Timeout | null = null;
const handleClick = () => {
if (clickTimeout) return;
setOpenMenu((prev) => !prev);
clickTimeout = setTimeout(() => {
clickTimeout = null;
}, 800);
};
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setOpenMenu(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (
<div className="project-dropdowm-container">
<button
className="project-dropdowm-container"
ref={containerRef}
onClick={handleClick}
>
<div className="project-name">
<div className="icon">
<ProjectIcon />
</div>
<RenameInput value="untitled" />
</div>
<div
className="more-options-button"
onClick={() => {
setOpenMenu(!openMenu);
}}
>
<div className="more-options-button">
<ArrowIcon />
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
</div>
</div>
</button>
);
};

View File

@@ -1,10 +1,14 @@
import React from "react";
import React, { useState } from "react";
import { ProductionCapacityIcon } from "../../icons/analysis";
const ProductionCapacity = () => {
const ProductionCapacity = ({
progressPercent = 10,
avgProcessTime = "28.4 Secs/unit",
machineUtilization = "78%",
throughputValue = 128,
timeRange = { startTime: "08:00 AM", endTime: "09:00 AM" },
}) => {
const totalBars = 6;
const progressPercent = 50;
const barsToFill = Math.floor((progressPercent / 100) * totalBars);
const partialFillPercent =
((progressPercent / 100) * totalBars - barsToFill) * 100;
@@ -14,8 +18,10 @@ const ProductionCapacity = () => {
<div className="productionCapacity-wrapper analysis-card-wrapper">
<div className="card-header">
<div className="header">
<div className="main-header">Throughput Summary</div>
<div className="sub-header">08:00 - 09:00 AM</div>
<div className="main-header">Production Capacity</div>
<div className="sub-header">
{timeRange.startTime} - {timeRange.endTime}
</div>
</div>
<div className="icon-wrapper">
<ProductionCapacityIcon />
@@ -24,10 +30,10 @@ const ProductionCapacity = () => {
<div className="process-container">
<div className="throughput-value">
<span className="value">128</span> Units/hour
<span className="value">{throughputValue}</span> Units/hour
</div>
{/* Progress Bar */}
{/* Dynamic Progress Bar */}
<div className="progress-bar-wrapper">
{[...Array(totalBars)].map((_, i) => (
<div className="progress-bar" key={i}>
@@ -47,11 +53,11 @@ const ProductionCapacity = () => {
<div className="metrics-section">
<div className="metric">
<span className="label">Avg. Process Time</span>
<span className="value">28.4 Secs/unit</span>
<span className="value">{avgProcessTime}</span>
</div>
<div className="metric">
<span className="label">Machine Utilization</span>
<span className="value">78%</span>
<span className="value">{machineUtilization}</span>
</div>
</div>
</div>

View File

@@ -1,10 +1,71 @@
import React from "react";
import React, { useState } from "react";
import { ROISummaryIcon } from "../../icons/analysis";
import SemiCircleProgress from "./SemiCircleProgress";
const ROISummary = ({
roiSummaryData = {
productName: "Product name",
roiPercentage: 133,
paybackPeriod: 50.3,
totalCost: "₹ 1,20,000",
revenueGenerated: "₹ 2,80,000",
netProfit: "₹ 1,60,000",
costBreakdown: [
{
item: "Raw Material A",
unitCost: "₹ 10/unit",
laborCost: "₹ 0",
totalCost: "₹ 1000",
sellingPrice: "₹ 1500",
},
{
item: "Labor",
unitCost: "₹ 10/unit",
laborCost: "₹ 500",
totalCost: "₹ 500",
sellingPrice: "N/A",
},
{
item: "Product 1",
unitCost: "₹ 10/unit",
laborCost: "₹ 200",
totalCost: "₹ 200",
sellingPrice: "₹ 2000",
},
{
item: "Machine",
unitCost: "-",
laborCost: "-",
totalCost: "₹ 20,000",
sellingPrice: "N/A",
},
{
item: "Total",
unitCost: "-",
laborCost: "-",
totalCost: "₹ 1,20,000",
sellingPrice: "-",
},
{
item: "Net Profit",
unitCost: "-",
laborCost: "-",
totalCost: "₹ 1,60,000",
sellingPrice: "-",
},
],
},
}) => {
const [isTableOpen, setIsTableOpen] = useState(false); // State to handle the table open/close
// Function to toggle the breakdown table visibility
const toggleTable = () => {
setIsTableOpen(!isTableOpen);
};
const ROISummary = () => {
return (
<div className="analysis-card">
<div className="throughoutSummary-wrapper analysis-card-wrapper">
<div className="roiSummary-container analysis-card">
<div className="roiSummary-wrapper analysis-card-wrapper">
<div className="card-header">
<div className="header">
<div className="main-header">ROI Summary</div>
@@ -14,6 +75,111 @@ const ROISummary = () => {
<ROISummaryIcon />
</div>
</div>
<div className="product-info">
<div className="product-label">Product :</div>
<div className="product-name">{roiSummaryData.productName}</div>
</div>
<div className="playBack">
<div className="icon"></div>
<div className="info">
<span>+{roiSummaryData.roiPercentage}%</span> ROI with payback in
just <span>{roiSummaryData.paybackPeriod}</span> months
</div>
</div>
<div className="roi-details">
<div className="progress-wrapper">
<SemiCircleProgress />
<div className="content">
you're on track to hit it by
<div className="key">July 2029</div>
</div>
</div>
<div className="metrics">
<div className="metric-wrapper">
<div className="metric-item">
<span className="metric-label">Total Cost Incurred</span>
<span className="metric-value">{roiSummaryData.totalCost}</span>
</div>
<div className="metric-item">
<span className="metric-label">Revenue Generated</span>
<span className="metric-value">
{roiSummaryData.revenueGenerated}
</span>
</div>
</div>
<div className="metric-item net-profit">
<span className="metric-label">Net Profit</span>
<span className="metric-value">{roiSummaryData.netProfit}</span>
</div>
</div>
</div>
<div className="cost-breakdown">
<div className="breakdown-header" onClick={toggleTable}>
<div className="section-wrapper">
<span className="section-number"></span>
<span className="section-title">Cost Breakdown</span>
</div>
<span className={`expand-icon ${isTableOpen ? "open" : ""}`}>
{isTableOpen ? "⌵" : "⌵"}
</span>
</div>
<div
className={`breakdown-table-wrapper ${
isTableOpen ? "open" : "closed"
}`}
style={{
transition: "max-height 0.3s ease-in-out",
overflow: "hidden",
}}
>
<table className="breakdown-table">
<thead>
<tr>
<th>Item</th>
<th>Unit Cost</th>
<th>Labor Cost</th>
<th>Total Cost</th>
<th>Selling Price</th>
</tr>
</thead>
<tbody>
{roiSummaryData.costBreakdown.map((row, index) => (
<tr
key={index}
className={
row.item === "Total"
? "total-row"
: row.item === "Net Profit"
? "net-profit-row"
: ""
}
>
<td>{row.item}</td>
<td>{row.unitCost}</td>
<td>{row.laborCost}</td>
<td>{row.totalCost}</td>
<td>{row.sellingPrice}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="tips-section">
<div className="tip-header">
<span className="lightbulb-icon">💡</span>
<span className="tip-title">How to improve ROI?</span>
</div>
<div className="tip-description">
Increase CNC utilization by <span className="highlight">10%</span>{" "}
to shave <span className="highlight">0.5</span> months of payback
period
</div>
<button className="get-tips-button">
<div className="btn">Get ROI Boost Tips</div>
</button>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,27 @@
import React from "react";
const SemiCircleProgress = () => {
const progress = 50;
const clampedProgress = Math.min(Math.max(progress, 0), 100);
const gradientProgress = clampedProgress * 0.5;
return (
<div className="semi-circle-wrapper">
<div
className="semi-circle"
style={{
background: `conic-gradient(from 270deg, skyblue 0% ${gradientProgress}%, lightgray ${gradientProgress}% 100%)`,
}}
>
<div className="progress-cover"></div>
</div>
<div className="label-wrapper">
<div className="label">{clampedProgress}%</div>
<div className="label-content">Years</div>
</div>
<div className="content">you're on track to hit it by July 2029</div>
</div>
);
};
export default SemiCircleProgress;

View File

@@ -11,21 +11,57 @@ import { PowerIcon, ThroughputSummaryIcon } from "../../icons/analysis";
ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement);
// Helper function to generate random colors
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const ThroughputSummary = () => {
const data = {
// Define all data internally within the component
const timeRange = {
startTime: "08:00 AM",
endTime: "09:00 AM",
};
const throughputData = {
labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50", "09:00"],
data: [100, 120, 110, 130, 125, 128, 132],
totalThroughput: 1240,
assetUsage: 85,
};
const energyConsumption = {
energyConsumed: 456,
unit: "KWH",
};
// Dynamic shift data
const shiftUtilization = [
{ shift: 1, percentage: 30 },
{ shift: 2, percentage: 40 },
{ shift: 3, percentage: 30 },
];
// Chart data configuration
const chartData = {
labels: throughputData.labels,
datasets: [
{
label: "Units/hour",
data: [100, 120, 110, 130, 125, 128, 132],
data: throughputData.data,
borderColor: "#B392F0",
tension: 0.4,
pointRadius: 0, // hide points
pointRadius: 0, // Hide points
},
],
};
const options = {
const chartOptions = {
responsive: true,
scales: {
x: {
@@ -57,19 +93,15 @@ const ThroughputSummary = () => {
},
};
const shiftUtilization = {
"shift 1": 25,
"shift 2": 45,
"shift 3": 15,
};
return (
<div className="throughoutSummary analysis-card">
<div className="throughoutSummary-wrapper analysis-card-wrapper">
<div className="card-header">
<div className="header">
<div className="main-header">Throughput Summary</div>
<div className="sub-header">08:00 - 09:00 AM</div>
<div className="sub-header">
{timeRange.startTime} - {timeRange.endTime}
</div>
</div>
<div className="icon-wrapper">
<ThroughputSummaryIcon />
@@ -78,14 +110,15 @@ const ThroughputSummary = () => {
<div className="process-container">
<div className="throughput-value">
<span className="value">1240</span> Units/hour
<span className="value">{throughputData.totalThroughput}</span>{" "}
Units/hour
</div>
<div className="lineChart">
<div className="assetUsage">
<div className="key">Asset usage</div>
<div className="value">85%</div>
<div className="value">{throughputData.assetUsage}%</div>
</div>
<Line data={data} options={options} />
<Line data={chartData} options={chartOptions} />
</div>
</div>
@@ -97,43 +130,41 @@ const ThroughputSummary = () => {
<PowerIcon />
</div>
<div className="value-wrapper">
<div className="value">456</div>
<div className="unit">KWH</div>
<div className="value">{energyConsumption.energyConsumed}</div>
<div className="unit">{energyConsumption.unit}</div>
</div>
</div>
</div>
<div className="shiftUtilization footer-card">
<div className="header">Shift Utilization</div>
<div className="value-container">
<div className="value">85%</div>
<div className="value">{throughputData.assetUsage}%</div>
<div className="progress-wrapper">
<div
className="progress shift-1"
style={{ width: "30%" }}
></div>
<div
className="progress shift-2"
style={{ width: "40%" }}
></div>
<div
className="progress shift-3"
style={{ width: "30%" }}
></div>
{/* Dynamically create progress bars based on shiftUtilization array */}
{shiftUtilization.map((shift) => (
<div
key={shift.shift}
className={`progress shift-${shift.shift}`}
style={{
width: `${shift.percentage}%`,
backgroundColor: getRandomColor(),
}}
></div>
))}
</div>
<div className="progress-indicator">
<div className="shift-wrapper">
<span className="indicator shift-1"></span>
<label>Shift 1</label>
</div>
<div className="shift-wrapper">
<span className="indicator shift-2"></span>
<label>Shift 2</label>
</div>
<div className="shift-wrapper">
<span className="indicator shift-3"></span>
<label>Shift 3</label>
</div>
{/* Dynamically create shift indicators with random colors */}
{shiftUtilization.map((shift) => (
<div className="shift-wrapper" key={shift.shift}>
<span
className={`indicator shift-${shift.shift}`}
style={{ backgroundColor: getRandomColor() }} // Random color for indicator
></span>
<label>Shift {shift.shift}</label>
</div>
))}
</div>
</div>
</div>

View File

@@ -24,11 +24,16 @@ const InputToggle: React.FC<InputToggleProps> = ({
<label htmlFor={`toogle-input-${inputKey}`} className="label">
{label}
</label>
<div className={"check-box"} onClick={handleOnClick}>
<div className={"check-box"} onClick={handleOnClick}
style={{
background: value ? "var(--background-color-accent)" : "",
outline: value ? "" : "1px solid var(--border-color)",
}}
>
<div
className="check-box-style"
style={{
left: value ? "50%" : "2px",
left: value ? "16px" : "2px",
background: value ? "" : "var(--text-disabled)",
}}
></div>

View File

@@ -7,6 +7,15 @@ import {
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import {
DailyProductionIcon,
EndIcon,
ExpandIcon,
HourlySimulationIcon,
MonthlyROI,
SpeedIcon,
StartIcon,
} from "../../icons/ExportCommonIcons";
const SimulationPlayer: React.FC = () => {
const { speed, setSpeed } = useAnimationPlaySpeed();
@@ -31,7 +40,7 @@ const SimulationPlayer: React.FC = () => {
const handleExit = () => {
setPlaySimulation(false);
setIsPlaying(false);
setActiveTool("cursor")
setActiveTool("cursor");
};
// Slider functions starts
@@ -73,70 +82,277 @@ const SimulationPlayer: React.FC = () => {
}, []);
// Slider function ends
// UI-Part
const hourlySimulation = 25;
const dailyProduction = 75;
const monthlyROI = 50;
const process = [
{ name: "process 1", completed: 0 }, // 0% completed
{ name: "process 2", completed: 20 }, // 20% completed
{ name: "process 3", completed: 40 }, // 40% completed
{ name: "process 4", completed: 60 }, // 60% completed
{ name: "process 5", completed: 80 }, // 80% completed
{ name: "process 6", completed: 100 }, // 100% completed
{ name: "process 7", completed: 0 }, // 0% completed
{ name: "process 8", completed: 50 }, // 50% completed
{ name: "process 9", completed: 90 }, // 90% completed
{ name: "process 10", completed: 30 }, // 30% completed
];
const [expand, setExpand] = useState(false);
// Move getRandomColor out of render
const getRandomColor = () => {
const letters = "0123456789ABCDEF";
let color = "#";
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
// Store colors for each process item
const [processColors, setProcessColors] = useState<string[]>([]);
// Generate colors on mount or when process changes
useEffect(() => {
const generatedColors = process.map(() => getRandomColor());
setProcessColors(generatedColors);
}, []);
const intervals = [10, 20, 30, 40, 50, 60]; // in minutes
const totalSegments = intervals.length;
const progress = 80; // percent (example)
const processPlayerRef = useRef<HTMLDivElement>(null);
const processWrapperRef = useRef<HTMLDivElement>(null);
const [playerPosition, setPlayerPosition] = useState(0);
const handleProcessMouseDown = (e: React.MouseEvent) => {
if (!processWrapperRef.current) return;
const rect = processWrapperRef.current.getBoundingClientRect();
let x = e.clientX - rect.left;
x = Math.max(0, Math.min(x, rect.width));
setPlayerPosition(x);
const onMouseMove = (e: MouseEvent) => {
if (!processWrapperRef.current) return;
const newRect = processWrapperRef.current.getBoundingClientRect();
let newX = e.clientX - newRect.left;
newX = Math.max(0, Math.min(newX, newRect.width));
setPlayerPosition(newX);
const progressPercent = (newX / newRect.width) * 100;
console.log(`Dragging at progress: ${progressPercent.toFixed(1)}%`);
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
return (
<div className="simulation-player-wrapper">
<div className="simulation-player-container">
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
<div className="controls-container">
<div
className="simulation-button-container"
onClick={() => {
handleReset();
}}
>
<ResetIcon />
Reset
</div>
<div
className="simulation-button-container"
onClick={() => {
handlePlayStop();
}}
>
<PlayStopIcon />
{playSimulation ? "Play" : "Stop"}
</div>
<div
className="simulation-button-container"
onClick={() => {
handleExit();
}}
>
<ExitIcon />
Exit
</div>
</div>
<div className="speed-control-container">
<div className="min-value">0.5x</div>
<div className="slider-container" ref={sliderRef}>
<div className="marker marker-10"></div>
<div className="marker marker-20"></div>
<div className="marker marker-30"></div>
<div className="marker marker-40"></div>
<div className="marker marker-50"></div>
<div className="marker marker-60"></div>
<div className="marker marker-70"></div>
<div className="marker marker-80"></div>
<div className="marker marker-90"></div>
<div className="custom-slider">
<div
className={`slider-handle ${isDragging ? "dragging" : ""}`}
style={{ left: `${calculateHandlePosition()}%` }}
onMouseDown={handleMouseDown}
>
{speed.toFixed(1)}x
<div className="production-details">
{/* hourlySimulation */}
<div className="hourly-wrapper production-wrapper">
<div className="header">
<div className="icon">
<HourlySimulationIcon />
</div>
<div className="label">Hourly Simulation</div>
</div>
<input
type="range"
min="0.5"
max="8"
step="0.1"
value={speed}
onChange={handleSpeedChange}
className="slider-input"
/>
<div className="progress-wrapper">
<div
className="progress"
style={{ width: hourlySimulation }}
></div>
</div>
</div>
{/* dailyProduction */}
<div className="dailyProduction-wrapper production-wrapper">
<div className="header">
<div className="icon">
<DailyProductionIcon />
</div>
<div className="label">Daily Production</div>
</div>
<div className="progress-wrapper">
<div
className="progress"
style={{ width: dailyProduction }}
></div>
</div>
</div>
{/* monthlyROI */}
<div className="monthlyROI-wrapper production-wrapper">
<div className="header">
<div className="icon">
<MonthlyROI />
</div>
<div className="label">Monthly ROI</div>
</div>
<div className="progress-wrapper">
<div className="progress" style={{ width: monthlyROI }}></div>
</div>{" "}
</div>
</div>
<div className="max-value">8x</div>
<div className="controls-wrapper">
<div
className="simulation-button-container"
onClick={() => {
handleReset();
}}
>
<ResetIcon />
Reset
</div>
<div
className="simulation-button-container"
onClick={() => {
handlePlayStop();
}}
>
<PlayStopIcon />
{playSimulation ? "Play" : "Stop"}
</div>
<div
className="simulation-button-container"
onClick={() => {
handleExit();
}}
>
<ExitIcon />
Exit
</div>
<div
className="simulation-button-container"
onClick={() => setExpand(!expand)}
>
<ExpandIcon />
</div>
</div>
</div>
<div className="progresser-wrapper">
<div className="time-displayer">
<div className="start-time-wrappper">
<div className="icon">
<StartIcon />
</div>
<div className="time-wrapper">
<div className="date">23 April ,25</div>
<div className="time">04:41 PM</div>
</div>
</div>
<div className="time-progresser">
<div className="timeline">
{intervals.map((label, index) => {
const segmentProgress = (index / totalSegments) * 100;
const isFilled = progress >= segmentProgress;
return (
<React.Fragment key={index}>
<div className="label-dot-wrapper">
<div className="label">{label} mins</div>
<div
className={`dot ${isFilled ? "filled" : ""}`}
></div>
</div>
{index < intervals.length - 1 && (
<div
className={`line ${
progress >= ((index + 1) / totalSegments) * 100
? "filled"
: ""
}`}
></div>
)}
</React.Fragment>
);
})}
</div>
</div>
<div className="end-time-wrappper">
<div className="time-wrapper">
<div className="time">00:10:20</div>
</div>
<div className="icon">
<EndIcon />
</div>
</div>
</div>
<div className="speed-control-container">
<div className="min-value">
<div className="icon">
<SpeedIcon />
</div>
Speed
</div>
<div className="slider-container" ref={sliderRef}>
<div className="speed-label mix-value">0X</div>
<div className="marker marker-10"></div>
<div className="marker marker-20"></div>
<div className="marker marker-30"></div>
<div className="marker marker-40"></div>
<div className="marker marker-50"></div>
<div className="marker marker-60"></div>
<div className="marker marker-70"></div>
<div className="marker marker-80"></div>
<div className="marker marker-90"></div>
<div className="custom-slider">
<div
className={`slider-handle ${isDragging ? "dragging" : ""}`}
style={{ left: `${calculateHandlePosition()}%` }}
onMouseDown={handleMouseDown}
>
{speed.toFixed(1)}x
</div>
<input
type="range"
min="0.5"
max="8"
step="0.1"
value={speed}
onChange={handleSpeedChange}
className="slider-input"
/>
</div>
<div className="speed-label max-value">8x</div>
</div>
</div>
</div>
<div className="processDisplayer">
<div
className="process-player"
style={{ left: playerPosition, position: "absolute" }}
></div>
<div
className="process-wrapper"
ref={processWrapperRef}
onMouseDown={handleProcessMouseDown}
>
{process.map((item, index) => (
<div
key={index}
className="process"
style={{
width: `${item.completed}%`,
backgroundColor: processColors[index],
}}
></div>
))}
</div>
<div
className="process-player"
ref={processPlayerRef}
style={{ left: playerPosition, position: "absolute" }}
></div>
</div>
</div>
</div>