diff --git a/app/src/components/layout/sidebarRight/analysis/Analysis.tsx b/app/src/components/layout/sidebarRight/analysis/Analysis.tsx index 9b16186..0e88e18 100644 --- a/app/src/components/layout/sidebarRight/analysis/Analysis.tsx +++ b/app/src/components/layout/sidebarRight/analysis/Analysis.tsx @@ -3,6 +3,7 @@ import { AIIcon } from "../../../icons/ExportCommonIcons"; import RegularDropDown from "../../../ui/inputs/RegularDropDown"; import { AnalysisPresetsType } from "../../../../types/analysis"; import RenderAnalysisInputs from "./RenderAnalysisInputs"; +import { useInputValues } from "../../../../store/builder/store"; const Analysis: React.FC = () => { const [selectedOption, setSelectedOption] = useState("Throughput time"); @@ -48,6 +49,10 @@ const Analysis: React.FC = () => { type: "default", inputs: { label: "Fixed costs", activeOption: "INR" }, }, + { + type: "default", + inputs: { label: "Initial Investment", activeOption: "INR" }, + }, { type: "default", inputs: { label: "Salvage value", activeOption: "Hrs" }, @@ -63,6 +68,8 @@ const Analysis: React.FC = () => { ], }; + const { inputValues, setInputValues, updateInputValue } = useInputValues(); + return (
@@ -88,10 +95,14 @@ const Analysis: React.FC = () => { presets={ AnalysisPresets[selectedOption as keyof AnalysisPresetsType] } + inputValues={inputValues} + onInputChange={(label, value) => { + updateInputValue(label, value); + }} />
- - + setInputValues({})} /> + setInputValues(inputValues)} />
Create Custom Analysis
diff --git a/app/src/components/layout/sidebarRight/analysis/RenderAnalysisInputs.tsx b/app/src/components/layout/sidebarRight/analysis/RenderAnalysisInputs.tsx index e14b542..0204582 100644 --- a/app/src/components/layout/sidebarRight/analysis/RenderAnalysisInputs.tsx +++ b/app/src/components/layout/sidebarRight/analysis/RenderAnalysisInputs.tsx @@ -6,12 +6,11 @@ import { AnalysisPresetsType } from "../../../../types/analysis"; interface InputRendererProps { keyName: string; presets: AnalysisPresetsType[keyof AnalysisPresetsType]; + inputValues: Record; // <-- Add this line + onInputChange: (label: string, value: string) => void; } -const RenderAnalysisInputs: React.FC = ({ - keyName, - presets, -}) => { +const RenderAnalysisInputs: React.FC = ({ keyName, presets,inputValues, onInputChange }) => { return (
{presets.map((preset, index) => { @@ -20,9 +19,9 @@ const RenderAnalysisInputs: React.FC = ({ {}} + onChange={(newValue) => onInputChange(preset.inputs.label, newValue)} /> ); } diff --git a/app/src/components/ui/analysis/ProductionCapacity.tsx b/app/src/components/ui/analysis/ProductionCapacity.tsx index b8b17c8..0bc8b91 100644 --- a/app/src/components/ui/analysis/ProductionCapacity.tsx +++ b/app/src/components/ui/analysis/ProductionCapacity.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Line } from "react-chartjs-2"; import { Chart as ChartJS, @@ -9,23 +9,18 @@ import { } from "chart.js"; import { PowerIcon, ProductionCapacityIcon } from "../../icons/analysis"; import SkeletonUI from "../../templates/SkeletonUI"; +import { useInputValues, useMachineUptime, useProductionCapacityData } from "../../../store/builder/store"; ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement); -const ThroughputSummary:React.FC = () => { +const ThroughputSummary: React.FC = () => { // Define all data internally within the component const timeRange = { startTime: "08:00 AM", endTime: "06:00 PM", }; - const throughputData = { - labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50"], - data: [5, 10, 8, 10, 12, 10], - totalThroughput: 1240, - assetUsage: 85, - }; - + const { machineActiveTime } = useMachineUptime(); const energyConsumption = { energyConsumed: 456, unit: "KWH", @@ -38,19 +33,8 @@ const ThroughputSummary:React.FC = () => { { shift: 3, percentage: 30, color: "#7981F5" }, ]; - // Chart data configuration - const chartData = { - labels: throughputData.labels, - datasets: [ - { - label: "Units/hour", - data: throughputData.data, - borderColor: "#B392F0", - tension: 0.4, - pointRadius: 0, // Hide points - }, - ], - }; + const { productionCapacityData } = useProductionCapacityData() + const chartOptions = { responsive: true, @@ -73,7 +57,47 @@ const ThroughputSummary:React.FC = () => { }, }; - const isLoading = false; + const assetUsage = 85; + + // machineActiveTime => Throughput + // shiftUtilization.length => Shifts per day + // 8 => Shift length + // assetUsage => Yield rate + + const throughtputMachineData = machineActiveTime * shiftUtilization.length * 8 + + const throughputData = { + labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50"], + data: [5, 10, 8, 10, 12, 10], + totalThroughput: (throughtputMachineData) * assetUsage / 100, + assetUsage: assetUsage, + }; + + const { inputValues } = useInputValues(); + // Chart data configuration + const chartData = { + labels: throughputData.labels, + datasets: [ + { + label: "Units/hour", + data: throughputData.data, + borderColor: "#B392F0", + tension: 0.4, + pointRadius: 0, // Hide points + }, + ], + }; + + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (productionCapacityData >= 0) { + setIsLoading(false); + console.log("productionCapacityData: ", productionCapacityData); + } else { + setIsLoading(true); + } + }, [productionCapacityData]); return (
@@ -94,13 +118,13 @@ const ThroughputSummary:React.FC = () => { <>
- {throughputData.totalThroughput}{" "} + {productionCapacityData}{" "} Units/hour
Asset usage
-
{throughputData.assetUsage}%
+
{parseFloat(inputValues["Yield rate"])}%
diff --git a/app/src/components/ui/analysis/ROISummary.tsx b/app/src/components/ui/analysis/ROISummary.tsx index e0edb74..287d631 100644 --- a/app/src/components/ui/analysis/ROISummary.tsx +++ b/app/src/components/ui/analysis/ROISummary.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { CostBreakDownIcon, LightBulpIcon, @@ -9,6 +9,8 @@ import { import SemiCircleProgress from "./SemiCircleProgress"; import { ArrowIcon } from "../../icons/ExportCommonIcons"; import SkeletonUI from "../../templates/SkeletonUI"; +import { useROISummaryData } from "../../../store/builder/store"; +import { set } from "immer/dist/internal"; const ROISummary = ({ roiSummaryData = { @@ -79,8 +81,17 @@ const ROISummary = ({ const year = now.getFullYear(); return `${day} ${month}, ${year}`; } + const [isLoading, setIsLoading] = useState(false); + const { roiSummary } = useROISummaryData(); + + useEffect(() => { + if (roiSummary && typeof roiSummary === "object") { + setIsLoading(false); // Data loaded + } else { + setIsLoading(true); // Show skeleton while loading + } + }, [roiSummary]); - const isLoading = false; return (
@@ -98,14 +109,14 @@ const ROISummary = ({
Product :
-
{roiSummaryData.productName}
+
{roiSummary.productName}
- +{roiSummaryData.roiPercentage}% ROI with payback - in just {roiSummaryData.paybackPeriod} months + {roiSummary.roiPercentage}% ROI with payback + in just {roiSummary.paybackPeriod} months
@@ -122,7 +133,7 @@ const ROISummary = ({ Total Cost Incurred - {roiSummaryData.totalCost} + {roiSummary.totalCost}
@@ -130,14 +141,12 @@ const ROISummary = ({ - {roiSummaryData.revenueGenerated} + {roiSummary.revenueGenerated}
0 ? "profit" : "loss"}`} >
@@ -145,9 +154,9 @@ const ROISummary = ({
- {roiSummaryData.netProfit - ? roiSummaryData.netProfit - : roiSummaryData.netLoss} + {roiSummary.netProfit > 0 + ? roiSummary.netProfit + : roiSummary.netLoss}
@@ -164,9 +173,8 @@ const ROISummary = ({
{row.item} @@ -228,6 +236,7 @@ const ROISummary = ({ ) : ( + //
No Data
)}
diff --git a/app/src/components/ui/analysis/ThroughputSummary.tsx b/app/src/components/ui/analysis/ThroughputSummary.tsx index 8abdf22..bf570f9 100644 --- a/app/src/components/ui/analysis/ThroughputSummary.tsx +++ b/app/src/components/ui/analysis/ThroughputSummary.tsx @@ -1,21 +1,45 @@ +import { useEffect, useState } from "react"; +import { useMachineCount, useMachineUptime, useMaterialCycle, useProductionCapacityData, useThroughPutData } from "../../../store/builder/store"; import { ThroughputSummaryIcon, } from "../../icons/analysis"; import SkeletonUI from "../../templates/SkeletonUI"; const ProductionCapacity = ({ - progressPercent = 50, - avgProcessTime = "28.4", + avgProcessTime = "28.4 Secs/unit", machineUtilization = "78%", throughputValue = 128, timeRange = { startTime: "08:00 AM", endTime: "09:00 AM" }, }) => { + + + + const { machineActiveTime } = useMachineUptime(); + const { materialCycleTime } = useMaterialCycle(); + const { throughputData } = useThroughPutData() + const { productionCapacityData } = useProductionCapacityData() + + const progressPercent = machineActiveTime; + + const totalBars = 6; const barsToFill = Math.floor((progressPercent / 100) * totalBars); const partialFillPercent = ((progressPercent / 100) * totalBars - barsToFill) * 100; - const isLoading = true; + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (throughputData >= 0) { + // console.log('machineActiveTime: ', machineActiveTime); + // console.log('materialCycleTime: ', materialCycleTime); + // console.log('throughputData: ', throughputData); + // console.log('productionCapacityData: ', productionCapacityData); + setIsLoading(true); + } + + }, [throughputData]) + return (
@@ -34,7 +58,8 @@ const ProductionCapacity = ({ <>
- {avgProcessTime} secs/unit + {throughputData} Units/hour +
{/* Dynamic Progress Bar */} @@ -56,12 +81,13 @@ const ProductionCapacity = ({
- Units/hour - {throughputValue} avg. + Avg. Process Time + {materialCycleTime} secs/unit
Machine Utilization - {machineUtilization} + {machineActiveTime} + {/* {machineActiveTime} */}
diff --git a/app/src/components/ui/inputs/InputWithDropDown.tsx b/app/src/components/ui/inputs/InputWithDropDown.tsx index 3d42917..c6316d6 100644 --- a/app/src/components/ui/inputs/InputWithDropDown.tsx +++ b/app/src/components/ui/inputs/InputWithDropDown.tsx @@ -52,7 +52,8 @@ const InputWithDropDown: React.FC = ({ max={max} step={step} type="number" - defaultValue={value} + // defaultValue={value} + value={value} onChange={(e) => { onChange(e.target.value); }} diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index de1652f..0a0efc7 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect } from "react"; import { ExitIcon, PlayStopIcon, ResetIcon } from "../../icons/SimulationIcons"; -import { useActiveTool } from "../../../store/builder/store"; +import { useActiveTool, useProcessBar } from "../../../store/builder/store"; import { useAnimationPlaySpeed, usePauseButtonStore, @@ -111,21 +111,25 @@ const SimulationPlayer: React.FC = () => { const hourlySimulation = 25; const dailyProduction = 75; const monthlyROI = 50; + const { processBar, setProcessBar } = useProcessBar(); + // 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 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 - ]; + useEffect(() => { + // console.log('processBar: ', processBar); + }, [processBar]) - const intervals = [10, 20, 30, 40, 50, 60]; // in minutes + const intervals = [50, 20, 30, 40, 50, 60]; // in minutes const totalSegments = intervals.length; const progress = 20; // percent (example) @@ -388,22 +392,26 @@ const SimulationPlayer: React.FC = () => { ref={processWrapperRef} onMouseDown={handleProcessMouseDown} > - {process.map((item, index) => ( -
+ {processBar?.length > 0 ? ( + processBar.map((item: any, index: any) => (
-
- ))} + key={`${index}-${item.name}`} + className="process" + style={{ + width: `${item.completed}%`, + backgroundColor: getAvatarColor(index), + }} + > +
+
+ )) + ) : ( +
No process data available
+ )}
diff --git a/app/src/modules/market/Card.tsx b/app/src/modules/market/Card.tsx index 2b878bb..4b75990 100644 --- a/app/src/modules/market/Card.tsx +++ b/app/src/modules/market/Card.tsx @@ -37,7 +37,6 @@ const Card: React.FC = ({ image, description, AssetID, - modelUrl, onSelectCard, }) => { const handleCardSelect = () => { diff --git a/app/src/modules/market/CardsContainer.tsx b/app/src/modules/market/CardsContainer.tsx index 5612b6b..2afa576 100644 --- a/app/src/modules/market/CardsContainer.tsx +++ b/app/src/modules/market/CardsContainer.tsx @@ -45,7 +45,7 @@ const CardsContainer: React.FC = ({ models }) => { }) => { setSelectedCard(cardData); const res = await fetchGltfUrl(cardData.assetName, cardData.AssetID); - console.log("res: ", res); + // console.log("res: ", res); setModelUrl(res.url); }; return ( @@ -65,8 +65,8 @@ const CardsContainer: React.FC = ({ models }) => { onSelectCard={handleCardSelect} AssetID={assetDetail.AssetID} image={assetDetail.thumbnail} + modelUrl={assetDetail.modelfileID} description={assetDetail.description} - modelUrl={modelUrl} /> ))} diff --git a/app/src/modules/market/MarketPlace.tsx b/app/src/modules/market/MarketPlace.tsx index e5f3e56..ef2cd67 100644 --- a/app/src/modules/market/MarketPlace.tsx +++ b/app/src/modules/market/MarketPlace.tsx @@ -33,7 +33,6 @@ const MarketPlace = () => { try { const filt = await getAssetImages("67d934ad0f42a1fdadb19aa6"); setModels(filt.items); - console.log('filt.items: ', filt.items); setFilteredModels(filt.items); setisLoading(false); } catch { diff --git a/app/src/modules/simulation/analysis/ROI/roiData.tsx b/app/src/modules/simulation/analysis/ROI/roiData.tsx new file mode 100644 index 0000000..f1ddda5 --- /dev/null +++ b/app/src/modules/simulation/analysis/ROI/roiData.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from 'react' +import { useInputValues, useProductionCapacityData, useROISummaryData } from '../../../../store/builder/store'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; + +export default function ROIData() { + const { inputValues } = useInputValues(); + const { productionCapacityData } = useProductionCapacityData() + const { selectedProduct } = useSelectedProduct(); + const { isPlaying } = usePlayButtonStore(); + const { setRoiSummaryData } = useROISummaryData(); + useEffect(() => { + if (!isPlaying) { + setRoiSummaryData({ + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }) + return; + } + + if (inputValues === undefined) return; + + const electricityCost = parseFloat(inputValues["Electricity cost"]); + const fixedCost = parseFloat(inputValues["Fixed costs"]); + const laborCost = parseFloat(inputValues["Labor Cost"]); + const maintenanceCost = parseFloat(inputValues["Maintenance cost"]); // Remove space typ + const materialCost = parseFloat(inputValues["Material cost"]); + const productionPeriod = parseFloat(inputValues["Production period"]); + const salvageValue = parseFloat(inputValues["Salvage value"]); + const sellingPrice = parseFloat(inputValues["Selling price"]); + const initialInvestment = parseFloat(inputValues["Initial Investment"]); + const shiftLength = parseFloat(inputValues["Shift length"]); + const shiftsPerDay = parseFloat(inputValues["Shifts / day"]); + const workingDaysPerYear = parseFloat(inputValues["Working days / year"]); + + if (!isNaN(electricityCost) && !isNaN(fixedCost) && !isNaN(laborCost) && !isNaN(maintenanceCost) && + !isNaN(materialCost) && !isNaN(productionPeriod) && !isNaN(salvageValue) && !isNaN(sellingPrice) && + !isNaN(shiftLength) && !isNaN(shiftsPerDay) && !isNaN(workingDaysPerYear) && productionCapacityData > 0) { + + + + + const totalHoursPerYear = shiftLength * shiftsPerDay * workingDaysPerYear; + + // Total good units produced per year + const annualProductionUnits = productionCapacityData * totalHoursPerYear; + + // Revenue for a year + const annualRevenue = annualProductionUnits * sellingPrice; + + // Costs + const totalMaterialCost = annualProductionUnits * materialCost; + const totalLaborCost = laborCost * totalHoursPerYear; + const totalEnergyCost = electricityCost * totalHoursPerYear; + const totalMaintenanceCost = maintenanceCost + fixedCost; + + const totalAnnualCost = totalMaterialCost + totalLaborCost + totalEnergyCost + totalMaintenanceCost; + + // Annual Profit + const annualProfit = annualRevenue - totalAnnualCost; + console.log('annualProfit: ', annualProfit); + + // Net Profit over production period + const netProfit = annualProfit * productionPeriod; + + // ROI + const roiPercentage = ((netProfit + salvageValue - initialInvestment) / initialInvestment) * 100; + + // Payback Period + const paybackPeriod = initialInvestment / (annualProfit || 1); // Avoid division by 0 + console.log('paybackPeriod: ', paybackPeriod); + + // console.log("--- ROI Breakdown ---"); + // console.log("Annual Production Units:", annualProductionUnits.toFixed(2)); + // console.log("Annual Revenue:", annualRevenue.toFixed(2)); + // console.log("Total Annual Cost:", totalAnnualCost.toFixed(2)); + // console.log("Annual Profit:", annualProfit.toFixed(2)); + // console.log("Net Profit:", netProfit.toFixed(2)); + // console.log("ROI %:", roiPercentage.toFixed(2)); + // console.log("Payback Period (years):", paybackPeriod.toFixed(2)); + + setRoiSummaryData({ + productName: selectedProduct.productName, + roiPercentage: parseFloat((roiPercentage / 100).toFixed(2)), // normalized to 0.x format + paybackPeriod: parseFloat(paybackPeriod.toFixed(2)), + totalCost: parseFloat(totalAnnualCost.toFixed(2)), + revenueGenerated: parseFloat(annualRevenue.toFixed(2)), + netProfit: netProfit > 0 ? parseFloat(netProfit.toFixed(2)) : 0, + netLoss: netProfit < 0 ? -netProfit : 0 + }); + + const productCount = 1000; + + // Cost per unit (based on full annual cost) + const costPerUnit = totalAnnualCost / annualProductionUnits; + + const costForTargetUnits = productCount * costPerUnit; + const revenueForTargetUnits = productCount * sellingPrice; + const profitForTargetUnits = revenueForTargetUnits - costForTargetUnits; + + const netProfitForTarget = profitForTargetUnits > 0 ? profitForTargetUnits : 0; + const netLossForTarget = profitForTargetUnits < 0 ? -profitForTargetUnits : 0; + + // console.log("--- Fixed Product Count (" + productCount + ") ---"); + // console.log("Cost per Unit:", costPerUnit.toFixed(2)); + // console.log("Total Cost for " + productCount + " Units:", costForTargetUnits.toFixed(2)); + // console.log("Revenue for " + productCount + " Units:", revenueForTargetUnits.toFixed(2)); + // console.log("Profit:", netProfitForTarget.toFixed(2)); + // console.log("Loss:", netLossForTarget.toFixed(2)); + + } + + }, [inputValues, productionCapacityData]); + + return ( + <> + ) +} + + diff --git a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx new file mode 100644 index 0000000..5c376a7 --- /dev/null +++ b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx @@ -0,0 +1,51 @@ +import React, { useEffect } from 'react' +import { useInputValues, useProductionCapacityData, useThroughPutData } from '../../../../store/builder/store' +import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; + +export default function ProductionCapacityData() { + const { throughputData } = useThroughPutData() + const { productionCapacityData, setProductionCapacityData } = useProductionCapacityData() + const { inputValues } = useInputValues(); + const { isPlaying } = usePlayButtonStore(); + + useEffect(() => { + if (!isPlaying) { + setProductionCapacityData(0); + return; + } + if (!inputValues || throughputData === undefined) return; + + const shiftLength = parseFloat(inputValues["Shift length"]); + const shiftsPerDay = parseFloat(inputValues["Shifts / day"]); + const workingDaysPerYear = parseFloat(inputValues["Working days / year"]); + const yieldRate = parseFloat(inputValues["Yield rate"]); + + if (!isNaN(shiftLength) && !isNaN(shiftsPerDay) && !isNaN(workingDaysPerYear) && + !isNaN(yieldRate) && throughputData >= 0) { + // Total units produced per day before yield + const dailyProduction = throughputData * shiftLength * shiftsPerDay; + + + // Units after applying yield rate + const goodUnitsPerDay = dailyProduction * (yieldRate / 100); + + + // Annual output + const annualProduction = goodUnitsPerDay * workingDaysPerYear; + + + // Final production capacity per hour (after yield) + const productionPerHour = throughputData * (yieldRate / 100); + + + // Set the final capacity (units/hour) + setProductionCapacityData(Number(productionPerHour.toFixed(2))); + } + }, [throughputData, inputValues]); + + return ( + <> + ) +} + + diff --git a/app/src/modules/simulation/analysis/simulationAnalysis.tsx b/app/src/modules/simulation/analysis/simulationAnalysis.tsx new file mode 100644 index 0000000..db5a88b --- /dev/null +++ b/app/src/modules/simulation/analysis/simulationAnalysis.tsx @@ -0,0 +1,25 @@ +import React, { useEffect } from 'react' +import { usePlayButtonStore } from '../../../store/usePlayButtonStore' +import ProductionCapacityData from './productionCapacity/productionCapacityData' +import ThroughPutData from './throughPut/throughPutData' +import ROIData from './ROI/roiData' + +function SimulationAnalysis() { + const { isPlaying } = usePlayButtonStore() + // useEffect(()=>{ + // if (isPlaying) { + // + // } else { + // + // } + // },[isPlaying]) + return ( + <> + + + + + ) +} + +export default SimulationAnalysis diff --git a/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx b/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx new file mode 100644 index 0000000..340e4e0 --- /dev/null +++ b/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx @@ -0,0 +1,153 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { determineExecutionMachineSequences } from '../../simulator/functions/determineExecutionMachineSequences'; +import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; +import { useMachineCount, useMachineUptime, useMaterialCycle, useProcessBar, useThroughPutData } from '../../../../store/builder/store'; +import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; +import { useMachineStore } from '../../../../store/simulation/useMachineStore'; +import { useConveyorStore } from '../../../../store/simulation/useConveyorStore'; +import { useStorageUnitStore } from '../../../../store/simulation/useStorageUnitStore'; +import { useMaterialStore } from '../../../../store/simulation/useMaterialStore'; +import { usePauseButtonStore, usePlayButtonStore } from '../../../../store/usePlayButtonStore'; +import { is, set } from 'immer/dist/internal'; + +export default function ThroughPutData() { + const { selectedProduct } = useSelectedProduct(); + const { products, getProductById } = useProductStore(); + const { armBots } = useArmBotStore(); + const { vehicles } = useVehicleStore(); + const { machines } = useMachineStore(); + const { conveyors } = useConveyorStore(); + const { storageUnits } = useStorageUnitStore(); + const { materialHistory } = useMaterialStore(); + const { machineCount, setMachineCount } = useMachineCount(); + const { machineActiveTime, setMachineActiveTime } = useMachineUptime(); + const { materialCycleTime, setMaterialCycleTime } = useMaterialCycle(); + const { setProcessBar } = useProcessBar(); + const { setThroughputData } = useThroughPutData() + const { isPlaying } = usePlayButtonStore(); + + // Setting machine count + let totalItems = 0; + let totalActiveTime = 0; + useEffect(() => { + if (!isPlaying) { + totalActiveTime = 0; + totalItems = 0; + setMachineCount(0); + setMachineActiveTime(0); + setMaterialCycleTime(0); + setProcessBar([]); + setThroughputData(0); + return; + } else { + let process: any = []; + const fetchProductSequenceData = async () => { + const productData = getProductById(selectedProduct.productId); + if (productData) { + const productSequenceData = await determineExecutionMachineSequences([productData]) + if (productSequenceData?.length > 0) { + productSequenceData.forEach((sequence) => { + sequence.forEach((item) => { + if (item.type === "roboticArm") { + armBots.filter(arm => arm.modelUuid === item.modelUuid) + .forEach(arm => { + if (arm.activeTime >= 0) { + process.push({ modelid: arm.modelUuid, modelName: arm.modelName, activeTime: arm?.activeTime }) + totalActiveTime += arm.activeTime; + + } + }); + } else if (item.type === "vehicle") { + vehicles.filter(vehicle => vehicle.modelUuid === item.modelUuid) + .forEach(vehicle => { + if (vehicle.activeTime >= 0) { + process.push({ modelid: vehicle.modelUuid, modelName: vehicle.modelName, activeTime: vehicle?.activeTime }) + + totalActiveTime += vehicle.activeTime; + } + }); + } else if (item.type === "machine") { + machines.filter(machine => machine.modelUuid === item.modelUuid) + .forEach(machine => { + if (machine.activeTime >= 0) { + process.push({ modelid: machine.modelUuid, modelName: machine.modelName, activeTime: machine?.activeTime }) + totalActiveTime += machine.activeTime; + } + }); + } else if (item.type === "transfer") { + conveyors.filter(conveyor => conveyor.modelUuid === item.modelUuid) + .forEach(conveyor => { + if (conveyor.activeTime >= 0) { + totalActiveTime += conveyor.activeTime; + } + }); + } else if (item.type === "storageUnit") { + storageUnits.filter(storage => storage.modelUuid === item.modelUuid) + .forEach(storage => { + if (storage.activeTime >= 0) { + totalActiveTime += storage.activeTime; + + } + }); + } + }); + + totalItems += sequence.length; + }); + + setMachineCount(totalItems); + setMachineActiveTime(totalActiveTime); + let arr = process.map((item: any) => ({ + name: item.modelName, + completed: Math.round((item.activeTime / totalActiveTime) * 100) + })); + setProcessBar(arr); + } + } + }; + + fetchProductSequenceData(); + } + // if (materialCycleTime <= 0) return + }, [products, selectedProduct, getProductById, setMachineCount, materialCycleTime, armBots, vehicles, machines]); + + // Setting material cycle time + useEffect(() => { + materialHistory.forEach((material) => { + const start = material.material.startTime ?? 0; + const end = material.material.endTime ?? 0; + if (start === 0 || end === 0) return; + const totalCycleTime = (end - start) / 1000; // Convert milliseconds to seconds + setMaterialCycleTime(Number(totalCycleTime.toFixed(2))); // Set the material cycle time in the store + }); + }, [materialHistory]); + + + + useEffect(() => { + if (machineActiveTime > 0 && materialCycleTime > 0 && machineCount > 0) { + const utilization = machineActiveTime / 3600; // Active time per hour + const unitsPerMachinePerHour = 3600 / materialCycleTime; + const throughput = unitsPerMachinePerHour * machineCount * utilization; + + setThroughputData(throughput.toFixed(2)); // Set throughput to state/store + + // console.log('---Throughput Results---'); + // console.log('Machine Active Time (s):', machineActiveTime); + // console.log('Material Cycle Time (s):', materialCycleTime); + // console.log('Machine Count:', machineCount); + // console.log('Utilization:', utilization); + // console.log('Throughput (units/hr):', throughput); + } + }, [machineActiveTime, materialCycleTime, machineCount]); + + + + + return ( + <> + + ); +} diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index 525c9c9..ae8c97f 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { useMachineStore } from '../../../../../store/simulation/useMachineStore'; -import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import MachineAnimator from '../animator/machineAnimator'; import { useProductStore } from '../../../../../store/simulation/useProductStore'; import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; @@ -9,22 +9,93 @@ import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHa function MachineInstance({ machineDetail }: { machineDetail: MachineStatus }) { const [currentPhase, setCurrentPhase] = useState('idle'); let isIncrememtable = useRef(true); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const isSpeedRef = useRef(0); + const isPausedRef = useRef(false); const { isPlaying } = usePlayButtonStore(); - const { machines, setMachineState, setMachineActive } = useMachineStore(); + const { machines, setMachineState, setMachineActive, incrementIdleTime, incrementActiveTime, resetTime } = useMachineStore(); const { selectedProduct } = useSelectedProduct(); const { getActionByUuid } = useProductStore(); const { triggerPointActions } = useTriggerHandler(); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + useEffect(() => { + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); const reset = () => { setCurrentPhase("idle"); setMachineState(machineDetail.modelUuid, 'idle'); setMachineActive(machineDetail.modelUuid, false); isIncrememtable.current = true; + isPausedRef.current = false; + resetTime(machineDetail.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } } function machineStatus(modelId: string, status: string) { // console.log(`${modelId} , ${status}`); } + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (machineDetail.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + // console.log(' activeTimeRef.current: ', activeTimeRef.current); + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + // console.log('idleTimeRef.curre: ', idleTimeRef.current); + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return + if (!machineDetail.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + // console.log('Final Active Time:', roundedActiveTime, 'seconds'); + incrementActiveTime(machineDetail.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + // console.log('Final Idle Time:', roundedIdleTime, 'seconds'); + incrementIdleTime(machineDetail.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [machineDetail, isPlaying]); useEffect(() => { if (isPlaying) { diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 3427733..1c8c447 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -5,7 +5,7 @@ import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; import MaterialAnimator from '../animator/materialAnimator'; import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb"; -import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; import { useProductStore } from '../../../../../store/simulation/useProductStore'; @@ -26,9 +26,11 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { const groupRef = useRef(null); const pauseTimeRef = useRef(null); const isPausedRef = useRef(false); + const isSpeedRef = useRef(null); + const isIdleRef = useRef(false); let startTime: number; - const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { armBots, setArmBotActive, setArmBotState, removeCurrentAction, incrementActiveTime, incrementIdleTime } = useArmBotStore(); const { decrementVehicleLoad, removeLastMaterial } = useVehicleStore(); const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = useStorageUnitStore(); const { setIsVisible, setIsPaused, getMaterialById } = useMaterialStore(); @@ -38,6 +40,13 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { const { isPlaying } = usePlayButtonStore(); const { isReset } = useResetButtonStore(); const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + + const activeSecondsElapsed = useRef(0); + const idleSecondsElapsed = useRef(0); + + const animationFrameIdRef = useRef(null); + const previousTimeRef = useRef(null); const checkActiveRoboticArms = useCheckActiveRoboticArmsInSubsequence(); const lastRemoved = useRef<{ type: string, materialId: string } | null>(null); @@ -176,6 +185,10 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { isPausedRef.current = isPaused; }, [isPaused]); + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + useEffect(() => { if (isReset || !isPlaying) { logStatus(armBot.modelUuid, "Simulation Play Reset Successfully") @@ -188,6 +201,14 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { isPausedRef.current = false pauseTimeRef.current = null startTime = 0 + activeSecondsElapsed.current = 0; + idleSecondsElapsed.current = 0; + previousTimeRef.current = null; + + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone ); if (targetBones && isPlaying) { @@ -200,6 +221,61 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { } }, [isReset, isPlaying]) + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + if (armBot.isActive) { + if (!isPausedRef.current) { + activeSecondsElapsed.current += deltaTime * isSpeedRef.current; + // console.log(' activeSecondsElapsed.current: ', activeSecondsElapsed.current); + } + } else { + if (!isPausedRef.current) { + idleSecondsElapsed.current += deltaTime * isSpeedRef.current; + // console.log('idleSecondsElapsed.current: ', idleSecondsElapsed.current); + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return + + if (!armBot.isActive && armBot.state === "idle" && (currentPhase === "rest" || currentPhase === "init")) { + cancelAnimationFrame(animationFrameIdRef.current!); + animationFrameIdRef.current = null; + const roundedActiveTime = Math.round(activeSecondsElapsed.current); // Get the final rounded active time + // console.log('🚨Final Active Time:',armBot.modelUuid, roundedActiveTime, 'seconds'); + incrementActiveTime(armBot.modelUuid, roundedActiveTime); + activeSecondsElapsed.current = 0; + + } else if (armBot.isActive && armBot.state !== "idle" && currentPhase !== "rest" && armBot.currentAction) { + cancelAnimationFrame(animationFrameIdRef.current!); + animationFrameIdRef.current = null; + const roundedIdleTime = Math.round(idleSecondsElapsed.current); // Get the final rounded idle time + // console.log('🕒 Final Idle Time:', armBot.modelUuid,roundedIdleTime, 'seconds'); + incrementIdleTime(armBot.modelUuid, roundedIdleTime); + idleSecondsElapsed.current = 0; + + } + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; // Reset the animation frame ID + } + }; + + }, [armBot.isActive, armBot.state, currentPhase]) + + useEffect(() => { const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid); if (targetMesh) { @@ -227,6 +303,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { //Moving to pickup point else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) { if (armBot.currentAction) { + setArmBotActive(armBot.modelUuid, true); setArmBotState(armBot.modelUuid, "running"); setCurrentPhase("rest-to-start"); @@ -260,6 +337,13 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { pauseTimeRef.current = null isPausedRef.current = false startTime = 0 + activeSecondsElapsed.current = 0; + idleSecondsElapsed.current = 0; + previousTimeRef.current = null; + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } removeCurrentAction(armBot.modelUuid) } @@ -311,6 +395,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { } } } + const logStatus = (id: string, status: string) => { // console.log('status: ', status); } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 7fe3c50..f918f56 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -12,6 +12,7 @@ import Simulator from './simulator/simulator'; import Products from './products/products'; import Trigger from './triggers/trigger'; import useModuleStore from '../../store/useModuleStore'; +import SimulationAnalysis from './analysis/simulationAnalysis'; function Simulation() { const { activeModule } = useModuleStore(); @@ -53,6 +54,8 @@ function Simulation() { + + } diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts index 211b28f..7c2f7ee 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts @@ -98,4 +98,4 @@ export async function determineExecutionMachineSequences(products: productsSchem }); return executionSequences; -} \ No newline at end of file +} diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 3a45409..00837a4 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -13,10 +13,7 @@ import { useProductStore } from '../../../../../store/simulation/useProductStore import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; import MaterialAnimator from '../animator/materialAnimator'; -type Timer = { - start: number | null; - active: boolean; -}; + function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); @@ -27,20 +24,30 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) const { triggerPointActions } = useTriggerHandler(); const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = useProductStore(); const { selectedProduct } = useSelectedProduct(); - const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial } = useVehicleStore(); + const { vehicles, setVehicleActive, setVehicleState, setVehiclePicking, clearCurrentMaterials, setVehicleLoad, decrementVehicleLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = useVehicleStore(); + const [currentPhase, setCurrentPhase] = useState('stationed'); const [path, setPath] = useState<[number, number, number][]>([]); const pauseTimeRef = useRef(null); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); let startTime: number; let fixedInterval: number; const { speed } = useAnimationPlaySpeed(); const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); useEffect(() => { isPausedRef.current = isPaused; }, [isPaused]); + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + const computePath = useCallback( (start: any, end: any) => { try { @@ -58,7 +65,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) ); function vehicleStatus(modelId: string, status: string) { - // + // console.log(`${modelId} , ${status}`); } // Function to reset everything @@ -72,6 +79,14 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) startTime = 0; isPausedRef.current = false; pauseTimeRef.current = 0; + resetTime(agvDetail.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } } useEffect(() => { @@ -119,12 +134,61 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); } } - } else { + } + else { reset() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [vehicles, currentPhase, path, isPlaying]); + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (agvDetail.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; // Scale idle time by speed + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return + if (!agvDetail.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + // console.log('Final Active Time:', roundedActiveTime, 'seconds'); + incrementActiveTime(agvDetail.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + // console.log('Final Idle Time:', roundedIdleTime, 'seconds'); + incrementIdleTime(agvDetail.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [agvDetail, isPlaying]); + + function handleCallBack() { if (currentPhase === 'stationed-pickup') { setCurrentPhase('picking'); @@ -211,7 +275,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) action: VehicleAction ) { startTime = performance.now(); - const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / speed)); + const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / isSpeedRef.current)); const unloadLoop = () => { if (isPausedRef.current) { @@ -417,7 +481,7 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) const elapsedTime = performance.now() - startTime; const unLoadDuration = agvDetail.point.action.unLoadDuration; - fixedInterval = ((unLoadDuration / agvDetail.currentLoad) * (1000 / speed)); + fixedInterval = ((unLoadDuration / agvDetail.currentLoad) * (1000 / isSpeedRef.current)); if (elapsedTime >= fixedInterval) { let droppedMat = droppedMaterial - 1; @@ -454,4 +518,10 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) ); } -export default VehicleInstance; \ No newline at end of file +export default VehicleInstance; + + + + + + diff --git a/app/src/modules/visualization/charts/BarGraphComponent.tsx b/app/src/modules/visualization/charts/BarGraphComponent.tsx deleted file mode 100644 index 9a07473..0000000 --- a/app/src/modules/visualization/charts/BarGraphComponent.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useMemo } from "react"; - -import { Bar } from "react-chartjs-2"; - -interface ChartComponentProps { - type: any; - title: string; - fontFamily?: string; - fontSize?: string; - fontWeight?: "Light" | "Regular" | "Bold"; - data: any; -} - -const LineGraphComponent = ({ - title, - fontFamily, - fontSize, - fontWeight = "Regular", -}: ChartComponentProps) => { - // Memoize Font Weight Mapping - const chartFontWeightMap = useMemo( - () => ({ - Light: "lighter" as const, - Regular: "normal" as const, - Bold: "bold" as const, - }), - [] - ); - - // Parse and Memoize Font Size - const fontSizeValue = useMemo( - () => (fontSize ? parseInt(fontSize) : 12), - [fontSize] - ); - - // Determine and Memoize Font Weight - const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], - [fontWeight, chartFontWeightMap] - ); - - // Memoize Chart Font Style - const chartFontStyle = useMemo( - () => ({ - family: fontFamily || "Arial", - size: fontSizeValue, - weight: fontWeightValue, - }), - [fontFamily, fontSizeValue, fontWeightValue] - ); - - const options = useMemo( - () => ({ - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: title, - font: chartFontStyle, - }, - legend: { - display: false, - }, - }, - scales: { - x: { - ticks: { - display: false, // This hides the x-axis labels - }, - }, - }, - }), - [title, chartFontStyle] - ); - - const chartData = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "My First Dataset", - data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: "#6f42c1", - borderColor: "#ffffff", - borderWidth: 2, - fill: false, - }, - ], - }; - - return ; -}; - -export default LineGraphComponent; diff --git a/app/src/modules/visualization/charts/LineGraphComponent.tsx b/app/src/modules/visualization/charts/LineGraphComponent.tsx deleted file mode 100644 index cf1a47f..0000000 --- a/app/src/modules/visualization/charts/LineGraphComponent.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useMemo } from "react"; -import { Line } from "react-chartjs-2"; - -interface ChartComponentProps { - type: any; - title: string; - fontFamily?: string; - fontSize?: string; - fontWeight?: "Light" | "Regular" | "Bold"; - data: any; -} - -const LineGraphComponent = ({ - title, - fontFamily, - fontSize, - fontWeight = "Regular", -}: ChartComponentProps) => { - // Memoize Font Weight Mapping - const chartFontWeightMap = useMemo( - () => ({ - Light: "lighter" as const, - Regular: "normal" as const, - Bold: "bold" as const, - }), - [] - ); - - // Parse and Memoize Font Size - const fontSizeValue = useMemo( - () => (fontSize ? parseInt(fontSize) : 12), - [fontSize] - ); - - // Determine and Memoize Font Weight - const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], - [fontWeight, chartFontWeightMap] - ); - - // Memoize Chart Font Style - const chartFontStyle = useMemo( - () => ({ - family: fontFamily || "Arial", - size: fontSizeValue, - weight: fontWeightValue, - }), - [fontFamily, fontSizeValue, fontWeightValue] - ); - - const options = useMemo( - () => ({ - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: title, - font: chartFontStyle, - }, - legend: { - display: false, - }, - }, - scales: { - x: { - ticks: { - display: false, // This hides the x-axis labels - }, - }, - }, - }), - [title, chartFontStyle] - ); - - const chartData = { - labels: ["January", "February", "March", "April", "May", "June", "July"], - datasets: [ - { - label: "My First Dataset", - data: [65, 59, 80, 81, 56, 55, 40], - backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple) - borderColor: "#ffffff", // Keeping border color white - borderWidth: 2, - fill: false, - }, - ], - }; - - return ; -}; - -export default LineGraphComponent; diff --git a/app/src/modules/visualization/charts/PieGraphComponent.tsx b/app/src/modules/visualization/charts/PieGraphComponent.tsx deleted file mode 100644 index 912cbc3..0000000 --- a/app/src/modules/visualization/charts/PieGraphComponent.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useMemo } from "react"; -import { Pie } from "react-chartjs-2"; - -interface ChartComponentProps { - type: any; - title: string; - fontFamily?: string; - fontSize?: string; - fontWeight?: "Light" | "Regular" | "Bold"; - data: any; -} - -const PieChartComponent = ({ - title, - fontFamily, - fontSize, - fontWeight = "Regular", -}: ChartComponentProps) => { - // Memoize Font Weight Mapping - const chartFontWeightMap = useMemo( - () => ({ - Light: "lighter" as const, - Regular: "normal" as const, - Bold: "bold" as const, - }), - [] - ); - - // Parse and Memoize Font Size - const fontSizeValue = useMemo( - () => (fontSize ? parseInt(fontSize) : 12), - [fontSize] - ); - - // Determine and Memoize Font Weight - const fontWeightValue = useMemo( - () => chartFontWeightMap[fontWeight], - [fontWeight, chartFontWeightMap] - ); - - // Memoize Chart Font Style - const chartFontStyle = useMemo( - () => ({ - family: fontFamily || "Arial", - size: fontSizeValue, - weight: fontWeightValue, - }), - [fontFamily, fontSizeValue, fontWeightValue] - ); - - // Access the CSS variable for the primary accent color - const accentColor = getComputedStyle(document.documentElement) - .getPropertyValue("--accent-color") - .trim(); - - const options = useMemo( - () => ({ - responsive: true, - maintainAspectRatio: false, - plugins: { - title: { - display: true, - text: title, - font: chartFontStyle, - }, - legend: { - display: false, - }, - }, - }), - [title, chartFontStyle] - ); - - const chartData = { - labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], - datasets: [ - { - label: "Dataset", - data: [12, 19, 3, 5, 2, 3], - backgroundColor: ["#6f42c1"], - borderColor: "#ffffff", - borderWidth: 2, - }, - ], - }; - - return ; -}; - -export default PieChartComponent; diff --git a/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx index dc96083..56c2e4f 100644 --- a/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx +++ b/app/src/modules/visualization/widgets/2d/charts/BarGraphComponent.tsx @@ -1,187 +1,4 @@ -// import React, { useEffect, useRef, useMemo, useState } from "react"; -// import { Chart } from "chart.js/auto"; -// import { useThemeStore } from "../../../../store/useThemeStore"; -// import io from "socket.io-client"; -// import { Bar } from 'react-chartjs-2'; -// import useChartStore from "../../../../store/useChartStore"; -// // WebSocket Connection -// // const socket = io("http://localhost:5000"); // Adjust to your backend URL - -// interface ChartComponentProps { -// type: any; -// title: string; -// fontFamily?: string; -// fontSize?: string; -// fontWeight?: "Light" | "Regular" | "Bold"; -// data: any; -// } - -// const LineGraphComponent = ({ -// type, -// title, -// fontFamily, -// fontSize, -// fontWeight = "Regular", -// data, -// }: ChartComponentProps) => { -// const canvasRef = useRef(null); -// const { themeColor } = useThemeStore(); -// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ -// labels: [], -// datasets: [], -// }); - -// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; - -// const defaultData = { -// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], -// datasets: [ -// { -// label: "Dataset", -// data: [12, 19, 3, 5, 2, 3], -// backgroundColor: ["#6f42c1"], -// borderColor: "#ffffff", -// borderWidth: 2, -// }, -// ], -// }; - -// // Memoize Theme Colors to Prevent Unnecessary Recalculations -// const buttonActionColor = useMemo( -// () => themeColor[0] || "#5c87df", -// [themeColor] -// ); -// const buttonAbortColor = useMemo( -// () => themeColor[1] || "#ffffff", -// [themeColor] -// ); - -// // Memoize Font Weight Mapping -// const chartFontWeightMap = useMemo( -// () => ({ -// Light: "lighter" as const, -// Regular: "normal" as const, -// Bold: "bold" as const, -// }), -// [] -// ); - -// // Parse and Memoize Font Size -// const fontSizeValue = useMemo( -// () => (fontSize ? parseInt(fontSize) : 12), -// [fontSize] -// ); - -// // Determine and Memoize Font Weight -// const fontWeightValue = useMemo( -// () => chartFontWeightMap[fontWeight], -// [fontWeight, chartFontWeightMap] -// ); - -// // Memoize Chart Font Style -// const chartFontStyle = useMemo( -// () => ({ -// family: fontFamily || "Arial", -// size: fontSizeValue, -// weight: fontWeightValue, -// }), -// [fontFamily, fontSizeValue, fontWeightValue] -// ); - -// // Memoize Chart Data -// // const data = useMemo(() => propsData, [propsData]); - -// // Memoize Chart Options -// const options = useMemo( -// () => ({ -// responsive: true, -// maintainAspectRatio: false, -// plugins: { -// title: { -// display: true, -// text: title, -// font: chartFontStyle, -// }, -// legend: { -// display: false, -// }, -// }, -// scales: { -// x: { -// ticks: { -// display: true, // This hides the x-axis labels -// }, -// }, -// }, -// }), -// [title, chartFontStyle] -// ); - -// const { measurements, setMeasurements, updateDuration, duration } = useChartStore(); - -// useEffect(() => { - -// const socket = io(`http://${iotApiUrl}`); - -// if ( measurements.length > 0 ) { -// var inputes = { -// measurements: measurements, -// duration: duration, -// interval: 1000, -// } - -// // Start stream -// const startStream = () => { -// socket.emit("lineInput", inputes); -// } - -// socket.on('connect', startStream); - -// socket.on("lineOutput", (response) => { -// const responceData = response.data; -// console.log("Received data:", responceData); - -// // Extract timestamps and values -// const labels = responceData.time; -// const datasets = measurements.map((measurement: any) => { -// const key = `${measurement.name}.${measurement.fields}`; -// return { -// label: key, -// data: responceData[key]?.values ?? [], // Ensure it exists -// backgroundColor: "#6f42c1", -// borderColor: "#ffffff", -// }; -// }); - -// setChartData({ labels, datasets }); -// }); -// } - -// return () => { -// socket.off("lineOutput"); -// socket.emit("stop_stream"); // Stop streaming when component unmounts -// }; -// }, [measurements, duration]); - -// // useEffect(() => { -// // if (!canvasRef.current) return; -// // const ctx = canvasRef.current.getContext("2d"); -// // if (!ctx) return; - -// // const chart = new Chart(ctx, { -// // type, -// // data: chartData, -// // options: options, -// // }); - -// // return () => chart.destroy(); -// // }, [chartData, type, title]); - -// return 0 ? chartData : defaultData} options={options} />; -// }; - -// export default LineGraphComponent; import React, { useEffect, useMemo, useState } from "react"; import { Bar } from "react-chartjs-2"; @@ -243,7 +60,7 @@ const BarGraphComponent = ({ ], }; - useEffect(() => {}, []); + useEffect(() => { }, []); // Memoize Theme Colors const buttonActionColor = useMemo( diff --git a/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx b/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx index e331593..fdea15e 100644 --- a/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx +++ b/app/src/modules/visualization/widgets/2d/charts/PieGraphComponent.tsx @@ -1,188 +1,3 @@ -// import React, { useEffect, useRef, useMemo, useState } from "react"; -// import { Chart } from "chart.js/auto"; -// import { useThemeStore } from "../../../../store/useThemeStore"; -// import io from "socket.io-client"; -// import { Pie } from 'react-chartjs-2'; -// import useChartStore from "../../../../store/useChartStore"; - -// // WebSocket Connection -// // const socket = io("http://localhost:5000"); // Adjust to your backend URL - -// interface ChartComponentProps { -// type: any; -// title: string; -// fontFamily?: string; -// fontSize?: string; -// fontWeight?: "Light" | "Regular" | "Bold"; -// data: any; -// } - -// const PieChartComponent = ({ -// type, -// title, -// fontFamily, -// fontSize, -// fontWeight = "Regular", -// data, -// }: ChartComponentProps) => { -// const canvasRef = useRef(null); -// const { themeColor } = useThemeStore(); -// const [chartData, setChartData] = useState<{ labels: string[]; datasets: any[] }>({ -// labels: [], -// datasets: [], -// }); - -// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL; - -// const defaultData = { -// labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], -// datasets: [ -// { -// label: "Dataset", -// data: [12, 19, 3, 5, 2, 3], -// backgroundColor: ["#6f42c1"], -// borderColor: "#ffffff", -// borderWidth: 2, -// }, -// ], -// }; - -// // Memoize Theme Colors to Prevent Unnecessary Recalculations -// const buttonActionColor = useMemo( -// () => themeColor[0] || "#6f42c1", -// [themeColor] -// ); -// const buttonAbortColor = useMemo( -// () => themeColor[1] || "#ffffff", -// [themeColor] -// ); - -// // Memoize Font Weight Mapping -// const chartFontWeightMap = useMemo( -// () => ({ -// Light: "lighter" as const, -// Regular: "normal" as const, -// Bold: "bold" as const, -// }), -// [] -// ); - -// // Parse and Memoize Font Size -// const fontSizeValue = useMemo( -// () => (fontSize ? parseInt(fontSize) : 12), -// [fontSize] -// ); - -// // Determine and Memoize Font Weight -// const fontWeightValue = useMemo( -// () => chartFontWeightMap[fontWeight], -// [fontWeight, chartFontWeightMap] -// ); - -// // Memoize Chart Font Style -// const chartFontStyle = useMemo( -// () => ({ -// family: fontFamily || "Arial", -// size: fontSizeValue, -// weight: fontWeightValue, -// }), -// [fontFamily, fontSizeValue, fontWeightValue] -// ); - -// // Memoize Chart Data -// // const data = useMemo(() => propsData, [propsData]); - -// // Memoize Chart Options -// const options = useMemo( -// () => ({ -// responsive: true, -// maintainAspectRatio: false, -// plugins: { -// title: { -// display: true, -// text: title, -// font: chartFontStyle, -// }, -// legend: { -// display: false, -// }, -// }, -// scales: { -// // x: { -// // ticks: { -// // display: true, // This hides the x-axis labels -// // }, -// // }, -// }, -// }), -// [title, chartFontStyle] -// ); - -// const { measurements, setMeasurements, updateDuration, duration } = useChartStore(); - -// useEffect(() => { - -// const socket = io(`http://${iotApiUrl}`); - -// if ( measurements.length > 0 ) { -// var inputes = { -// measurements: measurements, -// duration: duration, -// interval: 1000, -// } - -// // Start stream -// const startStream = () => { -// socket.emit("lineInput", inputes); -// } - -// socket.on('connect', startStream); - -// socket.on("lineOutput", (response) => { -// const responceData = response.data; -// console.log("Received data:", responceData); - -// // Extract timestamps and values -// const labels = responceData.time; -// const datasets = measurements.map((measurement: any) => { -// const key = `${measurement.name}.${measurement.fields}`; -// return { -// label: key, -// data: responceData[key]?.values ?? [], // Ensure it exists -// backgroundColor: "#6f42c1", -// borderColor: "#ffffff", -// }; -// }); - -// setChartData({ labels, datasets }); -// }); -// } - -// return () => { -// socket.off("lineOutput"); -// socket.emit("stop_stream"); // Stop streaming when component unmounts -// }; -// }, [measurements, duration]); - -// // useEffect(() => { -// // if (!canvasRef.current) return; -// // const ctx = canvasRef.current.getContext("2d"); -// // if (!ctx) return; - -// // const chart = new Chart(ctx, { -// // type, -// // data: chartData, -// // options: options, -// // }); - -// // return () => chart.destroy(); -// // }, [chartData, type, title]); - -// return 0 ? chartData : defaultData} options={options} />; -// }; - -// export default PieChartComponent; - import React, { useEffect, useMemo, useState } from "react"; import { Pie } from "react-chartjs-2"; import io from "socket.io-client"; diff --git a/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx index 53cc473..19fe97d 100644 --- a/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ProductionCapacity.tsx @@ -203,7 +203,7 @@ const ProductionCapacity: React.FC = ({ scale={[0.5, 0.5, 0.5]} rotation={rotation} transform - sprite={false} + sprite={true} zIndexRange={[1, 0]} // style={{ // transform: transformStyle.transform, diff --git a/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx index 5e638cf..6aed5ca 100644 --- a/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/ReturnOfInvestment.tsx @@ -242,7 +242,7 @@ const ReturnOfInvestment: React.FC = ({ rotation={rotation} scale={[0.5, 0.5, 0.5]} transform - sprite={false} + sprite={true} // style={{ // transform: transformStyle.transform, // transformStyle: "preserve-3d", diff --git a/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx index d5cc07e..da5c411 100644 --- a/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/StateWorking.tsx @@ -121,7 +121,7 @@ const StateWorking: React.FC = ({ scale={[0.5, 0.5, 0.5]} transform zIndexRange={[1, 0]} - sprite={false} + sprite={true} // style={{ // transform: transformStyle.transform, // transformStyle: "preserve-3d", diff --git a/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx index 18edb61..f003607 100644 --- a/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx +++ b/app/src/modules/visualization/widgets/3d/cards/Throughput.tsx @@ -83,6 +83,8 @@ const Throughput: React.FC = ({ const email = localStorage.getItem("email") || ""; const organization = email?.split("@")[1]?.split(".")[0]; + + // Sample data for the line graph const graphData: ChartData<"line"> = { labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], @@ -221,17 +223,11 @@ const Throughput: React.FC = ({ scale={[0.5, 0.5, 0.5]} transform zIndexRange={[1, 0]} - sprite={false} - // style={{ - // transform: transformStyle.transform, - // transformStyle: "preserve-3d", - // transition: "transform 0.1s ease-out", - // }} + sprite={true} >
setSelectedChartId({ id: id, type: type })} onContextMenu={onContextMenu} > diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index e93078a..f05a1b2 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -459,12 +459,88 @@ interface ShortcutStore { } export const useShortcutStore = create((set) => ({ - showShortcuts: false, - setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), + showShortcuts: false, + setShowShortcuts: (value) => set({ showShortcuts: value }), + toggleShortcuts: () => + set((state) => ({ showShortcuts: !state.showShortcuts })), })); +export const useMachineCount = create((set: any) => ({ + machineCount: 0, + setMachineCount: (x: any) => set({ machineCount: x }), +})); +export const useMachineUptime = create((set: any) => ({ + machineActiveTime: 0, + setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), +})); +export const useMaterialCycle = create((set: any) => ({ + materialCycleTime: 0, + setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), +})); + +export const useThroughPutData = create((set: any) => ({ + throughputData: 0, + setThroughputData: (x: any) => set({ throughputData: x }), +})); +export const useProductionCapacityData = create((set: any) => ({ + productionCapacityData: 0, + setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), +})); + +export const useProcessBar = create((set: any) => ({ + processBar: [], + setProcessBar: (x: any) => set({ processBar: x }), +})); + + +type InputValuesStore = { + inputValues: Record; + setInputValues: (values: Record) => void; + updateInputValue: (label: string, value: string) => void; // <- New +}; + +export const useInputValues = create((set) => ({ + inputValues: {}, + setInputValues: (values) => set({ inputValues: values }), + updateInputValue: (label, value) => + set((state) => ({ + inputValues: { + ...state.inputValues, + [label]: value, + }, + })), +})); + +export interface ROISummaryData { + productName: string; + roiPercentage: number; + paybackPeriod: number; + totalCost: number; + revenueGenerated: number; + netProfit: number; + netLoss: number; +} + +interface ROISummaryStore { + roiSummary: ROISummaryData; + setRoiSummaryData: (values: ROISummaryData) => void; +} + +export const useROISummaryData = create((set) => ({ + roiSummary: { + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }, + setRoiSummaryData: (values) => set({ roiSummary: values }), +})); + + + interface CompareStore { comparePopUp: boolean; setComparePopUp: (value: boolean) => void; diff --git a/app/src/store/simulation/useMachineStore.ts b/app/src/store/simulation/useMachineStore.ts index 2c4c98a..df480e0 100644 --- a/app/src/store/simulation/useMachineStore.ts +++ b/app/src/store/simulation/useMachineStore.ts @@ -1,154 +1,176 @@ -import { create } from 'zustand'; -import { immer } from 'zustand/middleware/immer'; +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; interface MachineStore { - machines: MachineStatus[]; + machines: MachineStatus[]; - addMachine: (productId: string, machine: MachineEventSchema) => void; - removeMachine: (modelUuid: string) => void; - updateMachine: ( - modelUuid: string, - updates: Partial> - ) => void; - clearMachines: () => void; + addMachine: (productId: string, machine: MachineEventSchema) => void; + removeMachine: (modelUuid: string) => void; + updateMachine: ( + modelUuid: string, + updates: Partial> + ) => void; + clearMachines: () => void; - addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string, materialId: string) => void; - removeCurrentAction: (modelUuid: string) => void; + addCurrentAction: ( + modelUuid: string, + actionUuid: string, + materialType: string, + materialId: string + ) => void; + removeCurrentAction: (modelUuid: string) => void; - setMachineActive: (modelUuid: string, isActive: boolean) => void; - setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void; + setMachineActive: (modelUuid: string, isActive: boolean) => void; + setMachineState: ( + modelUuid: string, + newState: MachineStatus["state"] + ) => void; - incrementActiveTime: (modelUuid: string, incrementBy: number) => void; - incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + incrementActiveTime: (modelUuid: string, incrementBy: number) => void; + incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; - getMachineById: (modelUuid: string) => MachineStatus | undefined; - getMachinesByProduct: (productId: string) => MachineStatus[]; - getMachinesBystate: (state: string) => MachineStatus[]; - getActiveMachines: () => MachineStatus[]; - getIdleMachines: () => MachineStatus[]; + getMachineById: (modelUuid: string) => MachineStatus | undefined; + getMachinesByProduct: (productId: string) => MachineStatus[]; + getMachinesBystate: (state: string) => MachineStatus[]; + getActiveMachines: () => MachineStatus[]; + getIdleMachines: () => MachineStatus[]; } export const useMachineStore = create()( - immer((set, get) => ({ - machines: [], + immer((set, get) => ({ + machines: [], - addMachine: (productId, machine) => { - set((state) => { - const exists = state.machines.some(m => m.modelUuid === machine.modelUuid); - if (!exists) { - state.machines.push({ - ...machine, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - state: 'idle', - }); - } - }); - }, + addMachine: (productId, machine) => { + set((state) => { + const exists = state.machines.some( + (m) => m.modelUuid === machine.modelUuid + ); + if (!exists) { + state.machines.push({ + ...machine, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + state: "idle", + }); + } + }); + }, - removeMachine: (modelUuid) => { - set((state) => { - state.machines = state.machines.filter(m => m.modelUuid !== modelUuid); - }); - }, + removeMachine: (modelUuid) => { + set((state) => { + state.machines = state.machines.filter( + (m) => m.modelUuid !== modelUuid + ); + }); + }, - updateMachine: (modelUuid, updates) => { - set((state) => { - const machine = state.machines.find(m => m.modelUuid === modelUuid); - if (machine) { - Object.assign(machine, updates); - } - }); - }, + updateMachine: (modelUuid, updates) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + Object.assign(machine, updates); + } + }); + }, - clearMachines: () => { - set((state) => { - state.machines = []; - }); - }, + clearMachines: () => { + set((state) => { + state.machines = []; + }); + }, - addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => { - set((state) => { - const armBot = state.machines.find(a => a.modelUuid === modelUuid); - if (armBot) { - const action = armBot.point.action; - if (action) { - armBot.currentAction = { - actionUuid: actionUuid, - actionName: action.actionName, - materialType: materialType, - materialId: materialId - }; - } - } - }); - }, + addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => { + set((state) => { + const armBot = state.machines.find((a) => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.action; + if (action) { + armBot.currentAction = { + actionUuid: actionUuid, + actionName: action.actionName, + materialType: materialType, + materialId: materialId, + }; + } + } + }); + }, - removeCurrentAction: (modelUuid) => { - set((state) => { - const armBot = state.machines.find(a => a.modelUuid === modelUuid); - if (armBot) { - armBot.currentAction = undefined; - } - }); - }, + removeCurrentAction: (modelUuid) => { + set((state) => { + const armBot = state.machines.find((a) => a.modelUuid === modelUuid); + if (armBot) { + armBot.currentAction = undefined; + } + }); + }, - setMachineActive: (modelUuid, isActive) => { - set((state) => { - const machine = state.machines.find(m => m.modelUuid === modelUuid); - if (machine) { - machine.isActive = isActive; - } - }); - }, + setMachineActive: (modelUuid, isActive) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + machine.isActive = isActive; + } + }); + }, - setMachineState: (modelUuid, newState) => { - set((state) => { - const machine = state.machines.find(m => m.modelUuid === modelUuid); - if (machine) { - machine.state = newState; - } - }); - }, + setMachineState: (modelUuid, newState) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + machine.state = newState; + } + }); + }, - incrementActiveTime: (modelUuid, incrementBy) => { - set((state) => { - const machine = state.machines.find(m => m.modelUuid === modelUuid); - if (machine) { - machine.activeTime += incrementBy; - } - }); - }, + incrementActiveTime: (modelUuid, incrementBy) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + machine.activeTime += incrementBy; + } + }); + }, - incrementIdleTime: (modelUuid, incrementBy) => { - set((state) => { - const machine = state.machines.find(m => m.modelUuid === modelUuid); - if (machine) { - machine.idleTime += incrementBy; - } - }); - }, + incrementIdleTime: (modelUuid, incrementBy) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + machine.idleTime += incrementBy; + } + }); + }, + resetTime: (modelUuid) => { + set((state) => { + const machine = state.machines.find((m) => m.modelUuid === modelUuid); + if (machine) { + machine.activeTime = 0; + machine.idleTime = 0; + } + }); + }, - getMachineById: (modelUuid) => { - return get().machines.find(m => m.modelUuid === modelUuid); - }, + getMachineById: (modelUuid) => { + return get().machines.find((m) => m.modelUuid === modelUuid); + }, - getMachinesByProduct: (productId) => { - return get().machines.filter(m => m.productId === productId); - }, + getMachinesByProduct: (productId) => { + return get().machines.filter((m) => m.productId === productId); + }, - getMachinesBystate: (state) => { - return get().machines.filter(m => m.state === state); - }, + getMachinesBystate: (state) => { + return get().machines.filter((m) => m.state === state); + }, - getActiveMachines: () => { - return get().machines.filter(m => m.isActive); - }, + getActiveMachines: () => { + return get().machines.filter((m) => m.isActive); + }, - getIdleMachines: () => { - return get().machines.filter(m => !m.isActive && m.state === 'idle'); - }, - })) + getIdleMachines: () => { + return get().machines.filter((m) => !m.isActive && m.state === "idle"); + }, + })) ); diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index b492c7e..d1ce04f 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -29,6 +29,7 @@ interface VehiclesStore { clearCurrentMaterials: (modelUuid: string) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; getVehicleById: (modelUuid: string) => VehicleStatus | undefined; getVehiclesByProduct: (productId: string) => VehicleStatus[]; @@ -223,6 +224,16 @@ export const useVehicleStore = create()( }); }, + resetTime: (modelUuid) => { + set((state) => { + const vehicle = state.vehicles.find((v) => v.modelUuid === modelUuid); + if (vehicle) { + vehicle.activeTime = 0; + vehicle.idleTime = 0; + } + }); + }, + getVehicleById: (modelUuid) => { return get().vehicles.find((v) => v.modelUuid === modelUuid); }, diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 498938a..53e3193 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -768,7 +768,6 @@ display: flex; flex-direction: column; border-radius: #{$border-radius-medium}; - overflow: hidden; padding: 4px; min-width: 150px; @@ -780,6 +779,7 @@ color: var(--text-color); text-wrap: nowrap; cursor: pointer; + pointer-events: all; &:hover { background: var(--highlight-accent-color);