feat: Implement the Simulation Dashboard editor with core components, state management, and types for blocks and elements.
This commit is contained in:
@@ -29,16 +29,16 @@ const AnalyzerManager: React.FC = () => {
|
||||
block.elements.forEach((element) => {
|
||||
// 1. Handle Label-Value (Direct DOM Manipulation)
|
||||
if (element.type === "label-value") {
|
||||
if (!element.dataSource || !element.dataValue) return;
|
||||
if (!element.dataBinding?.dataSource || !element.dataBinding?.dataValue) return;
|
||||
|
||||
let value: any = "-";
|
||||
// Ensure dataValue is treated as string if possible, or take first if array
|
||||
const mixedDataValue = element.dataValue;
|
||||
const mixedDataValue = element.dataBinding.dataValue;
|
||||
const dataValueStr = Array.isArray(mixedDataValue) ? mixedDataValue[0] : mixedDataValue;
|
||||
|
||||
if (typeof dataValueStr !== "string") return;
|
||||
|
||||
if (element.dataSource === "global") {
|
||||
if (element.dataBinding.dataSource === "global") {
|
||||
// Handle global metrics
|
||||
// Expected format: "global.systemPerformance.overallOEE" -> we need last part relative to metrics
|
||||
// But dataValue usually comes as "systemPerformance.overallOEE" if dataSource is global?
|
||||
@@ -58,7 +58,7 @@ const AnalyzerManager: React.FC = () => {
|
||||
|
||||
} else {
|
||||
// Handle Asset Specific
|
||||
const assetAnalysis = getAssetAnalysis(element.dataSource);
|
||||
const assetAnalysis = getAssetAnalysis(element.dataBinding.dataSource as string);
|
||||
if (assetAnalysis) {
|
||||
value = resolvePath(assetAnalysis, dataValueStr);
|
||||
}
|
||||
@@ -77,13 +77,13 @@ const AnalyzerManager: React.FC = () => {
|
||||
// Assuming we want to visualize historical data or current state trends.
|
||||
else if (element.type === "graph") {
|
||||
|
||||
if (element.dataType === "single-machine") {
|
||||
if (!element.dataSource || !element.dataValue) return;
|
||||
if (element.dataBinding?.dataType === "single-machine") {
|
||||
if (!element.dataBinding.dataSource || !element.dataBinding.dataValue) return;
|
||||
// Single machine, multiple values potentially?
|
||||
// element.dataValue is string[] for single-machine graph
|
||||
|
||||
const assetId = element.dataSource as string;
|
||||
const dataKeys = element.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
|
||||
const assetId = element.dataBinding.dataSource as string;
|
||||
const dataKeys = element.dataBinding.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
|
||||
|
||||
// For now support getting the FIRST value as the main value to plot?
|
||||
// Or if we track history?
|
||||
@@ -138,10 +138,10 @@ const AnalyzerManager: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (element.dataType === "multiple-machine") {
|
||||
} else if (element.dataBinding?.dataType === "multiple-machine") {
|
||||
// Multiple machines, single common value
|
||||
const assetIds = element.dataSource as string[];
|
||||
const commonValueKey = element.commonValue as string;
|
||||
const assetIds = element.dataBinding.dataSource as string[];
|
||||
const commonValueKey = element.dataBinding.commonValue as string;
|
||||
|
||||
if (assetIds && commonValueKey) {
|
||||
// Bar chart comparing machines?
|
||||
|
||||
@@ -21,6 +21,7 @@ import { handleBlockDragStart } from "./functions/block/handleBlockDragStart";
|
||||
import { getDashBoardBlocksApi } from "../../services/visulization/dashBoard/getDashBoardBlocks";
|
||||
import { upsetDashBoardBlocksApi } from "../../services/visulization/dashBoard/upsertDashBoardBlocks";
|
||||
import { deleteDashBoardBlocksApi } from "../../services/visulization/dashBoard/deleteDashBoardBlocks";
|
||||
import { deleteDashBoardElementsApi } from "../../services/visulization/dashBoard/deleteDashBoardElements";
|
||||
|
||||
const DashboardEditor: React.FC = () => {
|
||||
const { simulationDashBoardStore, versionStore } = useSceneContext();
|
||||
@@ -622,10 +623,22 @@ const DashboardEditor: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
handleRemoveElement={async (blockId, elementId) => {
|
||||
const updatedBlocks = peekRemoveElement(blockId, elementId);
|
||||
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId);
|
||||
if (updatedBlock) {
|
||||
// await updateBackend(updatedBlock);
|
||||
try {
|
||||
const data = await deleteDashBoardElementsApi({
|
||||
projectId: projectId!,
|
||||
versionId: selectedVersion!.versionId,
|
||||
elementDatas: [{ blockUuid: blockId, elementUuid: elementId }],
|
||||
});
|
||||
|
||||
if (data.elements.length > 0) {
|
||||
data.elements.forEach((element: any) => {
|
||||
if (element.message === "Element deleted successfully") {
|
||||
removeElement(element.blockUuid, element.elementUuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete element:", error);
|
||||
}
|
||||
}}
|
||||
setSwapSource={setSwapSource}
|
||||
|
||||
@@ -37,10 +37,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
updateBlockZIndex,
|
||||
handleRemoveBlock,
|
||||
}) => {
|
||||
|
||||
const [color, setColor] = useState("#000000");
|
||||
|
||||
|
||||
return (
|
||||
<div ref={blockEditorRef} className="panel block-editor-panel">
|
||||
<div className="header">
|
||||
@@ -55,20 +53,18 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="design-section-wrapper">
|
||||
<div className="design-section">
|
||||
<div className="section-header">Size</div>
|
||||
<InputWithDropDown
|
||||
label="Width"
|
||||
value={String(currentBlock.size?.width || 400)} // Ensure the value is a string
|
||||
value={String(currentBlock.size?.width || 400)} // Ensure the value is a string
|
||||
placeholder={"Width"}
|
||||
onChange={(newValue) => {
|
||||
updateBlockSize(selectedBlock, {
|
||||
...currentBlock.size!,
|
||||
width: Number(newValue), // Make sure to convert the string back to a number here
|
||||
})
|
||||
width: Number(newValue), // Make sure to convert the string back to a number here
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
@@ -79,7 +75,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
updateBlockSize(selectedBlock, {
|
||||
...currentBlock.size!,
|
||||
height: Number(newValue),
|
||||
})
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -100,56 +96,22 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
<div className="position-canvas">
|
||||
<div className="canvas">
|
||||
<div className="value padding-top">
|
||||
<RenameInput
|
||||
value={
|
||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
|
||||
: "120"
|
||||
}
|
||||
/>
|
||||
|
||||
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
|
||||
</div>
|
||||
<div className="value padding-right">
|
||||
<RenameInput
|
||||
value={
|
||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
|
||||
: "120"
|
||||
}
|
||||
/>
|
||||
|
||||
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
|
||||
</div>
|
||||
<div className="value padding-bottom">
|
||||
<RenameInput
|
||||
value={
|
||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
|
||||
: "120"
|
||||
}
|
||||
/>
|
||||
|
||||
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
|
||||
</div>
|
||||
<div className="value padding-left">
|
||||
<RenameInput
|
||||
value={
|
||||
getCurrentBlockStyleValue(currentBlock, "padding")
|
||||
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
|
||||
: "120"
|
||||
}
|
||||
/>
|
||||
|
||||
<RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="design-section-footer">
|
||||
<InputWithDropDown
|
||||
label="Layer"
|
||||
value={String(currentBlock.zIndex || 1)}
|
||||
placeholder={"Layer"}
|
||||
onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))}
|
||||
/>
|
||||
<InputWithDropDown label="Layer" value={String(currentBlock.zIndex || 1)} placeholder={"Layer"} onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
|
||||
|
||||
<InputRange
|
||||
label={"Radius"}
|
||||
@@ -159,10 +121,9 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
onChange={(newValue) => {
|
||||
updateBlockStyle(selectedBlock, {
|
||||
borderRadius: Number(newValue),
|
||||
})
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,7 +137,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))}
|
||||
// onChange={(e) => setColor(e.target.value)}
|
||||
onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<div className="colorValue">{color}</div>
|
||||
@@ -189,23 +149,10 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
|
||||
value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)}
|
||||
// onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))}
|
||||
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))}
|
||||
|
||||
/>
|
||||
<InputRange
|
||||
label={"Blur"}
|
||||
min={0}
|
||||
max={8}
|
||||
value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")}
|
||||
|
||||
/>
|
||||
|
||||
<InputRange label={"Blur"} min={0} max={8} value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ interface ElementDesignProps {
|
||||
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<DataBinding>) => 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;
|
||||
|
||||
@@ -20,7 +20,7 @@ interface ElementEditorProps {
|
||||
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<DataBinding>) => 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;
|
||||
@@ -62,7 +62,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
const { getElementById } = simulationDashBoardStore();
|
||||
const element = getElementById(selectedBlock, selectedElement);
|
||||
const [selectType, setSelectType] = useState("design");
|
||||
const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
|
||||
const [selectDataMapping, setSelectDataMapping] = useState(element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine" ? "multipleMachine" : "singleMachine");
|
||||
|
||||
const getAssetDropdownItems = useCallback(() => {
|
||||
if (!product?.eventDatas) return [];
|
||||
@@ -273,10 +273,12 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
);
|
||||
|
||||
const singleValueFields = useMemo(() => {
|
||||
if (element?.type === "graph" && element.dataType === "single-machine") {
|
||||
const dataValues = Array.isArray(element.dataValue) ? element.dataValue : [];
|
||||
if (element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
|
||||
const dataValues = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
|
||||
|
||||
const valueOptions = element.dataSource ? getLableValueDropdownItems(element.dataSource).flatMap((section) => section.items.map((item) => ({ id: item.id, label: item.label }))) : [];
|
||||
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}`,
|
||||
@@ -297,8 +299,8 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
}, [element, getLableValueDropdownItems]);
|
||||
|
||||
const multipleSourceFields = useMemo(() => {
|
||||
if (element?.type === "graph" && element.dataType === "multiple-machine") {
|
||||
const dataSources = Array.isArray(element.dataSource) ? element.dataSource : [];
|
||||
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}`,
|
||||
@@ -324,21 +326,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
|
||||
setSelectDataMapping(newDataType);
|
||||
|
||||
if (newDataType === "singleMachine" && element.dataType === "multiple-machine") {
|
||||
if (newDataType === "singleMachine" && element.dataBinding?.dataType === "multiple-machine") {
|
||||
updateDataType(selectedBlock, selectedElement, "single-machine");
|
||||
} else if (newDataType === "multipleMachine" && element.dataType === "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.dataType === "single-machine") {
|
||||
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : [];
|
||||
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.dataType === "multiple-machine") {
|
||||
const currentDataSource = Array.isArray(element.dataSource) ? element.dataSource : [];
|
||||
} 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);
|
||||
@@ -399,7 +401,15 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
<div className="data-details">
|
||||
{element?.type === "label-value" && (
|
||||
<div className="data-wrapper">
|
||||
<InputWithDropDown label="Title" value={`title`} placeholder={"Label 1"} min={0.1} step={0.1} max={2} onChange={() => {}} />
|
||||
<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"
|
||||
@@ -415,12 +425,18 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
},
|
||||
]}
|
||||
value={
|
||||
element.dataSource
|
||||
? { id: element.dataSource, label: getEventByModelUuid(selectedProduct.productUuid, element.dataSource)?.modelName ?? "", icon: <DeviceIcon /> }
|
||||
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) => {
|
||||
updateDataSource(selectedBlock, selectedElement, value.id);
|
||||
updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
|
||||
}}
|
||||
dropDownHeader={"RT-Data"}
|
||||
eyedroper={true}
|
||||
@@ -431,10 +447,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
<DataDetailedDropdown
|
||||
title="Data Value"
|
||||
placeholder="Select Value"
|
||||
sections={getLableValueDropdownItems(element.dataSource as string | undefined)}
|
||||
value={element.dataValue ? { id: element.dataValue, label: element.dataValue, icon: <ParametersIcon /> } : null}
|
||||
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) => {
|
||||
updateDataValue(selectedBlock, selectedElement, value.id);
|
||||
updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
|
||||
}}
|
||||
dropDownHeader={"RT-Data-Value"}
|
||||
/>
|
||||
@@ -456,14 +483,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectDataMapping === "singleMachine" && element.dataType === "single-machine" && (
|
||||
{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.dataSource)?.modelName ?? ""}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateDataSource(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
@@ -476,9 +503,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={field.options.find((option) => option.id === element.dataValue?.[index])?.label ?? ""}
|
||||
selected={field.options.find((option) => option.id === element.dataBinding?.dataValue?.[index])?.label ?? ""}
|
||||
onSelect={(value) => {
|
||||
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : [];
|
||||
const currentDataValue = Array.isArray(element.dataBinding?.dataValue) ? element.dataBinding!.dataValue : [];
|
||||
|
||||
const newDataValue = [...currentDataValue];
|
||||
newDataValue[index] = value.id;
|
||||
@@ -498,14 +525,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectDataMapping === "multipleMachine" && element.dataType === "multiple-machine" && (
|
||||
{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.commonValue)?.label ?? ""}
|
||||
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
|
||||
onSelect={(value) => {
|
||||
updateCommonValue(selectedBlock, selectedElement, value.id);
|
||||
}}
|
||||
@@ -518,9 +545,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
||||
key={field.id}
|
||||
label={field.label}
|
||||
options={field.options}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataSource?.[index])?.modelName ?? ""}
|
||||
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
|
||||
onSelect={(value) => {
|
||||
const current = Array.isArray(element.dataSource) ? element.dataSource : [];
|
||||
const current = Array.isArray(element.dataBinding?.dataSource) ? element.dataBinding!.dataSource : [];
|
||||
|
||||
const next = [...current];
|
||||
next[index] = value.id;
|
||||
|
||||
@@ -1,57 +1,12 @@
|
||||
import { dataModelManager } from "../../data/dataModel";
|
||||
import type { UIElement } from "../../../../types/exportedTypes";
|
||||
|
||||
export const resolveElementValue = (element: UIElement) => {
|
||||
// Implementation from original code
|
||||
if (!element.data) {
|
||||
if (!element.dataBinding) {
|
||||
return { value: "No data" };
|
||||
}
|
||||
|
||||
const binding = element.data;
|
||||
|
||||
switch (binding.dataSource) {
|
||||
case "static":
|
||||
return {
|
||||
label: binding.label,
|
||||
value: binding.staticValue,
|
||||
graphData: element.graphData,
|
||||
};
|
||||
|
||||
case "dynamic":
|
||||
if (binding.dynamicKey) {
|
||||
const dynamicValue = dataModelManager.getValue(binding.dynamicKey);
|
||||
if (dynamicValue !== undefined) {
|
||||
return {
|
||||
label: binding.label,
|
||||
value: dynamicValue as string,
|
||||
graphData: dataModelManager.parseGraphData(dynamicValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "formula":
|
||||
if (binding.formula) {
|
||||
const formulaValue = dataModelManager.evaluateFormula(binding.formula);
|
||||
return {
|
||||
label: binding.label,
|
||||
value: formulaValue,
|
||||
graphData: element.graphData,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case "api":
|
||||
return {
|
||||
label: binding.label,
|
||||
value: binding.staticValue || "API Data",
|
||||
graphData: element.graphData,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: binding.label,
|
||||
value: binding.staticValue,
|
||||
label: element.dataBinding.label,
|
||||
graphData: element.graphData,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ type InputWithDropDownProps = {
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
placeholder?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
@@ -31,6 +32,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
placeholder = "Inherit",
|
||||
type = "number",
|
||||
}) => {
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
@@ -54,7 +56,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
type="number"
|
||||
type={type}
|
||||
value={value} // Controlled input
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
@@ -62,19 +64,12 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => setOpenDropdown((prev) => !prev)}
|
||||
>
|
||||
<div className="dropdown" onClick={() => setOpenDropdown((prev) => !prev)}>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="dropdown-option"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div key={index} className="dropdown-option" onClick={onClick}>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
@@ -88,7 +83,3 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
|
||||
export const deleteDashBoardElementsApi = async ({ projectId, versionId, elementDatas }: { projectId: string; versionId: string; elementDatas: { blockUuid: string; elementUuid: string }[] }) => {
|
||||
try {
|
||||
const response = await fetch(`${url_Backend_dwinzo}/api/V1/simulation/elements`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
body: JSON.stringify({ projectId, versionId, elementDatas }),
|
||||
});
|
||||
const newAccessToken = response.headers.get("x-access-token");
|
||||
if (newAccessToken) {
|
||||
localStorage.setItem("token", newAccessToken);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
echo.error("Failed to delete dashBoardElements");
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch {
|
||||
echo.error("Failed to delete dashBoardElements");
|
||||
}
|
||||
};
|
||||
@@ -45,7 +45,7 @@ interface SimulationDashboardStore {
|
||||
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
|
||||
|
||||
// Element data operations
|
||||
updateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => 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, graphType: GraphTypes) => void;
|
||||
@@ -70,7 +70,7 @@ interface SimulationDashboardStore {
|
||||
peekUpdateElementPosition: (blockId: string, elementId: string, position: Position) => Block[];
|
||||
peekUpdateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => Block[];
|
||||
peekUpdateElementZIndex: (blockId: string, elementId: string, zIndex: number) => Block[];
|
||||
peekUpdateElementData: (blockId: string, elementId: string, updates: Partial<DataBinding>) => Block[];
|
||||
peekUpdateElementData: (blockId: string, elementId: string, updates: Partial<ElementDataBinding>) => Block[];
|
||||
peekUpdateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => Block[];
|
||||
peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[];
|
||||
peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[];
|
||||
@@ -244,6 +244,7 @@ export const createSimulationDashboardStore = () => {
|
||||
staticValue: "",
|
||||
label: undefined,
|
||||
},
|
||||
dataBinding: {},
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
@@ -251,9 +252,6 @@ export const createSimulationDashboardStore = () => {
|
||||
newElement = {
|
||||
...commonProps,
|
||||
type: "label-value",
|
||||
title: "Label Value",
|
||||
dataSource: "",
|
||||
dataValue: "",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
fontSize: 14,
|
||||
@@ -266,10 +264,11 @@ export const createSimulationDashboardStore = () => {
|
||||
labelColor: "#ffffff",
|
||||
valueColor: "#ffffff",
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Value",
|
||||
label: "Label",
|
||||
dataBinding: {
|
||||
label: "Label Value",
|
||||
dataSource: "",
|
||||
dataValue: "",
|
||||
dataType: "single-machine",
|
||||
},
|
||||
size: { width: 200, height: 60 },
|
||||
};
|
||||
@@ -299,18 +298,22 @@ export const createSimulationDashboardStore = () => {
|
||||
if (dataType === "multiple-machine") {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "multiple-machine" as const,
|
||||
title: "Multi Machine Chart",
|
||||
dataSource: [],
|
||||
commonValue: "",
|
||||
dataBinding: {
|
||||
dataType: "multiple-machine" as const,
|
||||
label: "Multi Machine Chart",
|
||||
dataSource: [],
|
||||
commonValue: "",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "single-machine" as const,
|
||||
title: "Single Machine Chart",
|
||||
dataSource: "",
|
||||
dataValue: [],
|
||||
dataBinding: {
|
||||
dataType: "single-machine" as const,
|
||||
label: "Single Machine Chart",
|
||||
dataSource: "",
|
||||
dataValue: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -324,10 +327,6 @@ export const createSimulationDashboardStore = () => {
|
||||
fontSize: 14,
|
||||
textAlign: "left" as const,
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Text",
|
||||
},
|
||||
size: { width: 200, height: 40 },
|
||||
};
|
||||
break;
|
||||
@@ -444,8 +443,8 @@ export const createSimulationDashboardStore = () => {
|
||||
const block = state.blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element?.data) {
|
||||
element.data = { ...element.data, ...updates };
|
||||
if (element?.dataBinding) {
|
||||
element.dataBinding = { ...element.dataBinding, ...updates };
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -618,9 +617,6 @@ export const createSimulationDashboardStore = () => {
|
||||
newElement = {
|
||||
...commonProps,
|
||||
type: "label-value",
|
||||
title: "Label Value",
|
||||
dataSource: "",
|
||||
dataValue: "metric-1",
|
||||
style: {
|
||||
color: "#ffffff",
|
||||
fontSize: 14,
|
||||
@@ -633,10 +629,10 @@ export const createSimulationDashboardStore = () => {
|
||||
labelColor: "#ffffff",
|
||||
valueColor: "#ffffff",
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Value",
|
||||
label: "Label",
|
||||
dataBinding: {
|
||||
label: "Label Value",
|
||||
dataSource: "",
|
||||
dataValue: "",
|
||||
},
|
||||
size: { width: 200, height: 60 },
|
||||
};
|
||||
@@ -666,18 +662,22 @@ export const createSimulationDashboardStore = () => {
|
||||
if (dataType === "multiple-machine") {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "multiple-machine" as const,
|
||||
title: "Multi Machine Chart",
|
||||
dataSource: [],
|
||||
commonValue: "",
|
||||
dataBinding: {
|
||||
dataType: "multiple-machine" as const,
|
||||
label: "Multi Machine Chart",
|
||||
dataSource: [],
|
||||
commonValue: "",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
newElement = {
|
||||
...baseGraphProps,
|
||||
dataType: "single-machine" as const,
|
||||
title: "Single Machine Chart",
|
||||
dataSource: "",
|
||||
dataValue: [],
|
||||
dataBinding: {
|
||||
dataType: "single-machine" as const,
|
||||
label: "Single Machine Chart",
|
||||
dataSource: "",
|
||||
dataValue: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -691,10 +691,6 @@ export const createSimulationDashboardStore = () => {
|
||||
fontSize: 14,
|
||||
textAlign: "left" as const,
|
||||
},
|
||||
data: {
|
||||
...commonProps.data,
|
||||
staticValue: "Text",
|
||||
},
|
||||
size: { width: 200, height: 40 },
|
||||
};
|
||||
break;
|
||||
@@ -806,8 +802,8 @@ export const createSimulationDashboardStore = () => {
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element?.data) {
|
||||
element.data = { ...element.data, ...updates };
|
||||
if (element?.dataBinding) {
|
||||
element.dataBinding = { ...element.dataBinding, ...updates };
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
@@ -851,7 +847,7 @@ export const createSimulationDashboardStore = () => {
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateDataType: (blockId, elementId, dataType) => {
|
||||
peekUpdateDataType: (blockId: string, elementId: string, dataType: DataType) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
@@ -861,70 +857,58 @@ export const createSimulationDashboardStore = () => {
|
||||
if (index === -1) return blocks;
|
||||
|
||||
const element = block.elements[index];
|
||||
if (element.type !== "graph" || element.dataType === dataType) return blocks;
|
||||
|
||||
let newElement: UIElement;
|
||||
if (element.type !== "graph" || !element.dataBinding || element.dataBinding.dataType === dataType) return blocks;
|
||||
|
||||
// We are updating the dataBinding object deeply
|
||||
if (dataType === "single-machine") {
|
||||
const { commonValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "multiple-machine" }>;
|
||||
|
||||
newElement = {
|
||||
...rest,
|
||||
dataType: "single-machine",
|
||||
dataSource: "",
|
||||
dataValue: [],
|
||||
};
|
||||
element.dataBinding.dataType = "single-machine";
|
||||
element.dataBinding.dataSource = "";
|
||||
element.dataBinding.dataValue = [];
|
||||
delete element.dataBinding.commonValue;
|
||||
} else {
|
||||
const { dataValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "single-machine" }>;
|
||||
|
||||
newElement = {
|
||||
...rest,
|
||||
dataType: "multiple-machine",
|
||||
dataSource: [],
|
||||
commonValue: "",
|
||||
};
|
||||
element.dataBinding.dataType = "multiple-machine";
|
||||
element.dataBinding.dataSource = [];
|
||||
element.dataBinding.commonValue = "";
|
||||
delete element.dataBinding.dataValue;
|
||||
}
|
||||
|
||||
block.elements[index] = newElement;
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateCommonValue: (blockId, elementId, commonValue) => {
|
||||
peekUpdateCommonValue: (blockId: string, elementId: string, commonValue: string) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element && element.type === "graph" && element.dataType === "multiple-machine") {
|
||||
element.commonValue = commonValue;
|
||||
if (element && element.dataBinding) {
|
||||
element.dataBinding.commonValue = commonValue;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateDataValue: (blockId, elementId, dataValue) => {
|
||||
peekUpdateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element && element.type === "graph" && element.dataType === "single-machine") {
|
||||
element.dataValue = dataValue as string[];
|
||||
} else if (element && element.type === "label-value") {
|
||||
element.dataValue = dataValue as string;
|
||||
if (element && element.dataBinding) {
|
||||
element.dataBinding.dataValue = dataValue;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
},
|
||||
|
||||
peekUpdateDataSource: (blockId, elementId, dataSource) => {
|
||||
peekUpdateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => {
|
||||
const blocks = cloneBlocks(get().blocks);
|
||||
const block = blocks.find((b) => b.blockUuid === blockId);
|
||||
if (block) {
|
||||
const element = block.elements.find((el) => el.elementUuid === elementId);
|
||||
if (element?.type === "label-value") {
|
||||
element.dataValue = "";
|
||||
if (element?.type === "label-value" && element.dataBinding) {
|
||||
element.dataBinding.dataValue = "";
|
||||
}
|
||||
if (element) {
|
||||
element.dataSource = dataSource;
|
||||
if (element && element.dataBinding) {
|
||||
element.dataBinding.dataSource = dataSource;
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
|
||||
@@ -21,39 +21,10 @@ export type UIElement = {
|
||||
graphType?: GraphTypes;
|
||||
graphTitle?: string;
|
||||
style: ExtendedCSSProperties;
|
||||
data?: DataBinding;
|
||||
dataBinding?: ElementDataBinding;
|
||||
position?: Position;
|
||||
positionType?: "relative" | "absolute" | "fixed";
|
||||
zIndex?: number;
|
||||
graphData?: GraphDataPoint[];
|
||||
size?: Size;
|
||||
} & (
|
||||
| {
|
||||
type: "label-value";
|
||||
title: string;
|
||||
dataSource: string;
|
||||
dataValue: string;
|
||||
}
|
||||
| {
|
||||
type: "graph";
|
||||
dataType: "single-machine";
|
||||
title: string;
|
||||
dataSource: string;
|
||||
dataValue: string[];
|
||||
}
|
||||
| {
|
||||
type: "graph";
|
||||
dataType: "multiple-machine";
|
||||
title: string;
|
||||
dataSource: string[];
|
||||
commonValue: string;
|
||||
}
|
||||
| {
|
||||
type: Exclude<UIType, "label-value" | "graph">;
|
||||
title?: never;
|
||||
dataSource?: never;
|
||||
dataValue?: never;
|
||||
commonValue?: never;
|
||||
dataType?: never;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
21
app/src/types/simulationDashboard.d.ts
vendored
21
app/src/types/simulationDashboard.d.ts
vendored
@@ -1,16 +1,3 @@
|
||||
type DataSourceType = "static" | "dynamic" | "api" | "formula";
|
||||
|
||||
type DataBinding = {
|
||||
key: string;
|
||||
label?: string;
|
||||
value?: string | number;
|
||||
dataSource: DataSourceType;
|
||||
staticValue?: string | number;
|
||||
dynamicKey?: string;
|
||||
formula?: string;
|
||||
apiEndpoint?: string;
|
||||
};
|
||||
|
||||
type DataModelValue = string | number | GraphDataPoint[];
|
||||
|
||||
type DataModel = Record<string, DataModelValue>;
|
||||
@@ -19,6 +6,14 @@ type UIType = "label-value" | "text" | "graph" | "icon";
|
||||
|
||||
type DataType = "single-machine" | "multiple-machine";
|
||||
|
||||
type ElementDataBinding = {
|
||||
label?: string;
|
||||
dataSource?: string | string[];
|
||||
dataValue?: string | string[];
|
||||
commonValue?: string;
|
||||
dataType?: "single-machine" | "multiple-machine";
|
||||
}
|
||||
|
||||
type Position = {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
Reference in New Issue
Block a user