first commit
This commit is contained in:
127
app/src/components/layout/sidebarRight/Header.tsx
Normal file
127
app/src/components/layout/sidebarRight/Header.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React, { useState } from "react";
|
||||
import orgImg from "../../../assets/image/orgTemp.png";
|
||||
import { useActiveUsers, useCamMode } from "../../../store/builder/store";
|
||||
import { ActiveUser } from "../../../types/users";
|
||||
import CollaborationPopup from "../../templates/CollaborationPopup";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
|
||||
import { useToggleStore } from "../../../store/useUIToggleStore";
|
||||
import { ToggleSidebarIcon } from "../../icons/HeaderIcons";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const { activeUsers } = useActiveUsers();
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
const guestUsers: ActiveUser[] = activeUsers.filter(
|
||||
(user: ActiveUser) => user.userName !== userName
|
||||
);
|
||||
|
||||
const [userManagement, setUserManagement] = useState(false);
|
||||
const { setSelectedUser } = useSelectedUserStore();
|
||||
const { setCamMode } = useCamMode();
|
||||
|
||||
function handleUserFollow(user: any, index: number) {
|
||||
const position = {
|
||||
x: user.position?.x!,
|
||||
y: user.position?.y!,
|
||||
z: user.position?.z!,
|
||||
};
|
||||
const target = {
|
||||
x: user.target?.x!,
|
||||
y: user.target?.y!,
|
||||
z: user.target?.z!,
|
||||
};
|
||||
const rotation = {
|
||||
x: user.rotation?.x!,
|
||||
y: user.rotation?.y!,
|
||||
z: user.rotation?.z!,
|
||||
};
|
||||
|
||||
// retun on no data
|
||||
if (!position || !target || !rotation) return;
|
||||
|
||||
// Set the selected user in the store
|
||||
setSelectedUser({
|
||||
color: getAvatarColor(index, user.userName),
|
||||
name: user.userName,
|
||||
id: user.id,
|
||||
location: { position, rotation, target },
|
||||
});
|
||||
setCamMode("FollowPerson");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{userManagement && (
|
||||
<CollaborationPopup setUserManagement={setUserManagement} />
|
||||
)}
|
||||
<div className="header-container">
|
||||
<div className="options-container">
|
||||
<button
|
||||
id="toggle-rightSidebar-ui-button"
|
||||
className={`toggle-sidebar-ui-button ${!toggleUIRight ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (activeModule !== "market") {
|
||||
setToggleUI(toggleUILeft, !toggleUIRight);
|
||||
localStorage.setItem(
|
||||
"navBarUiRight",
|
||||
JSON.stringify(!toggleUIRight)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="tooltip">
|
||||
{toggleUIRight ? "Hide" : "Show"} sidebar (ctrl + ])
|
||||
</div>
|
||||
<ToggleSidebarIcon />
|
||||
</button>
|
||||
<button
|
||||
id="share-button"
|
||||
className="share-button"
|
||||
onClick={() => {
|
||||
setUserManagement(true);
|
||||
}}
|
||||
>
|
||||
Share
|
||||
</button>
|
||||
{/* <div className="app-docker-button">
|
||||
<AppDockIcon />
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div className="users-container">
|
||||
<div className="guest-users-container">
|
||||
{guestUsers.length > 3 && (
|
||||
<div className="other-guest">+{guestUsers.length - 3}</div>
|
||||
)}
|
||||
{guestUsers.slice(0, 3).map((user, index) => (
|
||||
<button
|
||||
id="user-profile-button"
|
||||
key={`${index}-${user.userName}`}
|
||||
className="user-profile"
|
||||
style={{ background: getAvatarColor(index, user.userName) }}
|
||||
onClick={() => {
|
||||
handleUserFollow(user, index);
|
||||
}}
|
||||
>
|
||||
{user.userName[0]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="user-profile-container">
|
||||
<div className="user-profile">{userName[0]}</div>
|
||||
<div className="user-organization">
|
||||
<img src={orgImg} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
217
app/src/components/layout/sidebarRight/SideBarRight.tsx
Normal file
217
app/src/components/layout/sidebarRight/SideBarRight.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Header from "./Header";
|
||||
import useModuleStore, {
|
||||
useSubModuleStore,
|
||||
} from "../../../store/useModuleStore";
|
||||
import {
|
||||
AnalysisIcon,
|
||||
MechanicsIcon,
|
||||
PropertiesIcon,
|
||||
SimulationIcon,
|
||||
} from "../../icons/SimulationIcons";
|
||||
import { useToggleStore } from "../../../store/useUIToggleStore";
|
||||
import Visualization from "./visualization/Visualization";
|
||||
import Analysis from "./analysis/Analysis";
|
||||
import Simulations from "./simulation/Simulations";
|
||||
import useVersionHistoryStore, {
|
||||
useSaveVersion,
|
||||
useSelectedFloorItem,
|
||||
useToolMode,
|
||||
} from "../../../store/builder/store";
|
||||
import {
|
||||
useSelectedEventData,
|
||||
useSelectedEventSphere,
|
||||
} from "../../../store/simulation/useSimulationStore";
|
||||
import GlobalProperties from "./properties/GlobalProperties";
|
||||
import AsstePropertiies from "./properties/AssetProperties";
|
||||
import ZoneProperties from "./properties/ZoneProperties";
|
||||
import EventProperties from "./properties/eventProperties/EventProperties";
|
||||
import VersionHistory from "./versionHisory/VersionHistory";
|
||||
import AisleProperties from "./properties/AisleProperties";
|
||||
import WallProperties from "./properties/eventProperties/WallProperties";
|
||||
|
||||
const SideBarRight: React.FC = () => {
|
||||
const { activeModule } = useModuleStore();
|
||||
const { toggleUIRight } = useToggleStore();
|
||||
const { toolMode } = useToolMode();
|
||||
const { subModule, setSubModule } = useSubModuleStore();
|
||||
const { selectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
const { viewVersionHistory, setVersionHistory } = useVersionHistoryStore();
|
||||
const { isVersionSaved } = useSaveVersion();
|
||||
|
||||
// Reset activeList whenever activeModule changes
|
||||
useEffect(() => {
|
||||
if (activeModule !== "simulation") setSubModule("properties");
|
||||
if (activeModule === "simulation") setSubModule("simulations");
|
||||
}, [activeModule, setSubModule]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
activeModule !== "mechanics" &&
|
||||
selectedEventData &&
|
||||
selectedEventSphere
|
||||
) {
|
||||
setSubModule("mechanics");
|
||||
} else if (!selectedEventData && !selectedEventSphere) {
|
||||
if (activeModule === "simulation") {
|
||||
setSubModule("simulations");
|
||||
}
|
||||
}
|
||||
if (activeModule !== "simulation") {
|
||||
setSubModule("properties");
|
||||
}
|
||||
}, [activeModule, selectedEventData, selectedEventSphere, setSubModule]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`sidebar-right-wrapper ${toggleUIRight && (!isVersionSaved || activeModule !== "simulation") ? "open" : "closed"
|
||||
}`}
|
||||
>
|
||||
<Header />
|
||||
{toggleUIRight && (
|
||||
<>
|
||||
{!isVersionSaved && (
|
||||
<div className="sidebar-actions-container">
|
||||
{activeModule !== "simulation" && (
|
||||
<button
|
||||
id="sidebar-action-list-properties"
|
||||
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSubModule("properties");
|
||||
setVersionHistory(false);
|
||||
}}
|
||||
>
|
||||
<div className="tooltip">properties</div>
|
||||
<PropertiesIcon isActive={subModule === "properties"} />
|
||||
</button>
|
||||
)}
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<button
|
||||
id="sidebar-action-list-simulation"
|
||||
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSubModule("simulations");
|
||||
setVersionHistory(false);
|
||||
}}
|
||||
>
|
||||
<div className="tooltip">simulations</div>
|
||||
<SimulationIcon isActive={subModule === "simulations"} />
|
||||
</button>
|
||||
<button
|
||||
id="sidebar-action-list-mechanics"
|
||||
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSubModule("mechanics");
|
||||
setVersionHistory(false);
|
||||
}}
|
||||
>
|
||||
<div className="tooltip">mechanics</div>
|
||||
<MechanicsIcon isActive={subModule === "mechanics"} />
|
||||
</button>
|
||||
<button
|
||||
id="sidebar-action-list-analysis"
|
||||
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSubModule("analysis");
|
||||
setVersionHistory(false);
|
||||
}}
|
||||
>
|
||||
<div className="tooltip">analysis</div>
|
||||
<AnalysisIcon isActive={subModule === "analysis"} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{viewVersionHistory && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<VersionHistory />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* process builder */}
|
||||
{!viewVersionHistory &&
|
||||
subModule === "properties" &&
|
||||
activeModule !== "visualization" &&
|
||||
!selectedFloorItem && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
{(() => {
|
||||
if (toolMode === "Aisle") {
|
||||
return <AisleProperties />;
|
||||
} else if (toolMode === "Wall") {
|
||||
return <WallProperties />;
|
||||
} else {
|
||||
return <GlobalProperties />;
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!viewVersionHistory &&
|
||||
subModule === "properties" &&
|
||||
activeModule !== "visualization" &&
|
||||
selectedFloorItem && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<AsstePropertiies />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!viewVersionHistory &&
|
||||
subModule === "zoneProperties" &&
|
||||
(activeModule === "builder" || activeModule === "simulation") && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<ZoneProperties />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* simulation */}
|
||||
{!isVersionSaved &&
|
||||
!viewVersionHistory &&
|
||||
activeModule === "simulation" && (
|
||||
<>
|
||||
{subModule === "simulations" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<Simulations />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "mechanics" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<EventProperties />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "analysis" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<Analysis />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* realtime visualization */}
|
||||
{activeModule === "visualization" && <Visualization />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideBarRight;
|
||||
124
app/src/components/layout/sidebarRight/analysis/Analysis.tsx
Normal file
124
app/src/components/layout/sidebarRight/analysis/Analysis.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React, { useState } from "react";
|
||||
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
|
||||
import { AnalysisPresetsType } from "../../../../types/analysis";
|
||||
import RenderAnalysisInputs from "./RenderAnalysisInputs";
|
||||
import { useInputValues } from "../../../../store/builder/store";
|
||||
|
||||
const Analysis: React.FC = () => {
|
||||
const [selectedOption, setSelectedOption] = useState("Throughput time");
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setSelectedOption(option); // Normalize for key matching
|
||||
};
|
||||
|
||||
const AnalysisPresets: AnalysisPresetsType = {
|
||||
"Throughput time": [
|
||||
// { type: "default", inputs: { label: "Cycle time", activeOption: "s" } },
|
||||
// { type: "default", inputs: { label: "machines / lines", activeOption: "item" } },
|
||||
// { type: "default", inputs: { label: "Machine uptime", activeOption: "%" } },
|
||||
],
|
||||
"Production capacity": [
|
||||
{ type: "range", inputs: { label: "Shift length", activeOption: "hr" } },
|
||||
{ type: "default", inputs: { label: "Shifts / day", activeOption: "unit" } },
|
||||
{ type: "default", inputs: { label: "Working days / year", activeOption: "days" } },
|
||||
{ type: "default", inputs: { label: "Yield rate", activeOption: "%" } },
|
||||
],
|
||||
ROI: [
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Selling price", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Material cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Labor Cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Maintenance cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Electricity cost", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Fixed costs", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Initial Investment", activeOption: "INR" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Salvage value", activeOption: "Hrs" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Production period", activeOption: "yrs" },
|
||||
},
|
||||
{
|
||||
type: "default",
|
||||
inputs: { label: "Tax rate", activeOption: "%" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { inputValues, setInputValues, updateInputValue } = useInputValues();
|
||||
|
||||
return (
|
||||
<div className="analysis-main-wrapper">
|
||||
<div className="analysis-main-container">
|
||||
<div className="header">Object</div>
|
||||
<div className="generate-report-button">
|
||||
<AIIcon /> Generate Report
|
||||
</div>
|
||||
<div className="analysis-content-container section">
|
||||
<div className="dropdown-header-container">
|
||||
<div className="value">Create Analysis</div>
|
||||
</div>
|
||||
<div className="dropdown-content-container">
|
||||
<RegularDropDown
|
||||
header={selectedOption}
|
||||
options={["Throughput time", "Production capacity", "ROI"]}
|
||||
onSelect={handleSelect}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
{/* Render only the selected option */}
|
||||
<RenderAnalysisInputs
|
||||
keyName={selectedOption}
|
||||
presets={
|
||||
AnalysisPresets[selectedOption as keyof AnalysisPresetsType]
|
||||
}
|
||||
inputValues={inputValues}
|
||||
onInputChange={(label, value) => {
|
||||
updateInputValue(label, value);
|
||||
}}
|
||||
/>
|
||||
<div className="buttons-container">
|
||||
<input type="button" value={"Clear"} className="cancel" onClick={() => setInputValues({})} />
|
||||
<input type="button" value={"Update"} className="submit" onClick={() => setInputValues(inputValues)} />
|
||||
</div>
|
||||
<div className="create-custom-analysis-container">
|
||||
<div className="custom-analysis-header">Create Custom Analysis</div>
|
||||
<div className="content">
|
||||
Click <span>'Create'</span> to enhances decision-making by
|
||||
providing actionable insights, optimizing operations that adapts
|
||||
to the unique challenges.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Create"} className="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Analysis;
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
import InputRange from "../../../ui/inputs/InputRange";
|
||||
import { AnalysisPresetsType } from "../../../../types/analysis";
|
||||
|
||||
interface InputRendererProps {
|
||||
keyName: string;
|
||||
presets: AnalysisPresetsType[keyof AnalysisPresetsType];
|
||||
inputValues: Record<string, string>; // <-- Add this line
|
||||
onInputChange: (label: string, value: string) => void;
|
||||
}
|
||||
|
||||
const RenderAnalysisInputs: React.FC<InputRendererProps> = ({ keyName, presets,inputValues, onInputChange }) => {
|
||||
return (
|
||||
<div key={`main-${keyName}`} className="analysis-inputs">
|
||||
{presets.map((preset, index) => {
|
||||
if (preset.type === "default") {
|
||||
return (
|
||||
<InputWithDropDown
|
||||
key={index}
|
||||
label={preset.inputs.label}
|
||||
value={inputValues[preset.inputs.label] || ""}
|
||||
activeOption={preset.inputs.activeOption}
|
||||
onChange={(newValue) => onInputChange(preset.inputs.label, newValue)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (preset.type === "range") {
|
||||
return (
|
||||
<InputRange
|
||||
key={index}
|
||||
label={preset.inputs.label}
|
||||
min={0}
|
||||
max={8}
|
||||
value={5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderAnalysisInputs;
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import { EyeDroperIcon } from "../../../icons/ExportCommonIcons";
|
||||
|
||||
interface PositionInputProps {
|
||||
label?: string; // Optional label for the input
|
||||
onChange: (value: string) => void; // Callback for value change
|
||||
placeholder?: string; // Optional placeholder
|
||||
type?: string; // Input type (e.g., text, number, email)
|
||||
value1?: number;
|
||||
value2?: number;
|
||||
disabled?: boolean; // Optional disabled property
|
||||
isEyedrop?: boolean; // Optional eyedrop property
|
||||
handleEyeDropClick?: () => void; // Optional function for eye drop click
|
||||
}
|
||||
|
||||
const PositionInput: React.FC<PositionInputProps> = ({
|
||||
onChange,
|
||||
label = "Position", // Default label
|
||||
placeholder = "Enter value", // Default placeholder
|
||||
type = "number", // Default type
|
||||
value1 = "number",
|
||||
value2 = "number",
|
||||
disabled = false, // Default disabled value
|
||||
isEyedrop = false, // Default isEyedrop value
|
||||
handleEyeDropClick = () => {}, // Default function for eye drop click
|
||||
}) => {
|
||||
return (
|
||||
<div className="custom-input-container">
|
||||
<div className="header">{label}</div>
|
||||
<div className="inputs-container">
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">X : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
value={value1}
|
||||
disabled={disabled} // Apply disabled prop
|
||||
/>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">Y : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
value={value2}
|
||||
disabled={disabled} // Apply disabled prop
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isEyedrop && (
|
||||
<div className="eye-picker-button" onClick={handleEyeDropClick}>
|
||||
<EyeDroperIcon isActive={false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PositionInput;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
|
||||
interface RotationInputProps {
|
||||
onChange: (value: string) => void; // Callback for value change
|
||||
placeholder?: string; // Optional placeholder
|
||||
type?: string; // Input type (e.g., text, number, email)
|
||||
value?: number;
|
||||
}
|
||||
|
||||
const RotationInput: React.FC<RotationInputProps> = ({
|
||||
onChange,
|
||||
placeholder = "Enter value", // Default placeholder
|
||||
type = "number", // Default type
|
||||
value = "number",
|
||||
}) => {
|
||||
return (
|
||||
<div className="custom-input-container">
|
||||
<div className="header">Rotation</div>
|
||||
<div className="inputs-container" style={{ display: "block" }}>
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">Rotate : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RotationInput;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import { EyeDroperIcon } from "../../../icons/ExportCommonIcons";
|
||||
// import { useThree } from "@react-three/fiber";
|
||||
|
||||
interface PositionInputProps {
|
||||
onChange: (value: [number, number, number]) => void; // Callback for value change
|
||||
header: string;
|
||||
placeholder?: string; // Optional placeholder
|
||||
type?: string; // Input type (e.g., text, number, email)
|
||||
value: [number, number, number] | null;
|
||||
disabled?: boolean; // To enable/disable editing
|
||||
}
|
||||
|
||||
const Vector3Input: React.FC<PositionInputProps> = ({
|
||||
onChange,
|
||||
header,
|
||||
placeholder = "Enter value", // Default placeholder
|
||||
type = "string", // Default type
|
||||
value,
|
||||
disabled = false, // Default to disabled
|
||||
}) => {
|
||||
|
||||
const handleChange = (index: number, newValue: string) => {
|
||||
if (!value) return;
|
||||
const updatedValue = [...value] as [number, number, number];
|
||||
updatedValue[index] = parseFloat(newValue) || 0;
|
||||
console.log('updatedValue: ', updatedValue);
|
||||
onChange(updatedValue);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="custom-input-container">
|
||||
<div className="header">
|
||||
{header}
|
||||
</div>
|
||||
<div className="inputs-container">
|
||||
{["X", "Y", "Z"].map((axis, i) => (
|
||||
<div className="input-container" key={axis}>
|
||||
<div className="custom-input-label">{axis}:</div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
value={value?.[i] !== undefined ? value[i].toFixed(2) : ""}
|
||||
// onChange={(e) => handleChange(i, e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Vector3Input;
|
||||
@@ -0,0 +1,300 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
import { ArrowIcon } from "../../../icons/ExportCommonIcons";
|
||||
|
||||
// image imports
|
||||
import Arc from "../../../../assets/image/aisleTypes/Arc.png";
|
||||
import Arrow from "../../../../assets/image/aisleTypes/Arrow.png";
|
||||
import Arrows from "../../../../assets/image/aisleTypes/Arrows.png";
|
||||
import Circle from "../../../../assets/image/aisleTypes/Circle.png";
|
||||
import Dashed from "../../../../assets/image/aisleTypes/Dashed.png";
|
||||
import Directional from "../../../../assets/image/aisleTypes/Directional.png";
|
||||
import Dotted from "../../../../assets/image/aisleTypes/Dotted.png";
|
||||
import Solid from "../../../../assets/image/aisleTypes/Solid.png";
|
||||
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
|
||||
interface TextureItem {
|
||||
color: string;
|
||||
id: AisleColors;
|
||||
brief: string;
|
||||
texture: string;
|
||||
}
|
||||
|
||||
const AisleProperties: React.FC = () => {
|
||||
const [collapsePresets, setCollapsePresets] = useState(false);
|
||||
const [collapseTexture, setCollapseTexture] = useState(true);
|
||||
|
||||
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore();
|
||||
|
||||
const aisleTextureList: TextureItem[] = [
|
||||
{ color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" },
|
||||
{ color: "gray", id: "gray", brief: "basic", texture: "" },
|
||||
{ color: "green", id: "green", brief: "pedestrian walkways", texture: "" },
|
||||
{ color: "orange", id: "orange", brief: "material flow", texture: "" },
|
||||
{ color: "blue", id: "blue", brief: "vehicle paths", texture: "" },
|
||||
{ color: "purple", id: "purple", brief: "material flow", texture: "" },
|
||||
{ color: "red", id: "red", brief: "safety zone", texture: "" },
|
||||
{ color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
|
||||
{ color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
|
||||
{ color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
|
||||
];
|
||||
|
||||
const aisleTypes: {
|
||||
name: string;
|
||||
type: AisleTypes;
|
||||
id: string;
|
||||
thumbnail: string;
|
||||
}[] = [
|
||||
{ name: "Solid", type: "solid-aisle", id: "1", thumbnail: Solid },
|
||||
{ name: "Dotted", type: "dotted-aisle", id: "2", thumbnail: Dotted },
|
||||
{ name: "Dashed", type: "dashed-aisle", id: "3", thumbnail: Dashed },
|
||||
{ name: "Arrow", type: "arrow-aisle", id: "4", thumbnail: Arrow },
|
||||
{ name: "Continuous Arrows", type: "arrows-aisle", id: "5", thumbnail: Arrows },
|
||||
{ name: "Directional", type: "junction-aisle", id: "6", thumbnail: Directional },
|
||||
{ name: "Arc", type: "arc-aisle", id: "7", thumbnail: Arc },
|
||||
{ name: "Circle", type: "circle-aisle", id: "8", thumbnail: Circle },
|
||||
];
|
||||
|
||||
const handleAisleWidthChange = (value: string) => {
|
||||
const width = parseFloat(value);
|
||||
if (!isNaN(width)) {
|
||||
setAisleWidth(width);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDashLengthChange = (value: string) => {
|
||||
const length = parseFloat(value);
|
||||
if (!isNaN(length)) {
|
||||
setDashLength(length);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGapLengthChange = (value: string) => {
|
||||
const length = parseFloat(value);
|
||||
if (!isNaN(length)) {
|
||||
setGapLength(length);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDotRadiusChange = (value: string) => {
|
||||
const radius = parseFloat(value);
|
||||
if (!isNaN(radius)) {
|
||||
setDotRadius(radius);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAisleLengthChange = (value: string) => {
|
||||
const length = parseFloat(value);
|
||||
if (!isNaN(length)) {
|
||||
setAisleLength(length);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIsFlippedChange = () => {
|
||||
setIsFlipped(!aisleIsFlipped)
|
||||
};
|
||||
|
||||
const dashLengthValue = useMemo(() => {
|
||||
return dashLength.toString();
|
||||
}, [aisleType, dashLength]);
|
||||
|
||||
const dotRadiusValue = useMemo(() => {
|
||||
return dotRadius.toString();
|
||||
}, [aisleType, dotRadius]);
|
||||
|
||||
const gapLengthValue = useMemo(() => {
|
||||
return gapLength.toString();
|
||||
}, [aisleType, gapLength]);
|
||||
|
||||
const aisleWidthValue = useMemo(() => {
|
||||
return aisleWidth.toString();
|
||||
}, [aisleType, aisleWidth]);
|
||||
|
||||
const aisleLengthValue = useMemo(() => {
|
||||
return aisleLength.toString();
|
||||
}, [aisleType, aisleLength]);
|
||||
|
||||
const aisleIsFlipped = useMemo(() => {
|
||||
return isFlipped;
|
||||
}, [aisleType, isFlipped]);
|
||||
|
||||
const renderAdvancedProperties = () => {
|
||||
switch (aisleType) {
|
||||
case 'dashed-aisle':
|
||||
return (
|
||||
<>
|
||||
{aisleType &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Dash Length"
|
||||
value={`${dashLengthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleDashLengthChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Gap Length"
|
||||
value={`${gapLengthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleGapLengthChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
case 'dotted-aisle':
|
||||
return (
|
||||
<>
|
||||
{aisleType &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Dot Radius"
|
||||
value={`${dotRadiusValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleDotRadiusChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Gap Length"
|
||||
value={`${gapLengthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleGapLengthChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
case 'arrows-aisle':
|
||||
return (
|
||||
<>
|
||||
{aisleType &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Arrow Length"
|
||||
value={`${aisleLengthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleAisleLengthChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Gap Length"
|
||||
value={`${gapLengthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleGapLengthChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
case 'junction-aisle': case 'arc-aisle':
|
||||
return (
|
||||
<>
|
||||
{aisleType &&
|
||||
<InputToggle
|
||||
inputKey="Flip Ailse"
|
||||
label="Flip Aisle"
|
||||
value={aisleIsFlipped}
|
||||
onClick={handleIsFlippedChange}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="aisle-properties-container">
|
||||
<div className="header">Properties</div>
|
||||
|
||||
{/* Basic Properties */}
|
||||
<section>
|
||||
{aisleType !== 'dotted-aisle' &&
|
||||
<InputWithDropDown
|
||||
label="Aisle Width"
|
||||
value={`${aisleWidthValue}`}
|
||||
min={0.1}
|
||||
step={0.1}
|
||||
max={2}
|
||||
onChange={handleAisleWidthChange}
|
||||
/>
|
||||
}
|
||||
{renderAdvancedProperties()}
|
||||
</section>
|
||||
|
||||
{/* Presets */}
|
||||
<section>
|
||||
<button
|
||||
className="header"
|
||||
onClick={() => setCollapsePresets(!collapsePresets)}
|
||||
aria-expanded={!collapsePresets}
|
||||
>
|
||||
<div className="value">Presets</div>
|
||||
<div className="icon">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{!collapsePresets && (
|
||||
<div className="presets-list-container">
|
||||
{aisleTypes.map((val) => (
|
||||
<div className="preset-list" key={val.id}>
|
||||
<button
|
||||
className={`thumbnail ${aisleType === val.type ? "selected" : ""}`}
|
||||
title={val.name}
|
||||
onClick={() => setAisleType(val.type)}
|
||||
>
|
||||
<img src={val.thumbnail} alt="" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Texture */}
|
||||
<section>
|
||||
<button
|
||||
className="header"
|
||||
onClick={() => setCollapseTexture(!collapseTexture)}
|
||||
aria-expanded={!collapseTexture}
|
||||
>
|
||||
<div className="value">Aisle Texture</div>
|
||||
<div className="icon" style={{ rotate: collapseTexture ? "" : "-90deg" }}>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{collapseTexture && (
|
||||
<div className="aisle-texture-container">
|
||||
{aisleTextureList.map((val) => (
|
||||
<button
|
||||
key={val.id}
|
||||
title={val.brief || val.id}
|
||||
className={`aisle-list ${aisleColor === val.color ? "selected" : ""}`}
|
||||
onClick={() => setAisleColor(val.id)}
|
||||
aria-pressed={aisleColor === val.id}
|
||||
>
|
||||
<div className="texture-display">{val.texture}</div>
|
||||
<div className="aisle-color">{val.color}</div>
|
||||
<div className="aisle-brief">{`( ${val.brief} )`}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AisleProperties;
|
||||
@@ -0,0 +1,103 @@
|
||||
import React, { useState } from "react";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
|
||||
import { RemoveIcon } from "../../../icons/ExportCommonIcons";
|
||||
import PositionInput from "../customInput/PositionInputs";
|
||||
import RotationInput from "../customInput/RotationInput";
|
||||
import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store";
|
||||
|
||||
interface UserData {
|
||||
id: number; // Unique identifier for the user data
|
||||
label: string; // Label of the user data field
|
||||
value: string; // Value of the user data field
|
||||
}
|
||||
|
||||
const AssetProperties: React.FC = () => {
|
||||
const [userData, setUserData] = useState<UserData[]>([]); // State to track user data
|
||||
const [nextId, setNextId] = useState(1); // Unique ID for new entries
|
||||
const { selectedFloorItem } = useSelectedFloorItem();
|
||||
const { objectPosition } = useObjectPosition();
|
||||
const { objectRotation } = useObjectRotation();
|
||||
// Function to handle adding new user data
|
||||
const handleAddUserData = () => {
|
||||
const newUserData: UserData = {
|
||||
id: nextId,
|
||||
label: `Property ${nextId}`,
|
||||
value: "",
|
||||
};
|
||||
setUserData([...userData, newUserData]);
|
||||
setNextId(nextId + 1); // Increment the ID for the next entry
|
||||
};
|
||||
|
||||
// Function to update the value of a user data entry
|
||||
const handleUserDataChange = (id: number, newValue: string) => {
|
||||
setUserData((prevUserData) =>
|
||||
prevUserData.map((data) =>
|
||||
data.id === id ? { ...data, value: newValue } : data
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Remove user data
|
||||
const handleRemoveUserData = (id: number) => {
|
||||
setUserData((prevUserData) =>
|
||||
prevUserData.filter((data) => data.id !== id)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="asset-properties-container">
|
||||
{/* Name */}
|
||||
<div className="header">{selectedFloorItem.userData.modelName}</div>
|
||||
<section>
|
||||
{objectPosition.x && objectPosition.z &&
|
||||
<PositionInput
|
||||
onChange={() => { }}
|
||||
value1={parseFloat(objectPosition.x.toFixed(5))}
|
||||
value2={parseFloat(objectPosition.z.toFixed(5))}
|
||||
/>
|
||||
}
|
||||
{objectRotation.y &&
|
||||
<RotationInput
|
||||
onChange={() => { }}
|
||||
value={parseFloat(objectRotation.y.toFixed(5))}
|
||||
/>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div className="header">Render settings</div>
|
||||
<InputToggle inputKey="visible" label="Visible" />
|
||||
<InputToggle inputKey="frustumCull" label="Frustum cull" />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div className="header">User Data</div>
|
||||
{userData.map((data) => (
|
||||
<div className="input-container">
|
||||
<InputWithDropDown
|
||||
key={data.id}
|
||||
label={data.label}
|
||||
value={data.value}
|
||||
editableLabel
|
||||
onChange={(newValue) => handleUserDataChange(data.id, newValue)} // Pass the change handler
|
||||
/>
|
||||
<div
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveUserData(data.id)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Add new user data */}
|
||||
<div className="optimize-button" onClick={handleAddUserData}>
|
||||
+ Add
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetProperties;
|
||||
@@ -0,0 +1,314 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import InputRange from "../../../ui/inputs/InputRange";
|
||||
import InputToggle from "../../../ui/inputs/InputToggle";
|
||||
import { AIIcon } from "../../../icons/ExportCommonIcons";
|
||||
import LabeledButton from "../../../ui/inputs/LabledButton";
|
||||
import {
|
||||
useAzimuth,
|
||||
useElevation,
|
||||
useLimitDistance,
|
||||
useRenderDistance,
|
||||
useResetCamera,
|
||||
useRoofVisibility,
|
||||
useSelectedWallItem,
|
||||
useShadows,
|
||||
useSocketStore,
|
||||
useTileDistance,
|
||||
useToggleView,
|
||||
useWallVisibility,
|
||||
} from "../../../../store/builder/store";
|
||||
import { setEnvironment } from "../../../../services/factoryBuilder/environment/setEnvironment";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
import { useParams } from "react-router-dom";
|
||||
const GlobalProperties: React.FC = () => {
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
|
||||
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
|
||||
const { wallVisibility, setWallVisibility } = useWallVisibility();
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { resetCamera, setResetCamera } = useResetCamera();
|
||||
const { elevation, setElevation } = useElevation();
|
||||
const { azimuth, setAzimuth } = useAzimuth();
|
||||
const { renderDistance, setRenderDistance } = useRenderDistance();
|
||||
const { setPlaneValue, setGridValue, planeValue, gridValue } = useTileDistance();
|
||||
const { socket } = useSocketStore();
|
||||
const { limitDistance, setLimitDistance } = useLimitDistance();
|
||||
const [distance, setDistance] = useState<number>(40);
|
||||
|
||||
const [limitGridDistance, setLimitGridDistance] = useState(false);
|
||||
const [gridDistance, setGridDistance] = useState<number>(3);
|
||||
const { projectId } = useParams();
|
||||
|
||||
const optimizeScene = async (value: any) => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "defaultOrg";
|
||||
|
||||
setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
roofVisibility,
|
||||
shadows,
|
||||
30,
|
||||
true,
|
||||
projectId
|
||||
);
|
||||
setRenderDistance(30);
|
||||
setLimitDistance(true);
|
||||
};
|
||||
|
||||
const limitRenderDistance = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "defaultOrg";
|
||||
|
||||
if (limitDistance) {
|
||||
setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
roofVisibility,
|
||||
shadows,
|
||||
75,
|
||||
!limitDistance,
|
||||
projectId
|
||||
);
|
||||
setRenderDistance(75);
|
||||
} else {
|
||||
setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
roofVisibility,
|
||||
shadows,
|
||||
renderDistance,
|
||||
!limitDistance,
|
||||
projectId
|
||||
);
|
||||
}
|
||||
setLimitDistance(!limitDistance);
|
||||
};
|
||||
|
||||
function updateDistance(value: number) {
|
||||
setDistance(value);
|
||||
setRenderDistance(value);
|
||||
}
|
||||
function updateGridDistance(value: number) {
|
||||
setGridDistance(value);
|
||||
// setGridValue({ size: value * 100, divisions: (value * 100) / 4 });
|
||||
// setPlaneValue({ height: value * 100, width: value * 100 });
|
||||
}
|
||||
function updatedGrid(value: number) {
|
||||
// console.log(" (value * 100) / 4 : ", (value * 100) / 4);
|
||||
setGridValue({ size: value * 100, divisions: (value * 100) / 4 });
|
||||
setPlaneValue({ height: value * 100, width: value * 100 });
|
||||
}
|
||||
|
||||
const updatedDist = async (value: number) => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "defaultOrg";
|
||||
setRenderDistance(value);
|
||||
// setDistance(value);
|
||||
const data = await setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
roofVisibility,
|
||||
shadows,
|
||||
value,
|
||||
limitDistance,
|
||||
projectId
|
||||
);
|
||||
};
|
||||
|
||||
// Function to toggle roof visibility
|
||||
const changeRoofVisibility = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
//using REST
|
||||
const data = await setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
!roofVisibility,
|
||||
shadows,
|
||||
renderDistance,
|
||||
limitDistance,
|
||||
projectId
|
||||
);
|
||||
//
|
||||
|
||||
//using Socket
|
||||
// const visData = {
|
||||
// organization: organization,
|
||||
// userId: localStorage.getItem('userId')!,
|
||||
// wallVisibility: wallVisibility,
|
||||
// roofVisibility: !roofVisibility,
|
||||
// shadowVisibility: shadows,
|
||||
// socketId: socket.id
|
||||
// };
|
||||
// socket.emit('v1:Environment:set', visData)
|
||||
|
||||
setRoofVisibility(!roofVisibility); // Toggle roof visibility
|
||||
};
|
||||
// Function to toggle wall visibility
|
||||
const changeWallVisibility = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
//using REST
|
||||
const data = await setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
!wallVisibility,
|
||||
roofVisibility,
|
||||
shadows,
|
||||
renderDistance,
|
||||
limitDistance, projectId
|
||||
);
|
||||
//
|
||||
|
||||
//using Socket
|
||||
// const visData = {
|
||||
// organization: organization,
|
||||
// userId: localStorage.getItem('userId')!,
|
||||
// wallVisibility: !wallVisibility,
|
||||
// roofVisibility: roofVisibility,
|
||||
// shadowVisibility: shadows,
|
||||
// socketId: socket.id
|
||||
// };
|
||||
// socket.emit('v1:Environment:set', visData)
|
||||
|
||||
setWallVisibility(!wallVisibility); // Toggle wall visibility
|
||||
};
|
||||
|
||||
const shadowVisibility = async () => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
//using REST
|
||||
const data = await setEnvironment(
|
||||
organization,
|
||||
localStorage.getItem("userId")!,
|
||||
wallVisibility,
|
||||
roofVisibility,
|
||||
!shadows,
|
||||
renderDistance,
|
||||
limitDistance,
|
||||
projectId
|
||||
);
|
||||
//
|
||||
|
||||
//using Socket
|
||||
// const visData = {
|
||||
// organization: organization,
|
||||
// userId: localStorage.getItem('userId')!,
|
||||
// wallVisibility: wallVisibility,
|
||||
// roofVisibility: roofVisibility,
|
||||
// shadowVisibility: !shadows,
|
||||
// socketId: socket.id
|
||||
// };
|
||||
// socket.emit('v1:Environment:set', visData)
|
||||
|
||||
setShadows(!shadows);
|
||||
};
|
||||
|
||||
const toggleResetCamera = () => {
|
||||
if (!toggleView) {
|
||||
setResetCamera(true); // Trigger reset camera action
|
||||
}
|
||||
};
|
||||
|
||||
// function changeRenderDistance(e: any) {
|
||||
// if (parseInt(e.target.value) < 20) {
|
||||
// setRenderDistance(20);
|
||||
// } else if (parseInt(e.target.value) > 75) {
|
||||
// setRenderDistance(75);
|
||||
// } else {
|
||||
// setRenderDistance(parseInt(e.target.value));
|
||||
// }
|
||||
// }
|
||||
return (
|
||||
<div className="global-properties-container">
|
||||
<section>
|
||||
<div className="header">Environment</div>
|
||||
<div className="optimize-button" onClick={optimizeScene}>
|
||||
<AIIcon />
|
||||
Optimize
|
||||
</div>
|
||||
|
||||
<div className="split"></div>
|
||||
|
||||
<InputToggle
|
||||
value={roofVisibility}
|
||||
inputKey="1"
|
||||
label="Roof Visibility"
|
||||
onClick={changeRoofVisibility}
|
||||
/>
|
||||
<InputToggle
|
||||
value={wallVisibility}
|
||||
inputKey="2"
|
||||
label="Wall Visibility"
|
||||
onClick={changeWallVisibility}
|
||||
/>
|
||||
{/* <InputToggle
|
||||
value={shadows}
|
||||
inputKey="3"
|
||||
label="Shadows Visibility"
|
||||
onClick={shadowVisibility}
|
||||
/> */}
|
||||
<LabeledButton
|
||||
label="Reset Camera"
|
||||
onClick={toggleResetCamera}
|
||||
value="Reset"
|
||||
/>
|
||||
|
||||
<div className="split"></div>
|
||||
{/* //visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} */}
|
||||
<InputToggle
|
||||
inputKey="4"
|
||||
label="Limit Render Distance"
|
||||
value={limitDistance}
|
||||
// onClick={() => {
|
||||
// setLimitDistance(!limitDistance);
|
||||
// // setDistance(75);
|
||||
// // setRenderDistance(75);
|
||||
// }}
|
||||
onClick={async () => {
|
||||
await limitRenderDistance(); // Call the function here
|
||||
}}
|
||||
/>
|
||||
<InputRange
|
||||
label="Distance"
|
||||
disabled={!limitDistance}
|
||||
value={renderDistance}
|
||||
min={CONSTANTS.distanceConfig.minDistance}
|
||||
max={CONSTANTS.distanceConfig.maxDistance}
|
||||
onChange={(value: number) => updateDistance(value)}
|
||||
onPointerUp={updatedDist}
|
||||
key={"6"}
|
||||
/>
|
||||
|
||||
{/* <div className="split"></div>
|
||||
<InputToggle
|
||||
inputKey="6"
|
||||
label="Display Grid"
|
||||
value={limitGridDistance}
|
||||
onClick={() => {
|
||||
setLimitGridDistance(!limitGridDistance);
|
||||
}}
|
||||
/>
|
||||
<InputRange
|
||||
label="Tile Distance"
|
||||
disabled={!limitGridDistance}
|
||||
value={gridDistance}
|
||||
key={"7"}
|
||||
min={1}
|
||||
max={5}
|
||||
onChange={(value: number) => updateGridDistance(value)}
|
||||
onPointerUp={updatedGrid}
|
||||
/> */}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalProperties;
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import Vector3Input from "../customInput/Vector3Input";
|
||||
import { useSelectedZoneStore } from "../../../../store/visualization/useZoneStore";
|
||||
import {
|
||||
useEditPosition,
|
||||
usezonePosition,
|
||||
useZones,
|
||||
usezoneTarget,
|
||||
} from "../../../../store/builder/store";
|
||||
import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCameraUpdation";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const ZoneProperties: React.FC = () => {
|
||||
const { Edit, setEdit } = useEditPosition();
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
const { zonePosition, setZonePosition } = usezonePosition();
|
||||
const { zoneTarget, setZoneTarget } = usezoneTarget();
|
||||
const { zones, setZones } = useZones();
|
||||
const { projectId } = useParams()
|
||||
|
||||
useEffect(() => {
|
||||
setZonePosition(selectedZone.zoneViewPortPosition);
|
||||
setZoneTarget(selectedZone.zoneViewPortTarget);
|
||||
}, [selectedZone?.zoneViewPortPosition, selectedZone?.zoneViewPortTarget]);
|
||||
|
||||
async function handleSetView() {
|
||||
try {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
let zonesdata = {
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
viewPortposition: zonePosition,
|
||||
viewPortCenter: zoneTarget,
|
||||
};
|
||||
|
||||
let response = await zoneCameraUpdate(zonesdata, organization, projectId);
|
||||
console.log('response: ', response);
|
||||
if (response.message === "zone updated") {
|
||||
setEdit(false);
|
||||
} else {
|
||||
// console.log(response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to set zone view");
|
||||
}
|
||||
}
|
||||
|
||||
function handleEditView() {
|
||||
setEdit(!Edit); // This will toggle the `Edit` state correctly
|
||||
}
|
||||
|
||||
async function handleZoneNameChange(newName: string) {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const zonesdata = {
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
zoneName: newName,
|
||||
};
|
||||
// Call your API to update the zone
|
||||
let response = await zoneCameraUpdate(zonesdata, organization, projectId);
|
||||
if (response.message === "zone updated") {
|
||||
setSelectedZone((prev) => ({ ...prev, zoneName: newName }));
|
||||
setZones((prevZones: any[]) =>
|
||||
prevZones.map((zone) =>
|
||||
zone.zoneUuid === selectedZone.zoneUuid
|
||||
? { ...zone, zoneName: newName }
|
||||
: zone
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// console.log(response?.message);
|
||||
}
|
||||
}
|
||||
function handleVectorChange(
|
||||
key: "zoneViewPortTarget" | "zoneViewPortPosition",
|
||||
newValue: [number, number, number]
|
||||
) {
|
||||
setSelectedZone((prev) => ({ ...prev, [key]: newValue }));
|
||||
}
|
||||
const checkZoneNameDuplicate = (name: string) => {
|
||||
return zones.some(
|
||||
(zone: any) =>
|
||||
zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() &&
|
||||
zone.zoneUuid !== selectedZone.zoneUuid
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="zone-properties-container">
|
||||
<section>
|
||||
<div className="header">
|
||||
<RenameInput
|
||||
value={selectedZone.zoneName}
|
||||
onRename={handleZoneNameChange}
|
||||
checkDuplicate={checkZoneNameDuplicate}
|
||||
/>
|
||||
<div className="button" onClick={handleEditView}>
|
||||
{Edit ? "Cancel" : "Edit"}
|
||||
</div>
|
||||
</div>
|
||||
<Vector3Input
|
||||
onChange={(value) => handleVectorChange("zoneViewPortTarget", value)}
|
||||
header="Viewport Target"
|
||||
value={zoneTarget as [number, number, number]}
|
||||
disabled={!Edit}
|
||||
/>
|
||||
<Vector3Input
|
||||
onChange={(value) =>
|
||||
handleVectorChange("zoneViewPortPosition", value)
|
||||
}
|
||||
header="Viewport Position"
|
||||
value={zonePosition as [number, number, number]}
|
||||
disabled={!Edit}
|
||||
/>
|
||||
|
||||
{Edit && (
|
||||
<div className="button-save" onClick={handleSetView}>
|
||||
Set View
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZoneProperties;
|
||||
@@ -0,0 +1,137 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
useSelectedEventData,
|
||||
useSelectedEventSphere,
|
||||
} from "../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
||||
import ConveyorMechanics from "./mechanics/conveyorMechanics";
|
||||
import VehicleMechanics from "./mechanics/vehicleMechanics";
|
||||
import RoboticArmMechanics from "./mechanics/roboticArmMechanics";
|
||||
import MachineMechanics from "./mechanics/machineMechanics";
|
||||
import StorageMechanics from "./mechanics/storageMechanics";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import { handleAddEventToProduct } from "../../../../../modules/simulation/events/points/functions/handleAddEventToProduct";
|
||||
import { useEventsStore } from "../../../../../store/simulation/useEventsStore";
|
||||
import { useProductContext } from "../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const EventProperties: React.FC = () => {
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getEventByModelUuid } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const [currentEventData, setCurrentEventData] = useState<EventsSchema | null>(
|
||||
null
|
||||
);
|
||||
const [assetType, setAssetType] = useState<string | null>(null);
|
||||
const { products, addEvent } = useProductStore();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
const event = getCurrentEventData();
|
||||
setCurrentEventData(event);
|
||||
|
||||
const type = determineAssetType(event);
|
||||
setAssetType(type);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedEventData, selectedProduct]);
|
||||
|
||||
const getCurrentEventData = () => {
|
||||
if (!selectedEventData?.data || !selectedProduct) return null;
|
||||
return (
|
||||
getEventByModelUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid
|
||||
) ?? null
|
||||
);
|
||||
};
|
||||
|
||||
const determineAssetType = (event: EventsSchema | null) => {
|
||||
if (!event) return null;
|
||||
|
||||
switch (event.type) {
|
||||
case "transfer":
|
||||
return "conveyor";
|
||||
case "vehicle":
|
||||
return "vehicle";
|
||||
case "roboticArm":
|
||||
return "roboticArm";
|
||||
case "machine":
|
||||
return "machine";
|
||||
case "storageUnit":
|
||||
return "storageUnit";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="event-proprties-wrapper">
|
||||
{currentEventData && (
|
||||
<>
|
||||
<div className="header">
|
||||
<div className="header-value">
|
||||
{selectedEventData?.data.modelName}
|
||||
</div>
|
||||
</div>
|
||||
{assetType === "conveyor" && <ConveyorMechanics />}
|
||||
{assetType === "vehicle" && <VehicleMechanics />}
|
||||
{assetType === "roboticArm" && <RoboticArmMechanics />}
|
||||
{assetType === "machine" && <MachineMechanics />}
|
||||
{assetType === "storageUnit" && <StorageMechanics />}
|
||||
</>
|
||||
)}
|
||||
{!currentEventData && selectedEventSphere && (
|
||||
<div className="no-event-selected">
|
||||
<p>
|
||||
<strong>Oops!</strong> It looks like this object doesn't have an
|
||||
event assigned yet. To continue, please link it to one of the
|
||||
products below.
|
||||
</p>
|
||||
|
||||
<div className="products-list">
|
||||
<p>
|
||||
<strong>Here are some products you can add it to:</strong>
|
||||
</p>
|
||||
<div className="product-item">
|
||||
{products.map((product) => (
|
||||
<button
|
||||
id="add-event-to-product-button"
|
||||
key={product.productUuid}
|
||||
onClick={() => {
|
||||
if (selectedEventData) {
|
||||
handleAddEventToProduct({
|
||||
event: useEventsStore
|
||||
.getState()
|
||||
.getEventByModelUuid(
|
||||
selectedEventData?.data.modelUuid
|
||||
),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
projectId: projectId || ''
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
{product.productName}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!selectedEventSphere && (
|
||||
<div className="no-event-selected">
|
||||
<p>
|
||||
<strong>Oops!</strong> It looks like you haven't selected an event
|
||||
point yet. Please select an event to view its properties.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventProperties;
|
||||
@@ -0,0 +1,201 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown";
|
||||
import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons";
|
||||
|
||||
// Texture Imports
|
||||
import wallTexture1 from "../../../../../assets/image/wallTextures/wallTexture.png";
|
||||
import defaultTexture from "../../../../../assets/image/wallTextures/defaultTexture.jpg";
|
||||
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
|
||||
|
||||
// Define Material type
|
||||
type Material = {
|
||||
texture: string;
|
||||
textureName: string;
|
||||
};
|
||||
|
||||
// Initial and default material
|
||||
const initialMaterial: Material = {
|
||||
texture: wallTexture1,
|
||||
textureName: "Grunge Concrete Wall",
|
||||
};
|
||||
|
||||
const defaultMaterial: Material = {
|
||||
texture: defaultTexture,
|
||||
textureName: "Default Material",
|
||||
};
|
||||
|
||||
const WallProperties = () => {
|
||||
const { wallHeight, wallThickness, setWallHeight, setWallThickness } = useBuilderStore();
|
||||
|
||||
const [activeSide, setActiveSide] = useState<"side1" | "side2">("side1");
|
||||
|
||||
const [materials, setMaterials] = useState<Material[]>([initialMaterial]);
|
||||
|
||||
const [selectedMaterials, setSelectedMaterials] = useState<{
|
||||
side1: Material | null;
|
||||
side2: Material | null;
|
||||
}>({
|
||||
side1: null,
|
||||
side2: null,
|
||||
});
|
||||
|
||||
// Select initial material for both sides on mount
|
||||
useEffect(() => {
|
||||
setSelectedMaterials({
|
||||
side1: initialMaterial,
|
||||
side2: initialMaterial,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleHeightChange = (newValue: string) => {
|
||||
setWallHeight(parseFloat(newValue));
|
||||
};
|
||||
|
||||
const handleThicknessChange = (newValue: string) => {
|
||||
setWallThickness(parseFloat(newValue));
|
||||
};
|
||||
|
||||
const handleAddMaterial = () => {
|
||||
const newMaterial: Material = {
|
||||
texture: defaultMaterial.texture,
|
||||
textureName: `Material ${materials.length + 1}`,
|
||||
};
|
||||
setMaterials([...materials, newMaterial]);
|
||||
};
|
||||
|
||||
const handleSelectMaterial = (material: Material) => {
|
||||
setSelectedMaterials((prev) => ({
|
||||
...prev,
|
||||
[activeSide]: material,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveMaterial = (index: number) => {
|
||||
const updatedMaterials = materials.filter((_, i) => i !== index);
|
||||
|
||||
// Ensure there's always at least one material
|
||||
const newMaterials =
|
||||
updatedMaterials.length === 0 ? [defaultMaterial] : updatedMaterials;
|
||||
setMaterials(newMaterials);
|
||||
|
||||
// Deselect the material if it's the one removed
|
||||
setSelectedMaterials((prev) => {
|
||||
const updated = { ...prev };
|
||||
["side1", "side2"].forEach((side) => {
|
||||
if (
|
||||
updated[side as "side1" | "side2"]?.texture ===
|
||||
materials[index].texture
|
||||
) {
|
||||
updated[side as "side1" | "side2"] = defaultMaterial;
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="wall-properties-container">
|
||||
<div className="header">Wall</div>
|
||||
<div className="wall-properties">
|
||||
<InputWithDropDown
|
||||
label="Height"
|
||||
value={`${wallHeight}`}
|
||||
onChange={(val) => handleHeightChange(val)}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Thickness"
|
||||
value={`${wallThickness}`}
|
||||
onChange={(val) => handleThicknessChange(val)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div className="header-wrapper">
|
||||
<div className="header">Materials</div>
|
||||
<button className="addMaterial" onClick={handleAddMaterial}>
|
||||
<AddIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="material-preview">
|
||||
<div className="sides-wrapper">
|
||||
<div
|
||||
className={`side-wrapper ${activeSide === "side1" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveSide("side1")}
|
||||
>
|
||||
<div className="label">Side 1</div>
|
||||
<div className="texture-image">
|
||||
{selectedMaterials.side1 && (
|
||||
<img
|
||||
src={selectedMaterials.side1.texture}
|
||||
alt={selectedMaterials.side1.textureName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`side-wrapper ${activeSide === "side2" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveSide("side2")}
|
||||
>
|
||||
<div className="label">Side 2</div>
|
||||
<div className="texture-image">
|
||||
{selectedMaterials.side2 && (
|
||||
<img
|
||||
src={selectedMaterials.side2.texture}
|
||||
alt={selectedMaterials.side2.textureName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="preview">
|
||||
{selectedMaterials[activeSide] && (
|
||||
<img
|
||||
src={selectedMaterials[activeSide]!.texture}
|
||||
alt={selectedMaterials[activeSide]!.textureName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="materials">
|
||||
{materials.length === 0 ? (
|
||||
<div className="no-materials">No materials added yet.</div>
|
||||
) : (
|
||||
<div className="material-container">
|
||||
{materials.map((material, index) => (
|
||||
<div
|
||||
className="material-wrapper"
|
||||
key={`${material.textureName}_${index}`}
|
||||
onClick={() => handleSelectMaterial(material)}
|
||||
>
|
||||
<div className="material-property">
|
||||
<div className="material-image">
|
||||
<img src={material.texture} alt={material.textureName} />
|
||||
</div>
|
||||
<div className="material-name">{material.textureName}</div>
|
||||
</div>
|
||||
<div
|
||||
className="delete-material"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveMaterial(index);
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WallProperties;
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const DefaultAction:React.FC = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DefaultAction;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
|
||||
interface DelayActionProps {
|
||||
value: string;
|
||||
defaultValue: string;
|
||||
min: number;
|
||||
max: number;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const DelayAction: React.FC<DelayActionProps> = ({
|
||||
value,
|
||||
defaultValue,
|
||||
min,
|
||||
max,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<InputWithDropDown
|
||||
label="Delay"
|
||||
value={value}
|
||||
min={min}
|
||||
step={0.1}
|
||||
defaultValue={defaultValue}
|
||||
max={max}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DelayAction;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
|
||||
const DespawnAction: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DespawnAction;
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
interface PickAndPlaceActionProps {
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const PickAndPlaceAction: React.FC<PickAndPlaceActionProps> = ({
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="pick-and-place-action-clear-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PickAndPlaceAction;
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import SwapAction from "./SwapAction";
|
||||
|
||||
interface ProcessActionProps {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
swapOptions: string[];
|
||||
swapDefaultOption: string;
|
||||
onSwapSelect: (value: string) => void;
|
||||
}
|
||||
|
||||
const ProcessAction: React.FC<ProcessActionProps> = ({
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
defaultValue,
|
||||
onChange,
|
||||
swapOptions,
|
||||
swapDefaultOption,
|
||||
onSwapSelect,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Process Time"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SwapAction
|
||||
options={swapOptions}
|
||||
defaultOption={swapDefaultOption}
|
||||
onSelect={onSwapSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessAction;
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface SpawnActionProps {
|
||||
onChangeInterval: (value: string) => void;
|
||||
onChangeCount: (value: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
intervalValue: string;
|
||||
countValue: string;
|
||||
intervalMin: number;
|
||||
intervalMax: number;
|
||||
intervalDefaultValue: string;
|
||||
countMin: number;
|
||||
countMax: number;
|
||||
countDefaultValue: string;
|
||||
}
|
||||
|
||||
const SpawnAction: React.FC<SpawnActionProps> = ({
|
||||
onChangeInterval,
|
||||
onChangeCount,
|
||||
defaultOption,
|
||||
options,
|
||||
onSelect,
|
||||
intervalValue,
|
||||
countValue,
|
||||
intervalMin,
|
||||
intervalMax,
|
||||
intervalDefaultValue,
|
||||
countMin,
|
||||
countMax,
|
||||
countDefaultValue,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn interval"
|
||||
value={intervalValue}
|
||||
min={intervalMin}
|
||||
step={1}
|
||||
defaultValue={intervalDefaultValue}
|
||||
max={intervalMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeInterval}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Spawn count"
|
||||
value={countValue}
|
||||
min={countMin}
|
||||
step={1}
|
||||
defaultValue={countDefaultValue}
|
||||
max={countMax}
|
||||
activeOption="s"
|
||||
onClick={() => { }}
|
||||
onChange={onChangeCount}
|
||||
/>
|
||||
{/* <PreviewSelectionWithUpload /> */}
|
||||
<LabledDropdown
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpawnAction;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
|
||||
interface StorageActionProps {
|
||||
type: "store" | "spawn" | "default";
|
||||
value: string;
|
||||
min: number;
|
||||
max?: number;
|
||||
defaultValue: string;
|
||||
currentMaterialType: string;
|
||||
handleCapacityChange: (value: string) => void;
|
||||
handleMaterialTypeChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const StorageAction: React.FC<StorageActionProps> = ({ type, value, min, max, defaultValue, currentMaterialType, handleCapacityChange, handleMaterialTypeChange }) => {
|
||||
return (
|
||||
<>
|
||||
{type === 'store' &&
|
||||
<InputWithDropDown
|
||||
label="Storage Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
}
|
||||
{type === 'spawn' &&
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Spawn Capacity"
|
||||
value={value}
|
||||
min={min}
|
||||
step={1}
|
||||
max={max}
|
||||
defaultValue={defaultValue}
|
||||
activeOption="unit"
|
||||
onClick={() => { }}
|
||||
onChange={handleCapacityChange}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label={"Material Type"}
|
||||
defaultOption={currentMaterialType}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
onSelect={handleMaterialTypeChange}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageAction;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload";
|
||||
|
||||
interface SwapActionProps {
|
||||
onSelect: (option: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
const SwapAction: React.FC<SwapActionProps> = ({
|
||||
onSelect,
|
||||
defaultOption,
|
||||
options,
|
||||
}) => {
|
||||
return (
|
||||
<PreviewSelectionWithUpload
|
||||
label="Presets"
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwapAction;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import EyeDropInput from "../../../../../ui/inputs/EyeDropInput";
|
||||
|
||||
interface TravelActionProps {
|
||||
loadCapacity: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
unloadDuration: {
|
||||
value: string;
|
||||
min: number;
|
||||
max: number;
|
||||
defaultValue: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
pickPoint?: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
unloadPoint?: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
clearPoints: () => void;
|
||||
}
|
||||
|
||||
const TravelAction: React.FC<TravelActionProps> = ({
|
||||
loadCapacity,
|
||||
unloadDuration,
|
||||
pickPoint,
|
||||
unloadPoint,
|
||||
clearPoints,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<InputWithDropDown
|
||||
label="Load Capacity"
|
||||
value={loadCapacity.value}
|
||||
min={loadCapacity.min}
|
||||
max={loadCapacity.max}
|
||||
defaultValue={loadCapacity.defaultValue}
|
||||
step={1}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={loadCapacity.onChange}
|
||||
/>
|
||||
<InputWithDropDown
|
||||
label="Unload Duration"
|
||||
value={unloadDuration.value}
|
||||
min={unloadDuration.min}
|
||||
max={unloadDuration.max}
|
||||
defaultValue={unloadDuration.defaultValue}
|
||||
step={0.1}
|
||||
activeOption="s"
|
||||
onClick={() => {}}
|
||||
onChange={unloadDuration.onChange}
|
||||
/>
|
||||
<div className="selected-actions-list">
|
||||
<div className="value-field-container">
|
||||
<div className="label">Reset</div>
|
||||
<button
|
||||
id="rest-button"
|
||||
type="button"
|
||||
className="regularDropdown-container"
|
||||
onClick={() => {
|
||||
clearPoints();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{pickPoint && (
|
||||
<EyeDropInput
|
||||
label="Pick Point"
|
||||
value={pickPoint.value}
|
||||
onChange={pickPoint.onChange}
|
||||
/>
|
||||
)}
|
||||
{unloadPoint && (
|
||||
<EyeDropInput
|
||||
label="Unload Point"
|
||||
value={unloadPoint.value}
|
||||
onChange={unloadPoint.onChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TravelAction;
|
||||
@@ -0,0 +1,160 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
} from "../../../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
||||
import {
|
||||
useSelectedAction,
|
||||
} from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
interface ActionsListProps {
|
||||
selectedPointData: any;
|
||||
multipleAction?: boolean;
|
||||
handleAddAction?: () => void;
|
||||
handleDeleteAction?: (actionUuid: string) => void;
|
||||
}
|
||||
|
||||
const ActionsList: React.FC<ActionsListProps> = ({
|
||||
selectedPointData,
|
||||
multipleAction = false,
|
||||
handleAddAction,
|
||||
handleDeleteAction,
|
||||
}) => {
|
||||
const actionsContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// store
|
||||
const { renameAction } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { selectedAction, setSelectedAction } = useSelectedAction();
|
||||
const { projectId } = useParams();
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedAction.actionId) return;
|
||||
const event = renameAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedAction.actionId,
|
||||
newName
|
||||
);
|
||||
setSelectedAction(selectedAction.actionId, newName);
|
||||
if (event) {
|
||||
upsertProductOrEventApi({
|
||||
productName: selectedProduct.productName,
|
||||
productUuid: selectedProduct.productUuid,
|
||||
projectId,
|
||||
eventDatas: event,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionSelect = (actionUuid: string, actionName: string) => {
|
||||
setSelectedAction(actionUuid, actionName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="actions-list-container">
|
||||
<div className="actions">
|
||||
<div className="header">
|
||||
<div className="header-value">Actions</div>
|
||||
|
||||
<button
|
||||
id="add-action-button"
|
||||
className="add-button"
|
||||
onClick={() => {
|
||||
if (handleAddAction) {
|
||||
handleAddAction();
|
||||
}
|
||||
}}
|
||||
disabled={!multipleAction}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={actionsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{multipleAction &&
|
||||
selectedPointData?.actions?.map((action: any) => (
|
||||
<div
|
||||
key={action.actionUuid}
|
||||
className={`list-item ${selectedAction.actionId === action.actionUuid
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
id="action-button"
|
||||
className="value"
|
||||
onClick={() =>
|
||||
handleActionSelect(action.actionUuid, action.actionName)
|
||||
}
|
||||
>
|
||||
<RenameInput
|
||||
value={action.actionName}
|
||||
onRename={(value) => handleRenameAction(value)}
|
||||
/>
|
||||
</button>
|
||||
{selectedPointData?.actions?.length > 1 && (
|
||||
<button
|
||||
id="remove-action-button"
|
||||
className="remove-button"
|
||||
onClick={() => {
|
||||
if (handleDeleteAction) {
|
||||
handleDeleteAction(action.actionUuid);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!multipleAction && selectedPointData?.action && (
|
||||
<div
|
||||
key={selectedPointData.action.actionUuid}
|
||||
className={`list-item active`}
|
||||
>
|
||||
<button
|
||||
id="action-button"
|
||||
className="value"
|
||||
onClick={() =>
|
||||
handleActionSelect(
|
||||
selectedPointData.action.actionUuid,
|
||||
selectedPointData.action.actionName
|
||||
)
|
||||
}
|
||||
>
|
||||
<RenameInput
|
||||
value={selectedPointData.action.actionName}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{multipleAction && (
|
||||
<button
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e: any) => handleResize(e, actionsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsList;
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleActionToggle(uuid: string) {
|
||||
// This function handles the action toggle for the event properties.
|
||||
// It updates the selected action and its properties based on the provided UUID.
|
||||
// The function is currently empty and needs to be implemented.
|
||||
// You can add your logic here to handle the action toggle.
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function handleDeleteAction(uuid: string) {
|
||||
// This function handles the action toggle for the event properties.
|
||||
// It updates the selected action and its properties based on the provided UUID.
|
||||
// The function is currently empty and needs to be implemented.
|
||||
// You can add your logic here to handle the action toggle.
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import DelayAction from "../actions/DelayAction";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import DespawnAction from "../actions/DespawnAction";
|
||||
import SwapAction from "../actions/SwapAction";
|
||||
import SpawnAction from "../actions/SpawnAction";
|
||||
import DefaultAction from "../actions/DefaultAction";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function ConveyorMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "spawn" | "swap" | "delay" | "despawn">("default");
|
||||
const [selectedPointData, setSelectedPointData] = useState<ConveyorPointSchema | undefined>();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData?.data.modelUuid,
|
||||
selectedEventData?.selectedPoint
|
||||
) as ConveyorPointSchema | undefined;
|
||||
if (point && "action" in point) {
|
||||
setSelectedPointData(point);
|
||||
setActiveOption(point.action.actionType as | "default" | "spawn" | "swap" | "delay" | "despawn");
|
||||
setSelectedAction(point.action.actionUuid, point.action.actionName);
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedProduct, selectedEventData]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
const handleSpeedChange = (value: string) => {
|
||||
if (!selectedEventData) return;
|
||||
const event = updateEvent(selectedProduct.productUuid, selectedEventData.data.modelUuid, {
|
||||
speed: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionTypeChange = (option: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn";
|
||||
setActiveOption(validOption);
|
||||
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSpawnCountChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
spawnCount: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSpawnIntervalChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
spawnInterval: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMaterialSelect = (material: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { material });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelayChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
delay: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId ||'',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "default",
|
||||
options: ["default", "spawn", "swap", "delay", "despawn"],
|
||||
};
|
||||
|
||||
// Get current values from store
|
||||
const currentSpeed = (getEventByModelUuid(
|
||||
selectedProduct.productUuid, selectedEventData?.data.modelUuid || ""
|
||||
) as ConveyorEventSchema | undefined)?.speed?.toString() || "0.5";
|
||||
|
||||
const currentActionName = selectedPointData
|
||||
? selectedPointData.action.actionName
|
||||
: "Action Name";
|
||||
|
||||
const currentMaterial = selectedPointData
|
||||
? selectedPointData.action.material
|
||||
: "Default material";
|
||||
|
||||
const currentSpawnCount = selectedPointData
|
||||
? selectedPointData.action.spawnCount?.toString() || "1"
|
||||
: "1";
|
||||
|
||||
const currentSpawnInterval = selectedPointData
|
||||
? selectedPointData.action.spawnInterval?.toString() || "1"
|
||||
: "1";
|
||||
|
||||
const currentDelay = selectedPointData
|
||||
? selectedPointData.action.delay?.toString() || "0"
|
||||
: "0";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div key={selectedPointData?.uuid} className="global-props section">
|
||||
<div className="property-list-container">
|
||||
<div className="property-item">
|
||||
<InputWithDropDown
|
||||
label="Speed"
|
||||
value={currentSpeed}
|
||||
min={0}
|
||||
step={0.1}
|
||||
defaultValue={"0.5"}
|
||||
max={10}
|
||||
activeOption="m/s"
|
||||
onClick={() => { }}
|
||||
onChange={handleSpeedChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<ActionsList
|
||||
selectedPointData={selectedPointData}
|
||||
/>
|
||||
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={currentActionName}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption={
|
||||
selectedPointData
|
||||
? selectedPointData.action.actionType
|
||||
: "default"
|
||||
}
|
||||
options={availableActions.options}
|
||||
onSelect={handleActionTypeChange}
|
||||
/>
|
||||
{activeOption === "default" && <DefaultAction />}
|
||||
{activeOption === "spawn" && (
|
||||
<SpawnAction
|
||||
onChangeCount={handleSpawnCountChange}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
defaultOption={currentMaterial}
|
||||
onSelect={handleMaterialSelect}
|
||||
onChangeInterval={handleSpawnIntervalChange}
|
||||
intervalValue={currentSpawnInterval}
|
||||
countValue={currentSpawnCount}
|
||||
intervalMin={1}
|
||||
intervalMax={60}
|
||||
intervalDefaultValue="1"
|
||||
countMin={1}
|
||||
countMax={100}
|
||||
countDefaultValue="1"
|
||||
/>
|
||||
)}
|
||||
{activeOption === "swap" && (
|
||||
<SwapAction
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
defaultOption={currentMaterial}
|
||||
onSelect={handleMaterialSelect}
|
||||
/>
|
||||
)}
|
||||
{activeOption === "despawn" && <DespawnAction />}
|
||||
{activeOption === "delay" && (
|
||||
<DelayAction
|
||||
value={currentDelay}
|
||||
defaultValue="0"
|
||||
min={0}
|
||||
max={60}
|
||||
onChange={handleDelayChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger selectedPointData={selectedPointData as any} type={'Conveyor'} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConveyorMechanics;
|
||||
@@ -0,0 +1,181 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import ProcessAction from "../actions/ProcessAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function MachineMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "process">("default");
|
||||
const [selectedPointData, setSelectedPointData] = useState<MachinePointSchema | undefined>();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getPointByUuid, updateAction } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData?.data.modelUuid,
|
||||
selectedEventData?.selectedPoint
|
||||
) as MachinePointSchema | undefined;
|
||||
if (point && "action" in point) {
|
||||
setSelectedPointData(point);
|
||||
setActiveOption(point.action.actionType as "process");
|
||||
setSelectedAction(point.action.actionUuid, point.action.actionName);
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedProduct, selectedEventData]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
const handleActionTypeChange = (option: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const validOption = option as "process";
|
||||
setActiveOption(validOption);
|
||||
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
actionType: validOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProcessTimeChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
processTime: parseFloat(value),
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMaterialSelect = (material: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
swapMaterial: material,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Get current values from store
|
||||
const currentActionName = selectedPointData
|
||||
? selectedPointData.action.actionName
|
||||
: "Action Name";
|
||||
|
||||
const currentProcessTime = selectedPointData
|
||||
? selectedPointData.action.processTime.toString()
|
||||
: "1";
|
||||
|
||||
const currentMaterial = selectedPointData
|
||||
? selectedPointData.action.swapMaterial
|
||||
: "Default material";
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "process",
|
||||
options: ["process"],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedEventData && (
|
||||
<section>
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={currentActionName}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</div>
|
||||
<ActionsList
|
||||
selectedPointData={selectedPointData}
|
||||
/>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption="process"
|
||||
options={availableActions.options}
|
||||
onSelect={handleActionTypeChange}
|
||||
/>
|
||||
{activeOption === "process" && (
|
||||
<ProcessAction
|
||||
value={currentProcessTime}
|
||||
min={0.1}
|
||||
max={60}
|
||||
defaultValue="1"
|
||||
onChange={handleProcessTimeChange}
|
||||
swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
swapDefaultOption={currentMaterial}
|
||||
onSwapSelect={handleMaterialSelect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger selectedPointData={selectedPointData as any} type={'Machine'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MachineMechanics;
|
||||
@@ -0,0 +1,276 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { MathUtils } from "three";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import PickAndPlaceAction from "../actions/PickAndPlaceAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function RoboticArmMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">("default");
|
||||
const [selectedPointData, setSelectedPointData] = useState<RoboticArmPointSchema | undefined>();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction, } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
selectedEventData.selectedPoint
|
||||
) as RoboticArmPointSchema | undefined;
|
||||
if (point?.actions) {
|
||||
setSelectedPointData(point);
|
||||
if (point.actions.length > 0) {
|
||||
setActiveOption(
|
||||
point.actions[0].actionType as "default" | "pickAndPlace"
|
||||
);
|
||||
setSelectedAction(
|
||||
point.actions[0].actionUuid,
|
||||
point.actions[0].actionName
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedEventData, selectedProduct]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedAction.actionId) return;
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedAction.actionId,
|
||||
{ actionName: newName }
|
||||
);
|
||||
|
||||
if (selectedPointData) {
|
||||
const updatedActions = selectedPointData.actions.map((action) =>
|
||||
action.actionUuid === selectedAction.actionId
|
||||
? { ...action, actionName: newName }
|
||||
: action
|
||||
);
|
||||
setSelectedPointData({
|
||||
...selectedPointData,
|
||||
actions: updatedActions,
|
||||
});
|
||||
}
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSpeedChange = (value: string) => {
|
||||
if (!selectedEventData) return;
|
||||
const event = updateEvent(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
{ speed: parseFloat(value), }
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleClearPoints = () => {
|
||||
if (!selectedAction.actionId || !selectedPointData) return;
|
||||
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedAction.actionId,
|
||||
{
|
||||
process: {
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAction = () => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
|
||||
const newAction = {
|
||||
actionUuid: MathUtils.generateUUID(),
|
||||
actionName: `Action ${selectedPointData.actions.length + 1}`,
|
||||
actionType: "pickAndPlace" as const,
|
||||
process: {
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
},
|
||||
triggers: [] as TriggerSchema[],
|
||||
};
|
||||
|
||||
const event = addAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
selectedEventData.selectedPoint,
|
||||
newAction
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction], };
|
||||
setSelectedPointData(updatedPoint);
|
||||
setSelectedAction(newAction.actionUuid, newAction.actionName);
|
||||
};
|
||||
|
||||
const handleDeleteAction = (actionUuid: string) => {
|
||||
if (!selectedPointData) return;
|
||||
|
||||
const event = removeAction(selectedProduct.productUuid, actionUuid);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid);
|
||||
const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid);
|
||||
|
||||
const updatedPoint = {
|
||||
...selectedPointData,
|
||||
actions: newActions,
|
||||
};
|
||||
setSelectedPointData(updatedPoint);
|
||||
|
||||
if (selectedAction.actionId === actionUuid) {
|
||||
const nextAction = newActions[index] || newActions[index - 1];
|
||||
if (nextAction) {
|
||||
setSelectedAction(nextAction.actionUuid, nextAction.actionName);
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "pickAndPlace",
|
||||
options: ["pickAndPlace"],
|
||||
};
|
||||
|
||||
const currentSpeed = (getEventByModelUuid(selectedProduct.productUuid, selectedEventData?.data.modelUuid || "") as RoboticArmEventSchema | undefined)?.speed?.toString() || "0.5";
|
||||
|
||||
const currentAction = selectedPointData?.actions.find((a) => a.actionUuid === selectedAction.actionId);
|
||||
|
||||
const currentPickPoint = currentAction?.process.startPoint
|
||||
? `${currentAction.process.startPoint[0]},${currentAction.process.startPoint[1]},${currentAction.process.startPoint[2]}`
|
||||
: "";
|
||||
const currentPlacePoint = currentAction?.process.endPoint
|
||||
? `${currentAction.process.endPoint[0]},${currentAction.process.endPoint[1]},${currentAction.process.endPoint[2]}`
|
||||
: "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="global-props section">
|
||||
<div className="property-list-container">
|
||||
<div className="property-item">
|
||||
<InputWithDropDown
|
||||
label="Speed"
|
||||
value={currentSpeed}
|
||||
min={0}
|
||||
step={0.1}
|
||||
defaultValue={"0.5"}
|
||||
max={10}
|
||||
activeOption="m/s"
|
||||
onClick={() => { }}
|
||||
onChange={handleSpeedChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<ActionsList
|
||||
selectedPointData={selectedPointData}
|
||||
multipleAction
|
||||
handleAddAction={handleAddAction}
|
||||
handleDeleteAction={handleDeleteAction}
|
||||
/>
|
||||
|
||||
{selectedAction.actionId && currentAction && (
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={selectedAction.actionName || ""}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption={activeOption}
|
||||
options={availableActions.options}
|
||||
onSelect={() => { }}
|
||||
disabled={true}
|
||||
/>
|
||||
<PickAndPlaceAction clearPoints={handleClearPoints} />
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger
|
||||
selectedPointData={selectedPointData as any}
|
||||
type={"RoboticArm"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RoboticArmMechanics;
|
||||
@@ -0,0 +1,220 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import StorageAction from "../actions/StorageAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import { useSelectedAction, useSelectedEventData } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import * as THREE from 'three';
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function StorageMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default");
|
||||
const [selectedPointData, setSelectedPointData] = useState<StoragePointSchema | undefined>();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getPointByUuid, updateAction } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
const updateSelectedPointData = () => {
|
||||
if (selectedEventData && selectedProduct) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData?.data.modelUuid,
|
||||
selectedEventData?.selectedPoint
|
||||
) as StoragePointSchema | undefined;
|
||||
if (point && "action" in point) {
|
||||
setSelectedPointData(point);
|
||||
const uiOption = point.action.actionType === "retrieve" ? "spawn" : point.action.actionType;
|
||||
setActiveOption(uiOption as "store" | "spawn");
|
||||
setSelectedAction(point.action.actionUuid, point.action.actionName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData) {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData?.data.modelUuid,
|
||||
selectedEventData?.selectedPoint
|
||||
) as StoragePointSchema | undefined;
|
||||
if (point && "action" in point) {
|
||||
setSelectedPointData(point);
|
||||
const uiOption = point.action.actionType === "retrieve" ? "spawn" : point.action.actionType;
|
||||
setActiveOption(uiOption as "store" | "spawn");
|
||||
setSelectedAction(point.action.actionUuid, point.action.actionName);
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedProduct, selectedEventData]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
const handleActionTypeChange = (option: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const internalOption = actionTypeMap[option as keyof typeof actionTypeMap] as "store" | "retrieve";
|
||||
|
||||
setActiveOption(option as "store" | "spawn");
|
||||
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
actionType: internalOption,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
updateSelectedPointData();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, { actionName: newName });
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
updateSelectedPointData();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCapacityChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const newCapacity = parseInt(value);
|
||||
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
storageCapacity: newCapacity,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
updateSelectedPointData();
|
||||
}
|
||||
};
|
||||
|
||||
const createNewMaterial = (materialType: string): { materialType: string; materialId: string } | null => {
|
||||
if (!selectedEventData || !selectedPointData) return null;
|
||||
const materialId = THREE.MathUtils.generateUUID();
|
||||
return {
|
||||
materialType,
|
||||
materialId
|
||||
};
|
||||
};
|
||||
|
||||
const handleMaterialTypeChange = (value: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
|
||||
const event = updateAction(selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
materialType: value,
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
updateSelectedPointData();
|
||||
}
|
||||
};
|
||||
|
||||
const currentActionName = useMemo(() =>
|
||||
selectedPointData ? selectedPointData.action.actionName : "Action Name",
|
||||
[selectedPointData]
|
||||
);
|
||||
|
||||
const currentCapacity = useMemo(() =>
|
||||
selectedPointData ? selectedPointData.action.storageCapacity.toString() : "0",
|
||||
[selectedPointData]
|
||||
);
|
||||
|
||||
const currentMaterialType = useMemo(() =>
|
||||
selectedPointData?.action.materialType || "Default material",
|
||||
[selectedPointData]
|
||||
);
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "store",
|
||||
options: ["store", "spawn"],
|
||||
};
|
||||
|
||||
const actionTypeMap = {
|
||||
spawn: "retrieve",
|
||||
store: "store"
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedEventData && (
|
||||
<section>
|
||||
<ActionsList
|
||||
selectedPointData={selectedPointData}
|
||||
/>
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={currentActionName}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption={activeOption}
|
||||
options={availableActions.options}
|
||||
onSelect={handleActionTypeChange}
|
||||
/>
|
||||
<StorageAction
|
||||
type={activeOption}
|
||||
value={currentCapacity}
|
||||
defaultValue="0"
|
||||
min={0}
|
||||
currentMaterialType={currentMaterialType}
|
||||
handleCapacityChange={handleCapacityChange}
|
||||
handleMaterialTypeChange={handleMaterialTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger selectedPointData={selectedPointData as any} type={'StorageUnit'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default StorageMechanics;
|
||||
@@ -0,0 +1,296 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import Trigger from "../trigger/Trigger";
|
||||
import {
|
||||
useSelectedAction,
|
||||
useSelectedEventData,
|
||||
} from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import TravelAction from "../actions/TravelAction";
|
||||
import ActionsList from "../components/ActionsList";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function VehicleMechanics() {
|
||||
const [activeOption, setActiveOption] = useState<"default" | "travel">("default");
|
||||
const [selectedPointData, setSelectedPointData] = useState<VehiclePointSchema | undefined>();
|
||||
const { selectedEventData } = useSelectedEventData();
|
||||
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedEventData && selectedEventData.data.type === "vehicle") {
|
||||
const point = getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
selectedEventData.selectedPoint
|
||||
) as VehiclePointSchema | undefined;
|
||||
|
||||
if (point) {
|
||||
setSelectedPointData(point);
|
||||
setActiveOption(point.action.actionType as "travel");
|
||||
setSelectedAction(point.action.actionUuid, point.action.actionName);
|
||||
}
|
||||
} else {
|
||||
clearSelectedAction();
|
||||
}
|
||||
}, [selectedProduct, selectedEventData]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSpeedChange = (value: string) => {
|
||||
if (!selectedEventData) return;
|
||||
const event = updateEvent(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData.data.modelUuid,
|
||||
{
|
||||
speed: parseFloat(value),
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionTypeChange = (option: string) => {
|
||||
if (!selectedEventData || !selectedPointData) return;
|
||||
const validOption = option as "travel";
|
||||
setActiveOption(validOption);
|
||||
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedPointData.action.actionUuid,
|
||||
{
|
||||
actionType: validOption,
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRenameAction = (newName: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedPointData.action.actionUuid,
|
||||
{ actionName: newName }
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadCapacityChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedPointData.action.actionUuid,
|
||||
{
|
||||
loadCapacity: parseFloat(value),
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnloadDurationChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
selectedPointData.action.actionUuid,
|
||||
{
|
||||
unLoadDuration: parseFloat(value),
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePickPointChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
};
|
||||
|
||||
const handleUnloadPointChange = (value: string) => {
|
||||
if (!selectedPointData) return;
|
||||
};
|
||||
|
||||
// Get current values from store
|
||||
|
||||
const currentSpeed =
|
||||
(
|
||||
getEventByModelUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedEventData?.data.modelUuid || ""
|
||||
) as VehicleEventSchema | undefined
|
||||
)?.speed?.toString() || "0.5";
|
||||
|
||||
const currentActionName = selectedPointData
|
||||
? selectedPointData.action.actionName
|
||||
: "Action Name";
|
||||
|
||||
const currentLoadCapacity = selectedPointData
|
||||
? selectedPointData.action.loadCapacity.toString()
|
||||
: "1";
|
||||
|
||||
const currentUnloadDuration = selectedPointData
|
||||
? selectedPointData.action.unLoadDuration.toString()
|
||||
: "1";
|
||||
|
||||
function handleClearPoints() {
|
||||
|
||||
if (!selectedEventData || !selectedPointData?.action.actionUuid) return;
|
||||
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid, selectedPointData.action.actionUuid, {
|
||||
pickUpPoint: null,
|
||||
unLoadPoint: null,
|
||||
steeringAngle: 0,
|
||||
})
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const availableActions = {
|
||||
defaultOption: "travel",
|
||||
options: ["travel"],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedEventData && (
|
||||
<>
|
||||
<div className="global-props section">
|
||||
<div className="property-list-container">
|
||||
<div className="property-item">
|
||||
<InputWithDropDown
|
||||
label="Speed"
|
||||
value={currentSpeed}
|
||||
min={0}
|
||||
step={0.1}
|
||||
defaultValue={"0.5"}
|
||||
max={10}
|
||||
activeOption="m/s"
|
||||
onClick={() => { }}
|
||||
onChange={handleSpeedChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section>
|
||||
<ActionsList selectedPointData={selectedPointData} />
|
||||
<div className="selected-actions-details">
|
||||
<div className="selected-actions-header">
|
||||
<RenameInput
|
||||
value={currentActionName}
|
||||
onRename={handleRenameAction}
|
||||
/>
|
||||
</div>
|
||||
<div className="selected-actions-list">
|
||||
<LabledDropdown
|
||||
defaultOption="travel"
|
||||
options={availableActions.options}
|
||||
onSelect={handleActionTypeChange}
|
||||
/>
|
||||
|
||||
{activeOption === "travel" && (
|
||||
<TravelAction
|
||||
loadCapacity={{
|
||||
value: currentLoadCapacity,
|
||||
min: 1,
|
||||
max: 100,
|
||||
defaultValue: "1",
|
||||
onChange: handleLoadCapacityChange,
|
||||
}}
|
||||
unloadDuration={{
|
||||
value: currentUnloadDuration,
|
||||
min: 1,
|
||||
max: 60,
|
||||
defaultValue: "1",
|
||||
onChange: handleUnloadDurationChange,
|
||||
}}
|
||||
clearPoints={handleClearPoints}
|
||||
// pickPoint={{
|
||||
// value: currentPickPoint,
|
||||
// onChange: handlePickPointChange,
|
||||
// }}
|
||||
// unloadPoint={{
|
||||
// value: currentUnloadPoint,
|
||||
// onChange: handleUnloadPointChange,
|
||||
// }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger
|
||||
selectedPointData={selectedPointData as any}
|
||||
type={"Vehicle"}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default VehicleMechanics;
|
||||
@@ -0,0 +1,442 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { AddIcon, RemoveIcon, ResizeHeightIcon } from "../../../../../icons/ExportCommonIcons";
|
||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import { useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
type TriggerProps = {
|
||||
selectedPointData?: PointsScheme | undefined;
|
||||
type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit";
|
||||
};
|
||||
|
||||
const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||
const [currentAction, setCurrentAction] = useState<string | undefined>();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { getActionByUuid, getEventByModelUuid, getPointByUuid, getTriggerByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById, } = useProductStore();
|
||||
const [triggers, setTriggers] = useState<TriggerSchema[]>([]);
|
||||
const [selectedTrigger, setSelectedTrigger] = useState<TriggerSchema | undefined>();
|
||||
const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete");
|
||||
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { selectedAction } = useSelectedAction();
|
||||
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedPointData || !selectedProduct) return;
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
|
||||
if (type === "Conveyor" || type === "Vehicle" || type === "Machine" || type === "StorageUnit") {
|
||||
actionUuid = (selectedPointData as | ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
||||
} else if (type === "RoboticArm" && selectedAction.actionId) {
|
||||
actionUuid = selectedAction.actionId;
|
||||
}
|
||||
|
||||
setCurrentAction(actionUuid);
|
||||
}, [selectedPointData, selectedProduct, type, selectedAction]);
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAction || !selectedProduct) return;
|
||||
const action = getActionByUuid(selectedProduct.productUuid, currentAction);
|
||||
const actionTriggers = action?.triggers || [];
|
||||
setTriggers(actionTriggers);
|
||||
setSelectedTrigger(actionTriggers[0]);
|
||||
}, [currentAction, selectedProduct]);
|
||||
|
||||
const triggeredModel = useMemo(() => {
|
||||
if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredModel?.modelUuid)
|
||||
return undefined;
|
||||
return getEventByModelUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedTrigger.triggeredAsset.triggeredModel.modelUuid
|
||||
);
|
||||
}, [selectedProduct, selectedTrigger]);
|
||||
|
||||
const triggeredPoint = useMemo(() => {
|
||||
if (!selectedProduct || !triggeredModel || !selectedTrigger?.triggeredAsset?.triggeredPoint?.pointUuid)
|
||||
return undefined;
|
||||
return getPointByUuid(
|
||||
selectedProduct.productUuid,
|
||||
triggeredModel.modelUuid,
|
||||
selectedTrigger.triggeredAsset.triggeredPoint.pointUuid
|
||||
);
|
||||
}, [selectedProduct, triggeredModel, selectedTrigger]);
|
||||
|
||||
const triggeredAction = useMemo(() => {
|
||||
if (!selectedProduct || !selectedTrigger?.triggeredAsset?.triggeredAction?.actionUuid)
|
||||
return undefined;
|
||||
return getActionByUuid(
|
||||
selectedProduct.productUuid,
|
||||
selectedTrigger.triggeredAsset.triggeredAction.actionUuid
|
||||
);
|
||||
}, [selectedProduct, selectedTrigger]);
|
||||
|
||||
const modelOptions = getProductById(selectedProduct.productUuid)?.eventDatas || [];
|
||||
|
||||
const pointOptions: PointsScheme[] = useMemo(() => {
|
||||
if (!triggeredModel) return [];
|
||||
|
||||
const model = modelOptions.find((m) => m.modelUuid === triggeredModel.modelUuid);
|
||||
if (!model) return [];
|
||||
|
||||
if ("points" in model) {
|
||||
return (model).points;
|
||||
} else if ("point" in model) {
|
||||
return [model.point];
|
||||
}
|
||||
return [];
|
||||
}, [triggeredModel, modelOptions]);
|
||||
|
||||
const actionOptions: any = useMemo(() => {
|
||||
if (!triggeredPoint) return [];
|
||||
const point = pointOptions.find((p) => p.uuid === triggeredPoint.uuid);
|
||||
if (!point) return [];
|
||||
|
||||
if ("action" in point) {
|
||||
const typedPoint = point;
|
||||
return typedPoint.action ? [typedPoint.action] : [];
|
||||
} else if ("actions" in point) {
|
||||
const typedPoint = point;
|
||||
return typedPoint.actions;
|
||||
}
|
||||
return [];
|
||||
}, [triggeredPoint, pointOptions]);
|
||||
|
||||
const handleModelSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct) return;
|
||||
|
||||
const selectedModel = modelOptions.find((m) => m.modelName === option);
|
||||
if (!selectedModel) return;
|
||||
|
||||
const event = updateTrigger(selectedProduct.productUuid, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: selectedModel.modelName,
|
||||
modelUuid: selectedModel.modelUuid,
|
||||
},
|
||||
triggeredPoint: null,
|
||||
triggeredAction: null,
|
||||
},
|
||||
});
|
||||
|
||||
if (event) {
|
||||
const updatedTrigger = getTriggerByUuid(
|
||||
selectedProduct.productUuid,
|
||||
triggerUuid
|
||||
);
|
||||
setSelectedTrigger(updatedTrigger);
|
||||
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct || !selectedTrigger) return;
|
||||
|
||||
const pointUuid = pointOptions.find((p) => `Point ${p.uuid.slice(0, 4)}` === option)?.uuid;
|
||||
if (!pointUuid) return;
|
||||
|
||||
if (selectedTrigger.triggeredAsset?.triggeredModel) {
|
||||
const event = updateTrigger(selectedProduct.productUuid, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
...selectedTrigger.triggeredAsset,
|
||||
triggeredPoint: {
|
||||
pointName: option,
|
||||
pointUuid: pointUuid,
|
||||
},
|
||||
triggeredAction: null,
|
||||
},
|
||||
});
|
||||
|
||||
if (event) {
|
||||
const updatedTrigger = getTriggerByUuid(
|
||||
selectedProduct.productUuid,
|
||||
triggerUuid
|
||||
);
|
||||
setSelectedTrigger(updatedTrigger);
|
||||
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct || !selectedTrigger) return;
|
||||
|
||||
const selectedAction = actionOptions.find((a: any) => a.actionName === option);
|
||||
|
||||
if (!selectedAction) return;
|
||||
|
||||
if (selectedTrigger.triggeredAsset?.triggeredPoint) {
|
||||
const event = updateTrigger(selectedProduct.productUuid, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
...selectedTrigger.triggeredAsset,
|
||||
triggeredAction: {
|
||||
actionName: option,
|
||||
actionUuid: selectedAction.actionUuid,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddTrigger = () => {
|
||||
if (!selectedProduct || !currentAction) return;
|
||||
|
||||
const newTrigger: TriggerSchema = {
|
||||
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
triggerName: `New Trigger ${triggers.length + 1}`,
|
||||
triggerType: activeOption,
|
||||
delay: 0,
|
||||
triggeredAsset: null,
|
||||
};
|
||||
|
||||
const event = addTrigger(
|
||||
selectedProduct.productUuid,
|
||||
currentAction,
|
||||
newTrigger
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
const updatedAction = getActionByUuid(
|
||||
selectedProduct.productUuid,
|
||||
currentAction
|
||||
);
|
||||
const updatedTriggers = updatedAction?.triggers || [];
|
||||
|
||||
setTriggers(updatedTriggers);
|
||||
setSelectedTrigger(newTrigger);
|
||||
};
|
||||
|
||||
const handleRemoveTrigger = (triggerUuid: string) => {
|
||||
if (!selectedProduct || !currentAction) return;
|
||||
|
||||
const event = removeTrigger(selectedProduct.productUuid, triggerUuid);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
|
||||
const index = triggers.findIndex((t) => t.triggerUuid === triggerUuid);
|
||||
const newTriggers = triggers.filter((t) => t.triggerUuid !== triggerUuid);
|
||||
setTriggers(newTriggers);
|
||||
|
||||
if (selectedTrigger?.triggerUuid === triggerUuid) {
|
||||
const nextTrigger = newTriggers[index] || newTriggers[index - 1];
|
||||
setSelectedTrigger(nextTrigger);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTriggerRename = (triggerUuid: string, newName: string) => {
|
||||
if (!selectedProduct) return;
|
||||
const event = renameTrigger(
|
||||
selectedProduct.productUuid,
|
||||
triggerUuid,
|
||||
newName
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTriggerTypeChange = (option: string) => {
|
||||
if (!selectedTrigger || !selectedProduct) return;
|
||||
|
||||
const validTypes: Array<TriggerSchema["triggerType"]> = ["onComplete", "onStart", "onStop", "delay", "onError",];
|
||||
if (!validTypes.includes(option as TriggerSchema["triggerType"])) return;
|
||||
|
||||
setActiveOption(option as TriggerSchema["triggerType"]);
|
||||
const event = updateTrigger(
|
||||
selectedProduct.productUuid,
|
||||
selectedTrigger.triggerUuid,
|
||||
{
|
||||
triggerType: option as TriggerSchema["triggerType"],
|
||||
}
|
||||
);
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="trigger-wrapper">
|
||||
<div className="header">
|
||||
<div className="title">Trigger</div>
|
||||
<button
|
||||
id="add-trigger-button"
|
||||
className="add-button"
|
||||
onClick={handleAddTrigger}
|
||||
style={{ cursor: "pointer" }}
|
||||
disabled={!currentAction}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div className="trigger-list">
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={triggersContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{triggers.map((trigger) => (
|
||||
<div
|
||||
key={trigger.triggerUuid}
|
||||
className={`list-item ${selectedTrigger?.triggerUuid === trigger.triggerUuid
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => setSelectedTrigger(trigger)}
|
||||
>
|
||||
<button id="trigger" className="value">
|
||||
<RenameInput
|
||||
value={trigger.triggerName}
|
||||
onRename={(newName) =>
|
||||
handleTriggerRename(trigger.triggerUuid, newName)
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
{triggers.length > 1 && (
|
||||
<button
|
||||
id="remove-trigger-button"
|
||||
className="remove-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveTrigger(trigger.triggerUuid);
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e: any) => handleResize(e, triggersContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{selectedTrigger && (
|
||||
<div className="trigger-item">
|
||||
<div className="trigger-name">{selectedTrigger.triggerName}</div>
|
||||
<LabledDropdown
|
||||
label="Trigger Type"
|
||||
defaultOption={selectedTrigger.triggerType}
|
||||
// options={["onComplete", "onStart", "onStop", "delay", "onError"]}
|
||||
options={["onComplete"]}
|
||||
onSelect={handleTriggerTypeChange}
|
||||
/>
|
||||
|
||||
<div className="trigger-options">
|
||||
<LabledDropdown
|
||||
label="Triggered Object"
|
||||
defaultOption={triggeredModel?.modelName || ""}
|
||||
options={[...modelOptions.map((option) => option.modelName)]}
|
||||
onSelect={(option) => {
|
||||
handleModelSelect(option, selectedTrigger.triggerUuid);
|
||||
}}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Point"
|
||||
defaultOption={
|
||||
triggeredPoint?.uuid
|
||||
? `Point ${triggeredPoint?.uuid.slice(0, 4)}`
|
||||
: ""
|
||||
}
|
||||
options={[
|
||||
...pointOptions.map(
|
||||
(option) => `Point ${option.uuid.slice(0, 4)}`
|
||||
),
|
||||
]}
|
||||
onSelect={(option) => {
|
||||
handlePointSelect(option, selectedTrigger.triggerUuid);
|
||||
}}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Action"
|
||||
defaultOption={triggeredAction?.actionName || ""}
|
||||
options={[
|
||||
...actionOptions.map((option: any) => option.actionName),
|
||||
]}
|
||||
onSelect={(option) => {
|
||||
handleActionSelect(option, selectedTrigger.triggerUuid);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Trigger;
|
||||
@@ -0,0 +1,278 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { AddIcon, ArrowIcon, RemoveIcon, ResizeHeightIcon, } from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../functions/handleResizePannel";
|
||||
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
import { generateUUID } from "three/src/math/MathUtils";
|
||||
import RenderOverlay from "../../../templates/Overlay";
|
||||
import EditWidgetOption from "../../../ui/menu/EditWidgetOption";
|
||||
import { handleAddEventToProduct } from "../../../../modules/simulation/events/points/functions/handleAddEventToProduct";
|
||||
import { useEventsStore } from "../../../../store/simulation/useEventsStore";
|
||||
import { deleteEventDataApi } from "../../../../services/simulation/products/deleteEventDataApi";
|
||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
import { deleteProductApi } from "../../../../services/simulation/products/deleteProductApi";
|
||||
import { renameProductApi } from "../../../../services/simulation/products/renameProductApi";
|
||||
import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences";
|
||||
import ComparePopUp from "../../../ui/compareVersion/Compare";
|
||||
import { useCompareStore, useSaveVersion, } from "../../../../store/builder/store";
|
||||
import { useToggleStore } from "../../../../store/useUIToggleStore";
|
||||
import { useProductContext } from "../../../../modules/simulation/products/productContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
interface Event {
|
||||
modelName: string;
|
||||
modelId: string;
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
val: Event;
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ val }) => {
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div className="value">{val.modelName}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Simulations: React.FC = () => {
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, getProductById, } = useProductStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct, setSelectedProduct } = selectedProductStore();
|
||||
const { getEventByModelUuid } = useEventsStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const [openObjects, setOpenObjects] = useState(true);
|
||||
const [processes, setProcesses] = useState<Event[][]>();
|
||||
const { setToggleUI } = useToggleStore();
|
||||
const { projectId } = useParams();
|
||||
|
||||
const { comparePopUp, setComparePopUp } = useCompareStore();
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
|
||||
const handleSaveVersion = () => {
|
||||
setIsVersionSaved(true);
|
||||
setComparePopUp(false);
|
||||
setToggleUI(false, false);
|
||||
};
|
||||
|
||||
const handleAddProduct = () => {
|
||||
const id = generateUUID();
|
||||
const name = `Product ${products.length + 1}`;
|
||||
addProduct(name, id);
|
||||
upsertProductOrEventApi({
|
||||
productName: name,
|
||||
productUuid: id,
|
||||
projectId: projectId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveProduct = (productUuid: string) => {
|
||||
const currentIndex = products.findIndex((p) => p.productUuid === productUuid);
|
||||
const isSelected = selectedProduct.productUuid === productUuid;
|
||||
|
||||
const updatedProducts = products.filter((p) => p.productUuid !== productUuid);
|
||||
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productUuid,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct("", "");
|
||||
}
|
||||
}
|
||||
|
||||
removeProduct(productUuid);
|
||||
deleteProductApi({
|
||||
productUuid,
|
||||
projectId
|
||||
});
|
||||
};
|
||||
|
||||
const handleRenameProduct = (productUuid: string, newName: string) => {
|
||||
renameProduct(productUuid, newName);
|
||||
renameProductApi({ productName: newName, productUuid, projectId: projectId || '' });
|
||||
if (selectedProduct.productUuid === productUuid) {
|
||||
setSelectedProduct(productUuid, newName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveEventFromProduct = () => {
|
||||
if (selectedAsset) {
|
||||
|
||||
deleteEventDataApi({
|
||||
productUuid: selectedProduct.productUuid,
|
||||
modelUuid: selectedAsset.modelUuid,
|
||||
projectId: projectId,
|
||||
});
|
||||
removeEvent(selectedProduct.productUuid, selectedAsset.modelUuid);
|
||||
clearSelectedAsset();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const processes: Event[][] = [];
|
||||
const selectedProductData = getProductById(selectedProduct.productUuid);
|
||||
|
||||
if (selectedProductData) {
|
||||
determineExecutionMachineSequences([selectedProductData]).then(
|
||||
(sequences) => {
|
||||
sequences.forEach((sequence) => {
|
||||
const events: Event[] =
|
||||
sequence.map((event) => ({
|
||||
modelName: event.modelName,
|
||||
modelId: event.modelUuid,
|
||||
})) || [];
|
||||
processes.push(events);
|
||||
});
|
||||
setProcesses(processes);
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [selectedProduct.productUuid, products]);
|
||||
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
<div className="header">Simulations</div>
|
||||
<div className="add-product-container">
|
||||
<div className="actions section">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<button
|
||||
id="add-simulation"
|
||||
className="add-button"
|
||||
onClick={handleAddProduct}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={productsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.productUuid}
|
||||
className={`list-item ${selectedProduct.productUuid === product.productUuid
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="value"
|
||||
onClick={() =>
|
||||
setSelectedProduct(product.productUuid, product.productName)
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productUuid === product.productUuid}
|
||||
readOnly
|
||||
/>
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) =>
|
||||
handleRenameProduct(product.productUuid, newName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<button
|
||||
id="remove-product-button"
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveProduct(product.productUuid)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e: any) => handleResize(e, productsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-process section">
|
||||
<button
|
||||
id="collapse-header"
|
||||
className="collapse-header-container"
|
||||
onClick={() => setOpenObjects(!openObjects)}
|
||||
>
|
||||
<div className="header">Process Flow</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{openObjects &&
|
||||
processes?.map((process, index) => (
|
||||
<section key={index}>
|
||||
{process.map((event, index) => (
|
||||
<List key={`${index}-${event.modelName}`} val={event} />
|
||||
))}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click '<span>Compare</span>' to review and analyze the layout
|
||||
differences between them.
|
||||
</div>
|
||||
<button className="input" onClick={() => setComparePopUp(true)}>
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAsset && (
|
||||
<RenderOverlay>
|
||||
<EditWidgetOption
|
||||
options={["Add to Product", "Remove from Product"]}
|
||||
onClick={(option) => {
|
||||
if (option === "Add to Product") {
|
||||
handleAddEventToProduct({
|
||||
event: getEventByModelUuid(selectedAsset.modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset,
|
||||
projectId: projectId || ''
|
||||
});
|
||||
} else {
|
||||
handleRemoveEventFromProduct();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
|
||||
{comparePopUp && (
|
||||
<RenderOverlay>
|
||||
<ComparePopUp onClose={handleSaveVersion} />
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Simulations;
|
||||
@@ -0,0 +1,147 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
CloseIcon,
|
||||
KebabIcon,
|
||||
LocationIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
import { generateUniqueId } from "../../../../functions/generateUniqueId";
|
||||
|
||||
const VersionHistory = () => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
const { versions, addVersion, setVersions, updateVersion } =
|
||||
useVersionStore();
|
||||
const [selectedVersion, setSelectedVersion] = useState(
|
||||
versions.length > 0 ? versions[0] : null
|
||||
);
|
||||
|
||||
const addNewVersion = () => {
|
||||
const newVersion = {
|
||||
id: generateUniqueId(),
|
||||
versionLabel: `v${versions.length + 1}.0`,
|
||||
versionName: "",
|
||||
timestamp: new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
}),
|
||||
savedBy: userName,
|
||||
};
|
||||
|
||||
const newVersions = [newVersion, ...versions];
|
||||
addVersion(newVersion);
|
||||
setSelectedVersion(newVersion);
|
||||
setVersions(newVersions);
|
||||
};
|
||||
|
||||
const handleSelectVersion = (version: any) => {
|
||||
setSelectedVersion(version);
|
||||
const reordered = [version, ...versions.filter((v) => v.id !== version.id)];
|
||||
setVersions(reordered);
|
||||
};
|
||||
|
||||
const handleVersionNameChange = (newName: string, versionId: string) => {
|
||||
const updated = versions.map((v) =>
|
||||
v.id === versionId ? { ...v, versionName: newName } : v
|
||||
);
|
||||
setVersions(updated);
|
||||
updateVersion(versionId, { versionName: newName });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="version-history-container">
|
||||
{/* Header */}
|
||||
<div className="version-history-header">
|
||||
<div className="version-history-title">Version History</div>
|
||||
<div className="version-history-icons">
|
||||
<button
|
||||
id="add-version"
|
||||
className="icon add-icon"
|
||||
onClick={addNewVersion}
|
||||
>
|
||||
<AddIcon />
|
||||
</button>
|
||||
<div id="version-kebab" className="icon kebab-icon">
|
||||
<KebabIcon />
|
||||
</div>
|
||||
<div id="version-close" className="icon close-icon">
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Shortcut Info */}
|
||||
<div className="version-history-shortcut-info">
|
||||
<div className="info-icon">i</div>
|
||||
<div className="shortcut-text">
|
||||
Press Ctrl + Alt + S to add to version history while editing
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Current Version Display */}
|
||||
{selectedVersion && (
|
||||
<div className="version-history-location">
|
||||
<div className="location-label">
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="location-details">
|
||||
<div className="current-version">
|
||||
Current Version ({selectedVersion.versionLabel})
|
||||
</div>
|
||||
<div className="saved-history-count">
|
||||
{versions.length} Saved History
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Versions List */}
|
||||
<div className="saved-versions-list">
|
||||
{versions.length === 0 ? (
|
||||
<div className="no-versions-message">No saved versions</div>
|
||||
) : (
|
||||
versions.map((version) => (
|
||||
<button
|
||||
key={version.id}
|
||||
className="saved-version"
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionLabel}</div>
|
||||
<div className="version-details">
|
||||
<div className="details">
|
||||
<span className="timestamp">
|
||||
{version.versionName ? (
|
||||
<RenameInput
|
||||
value={version.versionName}
|
||||
onRename={(newName) =>
|
||||
handleVersionNameChange(newName, version.id)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<RenameInput
|
||||
value={version.timestamp}
|
||||
onRename={(newName) =>
|
||||
handleVersionNameChange(newName, version.id)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="saved-by">
|
||||
<div className="user-profile">{version.savedBy[0]}</div>
|
||||
<div className="user-name">{version.savedBy}</div>
|
||||
</span>
|
||||
</div>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionHistory;
|
||||
@@ -0,0 +1,207 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
import {
|
||||
CloseIcon,
|
||||
FinishEditIcon,
|
||||
RenameVersionIcon,
|
||||
SaveIcon,
|
||||
SaveVersionIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenderOverlay from "../../../templates/Overlay";
|
||||
|
||||
const VersionSaved = () => {
|
||||
const { versions, updateVersion } = useVersionStore();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [shouldDismiss, setShouldDismiss] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [newName, setNewName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [showEditedFinish, setShowEditedFinish] = useState(false);
|
||||
const [editedVersionName, setEditedVersionName] = useState("");
|
||||
const prevVersionCount = useRef(versions.length);
|
||||
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const latestVersion = versions?.[0];
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (versions.length > prevVersionCount.current && latestVersion) {
|
||||
setShowNotification(true);
|
||||
setShouldDismiss(false);
|
||||
setIsEditing(false);
|
||||
setNewName(latestVersion.versionName ?? "");
|
||||
setDescription(latestVersion.description ?? "");
|
||||
setEditedVersionName(latestVersion.versionName ?? ""); // Initialize editedVersionName
|
||||
|
||||
if (!isEditing) {
|
||||
startDismissTimer();
|
||||
}
|
||||
|
||||
prevVersionCount.current = versions.length;
|
||||
} else if (versions.length < prevVersionCount.current) {
|
||||
prevVersionCount.current = versions.length;
|
||||
}
|
||||
}, [versions, isEditing, latestVersion]);
|
||||
|
||||
const startDismissTimer = (delay = 5000) => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = setTimeout(() => {
|
||||
setShouldDismiss(true);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldDismiss) {
|
||||
const timer = setTimeout(() => setShowNotification(false), 200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [shouldDismiss]);
|
||||
|
||||
const handleEditName = () => {
|
||||
if (!latestVersion) return;
|
||||
|
||||
setIsEditing(true);
|
||||
setNewName(latestVersion.versionName ?? "");
|
||||
setDescription(latestVersion.description ?? "");
|
||||
if (dismissTimerRef.current) {
|
||||
clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinishEdit = () => {
|
||||
if (!latestVersion) return;
|
||||
|
||||
const updatedName =
|
||||
(newName.trim() || latestVersion.versionName) ?? latestVersion.timestamp;
|
||||
updateVersion(latestVersion.id, {
|
||||
versionName: updatedName,
|
||||
description,
|
||||
});
|
||||
|
||||
setEditedVersionName(updatedName);
|
||||
setIsEditing(false);
|
||||
setShowEditedFinish(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowEditedFinish(false);
|
||||
}, 5000);
|
||||
|
||||
startDismissTimer();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
startDismissTimer();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShouldDismiss(true);
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
|
||||
if (!showNotification || !latestVersion) return null;
|
||||
|
||||
return (
|
||||
<div className={`versionSaved ${shouldDismiss ? "dismissing" : ""}`}>
|
||||
{!isEditing && !showEditedFinish && (
|
||||
<div className="versionSaved-wrapper">
|
||||
<div className="version-header">
|
||||
<div className="header-wrapper">
|
||||
<div className="icon">
|
||||
<SaveIcon />
|
||||
</div>
|
||||
<span>Saved New Version</span>
|
||||
</div>
|
||||
<button className="close-btn" onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="version-details">
|
||||
<SaveVersionIcon />
|
||||
<div className="details">
|
||||
<div className="details-wrapper">
|
||||
New Version Created {latestVersion.versionLabel}{" "}
|
||||
{latestVersion.timestamp.toUpperCase()}
|
||||
</div>
|
||||
<button onClick={handleEditName}>Edit name</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isEditing && (
|
||||
<RenderOverlay>
|
||||
<div className="edit-version-popup-wrapper">
|
||||
<div className="details-wrapper-popup-container">
|
||||
<div className="header-wrapper">
|
||||
<RenameVersionIcon />
|
||||
<div className="label">Rename Version</div>
|
||||
</div>
|
||||
<div className="details-wrapper">
|
||||
<div className="version-name">
|
||||
<input
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
placeholder="Enter new version name"
|
||||
/>
|
||||
<div className="label">
|
||||
by @{latestVersion.savedBy}{" "}
|
||||
{new Date(latestVersion.timestamp).toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "2-digit",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="version-description">
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Add description"
|
||||
style={{ resize: "none" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-wrapper">
|
||||
<button className="cancel" onClick={handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="save" onClick={handleFinishEdit}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
|
||||
{showEditedFinish && (
|
||||
<RenderOverlay>
|
||||
<div className="finishEdit-version-popup-wrapper">
|
||||
<div className="finishEdit-wrapper-popup-container">
|
||||
<div className="icon">
|
||||
<FinishEditIcon />
|
||||
</div>
|
||||
<div className="versionname">
|
||||
{editedVersionName || latestVersion.versionName}
|
||||
</div>
|
||||
<div className="success-message">Saved Successfully!</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionSaved;
|
||||
@@ -0,0 +1,241 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const BarChartInput = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const { projectId } = useParams();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.log("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/data?widgetID=${selectedChartId.id}&zoneUuid=${selectedZone.zoneUuid}&projectId=${projectId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
// const userId = localStorage.getItem("userId");
|
||||
// let newWidget = {
|
||||
// id: selectedChartId.id,
|
||||
// panel: selectedChartId.panel,
|
||||
// widgetName: inputName,
|
||||
// Data: {
|
||||
// measurements: inputMeasurement,
|
||||
// duration: inputDuration,
|
||||
// }
|
||||
// }
|
||||
// const adding3dWidget = {
|
||||
// organization: organization,
|
||||
// widget: newWidget,
|
||||
// zoneUuid: selectedZone.zoneUuid,
|
||||
// projectId, userId
|
||||
// };
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v1:viz-3D-widget:add", adding3dWidget);
|
||||
// }
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/save`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
panel: selectedChartId.panel,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.title}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(3)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarChartInput;
|
||||
@@ -0,0 +1,205 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const FleetEfficiencyInputComponent = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setFlotingMeasurements, updateFlotingDuration, updateHeader } =
|
||||
useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const isSelected = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
setLoading(true);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.header);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setFlotingMeasurements(selections);
|
||||
updateFlotingDuration(duration);
|
||||
updateHeader(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/floatWidget/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
header: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.header}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(1)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
{/* <div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FleetEfficiencyInputComponent;
|
||||
@@ -0,0 +1,204 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const FlotingWidgetInput = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setFlotingMeasurements, updateFlotingDuration, updateHeader } =
|
||||
useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.header);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setFlotingMeasurements(selections);
|
||||
updateFlotingDuration(duration);
|
||||
updateHeader(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/floatWidget/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
header: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.header}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(6)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlotingWidgetInput;
|
||||
@@ -0,0 +1,209 @@
|
||||
import React from 'react'
|
||||
import LineGrapInput from './LineGrapInput'
|
||||
import BarChartInput from './BarChartInput'
|
||||
import PieChartInput from './PieChartInput'
|
||||
import FlotingWidgetInput from './FlotingWidgetInput'
|
||||
import FleetEfficiencyInputComponent from './FleetEfficiencyInputComponent'
|
||||
import Progress1Input from './Progress1Input'
|
||||
import Progress2Input from './Progress2Input'
|
||||
import Widget2InputCard3D from './Widget2InputCard3D'
|
||||
import Widget3InputCard3D from './Widget3InputCard3D'
|
||||
import Widget4InputCard3D from './Widget4InputCard3D'
|
||||
import WarehouseThroughputInputComponent from './WarehouseThroughputInputComponent'
|
||||
import { useWidgetStore } from '../../../../../store/useWidgetStore'
|
||||
|
||||
// const InputSelecterComponent = () => {
|
||||
// const { selectedChartId } = useWidgetStore();
|
||||
|
||||
// if (selectedChartId && selectedChartId.type && selectedChartId.type === 'bar' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <BarChartInput />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'line' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <LineGrapInput />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'pie' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <PieChartInput />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'doughnut' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <PieChartInput />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'polarArea' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <PieChartInput />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'progress 1' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <Progress1Input />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'progress 2' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">2D Widget Input</div>
|
||||
// <Progress2Input />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.className && selectedChartId.className === 'warehouseThroughput floating' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">Floting Widget Input</div>
|
||||
// <WarehouseThroughputInputComponent />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.className && selectedChartId.className === 'fleetEfficiency floating' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">Floting Widget Input</div>
|
||||
// <FleetEfficiencyInputComponent />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.className && selectedChartId.className === 'floating total-card' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">Floting Widget Input</div>
|
||||
// <FleetEfficiencyInputComponent />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'ui-Widget 1' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">3D Widget Input</div>
|
||||
// <Widget4InputCard3D />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'ui-Widget 2' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">3D Widget Input</div>
|
||||
// <Widget2InputCard3D />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'ui-Widget 3' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">3D Widget Input</div>
|
||||
// <Widget3InputCard3D />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else if (selectedChartId && selectedChartId.type && selectedChartId.type === 'ui-Widget 4' ) {
|
||||
// return (
|
||||
// <>
|
||||
// <div className="sideBarHeader">3D Widget Input</div>
|
||||
// <Widget4InputCard3D />
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// else {
|
||||
// return (
|
||||
// <div>No chart selected</div>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
const chartTypeMap: Record<| 'bar'| 'line'| 'pie' | 'doughnut' | 'polarArea'| 'progress 1' | 'progress 2'
|
||||
| 'ui-Widget 1'| 'ui-Widget 2'| 'ui-Widget 3'| 'ui-Widget 4',JSX.Element> = {
|
||||
bar: <BarChartInput />,
|
||||
line: <LineGrapInput />,
|
||||
pie: <PieChartInput />,
|
||||
doughnut: <PieChartInput />,
|
||||
polarArea: <PieChartInput />,
|
||||
'progress 1': <Progress1Input />,
|
||||
'progress 2': <Progress2Input />,
|
||||
'ui-Widget 1': <Widget4InputCard3D />,
|
||||
'ui-Widget 2': <Widget2InputCard3D />,
|
||||
'ui-Widget 3': <Widget3InputCard3D />,
|
||||
'ui-Widget 4': <Widget4InputCard3D />,
|
||||
};
|
||||
|
||||
const classNameMap: Record<
|
||||
| 'warehouseThroughput floating'
|
||||
| 'fleetEfficiency floating'
|
||||
| 'floating total-card',
|
||||
JSX.Element
|
||||
> = {
|
||||
'warehouseThroughput floating': <WarehouseThroughputInputComponent />,
|
||||
'fleetEfficiency floating': <FleetEfficiencyInputComponent />,
|
||||
'floating total-card': <FleetEfficiencyInputComponent />,
|
||||
};
|
||||
|
||||
const InputSelecterComponent = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
|
||||
if (selectedChartId) {
|
||||
const { type, className } = selectedChartId;
|
||||
|
||||
if (type && chartTypeMap[type as keyof typeof chartTypeMap]) {
|
||||
const label = ['ui-Widget 1', 'ui-Widget 2', 'ui-Widget 3', 'ui-Widget 4'].includes(type)
|
||||
? '3D Widget Input'
|
||||
: '2D Widget Input';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="sideBarHeader">{label}</div>
|
||||
{chartTypeMap[type as keyof typeof chartTypeMap]}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (className && classNameMap[className as keyof typeof classNameMap]) {
|
||||
return (
|
||||
<>
|
||||
<div className="sideBarHeader">Floting Widget Input</div>
|
||||
{classNameMap[className as keyof typeof classNameMap]}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <div>No chart selected</div>;
|
||||
};
|
||||
|
||||
export default InputSelecterComponent;
|
||||
@@ -0,0 +1,356 @@
|
||||
// import React, { useEffect, useState } from 'react'
|
||||
// import MultiLevelDropdown from '../../../../ui/inputs/MultiLevelDropDown'
|
||||
// import { AddIcon } from '../../../../icons/ExportCommonIcons'
|
||||
// import RegularDropDown from '../../../../ui/inputs/RegularDropDown'
|
||||
// import useChartStore from '../../../../../store/useChartStore'
|
||||
// import axios from 'axios'
|
||||
|
||||
// type Props = {}
|
||||
|
||||
// const LineGrapInput = (props: Props) => {
|
||||
// const [dropDowndata, setDropDownData] = useState({})
|
||||
// const [selections, setSelections] = useState<Record<string, { name: string, fields: string }>>({})
|
||||
// const [selectedOption, setSelectedOption] = useState('1h')
|
||||
// const { measurements, setMeasurements, updateDuration, duration } = useChartStore();
|
||||
// const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
|
||||
// const handleSelectDuration = (option: string) => {
|
||||
// updateDuration(option); // Normalize for key matching
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const fetchZoneData = async () => {
|
||||
// try {
|
||||
// const response = await axios.get(`http://${iotApiUrl}/getinput`);
|
||||
// if (response.status === 200) {
|
||||
// console.log('dropdown data:', response.data);
|
||||
// setDropDownData(response.data)
|
||||
// } else {
|
||||
// console.log('Unexpected response:', response);
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('There was an error!', error);
|
||||
// }
|
||||
// };
|
||||
// fetchZoneData();
|
||||
// }, []);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(selections);
|
||||
// }, [selections])
|
||||
|
||||
// const handleSelect = (inputKey: string, selectedData: { name: string, fields: string } | null) => {
|
||||
// setSelections(prev => {
|
||||
// if (selectedData === null) {
|
||||
// const newSelections = { ...prev };
|
||||
// delete newSelections[inputKey];
|
||||
// return newSelections;
|
||||
// } else {
|
||||
// return {
|
||||
// ...prev,
|
||||
// [inputKey]: selectedData
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// interface Measurement {
|
||||
// name: string;
|
||||
// fields: string;
|
||||
// }
|
||||
|
||||
// interface InputData {
|
||||
// [key: string]: Measurement;
|
||||
// }
|
||||
|
||||
// const extractMeasurements = (input: InputData): Measurement[] => {
|
||||
// return Object.values(input);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const measurementsData = extractMeasurements(selections);
|
||||
// setMeasurements(measurementsData);
|
||||
// }, [selections]);
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <div className="inputs-wrapper">
|
||||
// {[...Array(6)].map((_, index) => {
|
||||
// const inputKey = `input${index + 1}`;
|
||||
// return (
|
||||
// <div key={index} className="datas">
|
||||
// <div className="datas__label">Input {index + 1}</div>
|
||||
// <div className="datas__class">
|
||||
// <MultiLevelDropdown
|
||||
// data={dropDowndata}
|
||||
// onSelect={(selectedData) => handleSelect(inputKey, selectedData)}
|
||||
// onUnselect={() => handleSelect(inputKey, null)}
|
||||
// selectedValue={selections[inputKey]}
|
||||
// />
|
||||
// <div className="icon">
|
||||
// <AddIcon />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// })}
|
||||
// </div>
|
||||
// <div>
|
||||
// <div className="datas">
|
||||
// <div className="datas__label">duration</div>
|
||||
// <div className="datas__class">
|
||||
// <RegularDropDown
|
||||
// header={duration}
|
||||
// options={["1h", "2h", "12h"]}
|
||||
// onSelect={handleSelectDuration}
|
||||
// search={false}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// export default LineGrapInput
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const LineGrapInput = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const { projectId } = useParams();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/data?widgetID=${selectedChartId.id}&zoneUuid=${selectedZone.zoneUuid}&projectId=${projectId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
// const userId = localStorage.getItem("userId");
|
||||
// let newWidget = {
|
||||
// id: selectedChartId.id,
|
||||
// panel: selectedChartId.panel,
|
||||
// widgetName: inputName,
|
||||
// Data: {
|
||||
// measurements: inputMeasurement,
|
||||
// duration: inputDuration,
|
||||
// }
|
||||
// }
|
||||
// const adding3dWidget = {
|
||||
// organization: organization,
|
||||
// widget: newWidget,
|
||||
// zoneUuid: selectedZone.zoneUuid,
|
||||
// projectId, userId
|
||||
// };
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v1:viz-3D-widget:add", adding3dWidget);
|
||||
// }
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/save`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
panel: selectedChartId.panel,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.title}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(4)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LineGrapInput;
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const PieChartInput = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const { projectId } = useParams();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/data?widgetID=${selectedChartId.id}&zoneUuid=${selectedZone.zoneUuid}&projectId=${projectId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
|
||||
// const userId = localStorage.getItem("userId");
|
||||
// let newWidget = {
|
||||
// id: selectedChartId.id,
|
||||
// panel: selectedChartId.panel,
|
||||
// widgetName: inputName,
|
||||
// Data: {
|
||||
// measurements: inputMeasurement,
|
||||
// duration: inputDuration,
|
||||
// },
|
||||
// }
|
||||
// const adding3dWidget = {
|
||||
// organization: organization,
|
||||
// widget: newWidget,
|
||||
// zoneUuid: selectedZone.zoneUuid,
|
||||
// projectId, userId
|
||||
// };
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v1:viz-3D-widget:add", adding3dWidget);
|
||||
// }
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/save`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
panel: selectedChartId.panel,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.title}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(2)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PieChartInput;
|
||||
@@ -0,0 +1,235 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Progress1Input = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const { projectId } = useParams();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/data?widgetID=${selectedChartId.id}&zoneUuid=${selectedZone.zoneUuid}&projectId=${projectId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
// const userId = localStorage.getItem("userId");
|
||||
// let newWidget = {
|
||||
// id: selectedChartId.id,
|
||||
// panel: selectedChartId.panel,
|
||||
// widgetName: inputName,
|
||||
// Data: {
|
||||
// measurements: inputMeasurement,
|
||||
// duration: inputDuration,
|
||||
// },
|
||||
// }
|
||||
// const adding3dWidget = {
|
||||
// organization: organization,
|
||||
// widget: newWidget,
|
||||
// zoneUuid: selectedZone.zoneUuid,
|
||||
// projectId, userId
|
||||
// };
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v1:viz-3D-widget:add", adding3dWidget);
|
||||
// }
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/save`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
panel: selectedChartId.panel,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.title}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(1)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* <div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Progress1Input;
|
||||
@@ -0,0 +1,235 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Progress2Input = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const { projectId } = useParams();
|
||||
const { visualizationSocket } = useSocketStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/data?widgetID=${selectedChartId.id}&zoneUuid=${selectedZone.zoneUuid}&projectId=${projectId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
// const userId = localStorage.getItem("userId");
|
||||
// let newWidget = {
|
||||
// id: selectedChartId.id,
|
||||
// panel: selectedChartId.panel,
|
||||
// widgetName: inputName,
|
||||
// Data: {
|
||||
// measurements: inputMeasurement,
|
||||
// duration: inputDuration,
|
||||
// }
|
||||
// }
|
||||
// const adding3dWidget = {
|
||||
// organization: organization,
|
||||
// widget: newWidget,
|
||||
// zoneUuid: selectedZone.zoneUuid,
|
||||
// projectId, userId
|
||||
// };
|
||||
// if (visualizationSocket) {
|
||||
// visualizationSocket.emit("v1:viz-3D-widget:add", adding3dWidget);
|
||||
// }
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget/save`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer <access_token>",
|
||||
"Content-Type": "application/json",
|
||||
token: localStorage.getItem("token") || "",
|
||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||
},
|
||||
},
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
panel: selectedChartId.panel,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.title}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(2)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* <div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Progress2Input;
|
||||
@@ -0,0 +1,201 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const WarehouseThroughputInputComponent = (props: Props) => {
|
||||
const [widgetName, setWidgetName] = useState("Widget");
|
||||
const { setFlotingMeasurements, updateFlotingDuration, updateHeader } =
|
||||
useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/A_floatWidget/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.header);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setFlotingMeasurements(selections);
|
||||
updateFlotingDuration(duration);
|
||||
updateHeader(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/floatWidget/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
header: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput
|
||||
value={widgetName || selectedChartId?.header}
|
||||
onRename={handleNameChange}
|
||||
/>
|
||||
</div>
|
||||
{[...Array(1)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WarehouseThroughputInputComponent;
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Widget2InputCard3D = (props: Props) => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const [widgetName, setWidgetName] = useState("untited");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget3d/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput value={widgetName} onRename={handleNameChange} />
|
||||
</div>
|
||||
{[...Array(2)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widget2InputCard3D;
|
||||
@@ -0,0 +1,177 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
const Widget3InputCard3D = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const [widgetName, setWidgetName] = useState("untited");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/getinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget3d/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput value={widgetName} onRename={handleNameChange} />
|
||||
</div>
|
||||
{[...Array(7)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widget3InputCard3D;
|
||||
@@ -0,0 +1,197 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import MultiLevelDropdown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import { AddIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import useChartStore from "../../../../../store/visualization/useChartStore";
|
||||
import { useSelectedZoneStore } from "../../../../../store/visualization/useZoneStore";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import axios from "axios";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Widget4InputCard3D = (props: Props) => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const [widgetName, setWidgetName] = useState("untited");
|
||||
const { setMeasurements, updateDuration, updateName } = useChartStore();
|
||||
const [duration, setDuration] = useState("1h");
|
||||
const [dropDowndata, setDropDownData] = useState({});
|
||||
const [selections, setSelections] = useState<
|
||||
Record<string, { name: string; fields: string }>
|
||||
>({});
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
const iotApiUrl = process.env.REACT_APP_IOT_SOCKET_SERVER_URL;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZoneData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`http://${iotApiUrl}/floatinput`);
|
||||
if (response.status === 200) {
|
||||
// console.log("dropdown data:", response.data);
|
||||
setDropDownData(response.data);
|
||||
setLoading(false);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch zone data");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
};
|
||||
fetchZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSavedInputes = async () => {
|
||||
if (selectedChartId.id !== "") {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/v2/widget3D/${selectedChartId.id}/${organization}`
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setSelections(response.data.Data.measurements);
|
||||
setDuration(response.data.Data.duration);
|
||||
setWidgetName(response.data.widgetName);
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to fetch saved inputs");
|
||||
console.error("There was an error!", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchSavedInputes();
|
||||
}, [selectedChartId.id]);
|
||||
|
||||
// Sync Zustand state when component mounts
|
||||
useEffect(() => {
|
||||
setMeasurements(selections);
|
||||
updateDuration(duration);
|
||||
updateName(widgetName);
|
||||
}, [selections, duration, widgetName]);
|
||||
|
||||
const sendInputes = async (
|
||||
inputMeasurement: any,
|
||||
inputDuration: any,
|
||||
inputName: any
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}/api/V1/widget3d/save`,
|
||||
{
|
||||
organization: organization,
|
||||
zoneUuid: selectedZone.zoneUuid,
|
||||
widget: {
|
||||
id: selectedChartId.id,
|
||||
widgetName: inputName,
|
||||
Data: {
|
||||
measurements: inputMeasurement,
|
||||
duration: inputDuration,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
);
|
||||
if (response.status === 200) {
|
||||
return true;
|
||||
} else {
|
||||
console.log("Unexpected response:", response);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to send input");
|
||||
console.error("There was an error!", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = async (
|
||||
inputKey: string,
|
||||
selectedData: { name: string; fields: string } | null
|
||||
) => {
|
||||
// async() => {
|
||||
const newSelections = { ...selections };
|
||||
if (selectedData === null) {
|
||||
delete newSelections[inputKey];
|
||||
} else {
|
||||
newSelections[inputKey] = selectedData;
|
||||
}
|
||||
// setMeasurements(newSelections); // Update Zustand store
|
||||
// console.log(newSelections);
|
||||
if (await sendInputes(newSelections, duration, widgetName)) {
|
||||
setSelections(newSelections);
|
||||
}
|
||||
// sendInputes(newSelections, duration); // Send data to server
|
||||
// return newSelections;
|
||||
// };
|
||||
};
|
||||
|
||||
const handleSelectDuration = async (option: string) => {
|
||||
if (await sendInputes(selections, option, widgetName)) {
|
||||
setDuration(option);
|
||||
}
|
||||
// setDuration(option);
|
||||
};
|
||||
|
||||
const handleNameChange = async (name: any) => {
|
||||
console.log("name change requested", name);
|
||||
|
||||
if (await sendInputes(selections, duration, name)) {
|
||||
setWidgetName(name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="inputs-wrapper">
|
||||
<div className="datas">
|
||||
<div className="datas__label">Title</div>
|
||||
<RenameInput value={widgetName} onRename={handleNameChange} />
|
||||
</div>
|
||||
{[...Array(1)].map((_, index) => {
|
||||
const inputKey = `input${index + 1}`;
|
||||
return (
|
||||
<div key={index} className="datas">
|
||||
<div className="datas__label">Input {index + 1}</div>
|
||||
<div className="datas__class">
|
||||
<MultiLevelDropdown
|
||||
data={dropDowndata}
|
||||
onSelect={(selectedData) =>
|
||||
handleSelect(inputKey, selectedData)
|
||||
}
|
||||
onUnselect={() => handleSelect(inputKey, null)}
|
||||
selectedValue={selections[inputKey]} // Load from Zustand
|
||||
isLoading={isLoading}
|
||||
allSelections={selections}
|
||||
/>
|
||||
<div className="icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="datas">
|
||||
<div className="datas__label">Duration</div>
|
||||
<div className="datas__class">
|
||||
<RegularDropDown
|
||||
header={duration}
|
||||
options={["1h", "2h", "12h"]}
|
||||
onSelect={handleSelectDuration}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Widget4InputCard3D;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import ToggleHeader from "../../../ui/inputs/ToggleHeader";
|
||||
import Data from "./data/Data";
|
||||
import Design from "./design/Design";
|
||||
|
||||
const Visualization = () => {
|
||||
const [activeOption, setActiveOption] = useState("Data");
|
||||
|
||||
const handleToggleClick = (option: string) => {
|
||||
setActiveOption(option); // Update the active option
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="visualization-right-sideBar">
|
||||
<ToggleHeader
|
||||
options={["Data","Design"]}
|
||||
activeOption={activeOption}
|
||||
handleClick={handleToggleClick}
|
||||
/>
|
||||
<div className="sidebar-right-content-container">
|
||||
{activeOption === "Data" ? <Data /> : <Design />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Visualization;
|
||||
@@ -0,0 +1,192 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import MultiLevelDropDown from "../../../../ui/inputs/MultiLevelDropDown";
|
||||
import LineGrapInput from "../IotInputCards/LineGrapInput";
|
||||
import RenameInput from "../../../../ui/inputs/RenameInput";
|
||||
import InputSelecterComponent from "../IotInputCards/InputSelecterComponent";
|
||||
|
||||
// Define the data structure for demonstration purposes
|
||||
const DATA_STRUCTURE = {
|
||||
furnace: {
|
||||
coolingRate: "coolingRate",
|
||||
furnaceTemperature: "furnaceTemperature",
|
||||
heatingRate: "heatingRate",
|
||||
machineId: "machineId",
|
||||
powerConsumption: "powerConsumption",
|
||||
status: "status",
|
||||
timestamp: "timestamp",
|
||||
vacuumLevel: "vacuumLevel",
|
||||
},
|
||||
testDevice: {
|
||||
abrasiveLevel: {
|
||||
data1: "Data 1",
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
airPressure: "airPressure",
|
||||
machineId: "machineId",
|
||||
powerConsumption: "powerConsumption",
|
||||
status: "status",
|
||||
temperature: {
|
||||
data1: "Data 1",
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
timestamp: {
|
||||
data1: {
|
||||
Data01: "Data 01",
|
||||
Data02: "Data 02",
|
||||
Data03: "Data 03",
|
||||
},
|
||||
data2: "Data 2",
|
||||
data3: "Data 3",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
interface Child {
|
||||
id: number;
|
||||
easing: string;
|
||||
}
|
||||
|
||||
interface Group {
|
||||
id: number;
|
||||
easing: string;
|
||||
children: Child[];
|
||||
}
|
||||
|
||||
const Data = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
|
||||
// State to store groups for all widgets (using Widget.id as keys)
|
||||
const [chartDataGroups, setChartDataGroups] = useState<
|
||||
Record<string, Group[]>
|
||||
>({});
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize data groups for the newly selected widget if it doesn't exist
|
||||
if (selectedChartId && !chartDataGroups[selectedChartId.id]) {
|
||||
setChartDataGroups((prev) => ({
|
||||
...prev,
|
||||
[selectedChartId.id]: [
|
||||
{
|
||||
id: Date.now(),
|
||||
easing: "Connecter 1",
|
||||
children: [{ id: Date.now(), easing: "Linear" }],
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
}, [selectedChartId]);
|
||||
|
||||
// Handle adding a new child to the group
|
||||
const handleAddClick = (groupId: number) => {
|
||||
setChartDataGroups((prevGroups) => {
|
||||
const currentGroups = prevGroups[selectedChartId.id] || [];
|
||||
const group = currentGroups.find((g) => g.id === groupId);
|
||||
|
||||
if (group && group.children.length < 7) {
|
||||
const newChild = { id: Date.now(), easing: "Linear" };
|
||||
return {
|
||||
...prevGroups,
|
||||
[selectedChartId.id]: currentGroups.map((g) =>
|
||||
g.id === groupId ? { ...g, children: [...g.children, newChild] } : g
|
||||
),
|
||||
};
|
||||
}
|
||||
return prevGroups;
|
||||
});
|
||||
};
|
||||
|
||||
// Remove a child from a group
|
||||
const removeChild = (groupId: number, childId: number) => {
|
||||
setChartDataGroups((currentGroups) => {
|
||||
const currentChartData = currentGroups[selectedChartId.id] || [];
|
||||
|
||||
return {
|
||||
...currentGroups,
|
||||
[selectedChartId.id]: currentChartData.map((group) =>
|
||||
group.id === groupId
|
||||
? {
|
||||
...group,
|
||||
children: group.children.filter(
|
||||
(child) => child.id !== childId
|
||||
),
|
||||
}
|
||||
: group
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="dataSideBar">
|
||||
{/* {selectedChartId?.title && (
|
||||
<div className="sideBarHeader">{selectedChartId?.title}</div>
|
||||
)} */}
|
||||
|
||||
|
||||
{/* <RenameInput value={selectedChartId?.title || "untited"} /> */}
|
||||
|
||||
{/* Render groups dynamically */}
|
||||
{
|
||||
chartDataGroups[selectedChartId?.id] &&
|
||||
<>
|
||||
<InputSelecterComponent />
|
||||
</>
|
||||
}
|
||||
|
||||
{/* Info Box */}
|
||||
<div className="infoBox">
|
||||
<span className="infoIcon">i</span>
|
||||
<p>
|
||||
<em>
|
||||
By adding templates and widgets, you create a customizable and
|
||||
dynamic environment.
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Data;
|
||||
|
||||
|
||||
|
||||
// {chartDataGroups[selectedChartId?.id]?.map((group) => (
|
||||
// <div key={group.id} className="inputs-wrapper">
|
||||
// {group.children.map((child, index) => (
|
||||
// <div key={child.id} className="datas">
|
||||
// <div className="datas__label">Input {index + 1}</div>
|
||||
// <div className="datas__class">
|
||||
// <MultiLevelDropDown data={DATA_STRUCTURE} />
|
||||
// {/* Add Icon */}
|
||||
// {group.children.length < 7 && (
|
||||
// <div
|
||||
// className="icon"
|
||||
// onClick={() => handleAddClick(group.id)} // Pass groupId to handleAddClick
|
||||
// >
|
||||
// <AddIcon />
|
||||
// </div>
|
||||
// )}
|
||||
// {/* Remove Icon */}
|
||||
|
||||
// <span
|
||||
// className={`datas__separator ${
|
||||
// group.children.length > 1 ? "" : "disable"
|
||||
// }`}
|
||||
// onClick={(e) => {
|
||||
// e.stopPropagation(); // Prevent event bubbling
|
||||
// removeChild(group.id, child.id); // Pass groupId and childId to removeChild
|
||||
// }}
|
||||
// >
|
||||
// <RemoveIcon />
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// ))}
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ArrowIcon } from "../../../../icons/ExportCommonIcons";
|
||||
import RegularDropDown from "../../../../ui/inputs/RegularDropDown";
|
||||
import InputRange from "../../../../ui/inputs/InputRange";
|
||||
import { useWidgetStore } from "../../../../../store/useWidgetStore";
|
||||
import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent";
|
||||
|
||||
const defaultStyle = {
|
||||
theme: "Glass",
|
||||
elementColor: "#ffffff",
|
||||
blurEffect: 10,
|
||||
opacity: 10,
|
||||
selectedElement: "Glass",
|
||||
};
|
||||
|
||||
const defaultChartData = {
|
||||
duration: "1h",
|
||||
measurements: {},
|
||||
datasets: [
|
||||
{
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#b392f0",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
};
|
||||
|
||||
const Design = () => {
|
||||
const { selectedChartId } = useWidgetStore();
|
||||
const [styles, setStyles] = useState<Record<string, typeof defaultStyle>>({});
|
||||
|
||||
const currentStyle = selectedChartId
|
||||
? styles[selectedChartId.id] || defaultStyle
|
||||
: defaultStyle;
|
||||
|
||||
const updateStyle = (updates: Partial<typeof defaultStyle>) => {
|
||||
if (!selectedChartId) return;
|
||||
setStyles((prev) => ({
|
||||
...prev,
|
||||
[selectedChartId.id]: { ...currentStyle, ...updates },
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Styles", styles);
|
||||
}, [styles]);
|
||||
|
||||
return (
|
||||
<div className="design">
|
||||
<div className="appearance-container">
|
||||
<div className="header-container">
|
||||
<div className="head">Appearance</div>
|
||||
<div className="icon">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appearance-style">
|
||||
<div className="theme-wrapper">
|
||||
<div className="key">Theme</div>
|
||||
<div className="value">
|
||||
<RegularDropDown
|
||||
header={currentStyle.theme}
|
||||
options={["Glass", "Fill", "Transparent"]}
|
||||
onSelect={(theme) => updateStyle({ theme })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentStyle.theme === "Glass" && (
|
||||
<div className="blurEffect-wrapper">
|
||||
<InputRange
|
||||
label="Blur Effects"
|
||||
disabled={false}
|
||||
value={currentStyle.blurEffect}
|
||||
min={0}
|
||||
max={50}
|
||||
onChange={(blurEffect) => updateStyle({ blurEffect })}
|
||||
onPointerUp={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentStyle.theme !== "Fill" && (
|
||||
<div className="opacity-wrapper">
|
||||
<InputRange
|
||||
label="Opacity"
|
||||
disabled={false}
|
||||
value={currentStyle.opacity}
|
||||
min={0}
|
||||
max={50}
|
||||
onChange={(opacity) => updateStyle({ opacity })}
|
||||
onPointerUp={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="color-wrapper">
|
||||
<div className="key">Color</div>
|
||||
<div className="value">
|
||||
<input
|
||||
type="color"
|
||||
value={currentStyle.elementColor}
|
||||
onChange={(e) => updateStyle({ elementColor: e.target.value })}
|
||||
/>
|
||||
<span style={{ marginLeft: "10px" }}>
|
||||
{currentStyle.elementColor}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="element-container">
|
||||
<div className="display-element">
|
||||
{selectedChartId ? (
|
||||
<ChartComponent
|
||||
type={selectedChartId?.type ?? "bar"}
|
||||
title={selectedChartId?.title ?? "Chart"}
|
||||
data={{
|
||||
labels:
|
||||
selectedChartId?.data?.labels ?? defaultChartData.labels,
|
||||
datasets: selectedChartId?.data?.datasets?.length
|
||||
? selectedChartId.data.datasets
|
||||
: defaultChartData.datasets,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
"No Preview"
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="name-wrapper">
|
||||
<div className="key">Name</div>
|
||||
<input className="value" type="text" />
|
||||
</div>
|
||||
|
||||
<div className="element-wrapper">
|
||||
<div className="key">Element</div>
|
||||
<div className="value">
|
||||
<RegularDropDown
|
||||
header={currentStyle.selectedElement}
|
||||
options={["Glass", "Fill", "Transparent"]}
|
||||
onSelect={(selectedElement) => updateStyle({ selectedElement })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Design;
|
||||
Reference in New Issue
Block a user