Files
Dwinzo_Demo/app/src/components/SimulationDashboard/components/element/ElementEditor.tsx

578 lines
34 KiB
TypeScript

import { useCallback, useMemo, useState, type RefObject } from "react";
import { ExtendedCSSProperties, UIElement } from "../../../../types/exportedTypes";
import { DeleteIcon } from "../../../icons/ContextMenuIcons";
import DataSourceSelector from "../../../ui/inputs/DataSourceSelector";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import InputRange from "../../../ui/inputs/InputRange";
import RenameInput from "../../../ui/inputs/RenameInput";
import { AddIcon, DeviceIcon, ParametersIcon } from "../../../icons/ExportCommonIcons";
import DataDetailedDropdown from "../../../ui/inputs/DataDetailedDropdown";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import ElementDesign from "./ElementDesign";
interface ElementEditorProps {
elementEditorRef: RefObject<HTMLDivElement>;
currentElement: UIElement;
selectedBlock: string;
selectedElement: string;
updateElementStyle: (blockId: string, elementId: string, style: ExtendedCSSProperties) => void;
updateElementSize: (blockId: string, elementId: string, size: { width: number; height: number }) => void;
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void;
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void;
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
updateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => void;
updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;
updateDataType: (blockId: string, elementId: string, dataType: "single-machine" | "multiple-machine") => void;
updateCommonValue: (blockId: string, elementId: string, commonValue: string) => void;
updateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => void;
updateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => void;
handleRemoveElement: (blockId: string, elementId: string) => void;
setSwapSource: (source: string | null) => void;
setShowSwapUI: (show: boolean) => void;
}
const ElementEditor: React.FC<ElementEditorProps> = ({
elementEditorRef,
currentElement,
selectedBlock,
selectedElement,
updateElementStyle,
updateElementSize,
updateElementPosition,
updateElementPositionType,
updateElementZIndex,
updateElementData,
updateGraphData,
updateGraphTitle,
updateGraphType,
updateDataType,
updateCommonValue,
updateDataValue,
updateDataSource,
handleRemoveElement,
setSwapSource,
setShowSwapUI,
}) => {
const { simulationDashBoardStore, productStore, analysisStore } = useSceneContext();
const { products, selectedProduct, getProductById, getEventByModelUuid } = productStore();
const { analysis } = analysisStore();
const product = getProductById(selectedProduct.productUuid);
const { getElementById } = simulationDashBoardStore();
const element = getElementById(selectedBlock, selectedElement);
const [selectType, setSelectType] = useState("design");
const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
const getAssetDropdownItems = useCallback(() => {
if (!product?.eventDatas) return [];
return product.eventDatas.map((asset) => ({
id: asset.modelUuid,
label: asset.modelName,
icon: <DeviceIcon />,
}));
}, [product?.eventDatas]);
const getLableValueDropdownItems = useCallback(
(assetId: string | undefined) => {
if (!product || !assetId) return [];
const globalItems = [
{
title: "System Performance",
items: [
{ id: "global.systemPerformance.overallOEE", label: "Overall System OEE", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.systemThroughput", label: "System Throughput", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.systemUtilization", label: "System Utilization", icon: <ParametersIcon /> },
{ id: "global.materialFlow.totalMaterialsInSystem", label: "Total Materials in System", icon: <ParametersIcon /> },
{ id: "global.materialFlow.materialsCompleted", label: "Materials Completed", icon: <ParametersIcon /> },
{ id: "global.materialFlow.averageResidenceTime", label: "Average Residence Time", icon: <ParametersIcon /> },
],
},
{
title: "Critical System Metrics",
items: [
{ id: "global.systemPerformance.criticalMetrics.activeAssets", label: "Active Assets", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.criticalMetrics.totalAssets", label: "Total Assets", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.criticalMetrics.assetsInError", label: "Assets in Error", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.criticalMetrics.assetsIdle", label: "Assets Idle", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.criticalMetrics.totalDowntime", label: "Total Downtime", icon: <ParametersIcon /> },
{ id: "global.systemPerformance.criticalMetrics.systemUptime", label: "System Uptime", icon: <ParametersIcon /> },
],
},
{
title: "Predictive Insights",
items: [
{ id: "global.predictiveInsights.maintenanceAlerts", label: "Maintenance Alerts", icon: <ParametersIcon /> },
{ id: "global.predictiveInsights.optimizationOpportunities", label: "Optimization Opportunities", icon: <ParametersIcon /> },
],
},
];
if (assetId === "global") {
return [...globalItems];
}
const assetAnalysis = product?.eventDatas.find((e) => e.modelUuid === assetId);
if (!assetAnalysis) {
return [...globalItems];
}
const assetSpecificItems = [];
switch (assetAnalysis.type) {
case "transfer":
assetSpecificItems.push({
title: "Conveyor Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.isPaused", label: "Is Paused", icon: <ParametersIcon /> },
{ id: "currentStatus.speed", label: "Speed", icon: <ParametersIcon /> },
{ id: "currentStatus.currentMaterials", label: "Current Materials", icon: <ParametersIcon /> },
{ id: "timeMetrics.uptime", label: "Uptime", icon: <ParametersIcon /> },
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "throughput.itemsPerHour", label: "Items Per Hour", icon: <ParametersIcon /> },
{ id: "efficiency.overallEffectiveness", label: "Overall Effectiveness (OEE)", icon: <ParametersIcon /> },
{ id: "materialFlow.wip", label: "Work In Progress", icon: <ParametersIcon /> },
{ id: "quality.successRate", label: "Success Rate", icon: <ParametersIcon /> },
{ id: "costMetrics.operatingCost", label: "Operating Cost", icon: <ParametersIcon /> },
{ id: "energyMetrics.energyConsumed", label: "Energy Consumed", icon: <ParametersIcon /> },
],
});
break;
case "vehicle":
assetSpecificItems.push({
title: "Vehicle Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.currentPhase", label: "Current Phase", icon: <ParametersIcon /> },
{ id: "currentStatus.speed", label: "Speed", icon: <ParametersIcon /> },
{ id: "currentStatus.currentLoad", label: "Current Load", icon: <ParametersIcon /> },
{ id: "currentStatus.distanceTraveled", label: "Distance Traveled", icon: <ParametersIcon /> },
{ id: "movementMetrics.distanceTraveled", label: "Total Distance", icon: <ParametersIcon /> },
{ id: "movementMetrics.averageSpeedActual", label: "Average Speed", icon: <ParametersIcon /> },
{ id: "throughput.tripsCompleted", label: "Trips Completed", icon: <ParametersIcon /> },
{ id: "efficiency.loadUtilization", label: "Load Utilization", icon: <ParametersIcon /> },
{ id: "quality.onTimeDelivery", label: "On-Time Delivery", icon: <ParametersIcon /> },
{ id: "costMetrics.costPerMile", label: "Cost Per Mile", icon: <ParametersIcon /> },
],
});
break;
case "roboticArm":
assetSpecificItems.push({
title: "Robotic Arm Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.speed", label: "Speed", icon: <ParametersIcon /> },
{ id: "timeMetrics.uptime", label: "Uptime", icon: <ParametersIcon /> },
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "throughput.cyclesCompleted", label: "Cycles Completed", icon: <ParametersIcon /> },
{ id: "throughput.pickAndPlaceCount", label: "Pick & Place Count", icon: <ParametersIcon /> },
{ id: "efficiency.overallEffectiveness", label: "Overall Effectiveness", icon: <ParametersIcon /> },
{ id: "quality.pickSuccessRate", label: "Pick Success Rate", icon: <ParametersIcon /> },
{ id: "quality.placeAccuracy", label: "Place Accuracy", icon: <ParametersIcon /> },
{ id: "costMetrics.totalCost", label: "Total Cost", icon: <ParametersIcon /> },
],
});
break;
case "machine":
assetSpecificItems.push({
title: "Machine Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "timeMetrics.uptime", label: "Uptime", icon: <ParametersIcon /> },
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "throughput.cyclesCompleted", label: "Cycles Completed", icon: <ParametersIcon /> },
{ id: "throughput.partsProcessed", label: "Parts Processed", icon: <ParametersIcon /> },
{ id: "efficiency.overallEffectiveness", label: "Overall Effectiveness", icon: <ParametersIcon /> },
{ id: "quality.defectRate", label: "Defect Rate", icon: <ParametersIcon /> },
{ id: "quality.successRate", label: "Success Rate", icon: <ParametersIcon /> },
{ id: "quality.reworkRate", label: "Rework Rate", icon: <ParametersIcon /> },
{ id: "costMetrics.costPerUnit", label: "Cost Per Unit", icon: <ParametersIcon /> },
{ id: "energyMetrics.energyPerUnit", label: "Energy Per Unit", icon: <ParametersIcon /> },
],
});
break;
case "storageUnit":
assetSpecificItems.push({
title: "Storage Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.currentLoad", label: "Current Load", icon: <ParametersIcon /> },
{ id: "currentStatus.storageCapacity", label: "Storage Capacity", icon: <ParametersIcon /> },
{ id: "capacityMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "capacityMetrics.averageOccupancy", label: "Average Occupancy", icon: <ParametersIcon /> },
{ id: "throughput.storeOperations", label: "Store Operations", icon: <ParametersIcon /> },
{ id: "throughput.retrieveOperations", label: "Retrieve Operations", icon: <ParametersIcon /> },
{ id: "throughput.totalOperations", label: "Total Operations", icon: <ParametersIcon /> },
{ id: "efficiency.spaceUtilization", label: "Space Utilization", icon: <ParametersIcon /> },
{ id: "costMetrics.costPerStorageHour", label: "Cost Per Storage Hour", icon: <ParametersIcon /> },
],
});
break;
case "human":
assetSpecificItems.push({
title: "Human Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.currentPhase", label: "Current Phase", icon: <ParametersIcon /> },
{ id: "currentStatus.speed", label: "Speed", icon: <ParametersIcon /> },
{ id: "currentStatus.currentLoad", label: "Current Load", icon: <ParametersIcon /> },
{ id: "productivityMetrics.actionsCompleted", label: "Actions Completed", icon: <ParametersIcon /> },
{ id: "productivityMetrics.actionsPerHour", label: "Actions Per Hour", icon: <ParametersIcon /> },
{ id: "efficiency.laborProductivity", label: "Labor Productivity", icon: <ParametersIcon /> },
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "workloadDistribution", label: "Workload Distribution", icon: <ParametersIcon /> },
{ id: "costMetrics.costPerHour", label: "Cost Per Hour", icon: <ParametersIcon /> },
{ id: "productivityMetrics.distanceTraveled", label: "Distance Traveled", icon: <ParametersIcon /> },
],
});
break;
case "crane":
assetSpecificItems.push({
title: "Crane Metrics",
items: [
{ id: "currentStatus.isActive", label: "Is Active", icon: <ParametersIcon /> },
{ id: "currentStatus.isCarrying", label: "Is Carrying", icon: <ParametersIcon /> },
{ id: "currentStatus.currentLoad", label: "Current Load", icon: <ParametersIcon /> },
{ id: "throughput.cyclesCompleted", label: "Cycles Completed", icon: <ParametersIcon /> },
{ id: "throughput.loadsHandled", label: "Loads Handled", icon: <ParametersIcon /> },
{ id: "movementMetrics.totalLifts", label: "Total Lifts", icon: <ParametersIcon /> },
{ id: "efficiency.loadUtilization", label: "Load Utilization", icon: <ParametersIcon /> },
{ id: "quality.liftSuccessRate", label: "Lift Success Rate", icon: <ParametersIcon /> },
{ id: "quality.positioningAccuracy", label: "Positioning Accuracy", icon: <ParametersIcon /> },
{ id: "costMetrics.costPerLift", label: "Cost Per Lift", icon: <ParametersIcon /> },
{ id: "energyMetrics.energyPerLift", label: "Energy Per Lift", icon: <ParametersIcon /> },
],
});
break;
}
return assetSpecificItems;
},
[product]
);
const getCommonValueDropdownItems = useCallback(() => {
const commonItems = [
{ id: "timeMetrics.uptime", label: "Uptime", icon: <ParametersIcon /> },
{ id: "timeMetrics.utilizationRate", label: "Utilization Rate", icon: <ParametersIcon /> },
{ id: "efficiency.overallEffectiveness", label: "Overall Effectiveness", icon: <ParametersIcon /> },
{ id: "quality.successRate", label: "Success Rate", icon: <ParametersIcon /> },
{ id: "throughput.itemsPerHour", label: "Items Per Hour", icon: <ParametersIcon /> },
{ id: "costMetrics.operatingCost", label: "Operating Cost", icon: <ParametersIcon /> },
{ id: "energyMetrics.energyConsumed", label: "Energy Consumed", icon: <ParametersIcon /> },
];
return commonItems;
}, []);
const singleSourceFields = useMemo(
() => [{ id: "data-source", label: "Data Source", showEyeDropper: true, options: getAssetDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
[getAssetDropdownItems]
);
const singleValueFields = useMemo(() => {
if (element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const dataValues = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
const valueOptions = element.dataBinding.dataSource
? getLableValueDropdownItems(element.dataBinding.dataSource as string).flatMap((section) => section.items.map((item) => ({ id: item.id, label: item.label })))
: [];
return dataValues.map((value, index) => ({
id: `data-value-${index}`,
label: `Data Value ${index + 1}`,
showEyeDropper: false,
options: valueOptions,
}));
}
return [
{
id: "data-value",
label: "Data Value",
showEyeDropper: false,
options: [],
},
];
}, [element, getLableValueDropdownItems]);
const multipleSourceFields = useMemo(() => {
if (element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const dataSources = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
return dataSources.map((_, index) => ({
id: `data-source-${index}`,
label: `Data Source ${index + 1}`,
showEyeDropper: true,
options: getAssetDropdownItems().map((item) => ({
id: item.id,
label: item.label,
})),
}));
}
return [];
}, [element, getAssetDropdownItems]);
const multipleValueFields = useMemo(
() => [{ id: "data-value", label: "Data Value", showEyeDropper: false, options: getCommonValueDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
[getCommonValueDropdownItems]
);
const handleDataTypeSwitch = (newDataType: "singleMachine" | "multipleMachine") => {
if (!element || element.type !== "graph") return;
setSelectDataMapping(newDataType);
if (newDataType === "singleMachine" && element.dataBinding?.dataType === "multiple-machine") {
updateDataType(selectedBlock, selectedElement, "single-machine");
} else if (newDataType === "multipleMachine" && element.dataBinding?.dataType === "single-machine") {
updateDataType(selectedBlock, selectedElement, "multiple-machine");
}
};
const addField = () => {
if (selectDataMapping === "singleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const currentDataValue = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
const newDataValue = [...currentDataValue, ""];
updateDataValue(selectedBlock, selectedElement, newDataValue);
} else if (selectDataMapping === "multipleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const currentDataSource = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
const newDataSource = [...currentDataSource, ""];
updateDataSource(selectedBlock, selectedElement, newDataSource);
}
};
return (
<div ref={elementEditorRef} className="panel element-editor-panel">
<div className="header">
<h4>Element Style</h4>
<div
className="delete icon"
onClick={() => {
handleRemoveElement(selectedBlock, selectedElement);
}}
>
<DeleteIcon />
</div>
</div>
{(element?.type === "graph" || element?.type === "label-value") && (
<div className="type-switch">
<div className={`type ${selectType === "design" ? "active" : ""}`} onClick={() => setSelectType("design")}>
Design
</div>
<div className={`type ${selectType === "data" ? "active" : ""}`} onClick={() => setSelectType("data")}>
Data
</div>
</div>
)}
{selectType === "design" && (
<ElementDesign
element={element}
elementEditorRef={elementEditorRef}
currentElement={currentElement}
selectedBlock={selectedBlock}
selectedElement={selectedElement}
updateElementStyle={updateElementStyle}
updateElementSize={updateElementSize}
updateElementPosition={updateElementPosition}
updateElementPositionType={updateElementPositionType}
updateElementZIndex={updateElementZIndex}
updateElementData={updateElementData}
updateGraphData={updateGraphData}
updateGraphTitle={updateGraphTitle}
updateGraphType={updateGraphType}
updateDataType={updateDataType}
updateCommonValue={updateCommonValue}
updateDataValue={updateDataValue}
updateDataSource={updateDataSource}
handleRemoveElement={handleRemoveElement}
setSwapSource={setSwapSource}
setShowSwapUI={setShowSwapUI}
/>
)}
{selectType === "data" && (
<div className="data-details">
{element?.type === "label-value" && (
<div className="data-wrapper">
<InputWithDropDown
label="Label"
type="string"
value={element.dataBinding?.label || ""}
placeholder={"Label 1"}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { label: value });
}}
/>
<div className="data">
<DataDetailedDropdown
title="Data Source"
placeholder="Select assets"
sections={[
{
title: "Global",
items: [{ id: "global", label: "Global", icon: <DeviceIcon /> }],
},
{
title: "Assets",
items: getAssetDropdownItems(),
},
]}
value={
element.dataBinding?.dataSource
? {
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
: null
}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
}}
dropDownHeader={"RT-Data"}
eyedroper={true}
/>
</div>
<div className="data">
<DataDetailedDropdown
title="Data Value"
placeholder="Select Value"
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
value={
element.dataBinding?.dataValue
? {
id: element.dataBinding.dataValue as string,
label:
getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)
.flatMap((section) => section.items)
.find((item) => item.id === element.dataBinding?.dataValue)?.label ?? "",
icon: <ParametersIcon />,
}
: null
}
onChange={(value) => {
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
}}
dropDownHeader={"RT-Data-Value"}
/>
</div>
</div>
)}
{/* Data Mapping */}
{element?.type === "graph" && (
<div className="data-mapping">
<div className="heading">Data Mapping</div>
<div className="type-switch">
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
Single Machine
</div>
<div className={`type ${selectDataMapping === "multipleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("multipleMachine")}>
Multiple Machine
</div>
</div>
{selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
<div className="fields-wrapper">
{singleSourceFields.map((field) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
onSelect={(value) => {
updateDataSource(selectedBlock, selectedElement, value.id);
}}
showEyeDropper={field.showEyeDropper}
/>
))}
{singleValueFields.map((field, index) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={field.options.find((option) => option.id === element.dataBinding?.dataValue?.[index])?.label ?? ""}
onSelect={(value) => {
const currentDataValue = Array.isArray(element.dataBinding?.dataValue) ? element.dataBinding!.dataValue : [];
const newDataValue = [...currentDataValue];
newDataValue[index] = value.id;
updateDataValue(selectedBlock, selectedElement, newDataValue);
}}
showEyeDropper={field.showEyeDropper}
/>
))}
<div className="add-field" onClick={addField}>
<div className="icon">
<AddIcon />
</div>
<div className="label">Add Field</div>
</div>
</div>
)}
{selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
<div className="fields-wrapper">
{multipleValueFields.map((field) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
onSelect={(value) => {
updateCommonValue(selectedBlock, selectedElement, value.id);
}}
showEyeDropper={field.showEyeDropper}
/>
))}
{multipleSourceFields.map((field, index) => (
<DataSourceSelector
key={field.id}
label={field.label}
options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
onSelect={(value) => {
const current = Array.isArray(element.dataBinding?.dataSource) ? element.dataBinding!.dataSource : [];
const next = [...current];
next[index] = value.id;
updateDataSource(selectedBlock, selectedElement, next);
}}
showEyeDropper={field.showEyeDropper}
/>
))}
<div className="add-field" onClick={addField}>
<div className="icon">
<AddIcon />
</div>
<div className="label">Add Field</div>
</div>
</div>
)}
</div>
)}
</div>
)}
</div>
);
};
export default ElementEditor;