feat: Implement the Simulation Dashboard editor with core components, state management, and types for blocks and elements.

This commit is contained in:
2025-12-18 11:35:49 +05:30
parent 5b9f3f1728
commit 3746fccd19
11 changed files with 202 additions and 291 deletions

View File

@@ -29,16 +29,16 @@ const AnalyzerManager: React.FC = () => {
block.elements.forEach((element) => { block.elements.forEach((element) => {
// 1. Handle Label-Value (Direct DOM Manipulation) // 1. Handle Label-Value (Direct DOM Manipulation)
if (element.type === "label-value") { if (element.type === "label-value") {
if (!element.dataSource || !element.dataValue) return; if (!element.dataBinding?.dataSource || !element.dataBinding?.dataValue) return;
let value: any = "-"; let value: any = "-";
// Ensure dataValue is treated as string if possible, or take first if array // 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; const dataValueStr = Array.isArray(mixedDataValue) ? mixedDataValue[0] : mixedDataValue;
if (typeof dataValueStr !== "string") return; if (typeof dataValueStr !== "string") return;
if (element.dataSource === "global") { if (element.dataBinding.dataSource === "global") {
// Handle global metrics // Handle global metrics
// Expected format: "global.systemPerformance.overallOEE" -> we need last part relative to metrics // Expected format: "global.systemPerformance.overallOEE" -> we need last part relative to metrics
// But dataValue usually comes as "systemPerformance.overallOEE" if dataSource is global? // But dataValue usually comes as "systemPerformance.overallOEE" if dataSource is global?
@@ -58,7 +58,7 @@ const AnalyzerManager: React.FC = () => {
} else { } else {
// Handle Asset Specific // Handle Asset Specific
const assetAnalysis = getAssetAnalysis(element.dataSource); const assetAnalysis = getAssetAnalysis(element.dataBinding.dataSource as string);
if (assetAnalysis) { if (assetAnalysis) {
value = resolvePath(assetAnalysis, dataValueStr); value = resolvePath(assetAnalysis, dataValueStr);
} }
@@ -77,13 +77,13 @@ const AnalyzerManager: React.FC = () => {
// Assuming we want to visualize historical data or current state trends. // Assuming we want to visualize historical data or current state trends.
else if (element.type === "graph") { else if (element.type === "graph") {
if (element.dataType === "single-machine") { if (element.dataBinding?.dataType === "single-machine") {
if (!element.dataSource || !element.dataValue) return; if (!element.dataBinding.dataSource || !element.dataBinding.dataValue) return;
// Single machine, multiple values potentially? // Single machine, multiple values potentially?
// element.dataValue is string[] for single-machine graph // element.dataValue is string[] for single-machine graph
const assetId = element.dataSource as string; const assetId = element.dataBinding.dataSource as string;
const dataKeys = element.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"] const dataKeys = element.dataBinding.dataValue as string[]; // e.g. ["efficiency.overallEffectiveness"]
// For now support getting the FIRST value as the main value to plot? // For now support getting the FIRST value as the main value to plot?
// Or if we track history? // 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 // Multiple machines, single common value
const assetIds = element.dataSource as string[]; const assetIds = element.dataBinding.dataSource as string[];
const commonValueKey = element.commonValue as string; const commonValueKey = element.dataBinding.commonValue as string;
if (assetIds && commonValueKey) { if (assetIds && commonValueKey) {
// Bar chart comparing machines? // Bar chart comparing machines?

View File

@@ -21,6 +21,7 @@ import { handleBlockDragStart } from "./functions/block/handleBlockDragStart";
import { getDashBoardBlocksApi } from "../../services/visulization/dashBoard/getDashBoardBlocks"; import { getDashBoardBlocksApi } from "../../services/visulization/dashBoard/getDashBoardBlocks";
import { upsetDashBoardBlocksApi } from "../../services/visulization/dashBoard/upsertDashBoardBlocks"; import { upsetDashBoardBlocksApi } from "../../services/visulization/dashBoard/upsertDashBoardBlocks";
import { deleteDashBoardBlocksApi } from "../../services/visulization/dashBoard/deleteDashBoardBlocks"; import { deleteDashBoardBlocksApi } from "../../services/visulization/dashBoard/deleteDashBoardBlocks";
import { deleteDashBoardElementsApi } from "../../services/visulization/dashBoard/deleteDashBoardElements";
const DashboardEditor: React.FC = () => { const DashboardEditor: React.FC = () => {
const { simulationDashBoardStore, versionStore } = useSceneContext(); const { simulationDashBoardStore, versionStore } = useSceneContext();
@@ -622,10 +623,22 @@ const DashboardEditor: React.FC = () => {
} }
}} }}
handleRemoveElement={async (blockId, elementId) => { handleRemoveElement={async (blockId, elementId) => {
const updatedBlocks = peekRemoveElement(blockId, elementId); try {
const updatedBlock = getBlockFromPeekedBlocks(updatedBlocks, blockId); const data = await deleteDashBoardElementsApi({
if (updatedBlock) { projectId: projectId!,
// await updateBackend(updatedBlock); 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} setSwapSource={setSwapSource}

View File

@@ -37,10 +37,8 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
updateBlockZIndex, updateBlockZIndex,
handleRemoveBlock, handleRemoveBlock,
}) => { }) => {
const [color, setColor] = useState("#000000"); const [color, setColor] = useState("#000000");
return ( return (
<div ref={blockEditorRef} className="panel block-editor-panel"> <div ref={blockEditorRef} className="panel block-editor-panel">
<div className="header"> <div className="header">
@@ -55,8 +53,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
</div> </div>
</div> </div>
<div className="design-section-wrapper"> <div className="design-section-wrapper">
<div className="design-section"> <div className="design-section">
<div className="section-header">Size</div> <div className="section-header">Size</div>
@@ -68,7 +64,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
updateBlockSize(selectedBlock, { updateBlockSize(selectedBlock, {
...currentBlock.size!, ...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 <InputWithDropDown
@@ -79,7 +75,7 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
updateBlockSize(selectedBlock, { updateBlockSize(selectedBlock, {
...currentBlock.size!, ...currentBlock.size!,
height: Number(newValue), height: Number(newValue),
}) });
}} }}
/> />
</div> </div>
@@ -100,56 +96,22 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
<div className="position-canvas"> <div className="position-canvas">
<div className="canvas"> <div className="canvas">
<div className="value padding-top"> <div className="value padding-top">
<RenameInput <RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
</div> </div>
<div className="value padding-right"> <div className="value padding-right">
<RenameInput <RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
</div> </div>
<div className="value padding-bottom"> <div className="value padding-bottom">
<RenameInput <RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
</div> </div>
<div className="value padding-left"> <div className="value padding-left">
<RenameInput <RenameInput value={getCurrentBlockStyleValue(currentBlock, "padding") ? String(getCurrentBlockStyleValue(currentBlock, "padding")) : "120"} />
value={
getCurrentBlockStyleValue(currentBlock, "padding")
? String(getCurrentBlockStyleValue(currentBlock, "padding"))
: "120"
}
/>
</div> </div>
</div> </div>
</div> </div>
<div className="design-section-footer"> <div className="design-section-footer">
<InputWithDropDown <InputWithDropDown label="Layer" value={String(currentBlock.zIndex || 1)} placeholder={"Layer"} onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))} />
label="Layer"
value={String(currentBlock.zIndex || 1)}
placeholder={"Layer"}
onChange={(newValue) => updateBlockZIndex(selectedBlock, Number(newValue))}
/>
<InputRange <InputRange
label={"Radius"} label={"Radius"}
@@ -159,10 +121,9 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
onChange={(newValue) => { onChange={(newValue) => {
updateBlockStyle(selectedBlock, { updateBlockStyle(selectedBlock, {
borderRadius: Number(newValue), borderRadius: Number(newValue),
}) });
}} }}
/> />
</div> </div>
</div> </div>
@@ -176,7 +137,6 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))} value={rgbaToHex(getCurrentBlockStyleValue(currentBlock, "backgroundColor"))}
// onChange={(e) => setColor(e.target.value)} // onChange={(e) => setColor(e.target.value)}
onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)} onChange={(e) => handleBackgroundColorChange(currentBlock, selectedBlock, updateBlockStyle, e.target.value)}
/> />
<div className="colorValue">{color}</div> <div className="colorValue">{color}</div>
@@ -189,23 +149,10 @@ const BlockEditor: React.FC<BlockEditorProps> = ({
value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)} value={Math.round(getAlphaFromRgba(getCurrentBlockStyleValue(currentBlock, "backgroundColor")) * 100)}
// onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))} // onChange={(value: number) => handleBackgroundAlphaChange(currentBlock, selectedBlock, updateBlockStyle, Number(value))}
onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))} onChange={(value: number) => handleBlurAmountChange(selectedBlock, updateBlockStyle, Number(value))}
/> />
<InputRange <InputRange label={"Blur"} min={0} max={8} value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")} />
label={"Blur"}
min={0}
max={8}
value={parseInt(getCurrentBlockStyleValue(currentBlock, "backdropFilter")?.match(/\d+/)?.[0] || "10")}
/>
</div> </div>
</div> </div>
</div> </div>
); );
}; };

View File

@@ -19,7 +19,7 @@ interface ElementDesignProps {
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void; updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void;
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void; updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void;
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => 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; updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void; updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;

View File

@@ -20,7 +20,7 @@ interface ElementEditorProps {
updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void; updateElementPosition: (blockId: string, elementId: string, position: { x: number; y: number }) => void;
updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void; updateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => void;
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => 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; updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void; updateGraphType: (blockId: string, elementId: string, type: GraphTypes) => void;
@@ -62,7 +62,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
const { getElementById } = simulationDashBoardStore(); const { getElementById } = simulationDashBoardStore();
const element = getElementById(selectedBlock, selectedElement); const element = getElementById(selectedBlock, selectedElement);
const [selectType, setSelectType] = useState("design"); 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(() => { const getAssetDropdownItems = useCallback(() => {
if (!product?.eventDatas) return []; if (!product?.eventDatas) return [];
@@ -273,10 +273,12 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
); );
const singleValueFields = useMemo(() => { const singleValueFields = useMemo(() => {
if (element?.type === "graph" && element.dataType === "single-machine") { if (element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const dataValues = Array.isArray(element.dataValue) ? element.dataValue : []; 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) => ({ return dataValues.map((value, index) => ({
id: `data-value-${index}`, id: `data-value-${index}`,
@@ -297,8 +299,8 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
}, [element, getLableValueDropdownItems]); }, [element, getLableValueDropdownItems]);
const multipleSourceFields = useMemo(() => { const multipleSourceFields = useMemo(() => {
if (element?.type === "graph" && element.dataType === "multiple-machine") { if (element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const dataSources = Array.isArray(element.dataSource) ? element.dataSource : []; const dataSources = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
return dataSources.map((_, index) => ({ return dataSources.map((_, index) => ({
id: `data-source-${index}`, id: `data-source-${index}`,
@@ -324,21 +326,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
setSelectDataMapping(newDataType); setSelectDataMapping(newDataType);
if (newDataType === "singleMachine" && element.dataType === "multiple-machine") { if (newDataType === "singleMachine" && element.dataBinding?.dataType === "multiple-machine") {
updateDataType(selectedBlock, selectedElement, "single-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"); updateDataType(selectedBlock, selectedElement, "multiple-machine");
} }
}; };
const addField = () => { const addField = () => {
if (selectDataMapping === "singleMachine" && element?.type === "graph" && element.dataType === "single-machine") { if (selectDataMapping === "singleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "single-machine") {
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : []; const currentDataValue = Array.isArray(element.dataBinding.dataValue) ? element.dataBinding.dataValue : [];
const newDataValue = [...currentDataValue, ""]; const newDataValue = [...currentDataValue, ""];
updateDataValue(selectedBlock, selectedElement, newDataValue); updateDataValue(selectedBlock, selectedElement, newDataValue);
} else if (selectDataMapping === "multipleMachine" && element?.type === "graph" && element.dataType === "multiple-machine") { } else if (selectDataMapping === "multipleMachine" && element?.type === "graph" && element.dataBinding?.dataType === "multiple-machine") {
const currentDataSource = Array.isArray(element.dataSource) ? element.dataSource : []; const currentDataSource = Array.isArray(element.dataBinding.dataSource) ? element.dataBinding.dataSource : [];
const newDataSource = [...currentDataSource, ""]; const newDataSource = [...currentDataSource, ""];
updateDataSource(selectedBlock, selectedElement, newDataSource); updateDataSource(selectedBlock, selectedElement, newDataSource);
@@ -399,7 +401,15 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
<div className="data-details"> <div className="data-details">
{element?.type === "label-value" && ( {element?.type === "label-value" && (
<div className="data-wrapper"> <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"> <div className="data">
<DataDetailedDropdown <DataDetailedDropdown
title="Data Source" title="Data Source"
@@ -415,12 +425,18 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
}, },
]} ]}
value={ value={
element.dataSource element.dataBinding?.dataSource
? { id: element.dataSource, label: getEventByModelUuid(selectedProduct.productUuid, element.dataSource)?.modelName ?? "", icon: <DeviceIcon /> } ? {
id: element.dataBinding.dataSource as string,
label:
getEventByModelUuid(selectedProduct.productUuid, element.dataBinding.dataSource as string)?.modelName ??
(element.dataBinding.dataSource === "global" ? "Global" : ""),
icon: <DeviceIcon />,
}
: null : null
} }
onChange={(value) => { onChange={(value) => {
updateDataSource(selectedBlock, selectedElement, value.id); updateElementData(selectedBlock, selectedElement, { dataSource: value.id });
}} }}
dropDownHeader={"RT-Data"} dropDownHeader={"RT-Data"}
eyedroper={true} eyedroper={true}
@@ -431,10 +447,21 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
<DataDetailedDropdown <DataDetailedDropdown
title="Data Value" title="Data Value"
placeholder="Select Value" placeholder="Select Value"
sections={getLableValueDropdownItems(element.dataSource as string | undefined)} sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
value={element.dataValue ? { id: element.dataValue, label: element.dataValue, icon: <ParametersIcon /> } : null} 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) => { onChange={(value) => {
updateDataValue(selectedBlock, selectedElement, value.id); updateElementData(selectedBlock, selectedElement, { dataValue: value.id, label: value.label });
}} }}
dropDownHeader={"RT-Data-Value"} dropDownHeader={"RT-Data-Value"}
/> />
@@ -456,14 +483,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
</div> </div>
</div> </div>
{selectDataMapping === "singleMachine" && element.dataType === "single-machine" && ( {selectDataMapping === "singleMachine" && element.dataBinding?.dataType === "single-machine" && (
<div className="fields-wrapper"> <div className="fields-wrapper">
{singleSourceFields.map((field) => ( {singleSourceFields.map((field) => (
<DataSourceSelector <DataSourceSelector
key={field.id} key={field.id}
label={field.label} label={field.label}
options={field.options} options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataSource)?.modelName ?? ""} selected={getEventByModelUuid(selectedProduct.productUuid, element.dataBinding?.dataSource as string)?.modelName ?? ""}
onSelect={(value) => { onSelect={(value) => {
updateDataSource(selectedBlock, selectedElement, value.id); updateDataSource(selectedBlock, selectedElement, value.id);
}} }}
@@ -476,9 +503,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
key={field.id} key={field.id}
label={field.label} label={field.label}
options={field.options} 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) => { onSelect={(value) => {
const currentDataValue = Array.isArray(element.dataValue) ? element.dataValue : []; const currentDataValue = Array.isArray(element.dataBinding?.dataValue) ? element.dataBinding!.dataValue : [];
const newDataValue = [...currentDataValue]; const newDataValue = [...currentDataValue];
newDataValue[index] = value.id; newDataValue[index] = value.id;
@@ -498,14 +525,14 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
</div> </div>
)} )}
{selectDataMapping === "multipleMachine" && element.dataType === "multiple-machine" && ( {selectDataMapping === "multipleMachine" && element.dataBinding?.dataType === "multiple-machine" && (
<div className="fields-wrapper"> <div className="fields-wrapper">
{multipleValueFields.map((field) => ( {multipleValueFields.map((field) => (
<DataSourceSelector <DataSourceSelector
key={field.id} key={field.id}
label={field.label} label={field.label}
options={field.options} 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) => { onSelect={(value) => {
updateCommonValue(selectedBlock, selectedElement, value.id); updateCommonValue(selectedBlock, selectedElement, value.id);
}} }}
@@ -518,9 +545,9 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
key={field.id} key={field.id}
label={field.label} label={field.label}
options={field.options} options={field.options}
selected={getEventByModelUuid(selectedProduct.productUuid, element.dataSource?.[index])?.modelName ?? ""} selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
onSelect={(value) => { onSelect={(value) => {
const current = Array.isArray(element.dataSource) ? element.dataSource : []; const current = Array.isArray(element.dataBinding?.dataSource) ? element.dataBinding!.dataSource : [];
const next = [...current]; const next = [...current];
next[index] = value.id; next[index] = value.id;

View File

@@ -1,57 +1,12 @@
import { dataModelManager } from "../../data/dataModel";
import type { UIElement } from "../../../../types/exportedTypes"; import type { UIElement } from "../../../../types/exportedTypes";
export const resolveElementValue = (element: UIElement) => { export const resolveElementValue = (element: UIElement) => {
// Implementation from original code if (!element.dataBinding) {
if (!element.data) {
return { value: "No data" }; return { value: "No data" };
} }
const binding = element.data;
switch (binding.dataSource) {
case "static":
return { return {
label: binding.label, label: element.dataBinding.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,
graphData: element.graphData, graphData: element.graphData,
}; };
}; };

View File

@@ -15,6 +15,7 @@ type InputWithDropDownProps = {
onChange: (newValue: string) => void; onChange: (newValue: string) => void;
editableLabel?: boolean; editableLabel?: boolean;
placeholder?: string; placeholder?: string;
type?: string;
}; };
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
@@ -31,6 +32,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
onChange, onChange,
editableLabel = false, editableLabel = false,
placeholder = "Inherit", placeholder = "Inherit",
type = "number",
}) => { }) => {
const [openDropdown, setOpenDropdown] = useState(false); const [openDropdown, setOpenDropdown] = useState(false);
@@ -54,7 +56,7 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
min={min} min={min}
max={max} max={max}
step={step} step={step}
type="number" type={type}
value={value} // Controlled input value={value} // Controlled input
disabled={disabled} disabled={disabled}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
@@ -62,19 +64,12 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
/> />
{activeOption && ( {activeOption && (
<div <div className="dropdown" onClick={() => setOpenDropdown((prev) => !prev)}>
className="dropdown"
onClick={() => setOpenDropdown((prev) => !prev)}
>
<div className="active-option">{activeOption}</div> <div className="active-option">{activeOption}</div>
{options && openDropdown && ( {options && openDropdown && (
<div className="dropdown-options-list"> <div className="dropdown-options-list">
{options.map((option, index) => ( {options.map((option, index) => (
<div <div key={index} className="dropdown-option" onClick={onClick}>
key={index}
className="dropdown-option"
onClick={onClick}
>
{option} {option}
</div> </div>
))} ))}
@@ -88,7 +83,3 @@ const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
}; };
export default InputWithDropDown; export default InputWithDropDown;

View File

@@ -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");
}
};

View File

@@ -45,7 +45,7 @@ interface SimulationDashboardStore {
updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void; updateElementZIndex: (blockId: string, elementId: string, zIndex: number) => void;
// Element data operations // 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; updateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => void;
updateGraphTitle: (blockId: string, elementId: string, title: string) => void; updateGraphTitle: (blockId: string, elementId: string, title: string) => void;
updateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => void; updateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => void;
@@ -70,7 +70,7 @@ interface SimulationDashboardStore {
peekUpdateElementPosition: (blockId: string, elementId: string, position: Position) => Block[]; peekUpdateElementPosition: (blockId: string, elementId: string, position: Position) => Block[];
peekUpdateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => Block[]; peekUpdateElementPositionType: (blockId: string, elementId: string, positionType: "relative" | "absolute" | "fixed") => Block[];
peekUpdateElementZIndex: (blockId: string, elementId: string, zIndex: number) => 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[]; peekUpdateGraphData: (blockId: string, elementId: string, newData: GraphDataPoint[]) => Block[];
peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[]; peekUpdateGraphTitle: (blockId: string, elementId: string, title: string) => Block[];
peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[]; peekUpdateGraphType: (blockId: string, elementId: string, graphType: GraphTypes) => Block[];
@@ -244,6 +244,7 @@ export const createSimulationDashboardStore = () => {
staticValue: "", staticValue: "",
label: undefined, label: undefined,
}, },
dataBinding: {},
}; };
switch (type) { switch (type) {
@@ -251,9 +252,6 @@ export const createSimulationDashboardStore = () => {
newElement = { newElement = {
...commonProps, ...commonProps,
type: "label-value", type: "label-value",
title: "Label Value",
dataSource: "",
dataValue: "",
style: { style: {
color: "#ffffff", color: "#ffffff",
fontSize: 14, fontSize: 14,
@@ -266,10 +264,11 @@ export const createSimulationDashboardStore = () => {
labelColor: "#ffffff", labelColor: "#ffffff",
valueColor: "#ffffff", valueColor: "#ffffff",
}, },
data: { dataBinding: {
...commonProps.data, label: "Label Value",
staticValue: "Value", dataSource: "",
label: "Label", dataValue: "",
dataType: "single-machine",
}, },
size: { width: 200, height: 60 }, size: { width: 200, height: 60 },
}; };
@@ -299,18 +298,22 @@ export const createSimulationDashboardStore = () => {
if (dataType === "multiple-machine") { if (dataType === "multiple-machine") {
newElement = { newElement = {
...baseGraphProps, ...baseGraphProps,
dataBinding: {
dataType: "multiple-machine" as const, dataType: "multiple-machine" as const,
title: "Multi Machine Chart", label: "Multi Machine Chart",
dataSource: [], dataSource: [],
commonValue: "", commonValue: "",
},
}; };
} else { } else {
newElement = { newElement = {
...baseGraphProps, ...baseGraphProps,
dataBinding: {
dataType: "single-machine" as const, dataType: "single-machine" as const,
title: "Single Machine Chart", label: "Single Machine Chart",
dataSource: "", dataSource: "",
dataValue: [], dataValue: [],
},
}; };
} }
break; break;
@@ -324,10 +327,6 @@ export const createSimulationDashboardStore = () => {
fontSize: 14, fontSize: 14,
textAlign: "left" as const, textAlign: "left" as const,
}, },
data: {
...commonProps.data,
staticValue: "Text",
},
size: { width: 200, height: 40 }, size: { width: 200, height: 40 },
}; };
break; break;
@@ -444,8 +443,8 @@ export const createSimulationDashboardStore = () => {
const block = state.blocks.find((b) => b.blockUuid === blockId); const block = state.blocks.find((b) => b.blockUuid === blockId);
if (block) { if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId); const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.data) { if (element?.dataBinding) {
element.data = { ...element.data, ...updates }; element.dataBinding = { ...element.dataBinding, ...updates };
} }
} }
}); });
@@ -618,9 +617,6 @@ export const createSimulationDashboardStore = () => {
newElement = { newElement = {
...commonProps, ...commonProps,
type: "label-value", type: "label-value",
title: "Label Value",
dataSource: "",
dataValue: "metric-1",
style: { style: {
color: "#ffffff", color: "#ffffff",
fontSize: 14, fontSize: 14,
@@ -633,10 +629,10 @@ export const createSimulationDashboardStore = () => {
labelColor: "#ffffff", labelColor: "#ffffff",
valueColor: "#ffffff", valueColor: "#ffffff",
}, },
data: { dataBinding: {
...commonProps.data, label: "Label Value",
staticValue: "Value", dataSource: "",
label: "Label", dataValue: "",
}, },
size: { width: 200, height: 60 }, size: { width: 200, height: 60 },
}; };
@@ -666,18 +662,22 @@ export const createSimulationDashboardStore = () => {
if (dataType === "multiple-machine") { if (dataType === "multiple-machine") {
newElement = { newElement = {
...baseGraphProps, ...baseGraphProps,
dataBinding: {
dataType: "multiple-machine" as const, dataType: "multiple-machine" as const,
title: "Multi Machine Chart", label: "Multi Machine Chart",
dataSource: [], dataSource: [],
commonValue: "", commonValue: "",
},
}; };
} else { } else {
newElement = { newElement = {
...baseGraphProps, ...baseGraphProps,
dataBinding: {
dataType: "single-machine" as const, dataType: "single-machine" as const,
title: "Single Machine Chart", label: "Single Machine Chart",
dataSource: "", dataSource: "",
dataValue: [], dataValue: [],
},
}; };
} }
break; break;
@@ -691,10 +691,6 @@ export const createSimulationDashboardStore = () => {
fontSize: 14, fontSize: 14,
textAlign: "left" as const, textAlign: "left" as const,
}, },
data: {
...commonProps.data,
staticValue: "Text",
},
size: { width: 200, height: 40 }, size: { width: 200, height: 40 },
}; };
break; break;
@@ -806,8 +802,8 @@ export const createSimulationDashboardStore = () => {
const block = blocks.find((b) => b.blockUuid === blockId); const block = blocks.find((b) => b.blockUuid === blockId);
if (block) { if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId); const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.data) { if (element?.dataBinding) {
element.data = { ...element.data, ...updates }; element.dataBinding = { ...element.dataBinding, ...updates };
} }
} }
return blocks; return blocks;
@@ -851,7 +847,7 @@ export const createSimulationDashboardStore = () => {
return blocks; return blocks;
}, },
peekUpdateDataType: (blockId, elementId, dataType) => { peekUpdateDataType: (blockId: string, elementId: string, dataType: DataType) => {
const blocks = cloneBlocks(get().blocks); const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId); const block = blocks.find((b) => b.blockUuid === blockId);
@@ -861,70 +857,58 @@ export const createSimulationDashboardStore = () => {
if (index === -1) return blocks; if (index === -1) return blocks;
const element = block.elements[index]; const element = block.elements[index];
if (element.type !== "graph" || element.dataType === dataType) return blocks; if (element.type !== "graph" || !element.dataBinding || element.dataBinding.dataType === dataType) return blocks;
let newElement: UIElement;
// We are updating the dataBinding object deeply
if (dataType === "single-machine") { if (dataType === "single-machine") {
const { commonValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "multiple-machine" }>; element.dataBinding.dataType = "single-machine";
element.dataBinding.dataSource = "";
newElement = { element.dataBinding.dataValue = [];
...rest, delete element.dataBinding.commonValue;
dataType: "single-machine",
dataSource: "",
dataValue: [],
};
} else { } else {
const { dataValue, ...rest } = element as Extract<UIElement, { type: "graph"; dataType: "single-machine" }>; element.dataBinding.dataType = "multiple-machine";
element.dataBinding.dataSource = [];
newElement = { element.dataBinding.commonValue = "";
...rest, delete element.dataBinding.dataValue;
dataType: "multiple-machine",
dataSource: [],
commonValue: "",
};
} }
block.elements[index] = newElement;
return blocks; return blocks;
}, },
peekUpdateCommonValue: (blockId, elementId, commonValue) => { peekUpdateCommonValue: (blockId: string, elementId: string, commonValue: string) => {
const blocks = cloneBlocks(get().blocks); const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId); const block = blocks.find((b) => b.blockUuid === blockId);
if (block) { if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId); const element = block.elements.find((el) => el.elementUuid === elementId);
if (element && element.type === "graph" && element.dataType === "multiple-machine") { if (element && element.dataBinding) {
element.commonValue = commonValue; element.dataBinding.commonValue = commonValue;
} }
} }
return blocks; return blocks;
}, },
peekUpdateDataValue: (blockId, elementId, dataValue) => { peekUpdateDataValue: (blockId: string, elementId: string, dataValue: string | string[]) => {
const blocks = cloneBlocks(get().blocks); const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId); const block = blocks.find((b) => b.blockUuid === blockId);
if (block) { if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId); const element = block.elements.find((el) => el.elementUuid === elementId);
if (element && element.type === "graph" && element.dataType === "single-machine") { if (element && element.dataBinding) {
element.dataValue = dataValue as string[]; element.dataBinding.dataValue = dataValue;
} else if (element && element.type === "label-value") {
element.dataValue = dataValue as string;
} }
} }
return blocks; return blocks;
}, },
peekUpdateDataSource: (blockId, elementId, dataSource) => { peekUpdateDataSource: (blockId: string, elementId: string, dataSource: string | string[]) => {
const blocks = cloneBlocks(get().blocks); const blocks = cloneBlocks(get().blocks);
const block = blocks.find((b) => b.blockUuid === blockId); const block = blocks.find((b) => b.blockUuid === blockId);
if (block) { if (block) {
const element = block.elements.find((el) => el.elementUuid === elementId); const element = block.elements.find((el) => el.elementUuid === elementId);
if (element?.type === "label-value") { if (element?.type === "label-value" && element.dataBinding) {
element.dataValue = ""; element.dataBinding.dataValue = "";
} }
if (element) { if (element && element.dataBinding) {
element.dataSource = dataSource; element.dataBinding.dataSource = dataSource;
} }
} }
return blocks; return blocks;

View File

@@ -21,39 +21,10 @@ export type UIElement = {
graphType?: GraphTypes; graphType?: GraphTypes;
graphTitle?: string; graphTitle?: string;
style: ExtendedCSSProperties; style: ExtendedCSSProperties;
data?: DataBinding; dataBinding?: ElementDataBinding;
position?: Position; position?: Position;
positionType?: "relative" | "absolute" | "fixed"; positionType?: "relative" | "absolute" | "fixed";
zIndex?: number; zIndex?: number;
graphData?: GraphDataPoint[]; graphData?: GraphDataPoint[];
size?: Size; 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;
}
);

View File

@@ -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 DataModelValue = string | number | GraphDataPoint[];
type DataModel = Record<string, DataModelValue>; type DataModel = Record<string, DataModelValue>;
@@ -19,6 +6,14 @@ type UIType = "label-value" | "text" | "graph" | "icon";
type DataType = "single-machine" | "multiple-machine"; 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 = { type Position = {
x: number; x: number;
y: number; y: number;