diff --git a/app/src/components/icons/ContextMenuIcons.tsx b/app/src/components/icons/ContextMenuIcons.tsx index a1ced1b..217ad8f 100644 --- a/app/src/components/icons/ContextMenuIcons.tsx +++ b/app/src/components/icons/ContextMenuIcons.tsx @@ -42,44 +42,46 @@ export function FlipXAxisIcon() { } export function FlipYAxisIcon() { - - - - - - - ; + return ( + + + + + + + + ); } export function FlipZAxisIcon() { return ( diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 6e73c0e..5f89c92 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -168,20 +168,6 @@ export function AddIcon() { ); } -export function RmoveIcon() { - return ( - - - - ); -} - export function CloseIcon() { return ( { ); }; + +export const HourlySimulationIcon = () => { + return ( + + + + ); +}; + +export const DailyProductionIcon = () => { + return ( + + + + + + ); +}; + +export const MonthlyROI = () => { + return ( + + + + ); +}; + +export const ExpandIcon = () => { + return ( + + + + + + ); +}; + +export const StartIcon = () => { + return ( + + + + + + + ); +}; + +export const EndIcon = () => { + return ( + + + + + + + ); +}; + +export const SpeedIcon = () => { + return ( + + + + ); +}; +// export const DublicateIcon = () => { +// return ( +// +// +// +// ); +// }; diff --git a/app/src/components/icons/Logo.tsx b/app/src/components/icons/Logo.tsx index 06ed750..d15398c 100644 --- a/app/src/components/icons/Logo.tsx +++ b/app/src/components/icons/Logo.tsx @@ -124,7 +124,6 @@ export function LogoIconLarge() { fill="none" xmlns="http://www.w3.org/2000/svg" > - @@ -28,8 +28,8 @@ export function ProductionCapacityIcon() { > @@ -49,12 +49,12 @@ export function ROISummaryIcon() { ); diff --git a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx index b89bfb2..ed22a9a 100644 --- a/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx +++ b/app/src/components/layout/Dashboard/MarketPlaceBanner.tsx @@ -23,16 +23,16 @@ const MarketPlaceBanner = () => { diff --git a/app/src/components/layout/Dashboard/SidePannel.tsx b/app/src/components/layout/Dashboard/SidePannel.tsx index d44c72c..5d69ffc 100644 --- a/app/src/components/layout/Dashboard/SidePannel.tsx +++ b/app/src/components/layout/Dashboard/SidePannel.tsx @@ -11,7 +11,7 @@ import { import { SettingsIcon, TrashIcon } from "../../icons/ExportCommonIcons"; const SidePannel: React.FC = () => { - const userName = localStorage.getItem("userName") || "Anonymous"; + const userName = localStorage.getItem("userName") ?? "Anonymous"; return (
diff --git a/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx b/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx index 078f27d..4774bad 100644 --- a/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx +++ b/app/src/components/layout/confirmationPopup/ConfirmationPopup.tsx @@ -17,12 +17,12 @@ const ConfirmationPopup: React.FC = ({

{message}

-
+
-
+ +
+
diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index dc412f7..e0b56d4 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -5,8 +5,8 @@ import Header from "./Header"; import useToggleStore from "../../../store/useUIToggleStore"; import Assets from "./Assets"; import useModuleStore from "../../../store/useModuleStore"; -import Widgets from ".//visualization/widgets/Widgets"; -import Templates from "../../../modules//visualization/template/Templates"; +import Widgets from "./visualization/widgets/Widgets"; +import Templates from "../../../modules/visualization/template/Templates"; import Search from "../../ui/inputs/Search"; const SideBarLeft: React.FC = () => { diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx index 9d09291..0e10cf1 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/ChartComponent.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useMemo } from "react"; import { Chart } from "chart.js/auto"; -// import { useThemeStore } from "../../../../../store/useThemeStore"; // Define Props Interface interface ChartComponentProps { @@ -29,7 +28,6 @@ const ChartComponent = ({ data: propsData, }: ChartComponentProps) => { const canvasRef = useRef(null); - // const { themeColor } = useThemeStore(); // Memoize Theme Colors to Prevent Unnecessary Recalculations // const buttonActionColor = useMemo( @@ -66,7 +64,7 @@ const ChartComponent = ({ // Memoize Chart Font Style const chartFontStyle = useMemo( () => ({ - family: fontFamily || "Arial", + family: fontFamily ?? "Arial", size: fontSizeValue, weight: fontWeightValue, color: "#2B3344", diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx index d4a64ae..655e641 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/Widgets.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import ToggleHeader from "../../../../ui/inputs/ToggleHeader"; import Widgets2D from "./Widgets2D"; import Widgets3D from "./Widgets3D"; @@ -6,7 +5,6 @@ import WidgetsFloating from "./WidgetsFloating"; import { useWidgetSubOption } from "../../../../../store/store"; const Widgets = () => { - const [activeOption, setActiveOption] = useState("2D"); const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); const handleToggleClick = (option: string) => { diff --git a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx index 50d9712..4c085aa 100644 --- a/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx +++ b/app/src/components/layout/sidebarLeft/visualization/widgets/WidgetsFloating.tsx @@ -5,45 +5,14 @@ import { GlobeIcon, WalletIcon, } from "../../../../icons/3dChartIcons"; -import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard"; +import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard"; import WarehouseThroughput from "../../../../../modules//visualization/widgets/floating/cards/WarehouseThroughput"; -import ProductivityDashboard from "../../../../../modules//visualization/widgets/floating/cards/ProductivityDashboard"; import FleetEfficiency from "../../../../../modules//visualization/widgets/floating/cards/FleetEfficiency"; -interface Widget { - id: string; - name: string; -} - const WidgetsFloating = () => { - // const [widgets, setWidgets] = useState([ - // { id: "1", name: "Working State Widget" }, - // { id: "2", name: "Floating Widget 2" }, - // { id: "3", name: "Floating Widget 3" }, - // { id: "4", name: "Floating Widget 4" }, - // ]); - - // Function to handle drag start - const handleDragStart = ( - e: React.DragEvent, - widget: Widget - ) => { - e.dataTransfer.setData("application/json", JSON.stringify(widget)); - }; - return (
- {/* {widgets.map((widget) => ( -
handleDragStart(e, widget)} - > - {widget.name} -
- ))} */} {/* Floating 1 */} { const { toggleUI } = useToggleStore(); const { subModule, setSubModule } = useSubModuleStore(); const { selectedFloorItem } = useSelectedFloorItem(); + const { selectedEventData } = useSelectedEventData(); + const { selectedEventSphere } = useSelectedEventSphere(); // Reset activeList whenever activeModule changes useEffect(() => { if (activeModule !== "simulation") setSubModule("properties"); - if (activeModule === "simulation") setSubModule("mechanics"); - }, [activeModule]); + if (activeModule === "simulation") setSubModule("simulations"); + }, [activeModule, setSubModule]); - // romove late - const dummyData = { - assetType: "store", - selectedPoint: { - name: "Point A", - uuid: "123e4567-e89b-12d3-a456-426614174000", - actions: [ - { - uuid: "action-1", - name: "Action One", - }, - { - uuid: "action-2", - name: "Action Two", - }, - { - uuid: "action-3", - name: "Action Three", - }, - ], - }, - selectedItem: { - item: { - uuid: "item-1", - name: "Item One", - isUsed: false, - }, - }, - setSelectedPoint: (value: string) => { - console.log(`Selected point updated to: ${value}`); - }, - selectedActionSphere: "Sphere A", - }; + useEffect(() => { + if ( + activeModule !== "mechanics" && + selectedEventData && + selectedEventSphere + ) { + setSubModule("mechanics"); + } else if (!selectedEventData && !selectedEventSphere) { + if (activeModule === "simulation") { + setSubModule("simulations"); + } + } + }, [activeModule, selectedEventData, selectedEventSphere, setSubModule]); return (
{toggleUI && (
- {/* {activeModule === "builder" && ( */} -
setSubModule("properties")} - > - -
- {/* )} */} + {activeModule !== "simulation" && ( + + )} {activeModule === "simulation" && ( <> -
setSubModule("mechanics")} - > - -
-
setSubModule("simulations")} > -
-
+ +
+ )}
@@ -141,13 +127,19 @@ const SideBarRight: React.FC = () => {
)} {/* simulation */} - {toggleUI && activeModule === "simulation" && ( <> + {subModule === "simulations" && ( +
+
+ +
+
+ )} {subModule === "mechanics" && (
- +
)} @@ -158,16 +150,8 @@ const SideBarRight: React.FC = () => {
)} - {subModule === "simulations" && ( -
-
- -
-
- )} )} - {/* realtime visualization */} {toggleUI && activeModule === "visualization" && } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index c3ec6e7..87d3bf6 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -1,208 +1,130 @@ -import React, { useRef, useState } from "react"; -import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown"; -import LabledDropdown from "../../../../ui/inputs/LabledDropdown"; +import React, { useEffect, useState } from "react"; import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, -} from "../../../../icons/ExportCommonIcons"; -import RenameInput from "../../../../ui/inputs/RenameInput"; -import { handleResize } from "../../../../../functions/handleResizePannel"; -import { handleActionToggle } from "./functions/handleActionToggle"; -import { handleDeleteAction } from "./functions/handleDeleteAction"; -import DefaultAction from "./actions/DefaultAction"; -import SpawnAction from "./actions/SpawnAction"; -import SwapAction from "./actions/SwapAction"; -import DespawnAction from "./actions/DespawnAction"; -import TravelAction from "./actions/TravelAction"; -import PickAndPlaceAction from "./actions/PickAndPlaceAction"; -import ProcessAction from "./actions/ProcessAction"; -import StorageAction from "./actions/StorageAction"; -import Trigger from "./trigger/Trigger"; + useSelectedAsset, + useSelectedEventData, + useSelectedEventSphere, + useSelectedProduct, +} 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"; -interface EventPropertiesProps { - assetType: string; - selectedPoint: { - name: string; - uuid: string; - actions: { - uuid: string; - name: string; - }[]; +const EventProperties: React.FC = () => { + const { selectedEventData } = useSelectedEventData(); + const { getEventByModelUuid } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const [currentEventData, setCurrentEventData] = useState( + null + ); + const [assetType, setAssetType] = useState(null); + const { products, addEvent } = useProductStore(); + const { selectedEventSphere } = useSelectedEventSphere(); + + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); + 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.productId, + selectedEventData.data.modelUuid + ) ?? null + ); }; - selectedItem: { - item: { - uuid: string; - name: string; - } | null; - }; - setSelectedPoint: (value: string) => void; - selectedActionSphere: string; -} -const EventProperties: React.FC = ({ - assetType, - selectedPoint, - selectedItem, - setSelectedPoint, - selectedActionSphere, -}) => { - const actionsContainerRef = useRef(null); + const determineAssetType = (event: EventsSchema | null) => { + if (!event) return null; - const [activeOption, setActiveOption] = useState("default"); - const [dummyactiveOption, setTypeOption] = useState("default"); - - const getAvailableActions = () => { - if (assetType === "conveyor") { - return { - defaultOption: "default", - options: ["default", "spawn", "swap", "despawn"], - }; - } - if (assetType === "vehicle") { - return { - defaultOption: "travel", - options: ["travel"], - }; - } - if (assetType === "roboticArm") { - return { - defaultOption: "pickAndPlace", - options: ["pickAndPlace"], - }; - } - if (assetType === "machine") { - return { - defaultOption: "process", - options: ["process"], - }; - } - if (assetType === "store") { - return { - defaultOption: "store", - options: ["store", "spawn"], - }; - } else { - return { - defaultOption: "default", - options: ["default"], - }; + 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 (
-
-
{selectedPoint.name}
-
-
-
- {/*
- setTypeOption(option)} - /> -
*/} -
- {}} - onChange={(value) => console.log(value)} - /> -
-
- {}} - onChange={(value) => console.log(value)} - /> -
-
-
-
-
+ {currentEventData && ( + <>
-
Actions
-
{}}> - Add +
+ {selectedEventData?.data.modelName}
-
-
- {selectedPoint?.actions.map((action) => ( -
-
handleActionToggle(action.uuid)} + {assetType === "conveyor" && } + {assetType === "vehicle" && } + {assetType === "roboticArm" && } + {assetType === "machine" && } + {assetType === "storageUnit" && } + + )} + {!currentEventData && selectedEventSphere && ( +
+

+ Oops! It looks like this object doesn't have an + event assigned yet. To continue, please link it to one of the + products below. +

+ +
+

+ Here are some products you can add it to: +

+
    + {products.map((product) => ( +
  • +
- {selectedPoint?.actions.length > 1 && ( -
handleDeleteAction(action.uuid)} - > - -
- )} -
+ + {product.productName} + + ))} -
-
handleResize(e, actionsContainerRef)} - > - -
+
-
-
-
- + )} + {!selectedEventSphere && ( +
+

+ Oops! It looks like you haven't selected an event + point yet. Please select an event to view its properties. +

-
- setActiveOption(option)} - /> - {activeOption === "default" && } {/* done */} - {activeOption === "spawn" && } {/* done */} - {activeOption === "swap" && } {/* done */} - {activeOption === "despawn" && } {/* done */} - {activeOption === "travel" && } {/* done */} - {activeOption === "pickAndPlace" && } {/* done */} - {activeOption === "process" && } {/* done */} - {activeOption === "store" && } {/* done */} -
-
-
- -
+ )}
); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx new file mode 100644 index 0000000..6c33583 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx @@ -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 = ({ + value, + defaultValue, + min, + max, + onChange, +}) => { + return ( + {}} + onChange={onChange} + /> + ); +}; + +export default DelayAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx index a0f081f..88cbd2e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx @@ -1,20 +1,8 @@ import React from "react"; -import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; const DespawnAction: React.FC = () => { return ( <> - {}} - onChange={(value) => console.log(value)} - /> ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx index 9574669..175a824 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx @@ -1,13 +1,25 @@ import React from "react"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const PickAndPlaceAction: React.FC = () => { - return ( - <> - {}} /> - {}} /> - - ); +interface PickAndPlaceActionProps { + pickPointValue: string; + pickPointOnChange: (value: string) => void; + placePointValue: string; + placePointOnChange: (value: string) => void; +} + +const PickAndPlaceAction: React.FC = ({ + pickPointValue, + pickPointOnChange, + placePointValue, + placePointOnChange, +}) => { + return ( + <> + + + + ); }; export default PickAndPlaceAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx index a27894e..331cf1b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx @@ -2,23 +2,47 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import SwapAction from "./SwapAction"; -const ProcessAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - - - ); +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 = ({ + value, + min, + max, + defaultValue, + onChange, + swapOptions, + swapDefaultOption, + onSwapSelect, +}) => { + return ( + <> + { }} + onChange={onChange} + /> + + + ); }; export default ProcessAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx index 0c37cdb..7d8002e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx @@ -1,35 +1,72 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -const SpawnAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - {}} - onChange={(value) => console.log(value)} - /> - - - ); +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 = ({ + onChangeInterval, + onChangeCount, + defaultOption, + options, + onSelect, + intervalValue, + countValue, + intervalMin, + intervalMax, + intervalDefaultValue, + countMin, + countMax, + countDefaultValue, +}) => { + return ( + <> + { }} + onChange={onChangeInterval} + /> + { }} + onChange={onChangeCount} + /> + {/* */} + + + ); }; export default SpawnAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx index ab2109b..5c9bf86 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx @@ -1,20 +1,28 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -const StorageAction: React.FC = () => { - return ( - {}} - onChange={(value) => console.log(value)} - /> - ); +interface StorageActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; +} + +const StorageAction: React.FC = ({ value, min, max, defaultValue, onChange }) => { + return ( + { }} + onChange={onChange} + /> + ); }; export default StorageAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx index 2e18d80..5eaf991 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx @@ -1,11 +1,24 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; -const SwapAction: React.FC = () => { +interface SwapActionProps { + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const SwapAction: React.FC = ({ + onSelect, + defaultOption, + options, +}) => { return ( - <> - - + ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx index ee4bda0..49eb683 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx @@ -2,35 +2,77 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const TravelAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - {}} - onChange={(value) => console.log(value)} - /> - {}} /> - {}} /> - - ); +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; + }; +} + +const TravelAction: React.FC = ({ + loadCapacity, + unloadDuration, + pickPoint, + unloadPoint, +}) => { + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> + { }} + onChange={unloadDuration.onChange} + /> + {pickPoint && ( + + )} + {unloadPoint && ( + + )} + + ); }; export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx new file mode 100644 index 0000000..6d2c3de --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx @@ -0,0 +1,202 @@ +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, + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { MathUtils } from "three"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; + +interface ActionsListProps { + setSelectedPointData: (data: any) => void; // You can replace `any` with a more specific type if you have one + selectedPointData: any; // You can replace `any` with a more specific type if you have one + // ui control props + multipleAction?: boolean; +} + +const ActionsList: React.FC = ({ + setSelectedPointData, + selectedPointData, + multipleAction = false, +}) => { + const actionsContainerRef = useRef(null); + + // store + const { selectedEventData } = useSelectedEventData(); + const { updateAction, addAction, removeAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAction, setSelectedAction, clearSelectedAction } = + useSelectedAction(); + + 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[], + }; + + addAction( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + const updatedPoint = { + ...selectedPointData, + actions: [...selectedPointData.actions, newAction], + }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + removeAction(actionUuid); + const newActions = selectedPointData.actions.filter( + (a: any) => a.actionUuid !== actionUuid + ); + + const updatedPoint = { + ...selectedPointData, + actions: newActions, + }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + if (newActions.length > 0) { + setSelectedAction(newActions[0].actionUuid, newActions[0].actionName); + } else { + clearSelectedAction(); + } + } + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + updateAction(selectedAction.actionId, { actionName: newName }); + + if (selectedPointData?.actions) { + const updatedActions = selectedPointData.actions.map((action: any) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + } else { + // write logic for single action + return; + } + }; + + const handleActionSelect = (actionUuid: string, actionName: string) => { + setSelectedAction(actionUuid, actionName); + }; + + return ( +
+
+
+
Actions
+ + +
+
+
+ {multipleAction && + selectedPointData.actions.map((action: any) => ( +
+ + {selectedPointData.actions.length > 1 && ( + + )} +
+ ))} + {!multipleAction && selectedPointData && ( +
+ +
+ )} +
+ {multipleAction && ( + + )} +
+
+
+ ); +}; + +export default ActionsList; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx new file mode 100644 index 0000000..b370b3e --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -0,0 +1,224 @@ +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 { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import ActionsList from "../components/ActionsList"; + +function ConveyorMechanics() { + const [activeOption, setActiveOption] = useState< + "default" | "spawn" | "swap" | "delay" | "despawn" + >("default"); + const [selectedPointData, setSelectedPointData] = useState< + ConveyorPointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + 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" + ); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as + | "default" + | "spawn" + | "swap" + | "delay" + | "despawn"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleSpawnCountChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + spawnCount: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const handleSpawnIntervalChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + spawnInterval: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { material }); + }; + + const handleDelayChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + delay: value === "inherit" ? "inherit" : parseFloat(value), + }); + }; + + const availableActions = { + defaultOption: "default", + options: ["default", "spawn", "swap", "delay", "despawn"], + }; + + // Get current values from store + const currentSpeed = + selectedEventData?.data.type === "transfer" + ? selectedEventData.data.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 ( + <> + {selectedEventData && ( + <> +
+
+
+ {}} + onChange={handleSpeedChange} + /> +
+
+
+ + + +
+
+ +
+
+ + {activeOption === "default" && } + {activeOption === "spawn" && ( + + )} + {activeOption === "swap" && ( + + )} + {activeOption === "despawn" && } + {activeOption === "delay" && ( + + )} +
+
+
+ +
+ + )} + + ); +} + +export default ConveyorMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx new file mode 100644 index 0000000..fa2cfdf --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import ProcessAction from "../actions/ProcessAction"; +import ActionsList from "../components/ActionsList"; + +function MachineMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "process">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + MachinePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as MachinePointSchema | undefined; + if (point && "action" in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "process"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "process"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleProcessTimeChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + processTime: parseFloat(value), + }); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + swapMaterial: material, + }); + }; + + // 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 && ( + <> +
+
+ +
+ +
+ + {activeOption === "process" && ( + + )} +
+
+
+ +
+ + )} + + ); +} + +export default MachineMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx new file mode 100644 index 0000000..7c20ce5 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -0,0 +1,194 @@ +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 { + useSelectedEventData, + useSelectedProduct, + useSelectedAction, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import ActionsList from "../components/ActionsList"; + +function RoboticArmMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + RoboticArmPointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAction, setSelectedAction, clearSelectedAction } = + useSelectedAction(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as RoboticArmPointSchema | undefined; + if (point?.actions) { + setSelectedPointData(point); + setActiveOption( + point.actions[0].actionType as "default" | "pickAndPlace" + ); + if (point.actions.length > 0 && !selectedAction.actionId) { + setSelectedAction( + point.actions[0].actionUuid, + point.actions[0].actionName + ); + } + } + } else { + clearSelectedAction(); + } + }, [ + clearSelectedAction, + getPointByUuid, + selectedAction.actionId, + selectedEventData, + selectedProduct, + setSelectedAction, + ]); + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + updateAction(selectedAction.actionId, { actionName: newName }); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions, + }); + } + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + + updateAction(selectedAction.actionId, { + process: { + startPoint: [x, y, z] as [number, number, number], + endPoint: + selectedPointData.actions.find( + (a) => a.actionUuid === selectedAction.actionId + )?.process.endPoint || null, + }, + }); + }; + + const handlePlacePointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + + updateAction(selectedAction.actionId, { + process: { + startPoint: + selectedPointData.actions.find( + (a) => a.actionUuid === selectedAction.actionId + )?.process.startPoint || null, + endPoint: [x, y, z] as [number, number, number], + }, + }); + }; + + const availableActions = { + defaultOption: "pickAndPlace", + options: ["pickAndPlace"], + }; + + const currentSpeed = + selectedEventData?.data.type === "roboticArm" + ? selectedEventData.data.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 ( + <> + {selectedEventData && selectedPointData && ( + <> +
+
+
+ {}} + onChange={handleSpeedChange} + /> +
+
+
+ + + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ {}} + disabled={true} + /> + +
+
+ +
+
+ )} + + )} + + ); +} + +export default RoboticArmMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx new file mode 100644 index 0000000..d92ed80 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from "react"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import Trigger from "../trigger/Trigger"; +import { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import StorageAction from "../actions/StorageAction"; +import ActionsList from "../components/ActionsList"; + +function StorageMechanics() { + const [activeOption, setActiveOption] = useState< + "default" | "store" | "spawn" + >("default"); + const [selectedPointData, setSelectedPointData] = useState< + StoragePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as StoragePointSchema | undefined; + if (point && "action" in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "store" | "spawn"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "store" | "spawn"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + storageCapacity: parseInt(value), + }); + }; + + // Get current values from store + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentCapacity = selectedPointData + ? selectedPointData.action.storageCapacity.toString() + : "0"; + + const availableActions = { + defaultOption: "store", + options: ["store", "spawn"], + }; + + return ( + <> + {selectedEventData && ( + <> + +
+
+ +
+
+ + {activeOption === "store" && ( + + )} + {activeOption === "spawn" && ( +
+

Spawn configuration options would go here

+
+ )} +
+
+
+ +
+ + )} + + ); +} + +export default StorageMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx new file mode 100644 index 0000000..4524ceb --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -0,0 +1,199 @@ +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 { + useSelectedEventData, + useSelectedProduct, +} from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import TravelAction from "../actions/TravelAction"; +import ActionsList from "../components/ActionsList"; + +function VehicleMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "travel">( + "default" + ); + const [selectedPointData, setSelectedPointData] = useState< + VehiclePointSchema | undefined + >(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as VehiclePointSchema | undefined; + + if (point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "travel"); + } + } + }, [selectedProduct, selectedEventData, getPointByUuid]); + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + speed: parseFloat(value), + }); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "travel"; + setActiveOption(validOption); + + updateAction(selectedPointData.action.actionUuid, { + actionType: validOption, + }); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + }; + + const handleLoadCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + loadCapacity: parseFloat(value), + }); + }; + + const handleUnloadDurationChange = (value: string) => { + if (!selectedPointData) return; + updateAction(selectedPointData.action.actionUuid, { + unLoadDuration: parseFloat(value), + }); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + updateAction(selectedPointData.action.actionUuid, { + pickUpPoint: { x, y, z }, + }); + }; + + const handleUnloadPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(",").map(Number); + updateAction(selectedPointData.action.actionUuid, { + unLoadPoint: { x, y, z }, + }); + }; + + // Get current values from store + const currentSpeed = + selectedEventData?.data.type === "vehicle" + ? selectedEventData.data.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"; + + const currentPickPoint = selectedPointData?.action.pickUpPoint + ? `${selectedPointData.action.pickUpPoint.x},${selectedPointData.action.pickUpPoint.y},${selectedPointData.action.pickUpPoint.z}` + : ""; + + const currentUnloadPoint = selectedPointData?.action.unLoadPoint + ? `${selectedPointData.action.unLoadPoint.x},${selectedPointData.action.unLoadPoint.y},${selectedPointData.action.unLoadPoint.z}` + : ""; + + const availableActions = { + defaultOption: "travel", + options: ["travel"], + }; + + return ( + <> + {selectedEventData && ( + <> +
+
+
+ {}} + onChange={handleSpeedChange} + /> +
+
+
+ +
+
+ +
+
+ + + {activeOption === "travel" && ( + + )} +
+
+
+ +
+ + )} + + ); +} + +export default VehicleMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index f287b63..3b2549a 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,11 +1,19 @@ -import React, { useState } from "react"; -import { AddIcon, RemoveIcon } from "../../../../../icons/ExportCommonIcons"; +import React, { useRef, useState } from "react"; +import { + AddIcon, + RemoveIcon, + ResizeHeightIcon, +} from "../../../../../icons/ExportCommonIcons"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; +import RenameInput from "../../../../../ui/inputs/RenameInput"; +import { handleResize } from "../../../../../../functions/handleResizePannel"; const Trigger: React.FC = () => { // State to hold the list of triggers - const [triggers, setTriggers] = useState([]); + const [triggers, setTriggers] = useState(["Trigger 1"]); + const [selectedTrigger, setSelectedTrigger] = useState("Trigger 1"); const [activeOption, setActiveOption] = useState("onComplete"); + const triggersContainerRef = useRef(null); // States for dropdowns const [triggeredModel, setTriggeredModel] = useState([]); @@ -35,70 +43,94 @@ const Trigger: React.FC = () => {
Trigger
-
Add -
+
- {/* Map over triggers and render them */} - {triggers.map((trigger, index) => ( -
-
- {trigger} +
+
+ {triggers.map((trigger: any, index: number) => (
removeTrigger(index)} - style={{ cursor: "pointer" }} + key={index} + className={`list-item ${ + selectedTrigger === trigger ? "active" : "" + }`} + onClick={() => setSelectedTrigger(trigger)} > - + + {triggers.length > 1 && ( + + )}
+ ))} +
+ +
+
+
{selectedTrigger}
+ setActiveOption(option)} + /> +
+
+ { + const newModel = [...triggeredModel]; + newModel[0] = option; + setTriggeredModel(newModel); + }} + />
- setActiveOption(option)} - /> -
-
- { - const newModel = [...triggeredModel]; - newModel[index] = option; - setTriggeredModel(newModel); - }} - /> -
-
- { - const newPoint = [...triggeredPoint]; - newPoint[index] = option; - setTriggeredPoint(newPoint); - }} - /> -
-
- { - const newAction = [...triggeredAction]; - newAction[index] = option; - setTriggeredAction(newAction); - }} - /> -
+
+ { + const newPoint = [...triggeredPoint]; + newPoint[0] = option; + setTriggeredPoint(newPoint); + }} + /> +
+
+ { + const newAction = [...triggeredAction]; + newAction[0] = option; + setTriggeredAction(newAction); + }} + />
- ))} +
); diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 2a12734..b037098 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef } from "react"; import { AddIcon, ArrowIcon, @@ -7,65 +7,96 @@ import { } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../functions/handleResizePannel"; +import { + useSelectedAsset, + useSelectedProduct, +} 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 { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; +import { handleAddEventToProduct } from "../../../../modules/simulation/events/points/functions/handleAddEventToProduct"; -interface Path { - pathName: string; // Represents the name of the path - Children: string[]; // Represents the list of child points +interface Event { + pathName: string; } -interface DropListProps { - val: Path; // Use the Path interface for the val prop +interface ListProps { + val: Event; } -const DropList: React.FC = ({ val }) => { - const [openDrop, setOpenDrop] = useState(false); +const List: React.FC = ({ val }) => { return (
-
{ - setOpenDrop(!openDrop); - }} - > - {val.pathName} -
- -
-
- {val.Children && openDrop && ( -
- {val.Children.map((child, index) => ( -
- {child} -
- ))} -
- )} +
{val.pathName}
); }; const Simulations: React.FC = () => { const productsContainerRef = useRef(null); - const [productsList, setProductsList] = useState([]); - const [selectedItem, setSelectedItem] = useState(); + const { + products, + addProduct, + removeProduct, + renameProduct, + addEvent, + removeEvent, + } = useProductStore(); + const { selectedProduct, setSelectedProduct } = useSelectedProduct(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); - const handleAddAction = () => { - setProductsList([...productsList, `Product ${productsList.length + 1}`]); + const handleAddProduct = () => { + addProduct(`Product ${products.length + 1}`, generateUUID()); }; - const handleRemoveAction = (index: number) => { - setProductsList(productsList.filter((_, i) => i !== index)); - if (selectedItem === productsList[index]) { - setSelectedItem(""); + const handleRemoveProduct = (productId: string) => { + const currentIndex = products.findIndex((p) => p.productId === productId); + const isSelected = selectedProduct.productId === productId; + + const updatedProducts = products.filter((p) => p.productId !== productId); + + if (isSelected) { + if (updatedProducts.length > 0) { + let newSelectedIndex = currentIndex; + if (currentIndex >= updatedProducts.length) { + newSelectedIndex = updatedProducts.length - 1; + } + setSelectedProduct( + updatedProducts[newSelectedIndex].productId, + updatedProducts[newSelectedIndex].productName + ); + } else { + setSelectedProduct("", ""); + } + } + + removeProduct(productId); + }; + + const handleRenameProduct = (productId: string, newName: string) => { + renameProduct(productId, newName); + if (selectedProduct.productId === productId) { + setSelectedProduct(productId, newName); } }; - const Value = [ - { pathName: "Path 1", Children: ["Point 1", "Point 2"] }, - { pathName: "Path 2", Children: ["Point 1", "Point 2"] }, - { pathName: "Path 3", Children: ["Point 1", "Point 2"] }, - ]; + const handleRemoveEventFromProduct = () => { + if (selectedAsset) { + removeEvent(selectedProduct.productId, selectedAsset.modelUuid); + clearSelectedAsset(); + } + }; + + const selectedProductData = products.find( + (product) => product.productId === selectedProduct.productId + ); + + const events: Event[] = + selectedProductData?.eventDatas.map((event) => ({ + pathName: event.modelName, + })) || []; return (
@@ -74,7 +105,7 @@ const Simulations: React.FC = () => {
Products
-
+
Add
@@ -84,26 +115,42 @@ const Simulations: React.FC = () => { style={{ height: "120px" }} >
- {productsList.map((action, index) => ( + {products.map((product, index) => (
setSelectedItem(action)} + onClick={() => + setSelectedProduct(product.productId, product.productName) + } > - - -
-
handleRemoveAction(index)} - > - + + + handleRenameProduct(product.productId, newName) + } + />
+ {products.length > 1 && ( +
handleRemoveProduct(product.productId)} + > + +
+ )}
))}
@@ -116,17 +163,19 @@ const Simulations: React.FC = () => {
+
-
Operations
+
Events
- {Value.map((val, index) => ( - + {events.map((event, index) => ( + ))}
+
Need to Compare Layout? @@ -140,6 +189,26 @@ const Simulations: React.FC = () => {
+ + {selectedAsset && ( + + { + if (option === "Add to Product") { + handleAddEventToProduct({ + selectedAsset, + addEvent, + selectedProduct, + clearSelectedAsset, + }); + } else { + handleRemoveEventFromProduct(); + } + }} + /> + + )}
); }; diff --git a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx index 04a569a..234b936 100644 --- a/app/src/components/layout/sidebarRight/visualization/design/Design.tsx +++ b/app/src/components/layout/sidebarRight/visualization/design/Design.tsx @@ -1,9 +1,9 @@ import { useState, useEffect, useRef } from "react"; import { useWidgetStore } from "../../../../../store/useWidgetStore"; -import ChartComponent from "../../../sidebarLeft//visualization/widgets/ChartComponent"; +import ChartComponent from "../../../sidebarLeft/visualization/widgets/ChartComponent"; import RegularDropDown from "../../../../ui/inputs/RegularDropDown"; import { WalletIcon } from "../../../../icons/3dChartIcons"; -import SimpleCard from "../../../../../modules//visualization/widgets/floating/cards/SimpleCard"; +import SimpleCard from "../../../../../modules/visualization/widgets/floating/cards/SimpleCard"; interface Widget { id: string; diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index 70b1461..21ea6a7 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -15,7 +15,7 @@ import { } from "../icons/ExportToolsIcons"; import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons"; import useModuleStore, { useThreeDStore } from "../../store/useModuleStore"; -import { handleSaveTemplate } from "../../modules//visualization/functions/handleSaveTemplate"; +import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import useTemplateStore from "../../store/useTemplateStore"; import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; diff --git a/app/src/components/ui/analysis/ProductionCapacity.tsx b/app/src/components/ui/analysis/ProductionCapacity.tsx index 268ea81..192e009 100644 --- a/app/src/components/ui/analysis/ProductionCapacity.tsx +++ b/app/src/components/ui/analysis/ProductionCapacity.tsx @@ -1,10 +1,14 @@ -import React from "react"; +import React, { useState } from "react"; import { ProductionCapacityIcon } from "../../icons/analysis"; -const ProductionCapacity = () => { +const ProductionCapacity = ({ + progressPercent = 10, + avgProcessTime = "28.4 Secs/unit", + machineUtilization = "78%", + throughputValue = 128, + timeRange = { startTime: "08:00 AM", endTime: "09:00 AM" }, +}) => { const totalBars = 6; - const progressPercent = 50; - const barsToFill = Math.floor((progressPercent / 100) * totalBars); const partialFillPercent = ((progressPercent / 100) * totalBars - barsToFill) * 100; @@ -14,8 +18,10 @@ const ProductionCapacity = () => {
-
Throughput Summary
-
08:00 - 09:00 AM
+
Production Capacity
+
+ {timeRange.startTime} - {timeRange.endTime} +
@@ -24,10 +30,10 @@ const ProductionCapacity = () => {
- 128 Units/hour + {throughputValue} Units/hour
- {/* Progress Bar */} + {/* Dynamic Progress Bar */}
{[...Array(totalBars)].map((_, i) => (
@@ -47,11 +53,11 @@ const ProductionCapacity = () => {
Avg. Process Time - 28.4 Secs/unit + {avgProcessTime}
Machine Utilization - 78% + {machineUtilization}
diff --git a/app/src/components/ui/analysis/ROISummary.tsx b/app/src/components/ui/analysis/ROISummary.tsx index cacd2c7..11101fd 100644 --- a/app/src/components/ui/analysis/ROISummary.tsx +++ b/app/src/components/ui/analysis/ROISummary.tsx @@ -1,54 +1,68 @@ -import React from "react"; +import React, { useState } from "react"; import { ROISummaryIcon } from "../../icons/analysis"; import SemiCircleProgress from "./SemiCircleProgress"; -const ROISummary = () => { - // Data for the cost breakdown as an array of objects - const costBreakdownData = [ - { - item: "Raw Material A", - unitCost: "₹ 10/unit", - laborCost: "₹ 0", - totalCost: "₹ 1000", - sellingPrice: "₹ 1500", - }, - { - item: "Labor", - unitCost: "₹ 10/unit", - laborCost: "₹ 500", - totalCost: "₹ 500", - sellingPrice: "N/A", - }, - { - item: "Product 1", - unitCost: "₹ 10/unit", - laborCost: "₹ 200", - totalCost: "₹ 200", - sellingPrice: "₹ 2000", - }, - { - item: "Machine", - unitCost: "-", - laborCost: "-", - totalCost: "₹ 20,000", - sellingPrice: "N/A", - }, - { - item: "Total", - unitCost: "-", - laborCost: "-", - totalCost: "₹ 1,20,000", - sellingPrice: "-", - }, - { - item: "Net Profit", - unitCost: "-", - laborCost: "-", - totalCost: "₹ 1,60,000", - sellingPrice: "-", - }, - ]; - const progressValue = 50; +const ROISummary = ({ + roiSummaryData = { + productName: "Product name", + roiPercentage: 133, + paybackPeriod: 50.3, + totalCost: "₹ 1,20,000", + revenueGenerated: "₹ 2,80,000", + netProfit: "₹ 1,60,000", + costBreakdown: [ + { + item: "Raw Material A", + unitCost: "₹ 10/unit", + laborCost: "₹ 0", + totalCost: "₹ 1000", + sellingPrice: "₹ 1500", + }, + { + item: "Labor", + unitCost: "₹ 10/unit", + laborCost: "₹ 500", + totalCost: "₹ 500", + sellingPrice: "N/A", + }, + { + item: "Product 1", + unitCost: "₹ 10/unit", + laborCost: "₹ 200", + totalCost: "₹ 200", + sellingPrice: "₹ 2000", + }, + { + item: "Machine", + unitCost: "-", + laborCost: "-", + totalCost: "₹ 20,000", + sellingPrice: "N/A", + }, + { + item: "Total", + unitCost: "-", + laborCost: "-", + totalCost: "₹ 1,20,000", + sellingPrice: "-", + }, + { + item: "Net Profit", + unitCost: "-", + laborCost: "-", + totalCost: "₹ 1,60,000", + sellingPrice: "-", + }, + ], + }, +}) => { + const [isTableOpen, setIsTableOpen] = useState(false); // State to handle the table open/close + + // Function to toggle the breakdown table visibility + const toggleTable = () => { + setIsTableOpen(!isTableOpen); + }; + return (
@@ -63,71 +77,94 @@ const ROISummary = () => {
Product :
-
Product name
+
{roiSummaryData.productName}
- +133% ROI with payback in just 50.3 months + +{roiSummaryData.roiPercentage}% ROI with payback in + just {roiSummaryData.paybackPeriod} months
- - +
+ +
+ you're on track to hit it by +
July 2029
+
+
Total Cost Incurred - ₹ 1,20,000 + {roiSummaryData.totalCost}
Revenue Generated - ₹ 2,80,000 + + {roiSummaryData.revenueGenerated} +
Net Profit - ₹ 1,60,000 + {roiSummaryData.netProfit}
-
- - Cost Breakdown - +
+
+ + Cost Breakdown +
+ + + {isTableOpen ? "⌵" : "⌵"} +
- - - - - - - - - - - - {costBreakdownData.map((row, index) => ( - - - - - - +
+
ItemUnit CostLabor CostTotal CostSelling Price
{row.item}{row.unitCost}{row.laborCost}{row.totalCost}{row.sellingPrice}
+ + + + + + + - ))} - -
ItemUnit CostLabor CostTotal CostSelling Price
+ + + {roiSummaryData.costBreakdown.map((row, index) => ( + + {row.item} + {row.unitCost} + {row.laborCost} + {row.totalCost} + {row.sellingPrice} + + ))} + + +
diff --git a/app/src/components/ui/analysis/SemiCircleProgress.tsx b/app/src/components/ui/analysis/SemiCircleProgress.tsx index bd11d28..084636b 100644 --- a/app/src/components/ui/analysis/SemiCircleProgress.tsx +++ b/app/src/components/ui/analysis/SemiCircleProgress.tsx @@ -1,23 +1,25 @@ import React from "react"; const SemiCircleProgress = () => { - const progress = 10; - const clampedProgress = Math.min(Math.max(progress, 0), 100); // clamp 0-100 + const progress = 50; + const clampedProgress = Math.min(Math.max(progress, 0), 100); + const gradientProgress = clampedProgress * 0.5; + return (
-
{clampedProgress}%
+
+
{clampedProgress}%
+
Years
+
+
you're on track to hit it by July 2029
); }; diff --git a/app/src/components/ui/analysis/ThroughputSummary.tsx b/app/src/components/ui/analysis/ThroughputSummary.tsx index cb4fac5..b92a82d 100644 --- a/app/src/components/ui/analysis/ThroughputSummary.tsx +++ b/app/src/components/ui/analysis/ThroughputSummary.tsx @@ -11,21 +11,57 @@ import { PowerIcon, ThroughputSummaryIcon } from "../../icons/analysis"; ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement); +// Helper function to generate random colors +const getRandomColor = () => { + const letters = "0123456789ABCDEF"; + let color = "#"; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + const ThroughputSummary = () => { - const data = { + // Define all data internally within the component + const timeRange = { + startTime: "08:00 AM", + endTime: "09:00 AM", + }; + + const throughputData = { labels: ["08:00", "08:10", "08:20", "08:30", "08:40", "08:50", "09:00"], + data: [100, 120, 110, 130, 125, 128, 132], + totalThroughput: 1240, + assetUsage: 85, + }; + + const energyConsumption = { + energyConsumed: 456, + unit: "KWH", + }; + + // Dynamic shift data + const shiftUtilization = [ + { shift: 1, percentage: 30 }, + { shift: 2, percentage: 40 }, + { shift: 3, percentage: 30 }, + ]; + + // Chart data configuration + const chartData = { + labels: throughputData.labels, datasets: [ { label: "Units/hour", - data: [100, 120, 110, 130, 125, 128, 132], + data: throughputData.data, borderColor: "#B392F0", tension: 0.4, - pointRadius: 0, // hide points + pointRadius: 0, // Hide points }, ], }; - const options = { + const chartOptions = { responsive: true, scales: { x: { @@ -57,19 +93,15 @@ const ThroughputSummary = () => { }, }; - const shiftUtilization = { - "shift 1": 25, - "shift 2": 45, - "shift 3": 15, - }; - return (
Throughput Summary
-
08:00 - 09:00 AM
+
+ {timeRange.startTime} - {timeRange.endTime} +
@@ -78,14 +110,15 @@ const ThroughputSummary = () => {
- 1240 Units/hour + {throughputData.totalThroughput}{" "} + Units/hour
Asset usage
-
85%
+
{throughputData.assetUsage}%
- +
@@ -97,43 +130,41 @@ const ThroughputSummary = () => {
-
456
-
KWH
+
{energyConsumption.energyConsumed}
+
{energyConsumption.unit}
Shift Utilization
-
85%
+
{throughputData.assetUsage}%
-
-
-
+ {/* Dynamically create progress bars based on shiftUtilization array */} + {shiftUtilization.map((shift) => ( +
+ ))}
+
-
- - -
-
- - -
-
- - -
+ {/* Dynamically create shift indicators with random colors */} + {shiftUtilization.map((shift) => ( +
+ + +
+ ))}
diff --git a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx index e2c2936..53e03ce 100644 --- a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx +++ b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx @@ -1,47 +1,72 @@ import React, { useState } from "react"; -import LabledDropdown from "./LabledDropdown"; import { ArrowIcon } from "../../icons/ExportCommonIcons"; +import LabledDropdown from "./LabledDropdown"; -const PreviewSelectionWithUpload: React.FC = () => { - const [showPreview, setSetshowPreview] = useState(false); +interface PreviewSelectionWithUploadProps { + preview?: boolean; + upload?: boolean; + label?: string; + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const PreviewSelectionWithUpload: React.FC = ({ + preview = false, + upload = false, + onSelect, + label, + defaultOption, + options, +}) => { + const [showPreview, setShowPreview] = useState(false); return (
-
setSetshowPreview(!showPreview)} - > -
Preview
-
- -
-
- {showPreview && ( -
-
+ {preview && ( + <> + + {showPreview && ( +
+
+
+ )} + + )} + {upload && ( +
+
+
Upload Product
+ + +
)} -
-
-
Upload Product
- - -
- console.log(option)} - /> -
+
); }; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index cac0b43..06419af 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -10,7 +10,7 @@ import { ArrowIcon, EyeIcon, LockIcon, - RmoveIcon, + RemoveIcon, } from "../../icons/ExportCommonIcons"; import { useThree } from "@react-three/fiber"; import { useFloorItems, useZoneAssetId, useZones } from "../../../store/store"; @@ -142,9 +142,6 @@ const List: React.FC = ({ items = [], remove }) => { ) ); } - - console.log('newName: ', newName); - } const checkZoneNameDuplicate = (name: string) => { return zones.some( @@ -184,7 +181,7 @@ const List: React.FC = ({ items = [], remove }) => {
{remove && (
- +
)} {item.assets && item.assets.length > 0 && ( @@ -221,7 +218,7 @@ const List: React.FC = ({ items = [], remove }) => {
{remove && (
- +
)}
diff --git a/app/src/components/ui/menu/EditWidgetOption.tsx b/app/src/components/ui/menu/EditWidgetOption.tsx index 6fd1e94..eb937c8 100644 --- a/app/src/components/ui/menu/EditWidgetOption.tsx +++ b/app/src/components/ui/menu/EditWidgetOption.tsx @@ -1,23 +1,20 @@ import React, { useEffect } from "react"; import { - useEditWidgetOptionsStore, useLeftData, - useRightClickSelected, - useRightSelected, useTopData, } from "../../../store/visualization/useZone3DWidgetStore"; interface EditWidgetOptionProps { options: string[]; + onClick: (option: string) => void; } const EditWidgetOption: React.FC = ({ options, + onClick }) => { const { top } = useTopData(); const { left } = useLeftData(); - const { setRightSelect } = useRightSelected(); - const { setEditWidgetOptions } = useEditWidgetOptionsStore(); useEffect(() => { @@ -38,10 +35,7 @@ const EditWidgetOption: React.FC = ({
{ - setRightSelect(option); - setEditWidgetOptions(false); - }} + onClick={() => onClick(option)} > {option}
diff --git a/app/src/components/ui/simulation/simulationPlayer.tsx b/app/src/components/ui/simulation/simulationPlayer.tsx index 528fbc2..5313710 100644 --- a/app/src/components/ui/simulation/simulationPlayer.tsx +++ b/app/src/components/ui/simulation/simulationPlayer.tsx @@ -7,6 +7,15 @@ import { usePlayButtonStore, useResetButtonStore, } from "../../../store/usePlayButtonStore"; +import { + DailyProductionIcon, + EndIcon, + ExpandIcon, + HourlySimulationIcon, + MonthlyROI, + SpeedIcon, + StartIcon, +} from "../../icons/ExportCommonIcons"; const SimulationPlayer: React.FC = () => { const { speed, setSpeed } = useAnimationPlaySpeed(); @@ -30,7 +39,7 @@ const SimulationPlayer: React.FC = () => { const handleExit = () => { setPlaySimulation(false); setIsPlaying(false); - setActiveTool("cursor") + setActiveTool("cursor"); }; // Slider functions starts @@ -72,70 +81,277 @@ const SimulationPlayer: React.FC = () => { }, []); // Slider function ends + // UI-Part + const hourlySimulation = 25; + const dailyProduction = 75; + const monthlyROI = 50; + + const process = [ + { name: "process 1", completed: 0 }, // 0% completed + { name: "process 2", completed: 20 }, // 20% completed + { name: "process 3", completed: 40 }, // 40% completed + { name: "process 4", completed: 60 }, // 60% completed + { name: "process 5", completed: 80 }, // 80% completed + { name: "process 6", completed: 100 }, // 100% completed + { name: "process 7", completed: 0 }, // 0% completed + { name: "process 8", completed: 50 }, // 50% completed + { name: "process 9", completed: 90 }, // 90% completed + { name: "process 10", completed: 30 }, // 30% completed + ]; + const [expand, setExpand] = useState(false); + // Move getRandomColor out of render + const getRandomColor = () => { + const letters = "0123456789ABCDEF"; + let color = "#"; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; + }; + + // Store colors for each process item + const [processColors, setProcessColors] = useState([]); + + // Generate colors on mount or when process changes + useEffect(() => { + const generatedColors = process.map(() => getRandomColor()); + setProcessColors(generatedColors); + }, []); + + const intervals = [10, 20, 30, 40, 50, 60]; // in minutes + const totalSegments = intervals.length; + const progress = 80; // percent (example) + + const processPlayerRef = useRef(null); + const processWrapperRef = useRef(null); + const [playerPosition, setPlayerPosition] = useState(0); + + const handleProcessMouseDown = (e: React.MouseEvent) => { + if (!processWrapperRef.current) return; + + const rect = processWrapperRef.current.getBoundingClientRect(); + let x = e.clientX - rect.left; + x = Math.max(0, Math.min(x, rect.width)); + setPlayerPosition(x); + + const onMouseMove = (e: MouseEvent) => { + if (!processWrapperRef.current) return; + const newRect = processWrapperRef.current.getBoundingClientRect(); + let newX = e.clientX - newRect.left; + newX = Math.max(0, Math.min(newX, newRect.width)); + setPlayerPosition(newX); + + const progressPercent = (newX / newRect.width) * 100; + console.log(`Dragging at progress: ${progressPercent.toFixed(1)}%`); + }; + + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }; + return (
-
+
-
{ - handleReset(); - }} - > - - Reset -
-
{ - handlePlayStop(); - }} - > - - {playSimulation ? "Play" : "Stop"} -
-
{ - handleExit(); - }} - > - - Exit -
-
-
-
0.5x
-
-
-
-
-
-
-
-
-
-
-
-
- {speed.toFixed(1)}x +
+ {/* hourlySimulation */} +
+
+
+ +
+
Hourly Simulation
- +
+
+
+
+ {/* dailyProduction */} +
+
+
+ +
+
Daily Production
+
+
+
+
+
+ {/* monthlyROI */} +
+
+
+ +
+
Monthly ROI
+
+
+
+
{" "}
-
8x
+
+
{ + handleReset(); + }} + > + + Reset +
+
{ + handlePlayStop(); + }} + > + + {playSimulation ? "Play" : "Stop"} +
+
{ + handleExit(); + }} + > + + Exit +
+
setExpand(!expand)} + > + +
+
+
+
+
+
+
+ +
+
+
23 April ,25
+
04:41 PM
+
+
+
+
+ {intervals.map((label, index) => { + const segmentProgress = (index / totalSegments) * 100; + const isFilled = progress >= segmentProgress; + return ( + +
+
{label} mins
+
+
+ {index < intervals.length - 1 && ( +
= ((index + 1) / totalSegments) * 100 + ? "filled" + : "" + }`} + >
+ )} +
+ ); + })} +
+
+ +
+
+
00:10:20
+
+
+ +
+
+
+
+
+
+ +
+ Speed +
+
+
0X
+
+
+
+
+
+
+
+
+
+
+
+ {speed.toFixed(1)}x +
+ +
+
8x
+
+
+
+
+
+
+ {process.map((item, index) => ( +
+ ))} +
+
diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index e88dc3c..c46c0e7 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -8,10 +8,13 @@ import * as Types from "../../../types/world/worldTypes"; import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils'; import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi'; import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi'; +import PointsCalculator from '../../simulation/events/points/functions/pointsCalculator'; async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, + addEvent: (event: EventsSchema) => void, + renderDistance: number ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -63,14 +66,14 @@ async function loadInitialFloorItems( const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z); - if (cameraPosition.distanceTo(itemPosition) < 50) { + if (cameraPosition.distanceTo(itemPosition) < renderDistance) { await new Promise(async (resolve) => { // Check Three.js Cache const cachedModel = THREE.Cache.get(item.modelfileID!); if (cachedModel) { // console.log(`[Cache] Fetching ${item.modelname}`); - processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); return; @@ -85,7 +88,7 @@ async function loadInitialFloorItems( URL.revokeObjectURL(blobUrl); THREE.Cache.remove(blobUrl); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -106,7 +109,7 @@ async function loadInitialFloorItems( const modelBlob = await fetch(modelUrl).then((res) => res.blob()); await storeGLTF(item.modelfileID!, modelBlob); THREE.Cache.add(item.modelfileID!, gltf); - processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems); + processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems, addEvent); modelsLoaded++; checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve); }, @@ -148,8 +151,9 @@ function processLoadedModel( item: Types.FloorItemType, itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, + addEvent: (event: EventsSchema) => void, ) { - const model = gltf; + const model = gltf.clone(); model.uuid = item.modeluuid; model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap); model.userData = { name: item.modelname, modelId: item.modelfileID, modeluuid: item.modeluuid }; @@ -182,6 +186,242 @@ function processLoadedModel( }, ]); + if (item.modelfileID === "a1ee92554935007b10b3eb05") { + const data = PointsCalculator( + 'Vehicle', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const vehicleEvent: VehicleEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "vehicle", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Vehicle Action", + actionType: "travel", + unLoadDuration: 5, + loadCapacity: 10, + pickUpPoint: null, + unLoadPoint: null, + triggers: [] + } + } + }; + addEvent(vehicleEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + const data = PointsCalculator( + 'Conveyor', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const ConveyorEvent: ConveyorEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "transfer", + speed: 1, + points: data.points.map((point: THREE.Vector3, index: number) => ({ + uuid: THREE.MathUtils.generateUUID(), + position: [point.x, point.y, point.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action ${index + 1}`, + actionType: 'default', + material: 'Default material', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: [] + } + })) + }; + addEvent(ConveyorEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + // const data = PointsCalculator( + // 'Conveyor', + // gltf.clone(), + // new THREE.Vector3(...model.rotation) + // ); + + // if (!data || !data.points) return; + + // const points: ConveyorPointSchema[] = data.points.map((point: THREE.Vector3, index: number) => { + // const actionUuid = THREE.MathUtils.generateUUID(); + // return { + // uuid: THREE.MathUtils.generateUUID(), + // position: [point.x, point.y, point.z], + // rotation: [0, 0, 0], + // action: { + // actionUuid, + // actionName: `Action ${index}`, + // actionType: 'default', + // material: 'inherit', + // delay: 0, + // spawnInterval: 5, + // spawnCount: 1, + // triggers: [] + // } + // }; + // }); + + // points.forEach((point, index) => { + // if (index < points.length - 1) { + // const nextPoint = points[index + 1]; + // point.action.triggers.push({ + // triggerUuid: THREE.MathUtils.generateUUID(), + // triggerName: `Trigger 1`, + // triggerType: "onComplete", + // delay: 0, + // triggeredAsset: { + // triggeredModel: { + // modelName: item.modelname, + // modelUuid: item.modeluuid + // }, + // triggeredPoint: { + // pointName: `Point ${index + 1}`, + // pointUuid: nextPoint.uuid + // }, + // triggeredAction: { + // actionName: nextPoint.action.actionName, + // actionUuid: nextPoint.action.actionUuid + // } + // } + // }); + // } + // }); + + // const ConveyorEvent: ConveyorEventSchema = { + // modelUuid: item.modeluuid, + // modelName: item.modelname, + // position: item.position, + // rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + // state: "idle", + // type: "transfer", + // speed: 1, + // points + // }; + // addEvent(ConveyorEvent); + } else if (item.modelfileID === "7dc04e36882e4debbc1a8e3d") { + const data = PointsCalculator( + 'Conveyor', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const ConveyorEvent: ConveyorEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "transfer", + speed: 1, + points: data.points.map((point: THREE.Vector3, index: number) => ({ + uuid: THREE.MathUtils.generateUUID(), + position: [point.x, point.y, point.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action ${index}`, + actionType: 'default', + material: 'inherit', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: [] + } + })) + }; + addEvent(ConveyorEvent); + } else if (item.modelfileID === "29dee78715ad5b853f5c346d") { + const data = PointsCalculator( + 'StaticMachine', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const machineEvent: MachineEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "machine", + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Process Action", + actionType: "process", + processTime: 10, + swapMaterial: "material-id", + triggers: [] + } + } + }; + addEvent(machineEvent); + } else if (item.modelfileID === "52e6681fbb743a890d96c914") { + const data = PointsCalculator( + 'ArmBot', + gltf.clone(), + new THREE.Vector3(...model.rotation) + ); + + if (!data || !data.points) return; + + const roboticArmEvent: RoboticArmEventSchema = { + modelUuid: item.modeluuid, + modelName: item.modelname, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "roboticArm", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [data.points[0].x, data.points[0].y, data.points[0].z], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Pick and Place", + actionType: "pickAndPlace", + process: { + startPoint: [0, 0, 0], + endPoint: [0, 0, 0] + }, + triggers: [] + } + ] + } + }; + addEvent(roboticArmEvent); + } + gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' }); gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' }); } diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index e7f83e2..e5ff1c1 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -51,7 +51,7 @@ import Ground from "../scene/environment/ground"; // import ZoneGroup from "../groups/zoneGroup1"; import { findEnvironment } from "../../services/factoryBuilder/environment/findEnvironment"; import Layer2DVisibility from "./geomentries/layers/layer2DVisibility"; -import DrieHtmlTemp from "..//visualization/mqttTemp/drieHtmlTemp"; +import DrieHtmlTemp from "../visualization/mqttTemp/drieHtmlTemp"; import ZoneGroup from "./groups/zoneGroup"; import useModuleStore from "../../store/useModuleStore"; import MeasurementTool from "../scene/tools/measurementTool"; diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index 193dd41..d7c278c 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -10,7 +10,7 @@ import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils'; import { Socket } from 'socket.io-client'; import * as CONSTANTS from '../../../../types/world/worldConstants'; import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi'; -import PointsCalculator from '../../../simulation/events/points/pointsCalculator'; +import PointsCalculator from '../../../simulation/events/points/functions/pointsCalculator'; async function addAssetModel( raycaster: THREE.Raycaster, @@ -202,7 +202,7 @@ async function handleModelLoad( actionUuid: THREE.MathUtils.generateUUID(), actionName: `Action ${index}`, actionType: 'default', - material: 'inherit', + material: 'Default Material', delay: 0, spawnInterval: 5, spawnCount: 1, @@ -226,9 +226,8 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Vehicle Action", + actionName: "Action 1", actionType: "travel", - material: null, unLoadDuration: 5, loadCapacity: 10, pickUpPoint: null, @@ -254,11 +253,11 @@ async function handleModelLoad( actions: [ { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Pick and Place", + actionName: "Action 1", actionType: "pickAndPlace", process: { - startPoint: [0, 0, 0], - endPoint: [0, 0, 0] + startPoint: null, + endPoint: null }, triggers: [] } @@ -266,7 +265,7 @@ async function handleModelLoad( } }; addEvent(roboticArmEvent); - } else if (selectedItem.type === "Machine") { + } else if (selectedItem.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modeluuid, modelName: newFloorItem.modelname, @@ -280,10 +279,10 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Process Action", + actionName: "Action 1", actionType: "process", processTime: 10, - swapMaterial: "material-id", + swapMaterial: "Default Material", triggers: [] } } diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 2a3c2cc..f3f5050 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -75,7 +75,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject gltfLoaderWorker.postMessage({ floorItems: data }); } else { gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); @@ -94,7 +94,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject updateLoadingProgress(progress); if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index d3245fb..dd5607a 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -2,90 +2,107 @@ import * as THREE from "three"; import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; import { BlendFunction } from "postprocessing"; import { - useDeletableFloorItem, - useSelectedWallItem, - useSelectedFloorItem, + useDeletableFloorItem, + useSelectedWallItem, + useSelectedFloorItem, } from "../../../store/store"; import * as Types from "../../../types/world/worldTypes"; import * as CONSTANTS from "../../../types/world/worldConstants"; import { useEffect } from "react"; +import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; export default function PostProcessing() { - const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); - const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); + const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedEventSphere } = useSelectedEventSphere(); - function flattenChildren(children: any[]) { - const allChildren: any[] = []; - children.forEach((child) => { - allChildren.push(child); - if (child.children && child.children.length > 0) { - allChildren.push(...flattenChildren(child.children)); - } - }); - return allChildren; - } + function flattenChildren(children: any[]) { + const allChildren: any[] = []; + children.forEach((child) => { + allChildren.push(child); + if (child.children && child.children.length > 0) { + allChildren.push(...flattenChildren(child.children)); + } + }); + return allChildren; + } - return ( - <> - - - {deletableFloorItem && ( - - )} - {selectedWallItem && ( - child.name !== "CSG_REF" - )} - selectionLayer={10} - width={3000} - blendFunction={BlendFunction.ALPHA} - edgeStrength={5} - resolutionScale={2} - pulseSpeed={0} - visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} - blur={true} - xRay={true} - /> - )} - {selectedFloorItem && ( - - )} - - - ); + return ( + <> + + + {deletableFloorItem && ( + + )} + {selectedWallItem && ( + child.name !== "CSG_REF" + )} + selectionLayer={10} + width={3000} + blendFunction={BlendFunction.ALPHA} + edgeStrength={5} + resolutionScale={2} + pulseSpeed={0} + visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor} + blur={true} + xRay={true} + /> + )} + {selectedFloorItem && ( + + )} + {selectedEventSphere && ( + + )} + + + ); } diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index 6564032..0f2c489 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -9,28 +9,35 @@ import Simulation from "../simulation/simulation"; import Collaboration from "../collaboration/collaboration"; export default function Scene() { - const map = useMemo(() => [ - { name: "forward", keys: ["ArrowUp", "w", "W"] }, - { name: "backward", keys: ["ArrowDown", "s", "S"] }, - { name: "left", keys: ["ArrowLeft", "a", "A"] }, - { name: "right", keys: ["ArrowRight", "d", "D"] },], - []); + const map = useMemo( + () => [ + { name: "forward", keys: ["ArrowUp", "w", "W"] }, + { name: "backward", keys: ["ArrowDown", "s", "S"] }, + { name: "left", keys: ["ArrowLeft", "a", "A"] }, + { name: "right", keys: ["ArrowRight", "d", "D"] }, + ], + [] + ); - return ( - - { e.preventDefault(); }}> + return ( + + { + e.preventDefault(); + }} + > + - + - + - + - - - - - - - ); + + + + ); } diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 335f1f5..39fe257 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -1,148 +1,237 @@ -import React, { useEffect, useRef, useState } from 'react'; -import * as THREE from 'three'; -import { useEventsStore } from '../../../../../store/simulation/useEventsStore'; -import useModuleStore from '../../../../../store/useModuleStore'; -import { TransformControls } from '@react-three/drei'; -import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys'; +import React, { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; +import { useEventsStore } from "../../../../../store/simulation/useEventsStore"; +import useModuleStore from "../../../../../store/useModuleStore"; +import { TransformControls } from "@react-three/drei"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { + useSelectedEventSphere, + useSelectedEventData, +} from "../../../../../store/simulation/useSimulationStore"; function PointsCreator() { - const { events, updatePoint, getPointByUuid } = useEventsStore(); - const { activeModule } = useModuleStore(); - const transformRef = useRef(null); - const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null); - const [selectedPoint, setSelectedPoint] = useState(null); - const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const { events, updatePoint, getPointByUuid, getEventByModelUuid } = + useEventsStore(); + const { activeModule } = useModuleStore(); + const transformRef = useRef(null); + const [transformMode, setTransformMode] = useState< + "translate" | "rotate" | null + >(null); + const sphereRefs = useRef<{ [key: string]: THREE.Mesh }>({}); + const { + selectedEventSphere, + setSelectedEventSphere, + clearSelectedEventSphere, + } = useSelectedEventSphere(); + const { setSelectedEventData, clearSelectedEventData } = + useSelectedEventData(); - useEffect(() => { - setTransformMode(null); - const handleKeyDown = (e: KeyboardEvent) => { - const keyCombination = detectModifierKeys(e); - if (!selectedPoint) return; - if (keyCombination === "G") { - setTransformMode((prev) => (prev === "translate" ? null : "translate")); - } - if (keyCombination === "R") { - setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [selectedPoint]); - - const updatePointToState = (selectedPoint: THREE.Mesh) => { - let point = JSON.parse(JSON.stringify(getPointByUuid(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid))); - if (point) { - point.position = [selectedPoint.position.x, selectedPoint.position.y, selectedPoint.position.z]; - updatePoint(selectedPoint.userData.modelUuid, selectedPoint.userData.pointUuid, point) - } + useEffect(() => { + if (selectedEventSphere) { + const eventData = getEventByModelUuid( + selectedEventSphere.userData.modelUuid + ); + if (eventData) { + setSelectedEventData(eventData, selectedEventSphere.userData.pointUuid); + } else { + clearSelectedEventData(); + } + } else { + clearSelectedEventData(); } + }, [selectedEventSphere]); - return ( - <> - {activeModule === 'simulation' && - <> - - {events.map((event, i) => { - if (event.type === 'transfer') { - return ( - - {event.points.map((point, j) => ( - (sphereRefs.current[point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedPoint(sphereRefs.current[point.uuid]); - }} - onPointerMissed={() => { - setSelectedPoint(null); - }} - key={`${i}-${j}`} - position={new THREE.Vector3(...point.position)} - userData={{ modelUuid: event.modelUuid, pointUuid: point.uuid }} - > - - - - ))} - - ); - } else if (event.type === 'vehicle') { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); - }} - onPointerMissed={() => { - setSelectedPoint(null); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} - > - - - - - ); - } else if (event.type === 'roboticArm') { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); - }} - onPointerMissed={() => { - setSelectedPoint(null); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} - > - - - - - ); - } else if (event.type === 'machine') { - return ( - - (sphereRefs.current[event.point.uuid] = el!)} - onClick={(e) => { - e.stopPropagation(); - setSelectedPoint(sphereRefs.current[event.point.uuid]); - }} - onPointerMissed={() => { - setSelectedPoint(null); - }} - position={new THREE.Vector3(...event.point.position)} - userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} - > - - - - - ); - } else { - return null; - } - })} - - {(selectedPoint && transformMode) && - { updatePointToState(selectedPoint) }} /> - } - - } - + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + const keyCombination = detectModifierKeys(e); + if (!selectedEventSphere) return; + if (keyCombination === "G") { + setTransformMode((prev) => (prev === "translate" ? null : "translate")); + } + if (keyCombination === "R") { + setTransformMode((prev) => (prev === "rotate" ? null : "rotate")); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedEventSphere]); + + const updatePointToState = (selectedEventSphere: THREE.Mesh) => { + let point = JSON.parse( + JSON.stringify( + getPointByUuid( + selectedEventSphere.userData.modelUuid, + selectedEventSphere.userData.pointUuid + ) + ) ); + if (point) { + point.position = [ + selectedEventSphere.position.x, + selectedEventSphere.position.y, + selectedEventSphere.position.z, + ]; + updatePoint( + selectedEventSphere.userData.modelUuid, + selectedEventSphere.userData.pointUuid, + point + ); + } + }; + + return ( + <> + {activeModule === "simulation" && ( + <> + + {events.map((event, i) => { + if (event.type === "transfer") { + return ( + + {event.points.map((point, j) => ( + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + key={`${i}-${j}`} + position={new THREE.Vector3(...point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: point.uuid, + }} + > + + + + ))} + + ); + } else if (event.type === "vehicle") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else if (event.type === "machine") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else if (event.type === "roboticArm") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else { + return null; + } + })} + + {selectedEventSphere && transformMode && ( + { + updatePointToState(selectedEventSphere); + }} + /> + )} + + )} + + ); } export default PointsCreator; diff --git a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts new file mode 100644 index 0000000..7943c1c --- /dev/null +++ b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts @@ -0,0 +1,28 @@ +interface HandleAddEventToProductParams { + selectedAsset: any; // Replace `any` with specific type if you have it + addEvent: (productId: string, asset: any) => void; + selectedProduct: { + productId: string; + productName: string; + // Add other fields if needed + }; + clearSelectedAsset: () => void; +} + +export const handleAddEventToProduct = ({ + selectedAsset, + addEvent, + selectedProduct, + clearSelectedAsset, +}: HandleAddEventToProductParams) => { + console.log('selectedProduct: ', selectedProduct); + if (selectedAsset) { + addEvent(selectedProduct.productId, selectedAsset); + // upsertProductOrEventApi({ + // productName: selectedProduct.productName, + // productId: selectedProduct.productId, + // eventDatas: selectedAsset + // }); + clearSelectedAsset(); + } +}; diff --git a/app/src/modules/simulation/events/points/pointsCalculator.ts b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts similarity index 96% rename from app/src/modules/simulation/events/points/pointsCalculator.ts rename to app/src/modules/simulation/events/points/functions/pointsCalculator.ts index 86d368e..ccc2dbb 100644 --- a/app/src/modules/simulation/events/points/pointsCalculator.ts +++ b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { Group } from '../../../../types/world/worldTypes'; +import { Group } from '../../../../../types/world/worldTypes'; function PointsCalculator( type: string, diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx new file mode 100644 index 0000000..edb825f --- /dev/null +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function MachineInstance() { + return ( + <> + + ) +} + +export default MachineInstance \ No newline at end of file diff --git a/app/src/modules/simulation/machine/instances/machineInstances.tsx b/app/src/modules/simulation/machine/instances/machineInstances.tsx new file mode 100644 index 0000000..b0c2c9f --- /dev/null +++ b/app/src/modules/simulation/machine/instances/machineInstances.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import MachineInstance from './machineInstance/machineInstance' + +function MachineInstances() { + return ( + <> + + + + + ) +} + +export default MachineInstances \ No newline at end of file diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx new file mode 100644 index 0000000..32c3b8e --- /dev/null +++ b/app/src/modules/simulation/machine/machine.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import MachineInstances from './instances/machineInstances' + +function Machine() { + return ( + <> + + + + + ) +} + +export default Machine \ No newline at end of file diff --git a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx new file mode 100644 index 0000000..9eececc --- /dev/null +++ b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx @@ -0,0 +1,124 @@ +import { useThree } from '@react-three/fiber' +import { useEffect } from 'react' +import { Object3D } from 'three'; +import { useSubModuleStore } from '../../../../store/useModuleStore'; +import { useLeftData, useTopData } from '../../../../store/visualization/useZone3DWidgetStore'; +import { useEventsStore } from '../../../../store/simulation/useEventsStore'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; + +function AddOrRemoveEventsInProducts() { + const { gl, raycaster, scene } = useThree(); + const { subModule } = useSubModuleStore(); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + const { getIsEventInProduct } = useProductStore(); + const { getEventByModelUuid } = useEventsStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAsset, setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); + + useEffect(() => { + + const canvasElement = gl.domElement; + + let drag = false; + let isRightMouseDown = false; + + const onMouseDown = (evt: MouseEvent) => { + if (selectedAsset) { + clearSelectedAsset(); + } + if (evt.button === 2) { + isRightMouseDown = true; + drag = false; + } + }; + + const onMouseUp = (evt: MouseEvent) => { + if (evt.button === 2) { + isRightMouseDown = false; + } + } + + const onMouseMove = () => { + if (isRightMouseDown) { + drag = true; + } + }; + + const handleRightClick = (evt: MouseEvent) => { + if (drag) return; + evt.preventDefault(); + const canvasElement = gl.domElement; + if (!canvasElement) return; + + let intersects = raycaster.intersectObjects(scene.children, true); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + let currentObject = intersects[0].object; + + while (currentObject) { + if (currentObject.name === "Scene") { + break; + } + currentObject = currentObject.parent as Object3D; + } + if (currentObject) { + const isInProduct = getIsEventInProduct(selectedProduct.productId, currentObject.uuid); + + if (isInProduct) { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } else { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } + + } + } else { + clearSelectedAsset() + } + + }; + + if (subModule === 'simulations') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener('contextmenu', handleRightClick); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener('contextmenu', handleRightClick); + }; + + }, [gl, subModule, selectedProduct, selectedAsset]); + return ( + <> + ) +} + +export default AddOrRemoveEventsInProducts \ No newline at end of file diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx new file mode 100644 index 0000000..38175e2 --- /dev/null +++ b/app/src/modules/simulation/products/products.tsx @@ -0,0 +1,43 @@ +import * as THREE from 'three'; +import { useEffect } from 'react'; +import { useProductStore } from '../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; +import AddOrRemoveEventsInProducts from './events/addOrRemoveEventsInProducts'; +import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProductOrEventApi'; +import { getAllProductsApi } from '../../../services/simulation/getallProductsApi'; + +function Products() { + const { products, addProduct } = useProductStore(); + const { setSelectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (products.length === 0) { + const id = THREE.MathUtils.generateUUID(); + const name = 'Product 1'; + addProduct(name, id); + // upsertProductOrEventApi({ productName: name, productId: id }).then((data) => { + // console.log('data: ', data); + // }); + setSelectedProduct(id, name); + } + }, [products]) + + useEffect(() => { + // const email = localStorage.getItem('email') + // const organization = (email!.split("@")[1]).split(".")[0]; + // console.log(organization); + // getAllProductsApi(organization).then((data) => { + // console.log('data: ', data); + // }) + }, []) + + return ( + <> + + + + + ) +} + +export default Products \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 0cd4fe2..fd7590e 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,6 +1,64 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from "three" +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; + +function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) { + const { armBots } = useArmBotStore(); + const { scene } = useThree(); + const restSpeed = 0.1; + const restPosition = new THREE.Vector3(0, 2, 1.6); + const initialCurveRef = useRef(null); + const initialStartPositionRef = useRef(null); + const [initialProgress, setInitialProgress] = useState(0); + const [progress, setProgress] = useState(0); + const [needsInitialMovement, setNeedsInitialMovement] = useState(true); + const [isInitializing, setIsInitializing] = useState(true); + const { isPlaying } = usePlayButtonStore(); + const statusRef = useRef("idle"); + // Create a ref for initialProgress + const initialProgressRef = useRef(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + + useEffect(() => { + setCurrentPath(path) + }, [path]) + + useEffect(() => { + + }, [currentPath]) + + useFrame((_, delta) => { + if (!ikSolver || !currentPath || currentPath.length === 0) return; + + const bone = ikSolver.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + if (!bone) return; + + // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it + const curve = new THREE.CatmullRomCurve3( + currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2])) + ); + + const next = initialProgressRef.current + delta * 0.5; + if (next >= 1) { + bone.position.copy(restPosition); + HandleCallback(); // Call the callback when the path is completed + initialProgressRef.current = 0; // Set ref to 1 when done + } else { + + const point = curve.getPoint(next); // Get the interpolated point from the curve + bone.position.copy(point); // Update the bone position along the curve + initialProgressRef.current = next; // Update progress + } + + ikSolver.update(); + }); + + -function RoboticArmAnimator() { return ( <> ) diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 2817906..86eee6a 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,14 +1,179 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; +import { useThree } from "@react-three/fiber"; +import { useFloorItems } from '../../../../../store/store'; +import useModuleStore from '../../../../../store/useModuleStore'; +import { Vector3 } from "three"; +import * as THREE from "three"; + +interface Process { + triggerId: string; + startPoint?: Vector3; + endPoint?: Vector3; + speed: number; +} +function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { + + const { isPlaying } = usePlayButtonStore(); + const [currentPhase, setCurrentPhase] = useState<(string)>("init"); + const { scene } = useThree(); + const targetBone = "Target"; + const { activeModule } = useModuleStore(); + const [ikSolver, setIkSolver] = useState(null); + const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { floorItems } = useFloorItems(); + const groupRef = useRef(null); + const [processes, setProcesses] = useState([]); + const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] }) + const restPosition = new THREE.Vector3(0, 2, 1.6); + let armBotCurveRef = useRef(null) + const [path, setPath] = useState<[number, number, number][]>([]); + + useEffect(() => { + let armItems = floorItems?.filter((val: any) => + val.modeluuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d" + ); + // Get the first matching item + let armItem = armItems?.[0]; + if (armItem) { + const targetMesh = scene?.getObjectByProperty("uuid", armItem.modeluuid); + if (targetMesh) { + targetMesh.visible = activeModule !== "simulation" + } + } + const targetBones = ikSolver?.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + + if (isPlaying) { + //Moving armBot from initial point to rest position. + if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") { + + setArmBotActive(robot.modelUuid, true) + setArmBotState(robot.modelUuid, "running") + setCurrentPhase("init-to-rest"); + if (targetBones) { + let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from init to rest") + } + //Waiting for trigger. + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) { + console.log("trigger"); + setTimeout(() => { + addCurrentAction(robot.modelUuid, 'action-003'); + }, 3000); + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) { + if (robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("rest-to-start"); + const startPoint = robot.point.actions[0].process.startPoint; + if (startPoint) { + let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + } + logStatus(robot.modelUuid, "Starting from rest to start") + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("start-to-end"); + const startPoint = robot.point.actions[0].process.startPoint; + const endPoint = robot.point.actions[0].process.endPoint; + if (startPoint && endPoint) { + let curve = createCurveBetweenTwoPoints( + new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), + new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]) + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from start to end") + } + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("end-to-rest"); + const endPoint = robot.point.actions[0].process.endPoint; + if (endPoint) { + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from end to rest") + } + } + + }, [currentPhase, robot, isPlaying, ikSolver]) + + + function createCurveBetweenTwoPoints(p1: any, p2: any) { + const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); + mid.y += 0.5; + + const points = [p1, mid, p2]; + return new THREE.CatmullRomCurve3(points); + } + + + const HandleCallback = () => { + if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") { + console.log("Callback triggered: rest"); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") { + console.log("Callback triggered: pick."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("picking"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") { + console.log("Callback triggered: drop."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("dropping"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") { + console.log("Callback triggered: rest, cycle completed."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) + removeCurrentAction(robot.modelUuid) + } + } + const logStatus = (id: string, status: string) => { + console.log(id +","+ status); + } -function RoboticArmInstance() { return ( <> - - - + + ) diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 52a8610..a8d8782 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -1,8 +1,87 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as THREE from "three"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { clone } from "three/examples/jsm/utils/SkeletonUtils"; +import { useFrame, useLoader, useThree } from "@react-three/fiber"; +import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver"; +type IKInstanceProps = { + modelUrl: string; + ikSolver: any; + setIkSolver: any + robot: any; + groupRef: React.RefObject; + processes: any; + setArmBotCurvePoints: any +}; +function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) { + const { scene } = useThree(); + const gltf = useLoader(GLTFLoader, modelUrl, (loader) => { + const draco = new DRACOLoader(); + draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"); + loader.setDRACOLoader(draco); + }); + const cloned = useMemo(() => clone(gltf?.scene), [gltf]); + const targetBoneName = "Target"; + const skinnedMeshName = "link_0"; + useEffect(() => { + if (!gltf) return; + const OOI: any = {}; + cloned.traverse((n: any) => { + if (n.name === targetBoneName) OOI.Target_Bone = n; + if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n; + }); + if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return; + const iks = [ + { + target: 7, + effector: 6, + links: [ + { + index: 5, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0), + }, + { + index: 4, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(0, 0, 0), + }, + { + index: 3, + enabled: true, + rotationMin: new THREE.Vector3(0, 0, 0), + rotationMax: new THREE.Vector3(2, 0, 0), + }, + { index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) }, + { index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) }, + ], + }, + ]; + + const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks); + setIkSolver(solver); + + const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05); + + // scene.add(groupRef.current) + + + }, [gltf]); -function IKInstance() { return ( - <> + <> + + + + ) } diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx deleted file mode 100644 index d44ddd2..0000000 --- a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import IKInstance from './ikInstance/ikInstance'; - -function IkInstances() { - return ( - <> - - - - - ) -} - -export default IkInstances; \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx index 6e8a70a..1089fa5 100644 --- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx +++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx @@ -1,11 +1,15 @@ import React from 'react' import RoboticArmInstance from './armInstance/roboticArmInstance'; +import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; function RoboticArmInstances() { + const { armBots } = useArmBotStore(); + return ( <> - - + {armBots?.map((robot: ArmBotStatus) => ( + + ))} ) diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx index 1270d93..dcf01da 100644 --- a/app/src/modules/simulation/roboticArm/roboticArm.tsx +++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx @@ -1,15 +1,166 @@ -import React from 'react' -import RoboticArmInstances from './instances/roboticArmInstances'; -import IkInstances from './instances/ikInstances'; +import { useEffect } from "react"; +import RoboticArmInstances from "./instances/roboticArmInstances"; +import { useArmBotStore } from "../../../store/simulation/useArmBotStore"; +import { useFloorItems } from "../../../store/store"; function RoboticArm() { + const { armBots, addArmBot, removeArmBot } = useArmBotStore(); + const { floorItems } = useFloorItems(); + + const armBotStatusSample: RoboticArmEventSchema[] = [ + { + state: "idle", + modelUuid: "armbot-xyz-001", + modelName: "ArmBot-X200", + position: [91.94347308985614, 0, 6.742905194869091], + rotation: [0, 0, 0], + type: "roboticArm", + speed: 1.5, + point: { + uuid: "point-123", + position: [0, 1.5, 0], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: "action-003", + actionName: "Pick Component", + actionType: "pickAndPlace", + process: { + startPoint: [5.52543010919071, 1, -8.433681161200905], + endPoint: [10.52543010919071, 1, -12.433681161200905], + }, + triggers: [ + { + triggerUuid: "trigger-001", + triggerName: "Start Trigger", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "Conveyor A1", + modelUuid: "conveyor-01", + }, + triggeredPoint: { + pointName: "Start Point", + pointUuid: "conveyor-01-point-001", + }, + triggeredAction: { + actionName: "Move Forward", + actionUuid: "conveyor-action-01", + }, + }, + }, + { + triggerUuid: "trigger-002", + triggerName: "Complete Trigger", + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "StaticMachine B2", + modelUuid: "machine-02", + }, + triggeredPoint: { + pointName: "Receive Point", + pointUuid: "machine-02-point-001", + }, + triggeredAction: { + actionName: "Process Part", + actionUuid: "machine-action-01", + }, + }, + }, + ], + }, + ], + }, + }, + { + state: "idle", + modelUuid: "armbot-xyz-002", + modelName: "ArmBot-X200", + position: [95.94347308985614, 0, 6.742905194869091], + rotation: [0, 0, 0], + type: "roboticArm", + speed: 1.5, + point: { + uuid: "point-123", + position: [0, 1.5, 0], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: "action-001", + actionName: "Pick Component", + actionType: "pickAndPlace", + process: { + startPoint: [2.52543010919071, 0, 8.433681161200905], + endPoint: [95.3438373267953, 0, 9.0279187421610025], + }, + triggers: [ + { + triggerUuid: "trigger-001", + triggerName: "Start Trigger", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "Conveyor A1", + modelUuid: "conveyor-01", + }, + triggeredPoint: { + pointName: "Start Point", + pointUuid: "conveyor-01-point-001", + }, + triggeredAction: { + actionName: "Move Forward", + actionUuid: "conveyor-action-01", + }, + }, + }, + { + triggerUuid: "trigger-002", + triggerName: "Complete Trigger", + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "StaticMachine B2", + modelUuid: "machine-02", + }, + triggeredPoint: { + pointName: "Receive Point", + pointUuid: "machine-02-point-001", + }, + triggeredAction: { + actionName: "Process Part", + actionUuid: "machine-action-01", + }, + }, + }, + ], + }, + ], + }, + }, + ]; + + useEffect(() => { + + removeArmBot(armBotStatusSample[0].modelUuid); + addArmBot('123', armBotStatusSample[0]); + // addArmBot('123', armBotStatusSample[1]); + // addCurrentAction('armbot-xyz-001', 'action-001'); + }, []); + + useEffect(() => { + // + }, [armBots]); + return ( <> - - - ) + ); } -export default RoboticArm; \ No newline at end of file +export default RoboticArm; diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index b7bf36d..5ca0ec5 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -5,8 +5,16 @@ import Vehicles from './vehicle/vehicles'; import Points from './events/points/points'; import Conveyor from './conveyor/conveyor'; import RoboticArm from './roboticArm/roboticArm'; +import Materials from './materials/materials'; +import Machine from './machine/machine'; +import StorageUnit from './storageUnit/storageUnit'; +import Simulator from './simulator/simulator'; +import Products from './products/products'; +import Trigger from './triggers/trigger'; +import useModuleStore from '../../store/useModuleStore'; function Simulation() { + const { activeModule } = useModuleStore(); const { events } = useEventsStore(); const { products } = useProductStore(); @@ -21,16 +29,36 @@ function Simulation() { return ( <> - + {activeModule === 'simulation' && - + <> - + - + + + + + + + + + + + + + + + + + + + + + } - ) + ); } -export default Simulation \ No newline at end of file +export default Simulation; diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx new file mode 100644 index 0000000..c4f1c40 --- /dev/null +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -0,0 +1,18 @@ +import { useEffect } from 'react' +import { useProductStore } from '../../../store/simulation/useProductStore' + +function Simulator() { + const { products } = useProductStore(); + + useEffect(() => { + // console.log('products: ', products); + }, [products]) + + return ( + <> + + + ) +} + +export default Simulator \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx new file mode 100644 index 0000000..29d404e --- /dev/null +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstance/storageUnitInstance.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function storageUnitInstance() { + return ( + <> + + ) +} + +export default storageUnitInstance \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx new file mode 100644 index 0000000..d79b5d8 --- /dev/null +++ b/app/src/modules/simulation/storageUnit/instances/storageUnitInstances.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import StorageUnitInstance from './storageUnitInstance/storageUnitInstance' + +function StorageUnitInstances() { + return ( + <> + + + + + ) +} + +export default StorageUnitInstances \ No newline at end of file diff --git a/app/src/modules/simulation/storageUnit/storageUnit.tsx b/app/src/modules/simulation/storageUnit/storageUnit.tsx new file mode 100644 index 0000000..eee0875 --- /dev/null +++ b/app/src/modules/simulation/storageUnit/storageUnit.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import StorageUnitInstances from './instances/storageUnitInstances' + +function StorageUnit() { + return ( + <> + + + + + ) +} + +export default StorageUnit \ No newline at end of file diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx new file mode 100644 index 0000000..f836ea4 --- /dev/null +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -0,0 +1,103 @@ +import { useEffect } from "react"; +import { useThree } from "@react-three/fiber"; +import { useSubModuleStore } from "../../../../store/useModuleStore"; +import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../store/simulation/useProductStore"; +import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; + +function TriggerConnector() { + const { gl, raycaster, scene } = useThree(); + const { subModule } = useSubModuleStore(); + const { getPointByUuid, getIsEventInProduct } = useProductStore(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + + const canvasElement = gl.domElement; + + let drag = false; + let isRightMouseDown = false; + + const onMouseDown = (evt: MouseEvent) => { + if (selectedAsset) { + clearSelectedAsset(); + } + if (evt.button === 2) { + isRightMouseDown = true; + drag = false; + } + }; + + const onMouseUp = (evt: MouseEvent) => { + if (evt.button === 2) { + isRightMouseDown = false; + } + } + + const onMouseMove = () => { + if (isRightMouseDown) { + drag = true; + } + }; + + const handleRightClick = (evt: MouseEvent) => { + if (drag) return; + evt.preventDefault(); + const canvasElement = gl.domElement; + if (!canvasElement) return; + + let intersects = raycaster.intersectObjects(scene.children, true); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + let currentObject = intersects[0].object; + + if (currentObject && currentObject.name === 'Event-Sphere') { + + const isInProduct = getIsEventInProduct( + selectedProduct.productId, + currentObject.userData.modelUuid + ); + + // You left Here + + if (isInProduct) { + + const event = getPointByUuid( + selectedProduct.productId, + currentObject.userData.modelUuid, + currentObject.userData.pointUuid + ); + console.log('event: ', event); + } else { + + } + } + + } else { + + } + }; + + if (subModule === 'simulations') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener('contextmenu', handleRightClick); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener('contextmenu', handleRightClick); + }; + + }, [gl, subModule]); + + return ( + <> + + ) +} + +export default TriggerConnector \ No newline at end of file diff --git a/app/src/modules/simulation/triggers/temp.md b/app/src/modules/simulation/triggers/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/modules/simulation/triggers/trigger.tsx b/app/src/modules/simulation/triggers/trigger.tsx new file mode 100644 index 0000000..110da2e --- /dev/null +++ b/app/src/modules/simulation/triggers/trigger.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import TriggerConnector from './connector/triggerConnector' + +function Trigger() { + return ( + <> + + + + + ) +} + +export default Trigger \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index cf5fd81..a0eeabd 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,62 +1,172 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useFrame, useThree } from '@react-three/fiber'; +import { useFloorItems } from '../../../../../store/store'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; interface VehicleAnimatorProps { - path: [number, number, number][]; - handleCallBack: () => void; - currentPhase: string; - agvUuid: number + path: [number, number, number][]; + handleCallBack: () => void; + currentPhase: string; + agvUuid: number; + agvDetail: any; } +function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail }: VehicleAnimatorProps) { + const { decrementVehicleLoad, vehicles } = useVehicleStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset } = useResetButtonStore(); + const [restRotation, setRestingRotation] = useState(true); + const [progress, setProgress] = useState(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + let startTime: number; + let pausedTime: number; + let fixedInterval: number; -function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid }: VehicleAnimatorProps) { - const [progress, setProgress] = useState(0) - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const { scene } = useThree(); + useEffect(() => { + if (currentPhase === 'stationed-pickup' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setCurrentPath(path); + } + }, [currentPhase, path]); - useEffect(() => { + useEffect(() => { + setProgress(0); + completedRef.current = false; + }, [currentPath]); - if (currentPhase === 'stationed-pickup' && path.length > 0) { - setCurrentPath(path); + useFrame((_, delta) => { + const object = scene.getObjectByProperty('uuid', agvUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + let coveredDistance = progressRef.current; + let accumulatedDistance = 0; + let index = 0; + + while ( + index < distances.length && + coveredDistance > accumulatedDistance + distances[index] + ) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const rotationSpeed = 2.0; + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = rotationSpeed * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * agvDetail.speed); + coveredDistance = progressRef.current; + + const t = (coveredDistance - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation) { + const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); + object.quaternion.slerp(targetQuaternion, delta * 2); + const angleDiff = object.quaternion.angleTo(targetQuaternion); + if (angleDiff < 0.01) { + let objectRotation = agvDetail.point.rotation + object.rotation.set(objectRotation[0], objectRotation[1], objectRotation[2]); + setRestingRotation(false); } + return; + } + } - }, [currentPhase, path]) + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(firstFrame); + } + } + }); - useFrame((_, delta) => { - if (!path || path.length < 2) return; + function firstFrame() { + const unLoadDuration = agvDetail.point.action.unLoadDuration; + const droppedMaterial = agvDetail.currentLoad; + fixedInterval = (unLoadDuration / droppedMaterial) * 1000; + startTime = performance.now(); + step(droppedMaterial); + } - const object = scene.getObjectByProperty("uuid", agvUuid) - if (!object) return; + function step(droppedMaterial: number) { + const elapsedTime = performance.now() - startTime; - setProgress(prev => { - const next = prev + delta * 0.1; // speed - return next >= 1 ? 1 : next; - }); + if (elapsedTime >= fixedInterval) { + console.log('fixedInterval: ', fixedInterval); + console.log('elapsedTime: ', elapsedTime); + let droppedMat = droppedMaterial - 1; + decrementVehicleLoad(agvDetail.modelUuid, 1); + if (droppedMat === 0) return; + startTime = performance.now(); + requestAnimationFrame(() => step(droppedMat)); + } else { + requestAnimationFrame(() => step(droppedMaterial)); + } + } - const totalSegments = path.length - 1; - const segmentIndex = Math.floor(progress * totalSegments); - const t = progress * totalSegments - segmentIndex; - - const start = path[segmentIndex]; - const end = path[segmentIndex + 1] || start; - - // Directly set position without creating a new Vector3 - object.position.x = start[0] + (end[0] - start[0]) * t; - object.position.y = start[1] + (end[1] - start[1]) * t; - object.position.z = start[2] + (end[2] - start[2]) * t; - }); - // useFrame(() => { - // if (currentPath.length === 0) return; - // const object = scene.getObjectByProperty("uuid", agvUuid); - // if (!object) return; - - - - // }) - return ( + return ( + <> + {currentPath.length > 0 && ( <> + + {currentPath.map((point, index) => ( + + + + + ))} - ) + )} + + ); } -export default VehicleAnimator \ No newline at end of file +export default VehicleAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index bf767ec..d0691f9 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,66 +1,123 @@ -import React, { useCallback, useEffect, useState } from 'react' -import VehicleAnimator from '../animator/vehicleAnimator' -import * as THREE from "three"; +import React, { useCallback, useEffect, useState } from 'react'; +import VehicleAnimator from '../animator/vehicleAnimator'; +import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/store'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; -function VehicleInstance({ agvDetails }: any) { - const { navMesh } = useNavMesh(); - const { isPlaying } = usePlayButtonStore(); - const { setVehicleActive, setVehicleState } = useVehicleStore(); - const [currentPhase, setCurrentPhase] = useState<(string)>("stationed"); - const [path, setPath] = useState<[number, number, number][]>([]); +function VehicleInstance({ agvDetail }: any) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore(); + const [currentPhase, setCurrentPhase] = useState('stationed'); + const [path, setPath] = useState<[number, number, number][]>([]); - const computePath = useCallback((start: any, end: any) => { + const computePath = useCallback( + (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + return ( + segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || [] + ); + } catch { + return []; + } + }, + [navMesh] + ); + function vehicleStatus(modelid: string, status: string) { + // console.log(`AGV ${modelid}: ${status}`); + } - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - return ( - segmentPath?.map( - ({ x, y, z }) => [x, y + 0.1, z] as [number, number, number] - ) || [] - ); - } catch { - return []; - } - }, [navMesh]); - - useEffect(() => { - - - if (isPlaying) { - if (!agvDetails.isActive && agvDetails.state == "idle" && currentPhase == "stationed") { - const toPickupPath = computePath(new THREE.Vector3(agvDetails.position[0], agvDetails.position[1], agvDetails.position[2]), agvDetails.point.action.pickUpPoint); - setPath(toPickupPath) - setVehicleActive(agvDetails.modelUuid, true) - setVehicleState(agvDetails.modelUuid, "running") - setCurrentPhase("stationed-pickup") - // - } - } - }, [agvDetails, currentPhase, path, isPlaying]) - - function handleCallBack() { - if (currentPhase === "stationed-pickup") { - setVehicleActive(agvDetails.modelUuid, false) - setVehicleState(agvDetails.modelUuid, "idle") - setCurrentPhase("picking") - setPath([]) + useEffect(() => { + if (isPlaying) { + if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { + const toPickupPath = computePath( + new THREE.Vector3(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]), + agvDetail.point.action.pickUpPoint + ); + setPath(toPickupPath); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('stationed-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); + return; + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'picking' + ) { + + setTimeout(() => { + incrementVehicleLoad(agvDetail.modelUuid, 2); + }, 5000); + + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { + const toDrop = computePath( + agvDetail.point.action.pickUpPoint, + agvDetail.point.action.unLoadPoint + ); + setPath(toDrop); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('pickup-drop'); + vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'dropping' && + agvDetail.currentLoad === 0 + ) { + const dropToPickup = computePath( + agvDetail.point.action.unLoadPoint, + agvDetail.point.action.pickUpPoint + ); + setPath(dropToPickup); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('drop-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); + } } + }, [vehicles, currentPhase, path, isPlaying]); + function handleCallBack() { + if (currentPhase === 'stationed-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (currentPhase === 'pickup-drop') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('dropping'); + vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); + setPath([]); + } else if (currentPhase === 'drop-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + setPath([]); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); + } + } - return ( - <> - - - - - ) + return ( + <> + + + ); } -export default VehicleInstance \ No newline at end of file +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 0848883..2a0070b 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -1,15 +1,14 @@ import React from 'react' import VehicleInstance from './instance/vehicleInstance' -import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; +import { useVehicleStore } from '../../../../store/simulation/useVehicleStore' function VehicleInstances() { const { vehicles } = useVehicleStore(); - return ( <> {vehicles.map((val: any, i: any) => - + )} diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index fa0d9dd..370304e 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -17,7 +17,7 @@ export default function PolygonGenerator({ useEffect(() => { let allLines = arrayLinesToObject(lines.current); const wallLines = allLines?.filter((line) => line?.type === "WallLine"); - const aisleLines = allLines?.filter((line) => line?.type === "AisleLine"); + const aisleLines = allLines?.filter((line) => line?.type === "AisleLine") const wallPoints = wallLines .map((pair) => pair?.line.map((vals) => vals.position)) @@ -39,9 +39,10 @@ export default function PolygonGenerator({ ); const polygons = turf.polygonize(turf.featureCollection(lineFeatures)); + renderWallGeometry(wallPoints); - if (polygons.features.length > 1) { + if (polygons.features.length > 0) { polygons.features.forEach((feature) => { if (feature.geometry.type === "Polygon") { diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 3364717..f0b4d17 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,17 +1,19 @@ import React, { useEffect } from 'react' import VehicleInstances from './instances/vehicleInstances'; - import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; +import { useFloorItems } from '../../../store/store'; function Vehicles() { const { vehicles, addVehicle } = useVehicleStore(); + const { floorItems } = useFloorItems(); + const vehicleStatusSample: VehicleEventSchema[] = [ { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74", + modelName: "AGV", + position: [97.9252965204558, 0, 37.96138815638661], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -24,11 +26,10 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", - unLoadDuration: 15, - loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, - unLoadPoint: { x: 20, y: 0, z: 10 }, + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, + unLoadPoint: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, triggers: [ { triggerUuid: "trig-001", @@ -53,9 +54,51 @@ function Vehicles() { } }, { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4", + modelName: "AGV", + position: [89.61609306554463, 0, 33.634136622267356], + rotation: [0, 0, 0], + state: "idle", + type: "vehicle", + speed: 2.5, + point: { + uuid: "point-789", + position: [0, 1, 0], + rotation: [0, 0, 0], + action: { + actionUuid: "action-456", + actionName: "Deliver to Zone A", + actionType: "travel", + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 90, y: 0, z: 28 }, + unLoadPoint: { x: 20, y: 0, z: 10 }, + triggers: [ + { + triggerUuid: "trig-001", + triggerName: "Start Travel", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" }, + triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" }, + triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" } + } + }, + { + triggerUuid: "trig-002", + triggerName: "Complete Travel", + triggerType: "onComplete", + delay: 2, + triggeredAsset: null + } + ] + } + } + }, { + modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79", + modelName: "forklift", + position: [98.85729337188162, 0, 38.36616546567653], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -68,10 +111,9 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", unLoadDuration: 15, loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, unLoadPoint: { x: 20, y: 0, z: 10 }, triggers: [ { @@ -101,19 +143,18 @@ function Vehicles() { useEffect(() => { addVehicle('123', vehicleStatusSample[0]); - addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[2]); }, []) useEffect(() => { - // console.log('vehicles: ', vehicles); + console.log('vehicles: ', vehicles); }, [vehicles]) return ( <> - - ) } diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index 50b72ba..71faefb 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -12,15 +12,12 @@ import { useFloatingWidget, } from "../../store/visualization/useDroppedObjectsStore"; import { - useAsset3dWidget, useSocketStore, useWidgetSubOption, - useZones, } from "../../store/store"; import { getZone2dData } from "../../services/visulization/zone/getZoneData"; import { generateUniqueId } from "../../functions/generateUniqueId"; import { determinePosition } from "./functions/determinePosition"; -import { addingFloatingWidgets } from "../../services/visulization/zone/addFloatingWidgets"; import SocketRealTimeViz from "./socket/realTimeVizSocket.dev"; import RenderOverlay from "../../components/templates/Overlay"; import ConfirmationPopup from "../../components/layout/confirmationPopup/ConfirmationPopup"; @@ -68,20 +65,15 @@ const RealTimeVisulization: React.FC = () => { const containerRef = useRef(null); const { isPlaying } = usePlayButtonStore(); const { activeModule } = useModuleStore(); - const [droppedObjects, setDroppedObjects] = useState([]); const [zonesData, setZonesData] = useState({}); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - const { rightSelect, setRightSelect } = useRightSelected(); - const { editWidgetOptions, setEditWidgetOptions } = - useEditWidgetOptionsStore(); + const { setRightSelect } = useRightSelected(); + const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore(); const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); - - // const [floatingWidgets, setFloatingWidgets] = useState>({}); - const { floatingWidget, setFloatingWidget } = useFloatingWidget(); - const { widgetSelect, setWidgetSelect } = useAsset3dWidget(); - const { widgetSubOption, setWidgetSubOption } = useWidgetSubOption(); + const { setFloatingWidget } = useFloatingWidget(); + const { widgetSubOption } = useWidgetSubOption(); const { visualizationSocket } = useSocketStore(); const { setSelectedChartId } = useWidgetStore(); @@ -99,11 +91,10 @@ const RealTimeVisulization: React.FC = () => { useEffect(() => { async function GetZoneData() { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; try { const response = await getZone2dData(organization); - // console.log('response: ', response); if (!Array.isArray(response)) { return; @@ -125,7 +116,9 @@ const RealTimeVisulization: React.FC = () => { {} ); setZonesData(formattedData); - } catch (error) {} + } catch (error) { + console.log(error); + } } GetZoneData(); @@ -151,12 +144,10 @@ const RealTimeVisulization: React.FC = () => { }); }, [selectedZone]); - // useEffect(() => {}, [floatingWidgets]); - const handleDrop = async (event: React.DragEvent) => { event.preventDefault(); try { - const email = localStorage.getItem("email") || ""; + const email = localStorage.getItem("email") ?? ""; const organization = email?.split("@")[1]?.split(".")[0]; const data = event.dataTransfer.getData("text/plain"); @@ -172,8 +163,8 @@ const RealTimeVisulization: React.FC = () => { const relativeY = event.clientY - rect.top; // Widget dimensions - const widgetWidth = droppedData.width || 125; - const widgetHeight = droppedData.height || 100; + const widgetWidth = droppedData.width ?? 125; + const widgetHeight = droppedData.height ?? 100; // Center the widget at cursor const centerOffsetX = widgetWidth / 2; @@ -275,7 +266,7 @@ const RealTimeVisulization: React.FC = () => { return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, [setRightClickSelected]); + }, [setRightClickSelected, setRightSelect]); const [canvasDimensions, setCanvasDimensions] = useState({ width: 0, @@ -340,6 +331,7 @@ const RealTimeVisulization: React.FC = () => { borderRadius: isPlaying || activeModule !== "visualization" ? "" : "6px", }} + role="application" onDrop={(event) => handleDrop(event)} onDragOver={(event) => event.preventDefault()} > @@ -362,6 +354,10 @@ const RealTimeVisulization: React.FC = () => { "RotateY", "Delete", ]} + onClick={(e) => { + setRightSelect(e); + setEditWidgetOptions(false); + }} /> )} diff --git a/app/src/modules/visualization/visualization.tsx b/app/src/modules/visualization/visualization.tsx index e5b1692..77956f4 100644 --- a/app/src/modules/visualization/visualization.tsx +++ b/app/src/modules/visualization/visualization.tsx @@ -3,9 +3,8 @@ import Dropped3dWidgets from './widgets/3d/Dropped3dWidget' import ZoneCentreTarget from './zone/zoneCameraTarget' import ZoneAssets from './zone/zoneAssets' import MqttEvents from '../../services/factoryBuilder/mqtt/mqttEvents' -import DrieHtmlTemp from './mqttTemp/drieHtmlTemp' -const Visualization = () => { +const Visualization:React.FC = () => { return ( <> diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index 26d1d7d..0c28efa 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -60,11 +60,11 @@ const Project: React.FC = () => { return (
-
- {/* - */} + {/*
+ + -
+
*/} {/* {loadingProgress && } */} {!isPlaying && ( diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index a5a7c57..19393f9 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -2,7 +2,11 @@ import React, { useState, FormEvent } from "react"; import { useNavigate } from "react-router-dom"; import { LogoIconLarge } from "../components/icons/Logo"; import { EyeIcon } from "../components/icons/ExportCommonIcons"; -import { useLoadingProgress, useOrganization, useUserName } from "../store/store"; +import { + useLoadingProgress, + useOrganization, + useUserName, +} from "../store/store"; import { signInApi } from "../services/factoryBuilder/signInSignUp/signInApi"; import { signUpApi } from "../services/factoryBuilder/signInSignUp/signUpApi"; @@ -21,7 +25,7 @@ const UserAuth: React.FC = () => { const handleLogin = async (e: FormEvent) => { e.preventDefault(); - const organization = (email.split("@")[1]).split(".")[0]; + const organization = email.split("@")[1].split(".")[0]; try { const res = await signInApi(email, password, organization); @@ -39,7 +43,7 @@ const UserAuth: React.FC = () => { } else if (res.message === "User Not Found!!! Kindly signup...") { setError("Account not found"); } - } catch (error) { } + } catch (error) {} }; const handleRegister = async (e: FormEvent) => { @@ -47,7 +51,7 @@ const UserAuth: React.FC = () => { if (email && password && userName) { setError(""); try { - const organization = (email.split("@")[1]).split(".")[0]; + const organization = email.split("@")[1].split(".")[0]; const res = await signUpApi(userName, email, password, organization); if (res.message === "New User created") { @@ -56,123 +60,121 @@ const UserAuth: React.FC = () => { if (res.message === "User already exists") { setError("User already exists"); } - } catch (error) { } + } catch (error) {} } else { setError("Please fill all the fields!"); } }; return ( - <> -
-
- -
-

Welcome to Dwinzo

-

- {isSignIn ? ( - <> - Don’t have an account?{" "} - setIsSignIn(false)} - style={{ cursor: "pointer" }} - > - Register here! - - - ) : ( - <> - Already have an account?{" "} - setIsSignIn(true)} - style={{ cursor: "pointer" }} - > - Login here! - - - )} -

+
+
+ +
+

Welcome to Dwinzo

+

+ {isSignIn ? ( + <> + Don’t have an account?{" "} + setIsSignIn(false)} + style={{ cursor: "pointer" }} + > + Register here! + + + ) : ( + <> + Already have an account?{" "} + setIsSignIn(true)} + style={{ cursor: "pointer" }} + > + Login here! + + + )} +

- + - {error &&
🛈 {error}
} + {error &&
🛈 {error}
} -
- {!isSignIn && ( - setUserName(e.target.value)} - required - /> - )} + + {!isSignIn && ( setEmail(e.target.value)} + type="text" + value={userName} + placeholder="Username" + onChange={(e) => setUserName(e.target.value)} required /> -
- setPassword(e.target.value)} - required - /> - -
- {!isSignIn && ( -
- -
- I have read and agree to the terms of service -
-
- )} - -
-

- By signing up for, or logging into, an account, you agree to our{" "} - navigate("/privacy")} - style={{ cursor: "pointer" }} - > - privacy policy - {" "} - &{" "} - navigate("/terms")} - style={{ cursor: "pointer" }} - > - terms of service - {" "} - whether you read them or not. You can also find these terms on our - website. -

-
- +
+ {!isSignIn && ( +
+ +
+ I have read and agree to the terms of service +
+
+ )} + + +

+ By signing up for, or logging into, an account, you agree to our{" "} + navigate("/privacy")} + style={{ cursor: "pointer" }} + > + privacy policy + {" "} + &{" "} + navigate("/terms")} + style={{ cursor: "pointer" }} + > + terms of service + {" "} + whether you read them or not. You can also find these terms on our + website. +

+
); }; diff --git a/app/src/services/simulation/UpsertProductOrEventApi.ts b/app/src/services/simulation/UpsertProductOrEventApi.ts new file mode 100644 index 0000000..e2f45d1 --- /dev/null +++ b/app/src/services/simulation/UpsertProductOrEventApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const upsertProductOrEventApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/UpsertProductOrEvent`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to add product or event"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteEventDataApi.ts b/app/src/services/simulation/deleteEventDataApi.ts new file mode 100644 index 0000000..f263065 --- /dev/null +++ b/app/src/services/simulation/deleteEventDataApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteEventDataApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/EventDataDelete`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to delete event data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteProductDataApi.ts b/app/src/services/simulation/deleteProductDataApi.ts new file mode 100644 index 0000000..06718f8 --- /dev/null +++ b/app/src/services/simulation/deleteProductDataApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteProductDataApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDataDelete?productId=${productId}&organization=${organization}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to delete product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/getProductApi.ts b/app/src/services/simulation/getProductApi.ts new file mode 100644 index 0000000..cf80013 --- /dev/null +++ b/app/src/services/simulation/getProductApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getProductApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDatas?productId=${productId}&organization=${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/getallProductsApi.ts b/app/src/services/simulation/getallProductsApi.ts new file mode 100644 index 0000000..46627f9 --- /dev/null +++ b/app/src/services/simulation/getallProductsApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getAllProductsApi = async ( organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/AllProducts/${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch all products data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/temp.md b/app/src/services/simulation/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/services/visulization/zone/getSelect2dZoneData.ts b/app/src/services/visulization/zone/getSelect2dZoneData.ts index b2c39e9..00d4dfe 100644 --- a/app/src/services/visulization/zone/getSelect2dZoneData.ts +++ b/app/src/services/visulization/zone/getSelect2dZoneData.ts @@ -7,7 +7,7 @@ export const getSelect2dZoneData = async ( ) => { try { const response = await fetch( - `${url_Backend_dwinzo}/api/v2/Zone/visualization/${ZoneId}?organization=${organization}`, + `${url_Backend_dwinzo}/api/v2/ZoneVisualization/${ZoneId}?organization=${organization}`, { method: "GET", headers: { diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts index 493a068..642762f 100644 --- a/app/src/store/simulation/useArmBotStore.ts +++ b/app/src/store/simulation/useArmBotStore.ts @@ -17,8 +17,11 @@ interface ArmBotStore { addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void; removeAction: (modelUuid: string, actionUuid: string) => void; - setArmBotActive: (modelUuid: string, isActive: boolean) => void; + updateStartPoint: (modelUuid: string, actionUuid: string, startPoint: [number, number, number] | null) => void; + updateEndPoint: (modelUuid: string, actionUuid: string, endPoint: [number, number, number] | null) => void; + setArmBotActive: (modelUuid: string, isActive: boolean) => void; + setArmBotState: (modelUuid: string, newState: ArmBotStatus['state']) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; @@ -72,7 +75,6 @@ export const useArmBotStore = create()( actionUuid: action.actionUuid, actionName: action.actionName, }; - armBot.isActive = true; } } }); @@ -83,7 +85,6 @@ export const useArmBotStore = create()( const armBot = state.armBots.find(a => a.modelUuid === modelUuid); if (armBot) { armBot.currentAction = undefined; - armBot.isActive = false; } }); }, @@ -106,6 +107,30 @@ export const useArmBotStore = create()( }); }, + updateStartPoint: (modelUuid, actionUuid, startPoint) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + action.process.startPoint = startPoint; + } + } + }); + }, + + updateEndPoint: (modelUuid, actionUuid, endPoint) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + action.process.endPoint = endPoint; + } + } + }); + }, + setArmBotActive: (modelUuid, isActive) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); @@ -115,6 +140,15 @@ export const useArmBotStore = create()( }); }, + setArmBotState: (modelUuid, newState) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + armBot.state = newState; + } + }); + }, + incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useMaterialStore.ts b/app/src/store/simulation/useMaterialStore.ts new file mode 100644 index 0000000..56a35a7 --- /dev/null +++ b/app/src/store/simulation/useMaterialStore.ts @@ -0,0 +1,76 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +type MaterialsStore = { + materials: MaterialsSchema; + + addMaterial: (material: MaterialSchema) => void; + removeMaterial: (materialId: string) => void; + updateMaterial: (materialId: string, updates: Partial) => void; + + setStartTime: (materialId: string, startTime: string) => void; + setEndTime: (materialId: string, endTime: string) => void; + setCost: (materialId: string, cost: number) => void; + setWeight: (materialId: string, weight: number) => void; + + getMaterialById: (materialId: string) => MaterialSchema | undefined; +}; + +export const useMaterialStore = create()( + immer((set, get) => ({ + materials: [], + + addMaterial: (material) => { + set((state) => { + state.materials.push(material); + }); + }, + + removeMaterial: (materialId) => { + set((state) => { + state.materials = state.materials.filter(m => m.materialId !== materialId); + }); + }, + + updateMaterial: (materialId, updates) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) { + Object.assign(material, updates); + } + }); + }, + + setStartTime: (materialId, startTime) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.startTime = startTime; + }); + }, + + setEndTime: (materialId, endTime) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.endTime = endTime; + }); + }, + + setCost: (materialId, cost) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.cost = cost; + }); + }, + + setWeight: (materialId, weight) => { + set((state) => { + const material = state.materials.find(m => m.materialId === materialId); + if (material) material.weight = weight; + }); + }, + + getMaterialById: (materialId) => { + return get().materials.find(m => m.materialId === materialId); + }, + })) +); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 41a13f6..8ec74cf 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -7,7 +7,7 @@ type ProductsStore = { // Product-level actions addProduct: (productName: string, productId: string) => void; removeProduct: (productId: string) => void; - updateProduct: (productId: string, updates: Partial<{ productName: string; eventsData: EventsSchema[] }>) => void; + updateProduct: (productId: string, updates: Partial<{ productName: string; eventDatas: EventsSchema[] }>) => void; // Event-level actions addEvent: (productId: string, event: EventsSchema) => void; @@ -48,8 +48,18 @@ type ProductsStore = { updates: Partial ) => void; + // Renaming functions + renameProduct: (productId: string, newName: string) => void; + renameAction: (actionUuid: string, newName: string) => void; + renameTrigger: (triggerUuid: string, newName: string) => void; + // Helper functions - getProductById: (productId: string) => { productName: string; productId: string; eventsData: EventsSchema[] } | undefined; + getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined; + getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined; + getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; + getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; + getTriggerByUuid: (productId: string, triggerUuid: string) => TriggerSchema | undefined; + getIsEventInProduct: (productId: string, modelUuid: string) => boolean; }; export const useProductStore = create()( @@ -62,7 +72,7 @@ export const useProductStore = create()( const newProduct = { productName, productId: productId, - eventsData: [] + eventDatas: [] }; state.products.push(newProduct); }); @@ -88,7 +98,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData.push(event); + product.eventDatas.push(event); } }); }, @@ -97,7 +107,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData = product.eventsData.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); + product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); } }); }, @@ -106,7 +116,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event) { Object.assign(event, updates); } @@ -119,7 +129,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); } else if (event && 'point' in event) { @@ -133,7 +143,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { @@ -147,7 +157,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -165,7 +175,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -185,7 +195,7 @@ export const useProductStore = create()( removeAction: (actionUuid: string) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { // Handle ConveyorEventSchema for (const point of (event as ConveyorEventSchema).points) { @@ -209,7 +219,7 @@ export const useProductStore = create()( updateAction: (actionUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -239,7 +249,7 @@ export const useProductStore = create()( addTrigger: (actionUuid, trigger) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -268,7 +278,7 @@ export const useProductStore = create()( removeTrigger: (triggerUuid) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -295,7 +305,7 @@ export const useProductStore = create()( updateTrigger: (triggerUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -331,9 +341,170 @@ export const useProductStore = create()( }); }, + // Renaming functions + renameProduct: (productId, newName) => { + set((state) => { + const product = state.products.find(p => p.productId === productId); + if (product) { + product.productName = newName; + } + }); + }, + + renameAction: (actionUuid, newName) => { + set((state) => { + for (const product of state.products) { + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + return; + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && point.action.actionUuid === actionUuid) { + point.action.actionName = newName; + return; + } else if ('actions' in point) { + const action = point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action) { + action.actionName = newName; + return; + } + } + } + } + } + }); + }, + + renameTrigger: (triggerUuid, newName) => { + set((state) => { + for (const product of state.products) { + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action && 'triggers' in point.action) { + const trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && 'triggers' in point.action) { + const trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } else if ('actions' in point) { + for (const action of point.actions) { + if ('triggers' in action) { + const trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (trigger) { + trigger.triggerName = newName; + return; + } + } + } + } + } + } + } + }); + }, + // Helper functions getProductById: (productId) => { return get().products.find(p => p.productId === productId); + }, + + getEventByModelUuid: (productId, modelUuid) => { + const product = get().getProductById(productId); + if (!product) return undefined; + return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + }, + + getPointByUuid: (productId, modelUuid, pointUuid) => { + const event = get().getEventByModelUuid(productId, modelUuid); + if (!event) return undefined; + + if ('points' in event) { + return (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); + } else if ('point' in event && (event as any).point.uuid === pointUuid) { + return (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point; + } + return undefined; + }, + + getActionByUuid: (productId, actionUuid) => { + const product = get().products.find(p => p.productId === productId); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + if (point.action?.actionUuid === actionUuid) { + return point.action; + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point && point.action?.actionUuid === actionUuid) { + return point.action; + } else if ('actions' in point) { + const action = point.actions.find((a: any) => a.actionUuid === actionUuid); + if (action) return action; + } + } + } + return undefined; + }, + + getTriggerByUuid: (productId, triggerUuid) => { + const product = get().products.find(p => p.productId === productId); + if (!product) return undefined; + + for (const event of product.eventDatas) { + if ('points' in event) { + for (const point of (event as ConveyorEventSchema).points) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } else if ('point' in event) { + const point = (event as any).point; + if ('action' in point) { + for (const trigger of point.action?.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } else if ('actions' in point) { + for (const action of point.actions) { + for (const trigger of action.triggers || []) { + if (trigger.triggerUuid === triggerUuid) { + return trigger; + } + } + } + } + } + } + return undefined; + }, + + getIsEventInProduct: (productId, modelUuid) => { + const product = get().getProductById(productId); + if (!product) return false; + return product.eventDatas.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); } })) ); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts new file mode 100644 index 0000000..5085688 --- /dev/null +++ b/app/src/store/simulation/useSimulationStore.ts @@ -0,0 +1,117 @@ +import { create } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; +import * as THREE from 'three'; + +interface SelectedEventSphereState { + selectedEventSphere: THREE.Mesh | null; + setSelectedEventSphere: (mesh: THREE.Mesh | null) => void; + clearSelectedEventSphere: () => void; +} + +export const useSelectedEventSphere = create()( + immer((set) => ({ + selectedEventSphere: null, + setSelectedEventSphere: (mesh) => { + set((state) => { + state.selectedEventSphere = mesh; + }); + }, + clearSelectedEventSphere: () => { + set((state) => { + state.selectedEventSphere = null; + }); + }, + })) +); + +interface SelectedEventDataState { + selectedEventData: { data: EventsSchema; selectedPoint: string } | undefined; + setSelectedEventData: (data: EventsSchema, selectedPoint: string) => void; + clearSelectedEventData: () => void; +} + +export const useSelectedEventData = create()( + immer((set) => ({ + selectedEventData: undefined, + setSelectedEventData: (data, selectedPoint) => { + set((state) => { + state.selectedEventData = { data, selectedPoint }; + }); + }, + clearSelectedEventData: () => { + set((state) => { + state.selectedEventData = undefined; + }); + }, + })) +); + +interface SelectedAssetState { + selectedAsset: EventsSchema | undefined; + setSelectedAsset: (EventData: EventsSchema) => void; + clearSelectedAsset: () => void; +} + +export const useSelectedAsset = create()( + immer((set) => ({ + selectedAsset: undefined, + setSelectedAsset: (EventData) => { + set((state) => { + state.selectedAsset = EventData; + }); + }, + clearSelectedAsset: () => { + set((state) => { + state.selectedAsset = undefined; + }); + }, + })) +); + +interface SelectedProductState { + selectedProduct: { productId: string; productName: string }; + setSelectedProduct: (productId: string, productName: string) => void; + clearSelectedProduct: () => void; +} + +export const useSelectedProduct = create()( + immer((set) => ({ + selectedProduct: { productId: '', productName: '' }, + setSelectedProduct: (productId, productName) => { + set((state) => { + state.selectedProduct.productId = productId; + state.selectedProduct.productName = productName; + }); + }, + clearSelectedProduct: () => { + set((state) => { + state.selectedProduct.productId = ''; + state.selectedProduct.productName = ''; + }); + }, + })) +); + +interface SelectedActionState { + selectedAction: { actionId: string; actionName: string }; + setSelectedAction: (actionId: string, actionName: string) => void; + clearSelectedAction: () => void; +} + +export const useSelectedAction = create()( + immer((set) => ({ + selectedAction: { actionId: '', actionName: '' }, + setSelectedAction: (actionId, actionName) => { + set((state) => { + state.selectedAction.actionId = actionId; + state.selectedAction.actionName = actionName; + }); + }, + clearSelectedAction: () => { + set((state) => { + state.selectedAction.actionId = ''; + state.selectedAction.actionName = ''; + }); + }, + })) +); \ No newline at end of file diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index ce28916..449ceb7 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -1,3 +1,4 @@ + import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; @@ -88,7 +89,7 @@ export const useVehicleStore = create()( set((state) => { const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); if (vehicle) { - vehicle.currentLoad = decrementBy; + vehicle.currentLoad -= decrementBy; } }); }, diff --git a/app/src/store/useModuleStore.ts b/app/src/store/useModuleStore.ts index 1012792..3cc1d00 100644 --- a/app/src/store/useModuleStore.ts +++ b/app/src/store/useModuleStore.ts @@ -13,14 +13,17 @@ const useModuleStore = create((set) => ({ export default useModuleStore; // New store for subModule + +type SubModule = 'properties' | 'simulations' | 'mechanics' | 'analysis' | 'zoneProperties'; + interface SubModuleStore { - subModule: string; - setSubModule: (subModule: string) => void; + subModule: SubModule; + setSubModule: (subModule: SubModule) => void; } const useSubModuleStore = create((set) => ({ subModule: "properties", // Initial subModule state - setSubModule: (subModule) => set({ subModule }), // Update subModule state + setSubModule: (value) => set({ subModule: value }), // Update subModule state })); export { useSubModuleStore }; diff --git a/app/src/styles/abstracts/placeholders.scss b/app/src/styles/abstracts/placeholders.scss deleted file mode 100644 index 18f28f9..0000000 --- a/app/src/styles/abstracts/placeholders.scss +++ /dev/null @@ -1,6 +0,0 @@ -// center a element -%centered { - display: flex; - justify-content: center; - align-items: center; -} diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index 44e5627..6bb3d57 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -1,123 +1,132 @@ -/* ======================================================================== - Global SCSS Variables - ======================================================================== - This file contains the global variables used across the project for - colors, typography, spacing, shadows, and other design tokens. - ======================================================================== */ - @use "functions"; -// ======================================================================== -// Font Imports -// ======================================================================== @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Josefin+Sans:ital,wght@0,100..700;1,100..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"); -// ======================================================================== -// Colors -// ======================================================================== +// new variables -// Text colors -$text-color: #2b3344; // Primary text color -$text-disabled: #b7b7c6; // Disabled text color -$input-text-color: #595965; // Input field text color +// text colors +// ---------- light mode ---------- +$text-color: #2b3344; +$text-disabled: #b7b7c6; +$input-text-color: #595965; +$highlight-text-color: #6f42c1; -$text-color-dark: #f3f3fd; // Primary text color for dark mode -$text-disabled-dark: #6f6f7a; // Disabled text color for dark mode -$input-text-color-dark: #b5b5c8; // Input field text color for dark mode +// ---------- dark mode ---------- +$text-color-dark: #f3f3fd; +$text-disabled-dark: #6f6f7a; +$input-text-color-dark: #b5b5c8; +$highlight-text-color-dark: #B392F0; -// Accent colors -$accent-color: #6f42c1; // Primary accent color -$accent-color-dark: #c4abf1; // Primary accent color for dark mode -$highlight-accent-color: #e0dfff; // Highlighted accent for light mode -$highlight-accent-color-dark: #403e6a; // Highlighted accent for dark mode +// background colors +// ---------- light mode ---------- +$background-color: linear-gradient(-45deg, #FCFDFDCC 0%, #FCFDFD99 100%); +$background-color-secondary: #FCFDFD4D; +$background-color-accent: #6f42c1; +$background-color-button: #6f42c1; +$background-color-drop-down: #6F42C14D; +$background-color-input: #FFFFFF4D; +$background-color-input-focus: #F2F2F7; +$background-color-drop-down-gradient: linear-gradient(-45deg, #75649366 0%, #40257266 100%); +$background-color-selected: #E0DFFF; +$background-radial-gray-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); -// Background colors -$background-color: #fcfdfd; // Main background color -$background-color-dark: #19191d; // Main background color for dark mode -$background-color-secondary: #e1e0ff80; // Secondary background color -$background-color-secondary-dark: #39394f99; // Secondary background color for dark mode -$background-color-gray: #f3f3f3; // Main background color -$background-color-gray-dark: #232323; // Main background color for dark mode +// ---------- dark mode ---------- +$background-color-dark: linear-gradient(-45deg, #333333B3 0%, #2D2437B3 100%); +$background-color-secondary-dark: #19191D99; +$background-color-accent-dark: #6f42c1; +$background-color-button-dark: #6f42c1; +$background-color-drop-down-dark: #50505080; +$background-color-input-dark: #FFFFFF33; +$background-color-input-focus-dark: #333333; +$background-color-drop-down-gradient-dark: linear-gradient(-45deg, #8973B166 0%, #53427366 100%); +$background-color-selected-dark: #403E66; +$background-radial-gray-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%); -// Border colors -$border-color: #e0dfff; // Default border color -$border-color-dark: #403e6a; // Border color for dark mode +// border colors +// ---------- light mode ---------- +$border-color: #E0DFFF; +$border-color-accent: #6F42C1; -// Shadow color -$shadow-color: #3c3c431a; // Shadow base color for light and dark mode -$shadow-color-dark: #8f8f8f1a; // Shadow base color for light and dark mode +// ---------- dark mode ---------- +$border-color-dark: #564B69; +$border-color-accent-dark: #6F42C1; -// Gradients -$acent-gradient-dark: linear-gradient( - 90deg, - #b392f0 0%, - #a676ff 100% -); // Dark mode accent gradient -$acent-gradient: linear-gradient( - 90deg, - #6f42c1 0%, - #925df3 100% -); // Light mode accent gradient +// highlight colors +// ---------- light mode ---------- +$highlight-accent-color: #E0DFFF; +$highlight-secondary-color: #6F42C1; + +// ---------- dark mode ---------- +$highlight-accent-color-dark: #403E6A; +$highlight-secondary-color-dark: #C4ABF1; + +// colors +$color1: #A392CD; +$color2: #7b4cd3; +$color3: #B186FF; +$color4: #8752E8; +$color5: #C7A8FF; + + +// old variables +$accent-color: #6f42c1; +$accent-color-dark: #c4abf1; +$highlight-accent-color: #e0dfff; +$highlight-accent-color-dark: #403e6a; + +$background-color: #fcfdfd; +$background-color-dark: #19191d; +$background-color-secondary: #e1e0ff80; +$background-color-secondary-dark: #39394f99; +$background-color-gray: #f3f3f3; +$background-color-gray-dark: #232323; + +$border-color: #e0dfff; +$border-color-dark: #403e6a; + +$shadow-color: #3c3c431a; +$shadow-color-dark: #8f8f8f1a; + +$acent-gradient-dark: linear-gradient(90deg, #b392f0 0%, #a676ff 100%); +$acent-gradient: linear-gradient(90deg, #6f42c1 0%, #925df3 100%); $faint-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); $faint-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%); -// ======================================================================== -// Typography -// ======================================================================== +$font-inter: "Inter", sans-serif; +$font-josefin-sans: "Josefin Sans", sans-serif; +$font-poppins: "Poppins", sans-serif; +$font-roboto: "Roboto", sans-serif; -// Font Family Variables -$font-inter: "Inter", sans-serif; // Inter font -$font-josefin-sans: "Josefin Sans", sans-serif; // Josefin Sans font -$font-poppins: "Poppins", sans-serif; // Poppins font -$font-roboto: "Roboto", sans-serif; // Roboto font +$tiny: 0.625rem; +$small: 0.75rem; +$regular: 0.8rem; +$large: 1rem; +$xlarge: 1.125rem; +$xxlarge: 1.5rem; +$xxxlarge: 2rem; -// Font sizes (converted to rem using a utility function) -$tiny: 0.625rem; // Extra small text (10px) -$small: 0.75rem; // Small text (12px) -$regular: 0.8rem; // Default text size (14px) -$large: 1rem; // Large text size (16px) -$xlarge: 1.125rem; // Extra large text size (18px) -$xxlarge: 1.5rem; // Double extra large text size (24px) -$xxxlarge: 2rem; // Triple extra large text size (32px) +$thin-weight: 300; +$regular-weight: 400; +$medium-weight: 500; +$bold-weight: 600; -// Font weights -$thin-weight: 300; // Regular font weight -$regular-weight: 400; // Regular font weight -$medium-weight: 500; // Medium font weight -$bold-weight: 600; // Bold font weight +$z-index-drei-html: 1; +$z-index-default: 1; +$z-index-marketplace: 2; +$z-index-tools: 3; +$z-index-negative: -1; +$z-index-ui-base: 10; +$z-index-ui-overlay: 20; +$z-index-ui-popup: 30; +$z-index-ui-highest: 50; -// ======================================================================== -// Z-Index Levels -// ======================================================================== +$box-shadow-light: 0px 2px 4px $shadow-color; +$box-shadow-medium: 0px 4px 8px $shadow-color; +$box-shadow-heavy: 0px 8px 16px $shadow-color; -// Z-index variables for layering -$z-index-drei-html: 1; // For drei's Html components -$z-index-default: 1; // For drei's Html components -$z-index-marketplace: 2; // For drei's Html components -$z-index-tools: 3; // For drei's Html components -$z-index-negative: -1; // For drei's Html components -$z-index-ui-base: 10; // Base UI elements -$z-index-ui-overlay: 20; // Overlay UI elements (e.g., modals, tooltips) -$z-index-ui-popup: 30; // Popups, dialogs, or higher-priority UI elements -$z-index-ui-highest: 50; // Highest priority elements (e.g., notifications, loading screens) - -// ======================================================================== -// Shadows -// ======================================================================== - -// Box shadow variables -$box-shadow-light: 0px 2px 4px $shadow-color; // Light shadow -$box-shadow-medium: 0px 4px 8px $shadow-color; // Medium shadow -$box-shadow-heavy: 0px 8px 16px $shadow-color; // Heavy shadow - -// ======================================================================== -// Border Radius -// ======================================================================== - -// Border radius variables -$border-radius-small: 4px; // Small rounded corners -$border-radius-medium: 6px; // Medium rounded corners -$border-radius-large: 12px; // Large rounded corners -$border-radius-circle: 50%; // Fully circular -$border-radius-extra-large: 20px; // Extra-large rounded corners +$border-radius-small: 4px; +$border-radius-medium: 6px; +$border-radius-large: 12px; +$border-radius-circle: 50%; +$border-radius-extra-large: 20px; diff --git a/app/src/styles/base/global.scss b/app/src/styles/base/global.scss new file mode 100644 index 0000000..3ba017e --- /dev/null +++ b/app/src/styles/base/global.scss @@ -0,0 +1,5 @@ +section, .section{ + padding: 12px; + outline: 1px solid var(--border-color); + border-radius: 16px; +} diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss index 82d286e..ab77f9a 100644 --- a/app/src/styles/base/reset.scss +++ b/app/src/styles/base/reset.scss @@ -12,3 +12,10 @@ input[type="password"]::-webkit-clear-button, /* For Chrome/Safari clear button input[type="password"]::-webkit-inner-spin-button { /* Just in case */ display: none; } + +button{ + border: none; + outline: none; + background: none; + cursor: pointer; +} \ No newline at end of file diff --git a/app/src/styles/components/analysis/ROISummary.scss b/app/src/styles/components/analysis/ROISummary.scss index f4ee414..c1ba7d5 100644 --- a/app/src/styles/components/analysis/ROISummary.scss +++ b/app/src/styles/components/analysis/ROISummary.scss @@ -1,7 +1,6 @@ .roiSummary-container { - .roiSummary-wrapper { - background-color: #F2F2F7; + background-color: var(--background-color); .product-info { display: flex; @@ -22,7 +21,7 @@ } &:last-child { - color: #2B3344; + color: var(--text-color); } } } @@ -30,9 +29,30 @@ .roi-details { display: flex; + align-items: center; + gap: 12px; + + .progress-wrapper { + width: 250px; + display: flex; + flex-direction: column; + gap: 6px; + + .content { + display: flex; + flex-direction: column; + gap: 3px; + align-items: center; + + .key { + font-size: var(--font-size-xlarge); + color: var(--accent-color); + } + } + } .roi-progress { - width: 60%; + width: 100%; } .metrics { @@ -43,8 +63,8 @@ .metric-item { width: 100%; border-radius: 6px; - border: 1px solid var(--axis-colors-green, #43C06D); - background: var(--axis-colors-green-lite, #BEEECF); + border: 1px solid #00FF56; + background: #436D51; display: flex; flex-direction: column; padding: 4px 6px; @@ -58,7 +78,6 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } .metric-value { @@ -74,13 +93,11 @@ .metric-item { background-color: var(--background-color); border: 1px solid var(--Grays-Gray-6, #F2F2F7); - } } } } - .cost-breakdown { background-color: var(--background-color); border: 1px solid var(--text-disabled); @@ -90,9 +107,16 @@ .breakdown-header { display: flex; align-items: center; + justify-content: space-between; gap: 8px; margin-bottom: 16px; + .section-wrapper { + display: flex; + gap: 4px; + align-items: center; + } + .section-number { font-size: 20px; color: #00aaff; @@ -100,18 +124,23 @@ .section-title { font-size: var(--font-size-regular); - color: #333; + color: var(--text-color); } .expand-icon { font-size: 16px; - color: #666; + color: var(--text-color); cursor: pointer; + transform: rotate(90deg); + transition: transform 0.2s linear; + } + + .expand-icon.open { + transform: rotate(0deg); + } } - - .breakdown-table { width: 100%; border-collapse: collapse; @@ -125,28 +154,18 @@ border-bottom: 1px solid var(--text-disabled); } - /* Apply left border to first child */ - th:first-child { - border-left: 1px solid var(--text-disabled); - } - - /* Apply right border to last child */ - th:last-child { - border-right: 1px solid var(--text-disabled); - } - + th:first-child, td:first-child { border-left: 1px solid var(--text-disabled); } - /* Apply right border to last child */ + th:last-child, td:last-child { border-right: 1px solid var(--text-disabled); } th { background-color: var(--background-color); - color: #333; } @@ -156,12 +175,8 @@ color: #333; } } - - - } - .tips-section { background-color: var(--background-color); border-radius: 8px; @@ -182,17 +197,15 @@ .tip-description { span { + font-size: var(--font-size-xlarge); color: #34C759; - /* Default color for the first span */ &:first-child { color: #34C759; - /* Color for the first span */ } &:nth-child(2) { color: #488EF6; - /* Color for the second span */ } } } @@ -200,13 +213,10 @@ .get-tips-button { border: none; - border-radius: 5px; cursor: pointer; font-size: 14px; margin-top: 8px; - - /* Make the button content-width dependent */ display: inline-block; display: flex; justify-content: flex-end; @@ -216,54 +226,93 @@ background-color: var(--accent-color); color: var(--background-color); padding: 4px 6px; - /* Add padding to ensure it has space around the text */ border-radius: 5px; display: inline-block; - /* Ensure button width adjusts to its content */ font-size: 14px; text-align: center; - /* Ensure text is centered */ } } + } + .semi-circle-wrapper { + width: 100%; + height: 125px; + overflow-y: hidden; + position: relative; + } + .semi-circle { + width: 100%; + height: 250px; + border-radius: 50%; + position: relative; + transition: background 0.5s ease; + } + .progress-cover { + position: absolute; + width: 75%; + height: 75%; + top: 12.5%; + left: 12.5%; + background-color: var(--background-color); + border-radius: 50%; + } + + .label-wrapper { + .label { + font-size: var(--font-size-xxxlarge); + } + + position: absolute; + bottom: 0%; + left: 50%; + transform: translate(-50%, 0%); + font-weight: bold; + font-size: 1.2rem; + color: #333; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; } } +// Breakdown Table Open/Close Logic -.semi-circle-wrapper { - width: 250px; - height: 125px; - overflow: hidden; - position: relative; - } - - .semi-circle { - width: 250px; - height: 250px; - border-radius: 50%; - position: relative; - transition: background 0.5s ease; - transform: rotate(180deg); /* rotate so 0% is at left */ - } - - .progress-cover { - position: absolute; - width: 75%; - height: 75%; - top: 12.5%; - left: 12.5%; - background-color: white; - border-radius: 50%; - } - - .label { - position: absolute; - top: 40%; - left: 50%; - transform: translate(-50%, -50%); - font-weight: bold; - font-size: 1.2rem; - } - \ No newline at end of file +.breakdown-table-wrapper { + &.closed { + max-height: 0; + padding: 0; + } + + &.open { + max-height: 500px; + } + + + + + + .breakdown-table { + width: 100%; + border-collapse: collapse; + + th, + td { + padding: 10px; + border: 1px solid #ddd; + text-align: left; + } + + .total-row { + background-color: #f4f4f4; + font-weight: bold; + } + + .net-profit-row { + background-color: #dff0d8; + font-weight: bold; + } + } +} \ No newline at end of file diff --git a/app/src/styles/components/analysis/analysis.scss b/app/src/styles/components/analysis/analysis.scss index bc33556..db4ac67 100644 --- a/app/src/styles/components/analysis/analysis.scss +++ b/app/src/styles/components/analysis/analysis.scss @@ -1,18 +1,25 @@ .analysis { - position: absolute; + position: fixed; top: 0; left: 0; display: flex; - justify-content: center; - align-items: center; + justify-content: space-between; + align-items: start; width: 100%; height: 100vh; - z-index: 100000000000000000000000000000; + // pointer-events: none;k + z-index: 10000; + + .analysis-wrapper { + display: flex; + flex-direction: column; + gap: 12px; + } } .analysis-card { min-width: 333px; - // background: var(--primary-color); + background: var(--background-color); border-radius: 20px; padding: 8px; @@ -109,6 +116,7 @@ .throughoutSummary { + .throughoutSummary-wrapper { .process-container { display: flex; diff --git a/app/src/styles/components/simulation/simulation.scss b/app/src/styles/components/simulation/simulation.scss index eb109c2..815ec20 100644 --- a/app/src/styles/components/simulation/simulation.scss +++ b/app/src/styles/components/simulation/simulation.scss @@ -3,15 +3,75 @@ .simulation-player-wrapper { position: fixed; - bottom: 32px; + bottom: 50px; left: 50%; z-index: 2; transform: translate(-50%, 0); + width: 70%; + .simulation-player-container { + background-color: var(--background-color); + padding: 7px; + border-radius: 15px; + display: flex; + flex-direction: column; + gap: 8px; + + .progresser-wrapper { + background-color: var(--highlight-accent-color); + padding: 4px 5px; + border-radius: 12px; + display: flex; + flex-direction: column; + gap: 12px; + padding-top: 30px; + transition: height 0.2s linear; + } + + + + .controls-container { @include flex-center; gap: 12px; - margin-bottom: 4px; + justify-content: space-between; + + .production-details, + .controls-wrapper { + display: flex; + gap: 6px; + } + + .production-details { + .production-wrapper { + + display: flex; + align-items: center; + flex-direction: column; + gap: 6px; + + .header { + display: flex; + flex-direction: row; + gap: 6px + } + + .progress-wrapper { + width: 164px; + height: 8px; + border-radius: 5px; + // overflow: hidden; + background-color: var(--highlight-accent-color); + + .progress { + border-radius: 5px; + height: 100%; + background-color: var(--accent-color); + } + } + } + } + .simulation-button-container { @include flex-center; gap: 2px; @@ -20,38 +80,74 @@ background-color: var(--background-color); border-radius: #{$border-radius-small}; cursor: pointer; + &:hover { background-color: var(--highlight-accent-color); color: var(--accent-color); + path { stroke: var(--accent-color); } } } } + .speed-control-container { @include flex-center; gap: 18px; padding: 5px 16px; - background: var(--background-color); + // background: var(--background-color); border-radius: #{$border-radius-medium}; box-sizing: #{$box-shadow-medium}; + border-radius: 20px; + position: relative; + .min-value, .max-value { + display: flex; + align-items: center; font-weight: var(--font-weight-bold); } + .slider-container { - width: 580px; + width: 100%; max-width: 80vw; height: 28px; - background: var(--background-color-gray); + // background: var(--background-color-gray); border-radius: #{$border-radius-small}; position: relative; - padding: 4px 26px; + // padding: 4px 26px; + + + .speed-label { + font-size: var(--font-size-tiny); + position: absolute; + bottom: -4px; + + &:first-child { + left: 0; + } + + &:last-child { + right: 0; + } + } + + &::after { + content: ""; + background-color: #E5E5EA; + position: absolute; + top: 50%; + transform: translate(0, -50%); + width: 100%; + height: 3px; + } + .custom-slider { height: 100%; width: 100%; position: relative; + .slider-input { position: absolute; width: 100%; @@ -60,55 +156,209 @@ z-index: 3; cursor: pointer; } + .slider-handle { position: absolute; + top: 50%; width: 42px; line-height: 20px; text-align: center; background: var(--accent-color); color: var(--primary-color); border-radius: #{$border-radius-small}; - transform: translateX(-50%); + transform: translate(-50%, -50%); cursor: pointer; z-index: 2; } } - .marker{ - position: absolute; - background-color: var(--text-disabled); - width: 2px; - height: 12px; - border-radius: 1px; - top: 8px; + + .marker { + position: absolute; + background-color: var(--text-disabled); + width: 2px; + height: 12px; + border-radius: 1px; + top: 8px; + z-index: 1; } - .marker.marker-10{ - left: 10%; + + .marker.marker-10 { + left: 10%; } - .marker.marker-20{ - left: 20%; + + .marker.marker-20 { + left: 20%; } - .marker.marker-30{ - left: 30%; + + .marker.marker-30 { + left: 30%; } - .marker.marker-40{ - left: 40%; + + .marker.marker-40 { + left: 40%; } - .marker.marker-50{ - left: 50%; + + .marker.marker-50 { + left: 50%; } - .marker.marker-60{ - left: 60%; + + .marker.marker-60 { + left: 60%; } - .marker.marker-70{ - left: 70%; + + .marker.marker-70 { + left: 70%; } - .marker.marker-80{ - left: 80%; + + .marker.marker-80 { + left: 80%; } - .marker.marker-90{ - left: 90%; + + .marker.marker-90 { + left: 90%; } } } + + .time-displayer { + display: flex; + justify-content: space-between; + height: auto; + opacity: 1; + // overflow: hidden; + + transition: all 0.5s ease; + + .start-time-wrappper, + .end-time-wrappper { + display: flex; + align-items: center; + gap: 12px; + } + + .time-progresser { + width: 70%; + + .timeline { + padding: 16px; + // background: #f5f3fa; + background: linear-gradient(90.17deg, rgba(255, 255, 255, 0.64) 1.53%, rgba(255, 255, 255, 0.48) 98.13%); + + border-radius: 30px; + display: flex; + align-items: center; + width: 100%; + height: 33px; + + .label-dot-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + position: relative; + + .label { + position: absolute; + top: -200%; + transform: translate(0, -0); + font-size: 12px; + color: #666; + white-space: nowrap; + } + + .dot { + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #d3d3e2; + + &.filled { + background-color: #8f5cf2; + + border: 4px solid var(--accent-color); + } + } + } + + .line { + flex-grow: 1; + height: 4px; + background-color: #d3d3e2; + margin: 0 4px; + + &.filled { + background-color: #8f5cf2; + } + } + } + } + } + + + } + + } + +.processDisplayer { + border-radius: 5px; + // overflow: hidden; + background-color: var(--highlight-accent-color); + padding: 14px 6px; + position: relative; + + .process-player { + position: absolute; + top: 50%; + transform: translate(0, -50%); + width: 3.946108102798462px; + height: 26px; + left: 86.81px; + border-radius: 14px; + border-width: 1px; + background: var(--accent-color, #6F42C1); + + } + + .process-wrapper { + display: flex; + // padding: 0px 16px; + + .process { + height: 5px; + background-color: #4caf50; + color: white; + text-align: center; + line-height: 30px; + transition: width 0.3s ease; + } + } + +} + + +.simulation-player-container.open { + + .progresser-wrapper { + padding-top: 4px; + } + + .time-displayer { + height: 0; + opacity: 0; + pointer-events: none; + display: none; + } + + .processDisplayer { + padding: 0; + background: transparent; + + .process-player { + width: 0; + display: none !important; + } + } + +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index cab078e..5ac39ed 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -366,15 +366,66 @@ min-height: 50vh; padding-bottom: 12px; position: relative; - display: flex; - flex-direction: column; + overflow: auto; .sidebar-right-content-container { border-bottom: 1px solid var(--border-color); - // flex: 1; height: calc(100% - 36px); position: relative; - overflow: auto; + width: 320px; + .no-event-selected { + color: #666; + padding: 1.8rem 1rem; + grid-column: 1 / -1; + .products-list { + padding-top: 1rem; + .products-list-title { + text-align: start; + color: var(--accent-color); + font-size: var(--font-size-regular); + } + ul { + li { + text-align: start; + margin: 8px 0; + padding: 2px 0; + text-decoration: none; + &::marker { + content: ""; + } + button { + width: fit-content; + position: relative; + transition: all 0.2s ease; + @include flex-center; + gap: 4px; + &:before { + content: ""; + position: absolute; + left: 0; + bottom: -4px; + background: var(--accent-color); + height: 1px; + width: 0%; + transition: all 0.3s ease; + } + } + &:hover { + button { + path { + stroke: var(--accent-color); + strokeWidth: 1.5px; + } + color: var(--accent-color); + &:before { + width: 100%; + } + } + } + } + } + } + } } } @@ -642,7 +693,7 @@ path { stroke: var(--accent-color); - stroke-width: 1.5px; + strokeWidth: 1.5px; } &:hover { @@ -675,10 +726,14 @@ color: var(--primary-color); border-radius: #{$border-radius-small}; cursor: pointer; - + outline: none; + border: none; path { stroke: var(--primary-color); } + &:disabled { + background-color: var(--text-disabled); + } } } @@ -706,7 +761,7 @@ } .selected-actions-list { margin-bottom: 8px; - .eye-dropper-input-container{ + .eye-dropper-input-container { padding: 6px 12px; .regularDropdown-container { padding: 5px 8px; @@ -747,19 +802,21 @@ width: 100%; margin: 2px 0; border-radius: #{$border-radius-small}; - } + .value { + display: flex; + justify-content: flex-start; + align-items: center; + min-width: 80%; + gap: 6px; + .input-value { + text-align: start; + } - .value { - display: flex; - justify-content: flex-start; - align-items: center; - min-width: 80%; - gap: 6px; - - input { - width: fit-content; - outline: none; - accent-color: var(--accent-color); + input { + width: fit-content; + outline: none; + accent-color: var(--accent-color); + } } } @@ -797,6 +854,7 @@ @include flex-center; padding: 4px; cursor: grab; + width: 100%; &:active { cursor: grabbing; diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index f0f7704..66a60e7 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -1,12 +1,12 @@ // abstracts @use 'abstracts/variables'; @use 'abstracts/mixins'; -@use 'abstracts/placeholders'; @use 'abstracts/functions'; // base @use 'base/reset'; @use 'base/typography'; +@use 'base/global'; @use 'base/base'; // components diff --git a/app/src/styles/pages/realTimeViz.scss b/app/src/styles/pages/realTimeViz.scss index 0d4f98d..1354859 100644 --- a/app/src/styles/pages/realTimeViz.scss +++ b/app/src/styles/pages/realTimeViz.scss @@ -662,6 +662,7 @@ } .distance-line { + position: absolute; border-style: dashed; border-color: var(--accent-color); @@ -776,13 +777,13 @@ border-radius: 6px; overflow: hidden; padding: 4px; - min-width: 150px; .option { padding: 4px 10px; border-radius: #{$border-radius-small}; color: var(--text-color); + text-wrap: nowrap; cursor: pointer; &:hover { @@ -794,8 +795,8 @@ color: #f65648; &:hover { - background-color: #f65648; - color: white; + background-color: #f657484d; + color: #f65648; } } } diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index f6b0a0c..3293699 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -25,7 +25,7 @@ interface ConveyorPointSchema { action: { actionUuid: string; actionName: string; - actionType: "default" | "spawn" | "swap" | "despawn"; + actionType: "default" | "spawn" | "swap" | "delay" | "despawn"; material: string; delay: number | "inherit"; spawnInterval: number | "inherit"; @@ -42,7 +42,6 @@ interface VehiclePointSchema { actionUuid: string; actionName: string; actionType: "travel"; - material: string | null; unLoadDuration: number; loadCapacity: number; pickUpPoint: { x: number; y: number, z: number } | null; @@ -119,12 +118,14 @@ interface StorageEventSchema extends AssetEventSchema { point: StoragePointSchema; } +type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema; + type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema; type productsSchema = { productName: string; productId: string; - eventsData: EventsSchema[]; + eventDatas: EventsSchema[]; }[] @@ -133,6 +134,7 @@ interface ConveyorStatus extends ConveyorEventSchema { isActive: boolean; idleTime: number; activeTime: number; + } interface MachineStatus extends MachineEventSchema { @@ -168,4 +170,17 @@ interface StorageUnitStatus extends StorageEventSchema { idleTime: number; activeTime: number; currentLoad: number; -} \ No newline at end of file +} + +interface MaterialSchema { + materialId: string; + materialName: string; + materialType: string; + isActive: boolean; + startTime?: string; + endTime?: string; + cost?: number; + weight?: number; +} + +type MaterialsSchema = MaterialSchema[]; \ No newline at end of file